In-Band Registration (XEP-0077)
This commit is contained in:
parent
8ba7596ab5
commit
20994e2995
|
@ -15,7 +15,8 @@ import (
|
|||
)
|
||||
|
||||
const notEnoughArguments string = "Not enough arguments"
|
||||
const telegramNotInitialized string = "Telegram connection is not initialized yet"
|
||||
const TelegramNotInitialized string = "Telegram connection is not initialized yet"
|
||||
const TelegramAuthDone string = "Authorization is done already"
|
||||
const notOnline string = "Not online"
|
||||
|
||||
var permissionsAdmin = client.ChatAdministratorRights{
|
||||
|
@ -244,40 +245,29 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string
|
|||
}
|
||||
|
||||
if cmd == "login" {
|
||||
wasSessionLoginEmpty := c.Session.Login == ""
|
||||
c.Session.Login = args[0]
|
||||
|
||||
if wasSessionLoginEmpty && c.authorizer == nil {
|
||||
go func() {
|
||||
err := c.Connect(resource)
|
||||
if err != nil {
|
||||
log.Error(errors.Wrap(err, "TDlib connection failure"))
|
||||
}
|
||||
}()
|
||||
// a quirk for authorizer to become ready. If it's still not,
|
||||
// nothing bad: the command just needs to be resent again
|
||||
time.Sleep(1e5)
|
||||
err := c.TryLogin(resource, args[0])
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
if c.authorizer == nil {
|
||||
return telegramNotInitialized
|
||||
}
|
||||
|
||||
if c.authorizer.isClosed {
|
||||
return "Authorization is done already"
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
// sign in
|
||||
case "login":
|
||||
c.authorizer.PhoneNumber <- args[0]
|
||||
// check auth code
|
||||
case "code":
|
||||
c.authorizer.Code <- args[0]
|
||||
// check auth password
|
||||
case "password":
|
||||
c.authorizer.Password <- args[0]
|
||||
} else {
|
||||
if c.authorizer == nil {
|
||||
return TelegramNotInitialized
|
||||
}
|
||||
|
||||
if c.authorizer.isClosed {
|
||||
return TelegramAuthDone
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
// check auth code
|
||||
case "code":
|
||||
c.authorizer.Code <- args[0]
|
||||
// check auth password
|
||||
case "password":
|
||||
c.authorizer.Password <- args[0]
|
||||
}
|
||||
}
|
||||
// sign out
|
||||
case "logout":
|
||||
|
|
|
@ -3,6 +3,7 @@ package telegram
|
|||
import (
|
||||
"github.com/pkg/errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
|
||||
|
||||
|
@ -154,14 +155,49 @@ func (c *Client) Connect(resource string) error {
|
|||
log.Errorf("Could not retrieve chats: %v", err)
|
||||
}
|
||||
|
||||
gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribe"))
|
||||
gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribed"))
|
||||
gateway.SubscribeToTransport(c.xmpp, c.jid)
|
||||
gateway.SendPresence(c.xmpp, c.jid, gateway.SPStatus("Logged in as: "+c.Session.Login))
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) TryLogin(resource string, login string) error {
|
||||
wasSessionLoginEmpty := c.Session.Login == ""
|
||||
c.Session.Login = login
|
||||
|
||||
if wasSessionLoginEmpty && c.authorizer == nil {
|
||||
go func() {
|
||||
err := c.Connect(resource)
|
||||
if err != nil {
|
||||
log.Error(errors.Wrap(err, "TDlib connection failure"))
|
||||
}
|
||||
}()
|
||||
// a quirk for authorizer to become ready. If it's still not,
|
||||
// nothing bad: just re-login again
|
||||
time.Sleep(1e5)
|
||||
}
|
||||
|
||||
if c.authorizer == nil {
|
||||
return errors.New(TelegramNotInitialized)
|
||||
}
|
||||
|
||||
if c.authorizer.isClosed {
|
||||
return errors.New(TelegramAuthDone)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) SetPhoneNumber(login string) error {
|
||||
if c.authorizer == nil || c.authorizer.isClosed {
|
||||
return errors.New("Authorization not needed")
|
||||
}
|
||||
|
||||
c.authorizer.PhoneNumber <- login
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect drops TDlib connection and
|
||||
// returns the flag indicating if disconnecting is permitted
|
||||
func (c *Client) Disconnect(resource string, quit bool) bool {
|
||||
|
|
|
@ -193,6 +193,26 @@ type Replace struct {
|
|||
Id string `xml:"id,attr"`
|
||||
}
|
||||
|
||||
// QueryRegister is from XEP-0077
|
||||
type QueryRegister struct {
|
||||
XMLName xml.Name `xml:"jabber:iq:register query"`
|
||||
Instructions string `xml:"instructions"`
|
||||
Username string `xml:"username"`
|
||||
Registered *QueryRegisterRegistered `xml:"registered"`
|
||||
Remove *QueryRegisterRemove `xml:"remove"`
|
||||
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
|
||||
}
|
||||
|
||||
// QueryRegisterRegistered is a child element from XEP-0077
|
||||
type QueryRegisterRegistered struct {
|
||||
XMLName xml.Name `xml:"registered"`
|
||||
}
|
||||
|
||||
// QueryRegisterRemove is a child element from XEP-0077
|
||||
type QueryRegisterRemove struct {
|
||||
XMLName xml.Name `xml:"remove"`
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c PresenceNickExtension) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
|
@ -248,6 +268,16 @@ func (c Replace) Namespace() string {
|
|||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c QueryRegister) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
// GetSet getsets!
|
||||
func (c QueryRegister) GetSet() *stanza.ResultSet {
|
||||
return c.ResultSet
|
||||
}
|
||||
|
||||
// Name is a packet name
|
||||
func (ClientMessage) Name() string {
|
||||
return "message"
|
||||
|
@ -326,4 +356,10 @@ func init() {
|
|||
"urn:xmpp:message-correct:0",
|
||||
"replace",
|
||||
}, Replace{})
|
||||
|
||||
// register query
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
|
||||
"jabber:iq:register",
|
||||
"query",
|
||||
}, QueryRegister{})
|
||||
}
|
||||
|
|
|
@ -360,6 +360,12 @@ func ResumableSend(component *xmpp.Component, packet stanza.Packet) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// SubscribeToTransport ensures a two-way subscription to the transport
|
||||
func SubscribeToTransport(component *xmpp.Component, jid string) {
|
||||
SendPresence(component, jid, SPType("subscribe"))
|
||||
SendPresence(component, jid, SPType("subscribed"))
|
||||
}
|
||||
|
||||
// SplitJID tokenizes a JID string to bare JID and resource
|
||||
func SplitJID(from string) (string, string, bool) {
|
||||
fromJid, err := stanza.NewJid(from)
|
||||
|
|
204
xmpp/handlers.go
204
xmpp/handlers.go
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"strconv"
|
||||
|
@ -57,6 +58,22 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
|||
go handleGetDiscoInfo(s, iq)
|
||||
return
|
||||
}
|
||||
_, ok = iq.Payload.(*stanza.DiscoItems)
|
||||
if ok {
|
||||
go handleGetDiscoItems(s, iq)
|
||||
return
|
||||
}
|
||||
_, ok = iq.Payload.(*extensions.QueryRegister)
|
||||
if ok {
|
||||
go handleGetQueryRegister(s, iq)
|
||||
return
|
||||
}
|
||||
} else if iq.Type == "set" {
|
||||
query, ok := iq.Payload.(*extensions.QueryRegister)
|
||||
if ok {
|
||||
go handleSetQueryRegister(s, iq, query)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,8 +108,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||
session, ok := sessions[bare]
|
||||
if !ok {
|
||||
if msg.To == gatewayJid {
|
||||
gateway.SendPresence(component, msg.From, gateway.SPType("subscribe"))
|
||||
gateway.SendPresence(component, msg.From, gateway.SPType("subscribed"))
|
||||
gateway.SubscribeToTransport(component, msg.From)
|
||||
} else {
|
||||
log.Error("Message from stranger")
|
||||
}
|
||||
|
@ -444,6 +460,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|||
disco.AddIdentity("", "account", "registered")
|
||||
} else {
|
||||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||
disco.AddFeatures("jabber:iq:register")
|
||||
}
|
||||
answer.Payload = disco
|
||||
|
||||
|
@ -458,6 +475,189 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|||
_ = gateway.ResumableSend(component, answer)
|
||||
}
|
||||
|
||||
func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
|
||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: stanza.IQTypeResult,
|
||||
From: iq.To,
|
||||
To: iq.From,
|
||||
Id: iq.Id,
|
||||
Lang: "en",
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create answer IQ: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
answer.Payload = answer.DiscoItems()
|
||||
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
log.Error("Not a component")
|
||||
return
|
||||
}
|
||||
|
||||
_ = gateway.ResumableSend(component, answer)
|
||||
}
|
||||
|
||||
func handleGetQueryRegister(s xmpp.Sender, iq *stanza.IQ) {
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
log.Error("Not a component")
|
||||
return
|
||||
}
|
||||
|
||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: stanza.IQTypeResult,
|
||||
From: iq.To,
|
||||
To: iq.From,
|
||||
Id: iq.Id,
|
||||
Lang: "en",
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create answer IQ: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var login string
|
||||
bare, _, ok := gateway.SplitJID(iq.From)
|
||||
if ok {
|
||||
session, ok := sessions[bare]
|
||||
if ok {
|
||||
login = session.Session.Login
|
||||
}
|
||||
}
|
||||
|
||||
var query stanza.IQPayload
|
||||
if login == "" {
|
||||
query = extensions.QueryRegister{
|
||||
Instructions: fmt.Sprintf("Authorization in Telegram is a multi-step process, so please accept %v to your contacts and follow further instructions (provide the authentication code there, etc.).\nFor now, please provide your login.", iq.To),
|
||||
}
|
||||
} else {
|
||||
query = extensions.QueryRegister{
|
||||
Instructions: "Already logged in",
|
||||
Username: login,
|
||||
Registered: &extensions.QueryRegisterRegistered{},
|
||||
}
|
||||
}
|
||||
answer.Payload = query
|
||||
|
||||
log.Debugf("%#v", query)
|
||||
|
||||
_ = gateway.ResumableSend(component, answer)
|
||||
|
||||
if login == "" {
|
||||
gateway.SubscribeToTransport(component, iq.From)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) {
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
log.Error("Not a component")
|
||||
return
|
||||
}
|
||||
|
||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: stanza.IQTypeResult,
|
||||
From: iq.To,
|
||||
To: iq.From,
|
||||
Id: iq.Id,
|
||||
Lang: "en",
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create answer IQ: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer gateway.ResumableSend(component, answer)
|
||||
|
||||
if query.Remove != nil {
|
||||
iqAnswerSetError(answer, query, 405)
|
||||
return
|
||||
}
|
||||
|
||||
var login string
|
||||
var session *telegram.Client
|
||||
bare, resource, ok := gateway.SplitJID(iq.From)
|
||||
if ok {
|
||||
session, ok = sessions[bare]
|
||||
if ok {
|
||||
login = session.Session.Login
|
||||
}
|
||||
}
|
||||
|
||||
if login == "" {
|
||||
if !ok {
|
||||
session, ok = getTelegramInstance(bare, &persistence.Session{}, component)
|
||||
if !ok {
|
||||
iqAnswerSetError(answer, query, 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err := session.TryLogin(resource, query.Username)
|
||||
if err != nil {
|
||||
if err.Error() == telegram.TelegramAuthDone {
|
||||
iqAnswerSetError(answer, query, 406)
|
||||
} else {
|
||||
iqAnswerSetError(answer, query, 500)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = session.SetPhoneNumber(query.Username)
|
||||
if err != nil {
|
||||
iqAnswerSetError(answer, query, 500)
|
||||
return
|
||||
}
|
||||
|
||||
// everything okay, the response should be empty with no payload/error at this point
|
||||
gateway.SubscribeToTransport(component, iq.From)
|
||||
} else {
|
||||
iqAnswerSetError(answer, query, 406)
|
||||
}
|
||||
}
|
||||
|
||||
func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code int) {
|
||||
answer.Type = stanza.IQTypeError
|
||||
answer.Payload = *payload
|
||||
switch code {
|
||||
case 400:
|
||||
answer.Error = &stanza.Err{
|
||||
Code: code,
|
||||
Type: stanza.ErrorTypeModify,
|
||||
Reason: "bad-request",
|
||||
}
|
||||
case 405:
|
||||
answer.Error = &stanza.Err{
|
||||
Code: code,
|
||||
Type: stanza.ErrorTypeCancel,
|
||||
Reason: "not-allowed",
|
||||
Text: "Logging out is dangerous. If you are sure you would be able to receive the authentication code again, issue the /logout command to the transport",
|
||||
}
|
||||
case 406:
|
||||
answer.Error = &stanza.Err{
|
||||
Code: code,
|
||||
Type: stanza.ErrorTypeModify,
|
||||
Reason: "not-acceptable",
|
||||
Text: "Phone number already provided, chat with the transport for further instruction",
|
||||
}
|
||||
case 500:
|
||||
answer.Error = &stanza.Err{
|
||||
Code: code,
|
||||
Type: stanza.ErrorTypeWait,
|
||||
Reason: "internal-server-error",
|
||||
}
|
||||
default:
|
||||
log.Error("Unknown error code, falling back with empty reason")
|
||||
answer.Error = &stanza.Err{
|
||||
Code: code,
|
||||
Type: stanza.ErrorTypeCancel,
|
||||
Reason: "undefined-condition",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toToID(to string) (int64, bool) {
|
||||
toParts := strings.Split(to, "@")
|
||||
if len(toParts) < 2 {
|
||||
|
|
Loading…
Reference in a new issue