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 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"
|
const notOnline string = "Not online"
|
||||||
|
|
||||||
var permissionsAdmin = client.ChatAdministratorRights{
|
var permissionsAdmin = client.ChatAdministratorRights{
|
||||||
|
@ -244,34 +245,22 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd == "login" {
|
if cmd == "login" {
|
||||||
wasSessionLoginEmpty := c.Session.Login == ""
|
err := c.TryLogin(resource, args[0])
|
||||||
c.Session.Login = args[0]
|
|
||||||
|
|
||||||
if wasSessionLoginEmpty && c.authorizer == nil {
|
|
||||||
go func() {
|
|
||||||
err := c.Connect(resource)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(errors.Wrap(err, "TDlib connection failure"))
|
return err.Error()
|
||||||
}
|
|
||||||
}()
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.authorizer.PhoneNumber <- args[0]
|
||||||
|
} else {
|
||||||
if c.authorizer == nil {
|
if c.authorizer == nil {
|
||||||
return telegramNotInitialized
|
return TelegramNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.authorizer.isClosed {
|
if c.authorizer.isClosed {
|
||||||
return "Authorization is done already"
|
return TelegramAuthDone
|
||||||
}
|
}
|
||||||
|
|
||||||
switch cmd {
|
switch cmd {
|
||||||
// sign in
|
|
||||||
case "login":
|
|
||||||
c.authorizer.PhoneNumber <- args[0]
|
|
||||||
// check auth code
|
// check auth code
|
||||||
case "code":
|
case "code":
|
||||||
c.authorizer.Code <- args[0]
|
c.authorizer.Code <- args[0]
|
||||||
|
@ -279,6 +268,7 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string
|
||||||
case "password":
|
case "password":
|
||||||
c.authorizer.Password <- args[0]
|
c.authorizer.Password <- args[0]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// sign out
|
// sign out
|
||||||
case "logout":
|
case "logout":
|
||||||
if !c.Online() {
|
if !c.Online() {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package telegram
|
||||||
import (
|
import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
|
"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)
|
log.Errorf("Could not retrieve chats: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribe"))
|
gateway.SubscribeToTransport(c.xmpp, c.jid)
|
||||||
gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribed"))
|
|
||||||
gateway.SendPresence(c.xmpp, c.jid, gateway.SPStatus("Logged in as: "+c.Session.Login))
|
gateway.SendPresence(c.xmpp, c.jid, gateway.SPStatus("Logged in as: "+c.Session.Login))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
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
|
// Disconnect drops TDlib connection and
|
||||||
// returns the flag indicating if disconnecting is permitted
|
// returns the flag indicating if disconnecting is permitted
|
||||||
func (c *Client) Disconnect(resource string, quit bool) bool {
|
func (c *Client) Disconnect(resource string, quit bool) bool {
|
||||||
|
|
|
@ -193,6 +193,26 @@ type Replace struct {
|
||||||
Id string `xml:"id,attr"`
|
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!
|
// Namespace is a namespace!
|
||||||
func (c PresenceNickExtension) Namespace() string {
|
func (c PresenceNickExtension) Namespace() string {
|
||||||
return c.XMLName.Space
|
return c.XMLName.Space
|
||||||
|
@ -248,6 +268,16 @@ func (c Replace) Namespace() string {
|
||||||
return c.XMLName.Space
|
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
|
// Name is a packet name
|
||||||
func (ClientMessage) Name() string {
|
func (ClientMessage) Name() string {
|
||||||
return "message"
|
return "message"
|
||||||
|
@ -326,4 +356,10 @@ func init() {
|
||||||
"urn:xmpp:message-correct:0",
|
"urn:xmpp:message-correct:0",
|
||||||
"replace",
|
"replace",
|
||||||
}, 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
|
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
|
// SplitJID tokenizes a JID string to bare JID and resource
|
||||||
func SplitJID(from string) (string, string, bool) {
|
func SplitJID(from string) (string, string, bool) {
|
||||||
fromJid, err := stanza.NewJid(from)
|
fromJid, err := stanza.NewJid(from)
|
||||||
|
|
204
xmpp/handlers.go
204
xmpp/handlers.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -57,6 +58,22 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
||||||
go handleGetDiscoInfo(s, iq)
|
go handleGetDiscoInfo(s, iq)
|
||||||
return
|
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]
|
session, ok := sessions[bare]
|
||||||
if !ok {
|
if !ok {
|
||||||
if msg.To == gatewayJid {
|
if msg.To == gatewayJid {
|
||||||
gateway.SendPresence(component, msg.From, gateway.SPType("subscribe"))
|
gateway.SubscribeToTransport(component, msg.From)
|
||||||
gateway.SendPresence(component, msg.From, gateway.SPType("subscribed"))
|
|
||||||
} else {
|
} else {
|
||||||
log.Error("Message from stranger")
|
log.Error("Message from stranger")
|
||||||
}
|
}
|
||||||
|
@ -444,6 +460,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
disco.AddIdentity("", "account", "registered")
|
disco.AddIdentity("", "account", "registered")
|
||||||
} else {
|
} else {
|
||||||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||||
|
disco.AddFeatures("jabber:iq:register")
|
||||||
}
|
}
|
||||||
answer.Payload = disco
|
answer.Payload = disco
|
||||||
|
|
||||||
|
@ -458,6 +475,189 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
_ = gateway.ResumableSend(component, answer)
|
_ = 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) {
|
func toToID(to string) (int64, bool) {
|
||||||
toParts := strings.Split(to, "@")
|
toParts := strings.Split(to, "@")
|
||||||
if len(toParts) < 2 {
|
if len(toParts) < 2 {
|
||||||
|
|
Loading…
Reference in a new issue