Refactor login flow
This commit is contained in:
parent
7fe4f0b047
commit
6225b83d2f
15 changed files with 276 additions and 305 deletions
90
commands.go
90
commands.go
|
@ -18,6 +18,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
@ -82,57 +83,82 @@ func fnLogin(ce *WrappedCommandEvent) {
|
||||||
ce.Reply("You're already logged in. Perhaps you wanted to `reconnect`?")
|
ce.Reply("You're already logged in. Perhaps you wanted to `reconnect`?")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
} else if ce.User.pairSuccessChan != nil {
|
||||||
|
ce.Reply("You already have a login in progress")
|
||||||
ce.User.hackyLoginCommand = ce
|
|
||||||
ce.User.hackyLoginCommandPrevEvent = ""
|
|
||||||
_, err := ce.User.Login(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
ce.User.log.Errorf("Failed to log in:", err)
|
|
||||||
ce.Reply("Failed to log in: %v", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ch, err := ce.User.Login(context.Background(), 6)
|
||||||
|
if err != nil {
|
||||||
|
ce.ZLog.Err(err).Msg("Failed to start login")
|
||||||
|
ce.Reply("Failed to start login: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var prevEvent id.EventID
|
||||||
|
for item := range ch {
|
||||||
|
switch {
|
||||||
|
case item.qr != "":
|
||||||
|
ce.ZLog.Debug().Msg("Got code in QR channel")
|
||||||
|
prevEvent = ce.User.sendQR(ce, item.qr, prevEvent)
|
||||||
|
case item.err != nil:
|
||||||
|
ce.ZLog.Err(err).Msg("Error in QR channel")
|
||||||
|
prevEvent = ce.User.sendQREdit(ce, &event.MessageEventContent{
|
||||||
|
MsgType: event.MsgNotice,
|
||||||
|
Body: fmt.Sprintf("Failed to log in: %v", err),
|
||||||
|
}, prevEvent)
|
||||||
|
case item.success:
|
||||||
|
ce.ZLog.Debug().Msg("Got pair success in QR channel")
|
||||||
|
prevEvent = ce.User.sendQREdit(ce, &event.MessageEventContent{
|
||||||
|
MsgType: event.MsgNotice,
|
||||||
|
Body: "Successfully logged in",
|
||||||
|
}, prevEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ce.ZLog.Trace().Msg("Login command finished")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) sendQR(ce *WrappedCommandEvent, code string, prevEvent id.EventID) id.EventID {
|
func (user *User) sendQREdit(ce *WrappedCommandEvent, content *event.MessageEventContent, prevEvent id.EventID) id.EventID {
|
||||||
url, ok := user.uploadQR(ce, code)
|
|
||||||
if !ok {
|
|
||||||
return prevEvent
|
|
||||||
}
|
|
||||||
content := event.MessageEventContent{
|
|
||||||
MsgType: event.MsgImage,
|
|
||||||
Body: code,
|
|
||||||
URL: url.CUString(),
|
|
||||||
}
|
|
||||||
if len(prevEvent) != 0 {
|
if len(prevEvent) != 0 {
|
||||||
content.SetEdit(prevEvent)
|
content.SetEdit(prevEvent)
|
||||||
}
|
}
|
||||||
resp, err := ce.Bot.SendMessageEvent(ce.RoomID, event.EventMessage, &content)
|
resp, err := ce.Bot.SendMessageEvent(ce.RoomID, event.EventMessage, &content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user.log.Errorln("Failed to send edited QR code to user:", err)
|
ce.ZLog.Err(err).Msg("Failed to send edited QR code")
|
||||||
} else if len(prevEvent) == 0 {
|
} else if len(prevEvent) == 0 {
|
||||||
prevEvent = resp.EventID
|
prevEvent = resp.EventID
|
||||||
}
|
}
|
||||||
return prevEvent
|
return prevEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) uploadQR(ce *WrappedCommandEvent, code string) (id.ContentURI, bool) {
|
func (user *User) sendQR(ce *WrappedCommandEvent, code string, prevEvent id.EventID) id.EventID {
|
||||||
|
var content event.MessageEventContent
|
||||||
|
url, err := user.uploadQR(code)
|
||||||
|
if err != nil {
|
||||||
|
ce.ZLog.Err(err).Msg("Failed to upload QR code")
|
||||||
|
content = event.MessageEventContent{
|
||||||
|
MsgType: event.MsgNotice,
|
||||||
|
Body: fmt.Sprintf("Failed to upload QR code: %v", err),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = event.MessageEventContent{
|
||||||
|
MsgType: event.MsgImage,
|
||||||
|
Body: code,
|
||||||
|
URL: url.CUString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user.sendQREdit(ce, &content, prevEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) uploadQR(code string) (id.ContentURI, error) {
|
||||||
qrCode, err := qrcode.Encode(code, qrcode.Low, 256)
|
qrCode, err := qrcode.Encode(code, qrcode.Low, 256)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user.log.Errorln("Failed to encode QR code:", err)
|
return id.ContentURI{}, err
|
||||||
ce.Reply("Failed to encode QR code: %v", err)
|
|
||||||
return id.ContentURI{}, false
|
|
||||||
}
|
}
|
||||||
|
resp, err := user.bridge.Bot.UploadBytes(qrCode, "image/png")
|
||||||
bot := user.bridge.AS.BotClient()
|
|
||||||
|
|
||||||
resp, err := bot.UploadBytes(qrCode, "image/png")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user.log.Errorln("Failed to upload QR code:", err)
|
return id.ContentURI{}, err
|
||||||
ce.Reply("Failed to upload QR code: %v", err)
|
|
||||||
return id.ContentURI{}, false
|
|
||||||
}
|
}
|
||||||
return resp.ContentURI, true
|
return resp.ContentURI, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdLogout = &commands.FullHandler{
|
var cmdLogout = &commands.FullHandler{
|
||||||
|
@ -236,7 +262,7 @@ func fnPing(ce *WrappedCommandEvent) {
|
||||||
ce.Reply("You're not logged into Google Messages.")
|
ce.Reply("You're not logged into Google Messages.")
|
||||||
}
|
}
|
||||||
} else if ce.User.Client == nil || !ce.User.Client.IsConnected() {
|
} else if ce.User.Client == nil || !ce.User.Client.IsConnected() {
|
||||||
ce.Reply("You're logged in as %s (device #%d), but you don't have a Google Messages connection.", ce.User.Phone)
|
ce.Reply("You're logged in as %s, but you don't have a Google Messages connection.", ce.User.Phone)
|
||||||
} else {
|
} else {
|
||||||
ce.Reply("Logged in as %s, connection to Google Messages may be OK", ce.User.Phone)
|
ce.Reply("Logged in as %s, connection to Google Messages may be OK", ce.User.Phone)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,11 +39,13 @@ func main() {
|
||||||
w.TimeFormat = time.Stamp
|
w.TimeFormat = time.Stamp
|
||||||
})).With().Timestamp().Logger()
|
})).With().Timestamp().Logger()
|
||||||
file, err := os.Open("session.json")
|
file, err := os.Open("session.json")
|
||||||
|
var doLogin bool
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
sess = *libgm.NewAuthData()
|
sess = *libgm.NewAuthData()
|
||||||
|
doLogin = true
|
||||||
} else {
|
} else {
|
||||||
must(json.NewDecoder(file).Decode(&sess))
|
must(json.NewDecoder(file).Decode(&sess))
|
||||||
log.Info().Msg("Loaded session?")
|
log.Info().Msg("Loaded session?")
|
||||||
|
@ -51,7 +53,23 @@ func main() {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
cli = libgm.NewClient(&sess, log)
|
cli = libgm.NewClient(&sess, log)
|
||||||
cli.SetEventHandler(evtHandler)
|
cli.SetEventHandler(evtHandler)
|
||||||
must(cli.Connect())
|
if doLogin {
|
||||||
|
qr := mustReturn(cli.StartLogin())
|
||||||
|
qrterminal.GenerateHalfBlock(qr, qrterminal.L, os.Stdout)
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
if sess.Browser != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
qr := mustReturn(cli.RefreshPhoneRelay())
|
||||||
|
qrterminal.GenerateHalfBlock(qr, qrterminal.L, os.Stdout)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
must(cli.Connect())
|
||||||
|
}
|
||||||
|
|
||||||
c := make(chan os.Signal)
|
c := make(chan os.Signal)
|
||||||
input := make(chan string)
|
input := make(chan string)
|
||||||
|
@ -97,20 +115,12 @@ func evtHandler(rawEvt any) {
|
||||||
log.Debug().Any("data", evt).Msg("Client is ready!")
|
log.Debug().Any("data", evt).Msg("Client is ready!")
|
||||||
case *events.PairSuccessful:
|
case *events.PairSuccessful:
|
||||||
log.Debug().Any("data", evt).Msg("Pair successful")
|
log.Debug().Any("data", evt).Msg("Pair successful")
|
||||||
//kd := evt.Data.(*binary.AuthenticationContainer_KeyData)
|
|
||||||
//sess.DevicePair = &pblite.DevicePair{
|
|
||||||
// Mobile: kd.KeyData.Mobile,
|
|
||||||
// Browser: kd.KeyData.Browser,
|
|
||||||
//}
|
|
||||||
//sess.TachyonAuthToken = evt.AuthMessage.TachyonAuthToken
|
|
||||||
saveSession()
|
saveSession()
|
||||||
log.Debug().Msg("Wrote session")
|
log.Debug().Msg("Wrote session")
|
||||||
case *binary.Message:
|
case *binary.Message:
|
||||||
log.Debug().Any("data", evt).Msg("Message event")
|
log.Debug().Any("data", evt).Msg("Message event")
|
||||||
case *binary.Conversation:
|
case *binary.Conversation:
|
||||||
log.Debug().Any("data", evt).Msg("Conversation event")
|
log.Debug().Any("data", evt).Msg("Conversation event")
|
||||||
case *events.QR:
|
|
||||||
qrterminal.GenerateHalfBlock(evt.URL, qrterminal.L, os.Stdout)
|
|
||||||
case *events.BrowserActive:
|
case *events.BrowserActive:
|
||||||
log.Debug().Any("data", evt).Msg("Browser active")
|
log.Debug().Any("data", evt).Msg("Browser active")
|
||||||
default:
|
default:
|
||||||
|
|
156
libgm/client.go
156
libgm/client.go
|
@ -5,12 +5,10 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -21,7 +19,6 @@ import (
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/events"
|
"go.mau.fi/mautrix-gmessages/libgm/events"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/payload"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/util"
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
||||||
)
|
)
|
||||||
|
@ -50,11 +47,10 @@ type EventHandler func(evt any)
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Logger zerolog.Logger
|
Logger zerolog.Logger
|
||||||
rpc *RPC
|
rpc *RPC
|
||||||
pairer *Pairer
|
|
||||||
evHandler EventHandler
|
evHandler EventHandler
|
||||||
sessionHandler *SessionHandler
|
sessionHandler *SessionHandler
|
||||||
|
|
||||||
authData *AuthData
|
AuthData *AuthData
|
||||||
|
|
||||||
proxy Proxy
|
proxy Proxy
|
||||||
http *http.Client
|
http *http.Client
|
||||||
|
@ -73,7 +69,7 @@ func NewClient(authData *AuthData, logger zerolog.Logger) *Client {
|
||||||
responseTimeout: time.Duration(5000) * time.Millisecond,
|
responseTimeout: time.Duration(5000) * time.Millisecond,
|
||||||
}
|
}
|
||||||
cli := &Client{
|
cli := &Client{
|
||||||
authData: authData,
|
AuthData: authData,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
sessionHandler: sessionHandler,
|
sessionHandler: sessionHandler,
|
||||||
http: &http.Client{},
|
http: &http.Client{},
|
||||||
|
@ -102,46 +98,52 @@ func (c *Client) SetProxy(proxy string) error {
|
||||||
c.Logger.Debug().Any("proxy", proxyParsed.Host).Msg("SetProxy")
|
c.Logger.Debug().Any("proxy", proxyParsed.Host).Msg("SetProxy")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Connect() error {
|
func (c *Client) Connect() error {
|
||||||
if c.authData.TachyonAuthToken != nil {
|
if c.AuthData.TachyonAuthToken == nil {
|
||||||
refreshErr := c.refreshAuthToken()
|
return fmt.Errorf("no auth token")
|
||||||
if refreshErr != nil {
|
} else if c.AuthData.Browser == nil {
|
||||||
panic(refreshErr)
|
return fmt.Errorf("not logged in")
|
||||||
}
|
|
||||||
|
|
||||||
webEncryptionKeyResponse, webEncryptionKeyErr := c.GetWebEncryptionKey()
|
|
||||||
if webEncryptionKeyErr != nil {
|
|
||||||
c.Logger.Err(webEncryptionKeyErr).Any("response", webEncryptionKeyResponse).Msg("GetWebEncryptionKey request failed")
|
|
||||||
return webEncryptionKeyErr
|
|
||||||
}
|
|
||||||
c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
|
|
||||||
go c.rpc.ListenReceiveMessages()
|
|
||||||
c.sessionHandler.startAckInterval()
|
|
||||||
|
|
||||||
bugleRes, bugleErr := c.IsBugleDefault()
|
|
||||||
if bugleErr != nil {
|
|
||||||
panic(bugleErr)
|
|
||||||
}
|
|
||||||
c.Logger.Info().Any("isBugle", bugleRes.Success).Msg("IsBugleDefault")
|
|
||||||
sessionErr := c.SetActiveSession()
|
|
||||||
if sessionErr != nil {
|
|
||||||
panic(sessionErr)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
pairer, err := c.NewPairer(c.authData.RefreshKey, 20)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
c.pairer = pairer
|
|
||||||
registered, err2 := c.pairer.RegisterPhoneRelay()
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
c.authData.TachyonAuthToken = registered.AuthKeyData.TachyonAuthToken
|
|
||||||
go c.rpc.ListenReceiveMessages()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshErr := c.refreshAuthToken()
|
||||||
|
if refreshErr != nil {
|
||||||
|
panic(refreshErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
webEncryptionKeyResponse, webEncryptionKeyErr := c.GetWebEncryptionKey()
|
||||||
|
if webEncryptionKeyErr != nil {
|
||||||
|
c.Logger.Err(webEncryptionKeyErr).Any("response", webEncryptionKeyResponse).Msg("GetWebEncryptionKey request failed")
|
||||||
|
return webEncryptionKeyErr
|
||||||
|
}
|
||||||
|
c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
|
||||||
|
go c.rpc.ListenReceiveMessages()
|
||||||
|
c.sessionHandler.startAckInterval()
|
||||||
|
|
||||||
|
bugleRes, bugleErr := c.IsBugleDefault()
|
||||||
|
if bugleErr != nil {
|
||||||
|
panic(bugleErr)
|
||||||
|
}
|
||||||
|
c.Logger.Info().Any("isBugle", bugleRes.Success).Msg("IsBugleDefault")
|
||||||
|
sessionErr := c.SetActiveSession()
|
||||||
|
if sessionErr != nil {
|
||||||
|
panic(sessionErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) StartLogin() (string, error) {
|
||||||
|
registered, err := c.RegisterPhoneRelay()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
c.AuthData.TachyonAuthToken = registered.AuthKeyData.TachyonAuthToken
|
||||||
|
go c.rpc.ListenReceiveMessages()
|
||||||
|
qr, err := c.GenerateQRCodeData(registered.GetPairingKey())
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to generate QR code: %w", err)
|
||||||
|
}
|
||||||
|
return qr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Disconnect() {
|
func (c *Client) Disconnect() {
|
||||||
|
@ -154,7 +156,7 @@ func (c *Client) IsConnected() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) IsLoggedIn() bool {
|
func (c *Client) IsLoggedIn() bool {
|
||||||
return c.authData != nil && c.authData.Browser != nil
|
return c.AuthData != nil && c.AuthData.Browser != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Reconnect() error {
|
func (c *Client) Reconnect() error {
|
||||||
|
@ -164,10 +166,10 @@ func (c *Client) Reconnect() error {
|
||||||
}
|
}
|
||||||
err := c.Connect()
|
err := c.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Err(err).Any("tachyonAuthToken", c.authData.TachyonAuthToken).Msg("Failed to reconnect")
|
c.Logger.Err(err).Any("tachyonAuthToken", c.AuthData.TachyonAuthToken).Msg("Failed to reconnect")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.Logger.Debug().Any("tachyonAuthToken", c.authData.TachyonAuthToken).Msg("Successfully reconnected to server")
|
c.Logger.Debug().Any("tachyonAuthToken", c.AuthData.TachyonAuthToken).Msg("Successfully reconnected to server")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,8 +187,8 @@ func (c *Client) DownloadMedia(mediaID string, key []byte) ([]byte, error) {
|
||||||
},
|
},
|
||||||
AuthData: &binary.AuthMessage{
|
AuthData: &binary.AuthMessage{
|
||||||
RequestID: uuid.NewString(),
|
RequestID: uuid.NewString(),
|
||||||
TachyonAuthToken: c.authData.TachyonAuthToken,
|
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||||
ConfigVersion: payload.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
downloadMetadataBytes, err2 := proto.Marshal(downloadMetadata)
|
downloadMetadataBytes, err2 := proto.Marshal(downloadMetadata)
|
||||||
|
@ -242,7 +244,7 @@ func (c *Client) FetchConfigVersion() {
|
||||||
panic(parseErr)
|
panic(parseErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
currVersion := payload.ConfigMessage
|
currVersion := util.ConfigMessage
|
||||||
if version.Year != currVersion.Year || version.Month != currVersion.Month || version.Day != currVersion.Day {
|
if version.Year != currVersion.Year || version.Month != currVersion.Month || version.Day != currVersion.Day {
|
||||||
toLog := c.diffVersionFormat(currVersion, version)
|
toLog := c.diffVersionFormat(currVersion, version)
|
||||||
c.Logger.Info().Any("version", toLog).Msg("There's a new version available!")
|
c.Logger.Info().Any("version", toLog).Msg("There's a new version available!")
|
||||||
|
@ -257,56 +259,26 @@ func (c *Client) diffVersionFormat(curr *binary.ConfigVersion, latest *binary.Co
|
||||||
|
|
||||||
func (c *Client) updateWebEncryptionKey(key []byte) {
|
func (c *Client) updateWebEncryptionKey(key []byte) {
|
||||||
c.Logger.Debug().Any("key", key).Msg("Updated WebEncryptionKey")
|
c.Logger.Debug().Any("key", key).Msg("Updated WebEncryptionKey")
|
||||||
c.authData.WebEncryptionKey = key
|
c.AuthData.WebEncryptionKey = key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) updateTachyonAuthToken(t []byte, validFor int64) {
|
func (c *Client) updateTachyonAuthToken(t []byte, validFor int64) {
|
||||||
c.authData.TachyonAuthToken = t
|
c.AuthData.TachyonAuthToken = t
|
||||||
validForDuration := time.Duration(validFor) * time.Microsecond
|
validForDuration := time.Duration(validFor) * time.Microsecond
|
||||||
if validForDuration == 0 {
|
if validForDuration == 0 {
|
||||||
validForDuration = 24 * time.Hour
|
validForDuration = 24 * time.Hour
|
||||||
}
|
}
|
||||||
c.authData.TachyonExpiry = time.Now().UTC().Add(time.Microsecond * time.Duration(validFor))
|
c.AuthData.TachyonExpiry = time.Now().UTC().Add(time.Microsecond * time.Duration(validFor))
|
||||||
c.authData.TachyonTTL = validForDuration.Microseconds()
|
c.AuthData.TachyonTTL = validForDuration.Microseconds()
|
||||||
c.Logger.Debug().Time("tachyon_expiry", c.authData.TachyonExpiry).Int64("valid_for", validFor).Msg("Updated tachyon token")
|
c.Logger.Debug().Time("tachyon_expiry", c.AuthData.TachyonExpiry).Int64("valid_for", validFor).Msg("Updated tachyon token")
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) updateDevicePair(mobile, browser *binary.Device) {
|
|
||||||
c.authData.Mobile = mobile
|
|
||||||
c.authData.Browser = browser
|
|
||||||
c.Logger.Debug().Any("mobile", mobile).Any("browser", browser).Msg("Updated device pair")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) SaveAuthSession(path string) error {
|
|
||||||
toSaveJson, jsonErr := json.Marshal(c.authData)
|
|
||||||
if jsonErr != nil {
|
|
||||||
return jsonErr
|
|
||||||
}
|
|
||||||
writeErr := os.WriteFile(path, toSaveJson, os.ModePerm)
|
|
||||||
return writeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadAuthSession(path string) (*AuthData, error) {
|
|
||||||
jsonData, readErr := os.ReadFile(path)
|
|
||||||
if readErr != nil {
|
|
||||||
return nil, readErr
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionData := &AuthData{}
|
|
||||||
marshalErr := json.Unmarshal(jsonData, sessionData)
|
|
||||||
if marshalErr != nil {
|
|
||||||
return nil, marshalErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessionData, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) refreshAuthToken() error {
|
func (c *Client) refreshAuthToken() error {
|
||||||
if c.authData.Browser == nil || time.Until(c.authData.TachyonExpiry) > RefreshTachyonBuffer {
|
if c.AuthData.Browser == nil || time.Until(c.AuthData.TachyonExpiry) > RefreshTachyonBuffer {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
c.Logger.Debug().Time("tachyon_expiry", c.authData.TachyonExpiry).Msg("Refreshing auth token")
|
c.Logger.Debug().Time("tachyon_expiry", c.AuthData.TachyonExpiry).Msg("Refreshing auth token")
|
||||||
jwk := c.authData.RefreshKey
|
jwk := c.AuthData.RefreshKey
|
||||||
requestID := uuid.NewString()
|
requestID := uuid.NewString()
|
||||||
timestamp := time.Now().UnixMilli() * 1000
|
timestamp := time.Now().UnixMilli() * 1000
|
||||||
|
|
||||||
|
@ -319,10 +291,10 @@ func (c *Client) refreshAuthToken() error {
|
||||||
payload, err := pblite.Marshal(&binary.RegisterRefreshPayload{
|
payload, err := pblite.Marshal(&binary.RegisterRefreshPayload{
|
||||||
MessageAuth: &binary.AuthMessage{
|
MessageAuth: &binary.AuthMessage{
|
||||||
RequestID: requestID,
|
RequestID: requestID,
|
||||||
TachyonAuthToken: c.authData.TachyonAuthToken,
|
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||||
ConfigVersion: payload.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
CurrBrowserDevice: c.authData.Browser,
|
CurrBrowserDevice: c.AuthData.Browser,
|
||||||
UnixTimestamp: timestamp,
|
UnixTimestamp: timestamp,
|
||||||
Signature: sig,
|
Signature: sig,
|
||||||
EmptyRefreshArr: &binary.EmptyRefreshArr{EmptyArr: &binary.EmptyArr{}},
|
EmptyRefreshArr: &binary.EmptyRefreshArr{EmptyArr: &binary.EmptyArr{}},
|
||||||
|
@ -363,6 +335,6 @@ func (c *Client) refreshAuthToken() error {
|
||||||
validFor, _ := strconv.ParseInt(resp.GetTokenData().GetValidFor(), 10, 64)
|
validFor, _ := strconv.ParseInt(resp.GetTokenData().GetValidFor(), 10, 64)
|
||||||
|
|
||||||
c.updateTachyonAuthToken(token, validFor)
|
c.updateTachyonAuthToken(token, validFor)
|
||||||
c.triggerEvent(events.NewAuthTokenRefreshed(token))
|
c.triggerEvent(&events.AuthTokenRefreshed{})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (r *RPC) deduplicateUpdate(response *pblite.Response) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RPC) HandleRPCMsg(msg *binary.InternalMessage) {
|
func (r *RPC) HandleRPCMsg(msg *binary.InternalMessage) {
|
||||||
response, decodeErr := pblite.DecryptInternalMessage(msg, r.client.authData.RequestCrypto)
|
response, decodeErr := pblite.DecryptInternalMessage(msg, r.client.AuthData.RequestCrypto)
|
||||||
if decodeErr != nil {
|
if decodeErr != nil {
|
||||||
r.client.Logger.Error().Err(decodeErr).Msg("rpc decrypt msg err")
|
r.client.Logger.Error().Err(decodeErr).Msg("rpc decrypt msg err")
|
||||||
return
|
return
|
||||||
|
@ -55,7 +55,6 @@ func (r *RPC) HandleRPCMsg(msg *binary.InternalMessage) {
|
||||||
|
|
||||||
r.client.sessionHandler.queueMessageAck(response.ResponseID)
|
r.client.sessionHandler.queueMessageAck(response.ResponseID)
|
||||||
if r.client.sessionHandler.receiveResponse(response) {
|
if r.client.sessionHandler.receiveResponse(response) {
|
||||||
r.client.Logger.Debug().Str("request_id", response.Data.RequestID).Msg("Received response")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch response.BugleRoute {
|
switch response.BugleRoute {
|
||||||
|
|
|
@ -19,15 +19,7 @@ func NewClientReady(sessionID string, conversationList *binary.Conversations) *C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthTokenRefreshed struct {
|
type AuthTokenRefreshed struct{}
|
||||||
Token []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAuthTokenRefreshed(token []byte) *AuthTokenRefreshed {
|
|
||||||
return &AuthTokenRefreshed{
|
|
||||||
Token: token,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPError struct {
|
type HTTPError struct {
|
||||||
Action string
|
Action string
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/payload"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/util"
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -131,10 +130,10 @@ func (c *Client) buildStartUploadPayload() (string, error) {
|
||||||
ImageType: 1,
|
ImageType: 1,
|
||||||
AuthData: &binary.AuthMessage{
|
AuthData: &binary.AuthMessage{
|
||||||
RequestID: uuid.NewString(),
|
RequestID: uuid.NewString(),
|
||||||
TachyonAuthToken: c.authData.TachyonAuthToken,
|
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||||
ConfigVersion: payload.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
Mobile: c.authData.Mobile,
|
Mobile: c.AuthData.Mobile,
|
||||||
}
|
}
|
||||||
|
|
||||||
protoDataBytes, err := proto.Marshal(protoData)
|
protoDataBytes, err := proto.Marshal(protoData)
|
||||||
|
|
134
libgm/pair.go
134
libgm/pair.go
|
@ -3,38 +3,16 @@ package libgm
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/events"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/payload"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/util"
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Pairer struct {
|
func (c *Client) RegisterPhoneRelay() (*binary.RegisterPhoneRelayResponse, error) {
|
||||||
client *Client
|
key, err := x509.MarshalPKIXPublicKey(c.AuthData.RefreshKey.GetPublicKey())
|
||||||
KeyData *crypto.JWK
|
|
||||||
ticker *time.Ticker
|
|
||||||
tickerTime time.Duration
|
|
||||||
pairingKey []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) NewPairer(keyData *crypto.JWK, refreshQrCodeTime int) (*Pairer, error) {
|
|
||||||
p := &Pairer{
|
|
||||||
client: c,
|
|
||||||
KeyData: keyData,
|
|
||||||
tickerTime: time.Duration(refreshQrCodeTime) * time.Second,
|
|
||||||
}
|
|
||||||
c.pairer = p
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pairer) RegisterPhoneRelay() (*binary.RegisterPhoneRelayResponse, error) {
|
|
||||||
key, err := x509.MarshalPKIXPublicKey(p.KeyData.GetPublicKey())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -42,10 +20,10 @@ func (p *Pairer) RegisterPhoneRelay() (*binary.RegisterPhoneRelayResponse, error
|
||||||
body, err := proto.Marshal(&binary.AuthenticationContainer{
|
body, err := proto.Marshal(&binary.AuthenticationContainer{
|
||||||
AuthMessage: &binary.AuthMessage{
|
AuthMessage: &binary.AuthMessage{
|
||||||
RequestID: uuid.NewString(),
|
RequestID: uuid.NewString(),
|
||||||
Network: &payload.Network,
|
Network: &util.Network,
|
||||||
ConfigVersion: payload.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
BrowserDetails: payload.BrowserDetailsMessage,
|
BrowserDetails: util.BrowserDetailsMessage,
|
||||||
Data: &binary.AuthenticationContainer_KeyData{
|
Data: &binary.AuthenticationContainer_KeyData{
|
||||||
KeyData: &binary.KeyData{
|
KeyData: &binary.KeyData{
|
||||||
EcdsaKeys: &binary.ECDSAKeys{
|
EcdsaKeys: &binary.ECDSAKeys{
|
||||||
|
@ -56,91 +34,66 @@ func (p *Pairer) RegisterPhoneRelay() (*binary.RegisterPhoneRelayResponse, error
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.client.Logger.Err(err)
|
c.Logger.Err(err)
|
||||||
return &binary.RegisterPhoneRelayResponse{}, err
|
|
||||||
}
|
|
||||||
relayResponse, reqErr := p.client.MakeRelayRequest(util.RegisterPhoneRelayURL, body)
|
|
||||||
if reqErr != nil {
|
|
||||||
p.client.Logger.Err(reqErr)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
responseBody, err2 := io.ReadAll(relayResponse.Body)
|
relayResponse, reqErr := c.MakeRelayRequest(util.RegisterPhoneRelayURL, body)
|
||||||
if err2 != nil {
|
if reqErr != nil {
|
||||||
return nil, err2
|
c.Logger.Err(reqErr)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
responseBody, err := io.ReadAll(relayResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
relayResponse.Body.Close()
|
relayResponse.Body.Close()
|
||||||
res := &binary.RegisterPhoneRelayResponse{}
|
res := &binary.RegisterPhoneRelayResponse{}
|
||||||
err3 := proto.Unmarshal(responseBody, res)
|
err = proto.Unmarshal(responseBody, res)
|
||||||
if err3 != nil {
|
if err != nil {
|
||||||
return nil, err3
|
return nil, err
|
||||||
}
|
}
|
||||||
p.pairingKey = res.GetPairingKey()
|
|
||||||
p.client.Logger.Debug().Any("response", res).Msg("Registerphonerelay response")
|
|
||||||
url, qrErr := p.GenerateQRCodeData()
|
|
||||||
if qrErr != nil {
|
|
||||||
return nil, qrErr
|
|
||||||
}
|
|
||||||
p.client.triggerEvent(&events.QR{URL: url})
|
|
||||||
p.startRefreshRelayTask()
|
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pairer) startRefreshRelayTask() {
|
func (c *Client) RefreshPhoneRelay() (string, error) {
|
||||||
if p.ticker != nil {
|
|
||||||
p.ticker.Stop()
|
|
||||||
}
|
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
|
||||||
p.ticker = ticker
|
|
||||||
go func() {
|
|
||||||
for range ticker.C {
|
|
||||||
p.RefreshPhoneRelay()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pairer) RefreshPhoneRelay() {
|
|
||||||
body, err := proto.Marshal(&binary.AuthenticationContainer{
|
body, err := proto.Marshal(&binary.AuthenticationContainer{
|
||||||
AuthMessage: &binary.AuthMessage{
|
AuthMessage: &binary.AuthMessage{
|
||||||
RequestID: uuid.NewString(),
|
RequestID: uuid.NewString(),
|
||||||
Network: &payload.Network,
|
Network: &util.Network,
|
||||||
TachyonAuthToken: p.client.authData.TachyonAuthToken,
|
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||||
ConfigVersion: payload.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.client.Logger.Err(err).Msg("refresh phone relay err")
|
return "", err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
relayResponse, reqErr := p.client.MakeRelayRequest(util.RefreshPhoneRelayURL, body)
|
relayResponse, err := c.MakeRelayRequest(util.RefreshPhoneRelayURL, body)
|
||||||
if reqErr != nil {
|
if err != nil {
|
||||||
p.client.Logger.Err(reqErr).Msg("refresh phone relay err")
|
return "", err
|
||||||
}
|
}
|
||||||
responseBody, err2 := io.ReadAll(relayResponse.Body)
|
responseBody, err := io.ReadAll(relayResponse.Body)
|
||||||
defer relayResponse.Body.Close()
|
defer relayResponse.Body.Close()
|
||||||
if err2 != nil {
|
if err != nil {
|
||||||
p.client.Logger.Err(err2).Msg("refresh phone relay err")
|
return "", err
|
||||||
}
|
}
|
||||||
p.client.Logger.Debug().Any("responseLength", len(responseBody)).Msg("Response Body Length")
|
|
||||||
res := &binary.RefreshPhoneRelayResponse{}
|
res := &binary.RefreshPhoneRelayResponse{}
|
||||||
err3 := proto.Unmarshal(responseBody, res)
|
err = proto.Unmarshal(responseBody, res)
|
||||||
if err3 != nil {
|
if err != nil {
|
||||||
p.client.Logger.Err(err3)
|
return "", err
|
||||||
}
|
}
|
||||||
p.pairingKey = res.GetPairKey()
|
qr, err := c.GenerateQRCodeData(res.GetPairKey())
|
||||||
p.client.Logger.Debug().Any("res", res).Msg("RefreshPhoneRelayResponse")
|
if err != nil {
|
||||||
url, qrErr := p.GenerateQRCodeData()
|
return "", err
|
||||||
if qrErr != nil {
|
|
||||||
panic(qrErr)
|
|
||||||
}
|
}
|
||||||
p.client.triggerEvent(&events.QR{URL: url})
|
return qr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetWebEncryptionKey() (*binary.WebEncryptionKeyResponse, error) {
|
func (c *Client) GetWebEncryptionKey() (*binary.WebEncryptionKeyResponse, error) {
|
||||||
body, err := proto.Marshal(&binary.AuthenticationContainer{
|
body, err := proto.Marshal(&binary.AuthenticationContainer{
|
||||||
AuthMessage: &binary.AuthMessage{
|
AuthMessage: &binary.AuthMessage{
|
||||||
RequestID: uuid.NewString(),
|
RequestID: uuid.NewString(),
|
||||||
TachyonAuthToken: c.authData.TachyonAuthToken,
|
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||||
ConfigVersion: payload.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -160,25 +113,20 @@ func (c *Client) GetWebEncryptionKey() (*binary.WebEncryptionKeyResponse, error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if c.pairer != nil {
|
|
||||||
if c.pairer.ticker != nil {
|
|
||||||
c.pairer.ticker.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parsedResponse, nil
|
return parsedResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Unpair() (*binary.RevokeRelayPairingResponse, error) {
|
func (c *Client) Unpair() (*binary.RevokeRelayPairingResponse, error) {
|
||||||
if c.authData.TachyonAuthToken == nil || c.authData.Browser == nil {
|
if c.AuthData.TachyonAuthToken == nil || c.AuthData.Browser == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
payload, err := proto.Marshal(&binary.RevokeRelayPairing{
|
payload, err := proto.Marshal(&binary.RevokeRelayPairing{
|
||||||
AuthMessage: &binary.AuthMessage{
|
AuthMessage: &binary.AuthMessage{
|
||||||
RequestID: uuid.NewString(),
|
RequestID: uuid.NewString(),
|
||||||
TachyonAuthToken: c.authData.TachyonAuthToken,
|
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||||
ConfigVersion: payload.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
Browser: c.authData.Browser,
|
Browser: c.AuthData.Browser,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -17,7 +17,7 @@ func (c *Client) handlePairingEvent(response *pblite.Response) {
|
||||||
|
|
||||||
switch evt := pairEventData.Event.(type) {
|
switch evt := pairEventData.Event.(type) {
|
||||||
case *binary.PairEvents_Paired:
|
case *binary.PairEvents_Paired:
|
||||||
callbackErr := c.pairCallback(evt.Paired)
|
callbackErr := c.completePairing(evt.Paired)
|
||||||
if callbackErr != nil {
|
if callbackErr != nil {
|
||||||
panic(callbackErr)
|
panic(callbackErr)
|
||||||
}
|
}
|
||||||
|
@ -29,27 +29,12 @@ func (c *Client) handlePairingEvent(response *pblite.Response) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) NewDevicePair(mobile, browser *binary.Device) *pblite.DevicePair {
|
func (c *Client) completePairing(data *binary.PairedData) error {
|
||||||
return &pblite.DevicePair{
|
c.updateTachyonAuthToken(data.GetTokenData().GetTachyonAuthToken(), data.GetTokenData().GetTTL())
|
||||||
Mobile: mobile,
|
c.AuthData.Mobile = data.Mobile
|
||||||
Browser: browser,
|
c.AuthData.Browser = data.Browser
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) pairCallback(data *binary.PairedData) error {
|
c.triggerEvent(&events.PairSuccessful{PairedData: data})
|
||||||
|
|
||||||
tokenData := data.GetTokenData()
|
|
||||||
c.updateTachyonAuthToken(tokenData.GetTachyonAuthToken(), tokenData.GetTTL())
|
|
||||||
|
|
||||||
c.updateDevicePair(data.Mobile, data.Browser)
|
|
||||||
|
|
||||||
webEncryptionKeyResponse, webErr := c.GetWebEncryptionKey()
|
|
||||||
if webErr != nil {
|
|
||||||
return webErr
|
|
||||||
}
|
|
||||||
c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
|
|
||||||
|
|
||||||
c.triggerEvent(&events.PairSuccessful{data})
|
|
||||||
|
|
||||||
reconnectErr := c.Reconnect()
|
reconnectErr := c.Reconnect()
|
||||||
if reconnectErr != nil {
|
if reconnectErr != nil {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/routes"
|
"go.mau.fi/mautrix-gmessages/libgm/routes"
|
||||||
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SendMessageBuilder struct {
|
type SendMessageBuilder struct {
|
||||||
|
@ -32,7 +33,7 @@ func NewSendMessageBuilder(tachyonAuthToken []byte, pairedDevice *binary.Device,
|
||||||
MessageAuth: &binary.SendMessageAuth{
|
MessageAuth: &binary.SendMessageAuth{
|
||||||
RequestID: requestId,
|
RequestID: requestId,
|
||||||
TachyonAuthToken: tachyonAuthToken,
|
TachyonAuthToken: tachyonAuthToken,
|
||||||
ConfigVersion: ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
EmptyArr: &binary.EmptyArr{},
|
EmptyArr: &binary.EmptyArr{},
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,11 +9,11 @@ import (
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/util"
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Pairer) GenerateQRCodeData() (string, error) {
|
func (c *Client) GenerateQRCodeData(pairingKey []byte) (string, error) {
|
||||||
urlData := &binary.URLData{
|
urlData := &binary.URLData{
|
||||||
PairingKey: p.pairingKey,
|
PairingKey: pairingKey,
|
||||||
AESKey: p.client.authData.RequestCrypto.AESKey,
|
AESKey: c.AuthData.RequestCrypto.AESKey,
|
||||||
HMACKey: p.client.authData.RequestCrypto.HMACKey,
|
HMACKey: c.AuthData.RequestCrypto.HMACKey,
|
||||||
}
|
}
|
||||||
encodedURLData, err := proto.Marshal(urlData)
|
encodedURLData, err := proto.Marshal(urlData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/events"
|
"go.mau.fi/mautrix-gmessages/libgm/events"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/payload"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
||||||
|
@ -51,8 +50,8 @@ func (r *RPC) ListenReceiveMessages() {
|
||||||
receivePayload, err := pblite.Marshal(&binary.ReceiveMessagesRequest{
|
receivePayload, err := pblite.Marshal(&binary.ReceiveMessagesRequest{
|
||||||
Auth: &binary.AuthMessage{
|
Auth: &binary.AuthMessage{
|
||||||
RequestID: listenReqID,
|
RequestID: listenReqID,
|
||||||
TachyonAuthToken: r.client.authData.TachyonAuthToken,
|
TachyonAuthToken: r.client.AuthData.TachyonAuthToken,
|
||||||
ConfigVersion: payload.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
Unknown: &binary.ReceiveMessagesRequest_UnknownEmptyObject2{
|
Unknown: &binary.ReceiveMessagesRequest_UnknownEmptyObject2{
|
||||||
Unknown: &binary.ReceiveMessagesRequest_UnknownEmptyObject1{},
|
Unknown: &binary.ReceiveMessagesRequest_UnknownEmptyObject1{},
|
||||||
|
@ -91,7 +90,7 @@ func (r *RPC) ListenReceiveMessages() {
|
||||||
}
|
}
|
||||||
r.client.Logger.Debug().Int("statusCode", resp.StatusCode).Msg("Long polling opened")
|
r.client.Logger.Debug().Int("statusCode", resp.StatusCode).Msg("Long polling opened")
|
||||||
r.conn = resp.Body
|
r.conn = resp.Body
|
||||||
if r.client.authData.Browser != nil {
|
if r.client.AuthData.Browser != nil {
|
||||||
go func() {
|
go func() {
|
||||||
err := r.client.NotifyDittoActivity()
|
err := r.client.NotifyDittoActivity()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -73,9 +73,9 @@ func (s *SessionHandler) sendMessage(actionType binary.ActionType, encryptedData
|
||||||
|
|
||||||
func (s *SessionHandler) buildMessage(actionType binary.ActionType, encryptedData proto.Message) (string, []byte, binary.ActionType, error) {
|
func (s *SessionHandler) buildMessage(actionType binary.ActionType, encryptedData proto.Message) (string, []byte, binary.ActionType, error) {
|
||||||
var requestID string
|
var requestID string
|
||||||
pairedDevice := s.client.authData.Mobile
|
pairedDevice := s.client.AuthData.Mobile
|
||||||
sessionId := s.client.sessionHandler.sessionID
|
sessionId := s.client.sessionHandler.sessionID
|
||||||
token := s.client.authData.TachyonAuthToken
|
token := s.client.AuthData.TachyonAuthToken
|
||||||
|
|
||||||
routeInfo, ok := routes.Routes[actionType]
|
routeInfo, ok := routes.Routes[actionType]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -91,11 +91,11 @@ func (s *SessionHandler) buildMessage(actionType binary.ActionType, encryptedDat
|
||||||
tmpMessage := payload.NewSendMessageBuilder(token, pairedDevice, requestID, sessionId).SetRoute(routeInfo.Action).SetSessionId(s.sessionID)
|
tmpMessage := payload.NewSendMessageBuilder(token, pairedDevice, requestID, sessionId).SetRoute(routeInfo.Action).SetSessionId(s.sessionID)
|
||||||
|
|
||||||
if encryptedData != nil {
|
if encryptedData != nil {
|
||||||
tmpMessage.SetEncryptedProtoMessage(encryptedData, s.client.authData.RequestCrypto)
|
tmpMessage.SetEncryptedProtoMessage(encryptedData, s.client.AuthData.RequestCrypto)
|
||||||
}
|
}
|
||||||
|
|
||||||
if routeInfo.UseTTL {
|
if routeInfo.UseTTL {
|
||||||
tmpMessage.SetTTL(s.client.authData.TachyonTTL)
|
tmpMessage.SetTTL(s.client.AuthData.TachyonTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
message, buildErr := tmpMessage.Build()
|
message, buildErr := tmpMessage.Build()
|
||||||
|
@ -142,14 +142,14 @@ func (s *SessionHandler) sendAckRequest() {
|
||||||
for i, reqID := range dataToAck {
|
for i, reqID := range dataToAck {
|
||||||
ackMessages[i] = &binary.AckMessageData{
|
ackMessages[i] = &binary.AckMessageData{
|
||||||
RequestID: reqID,
|
RequestID: reqID,
|
||||||
Device: s.client.authData.Browser,
|
Device: s.client.AuthData.Browser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ackMessagePayload := &binary.AckMessagePayload{
|
ackMessagePayload := &binary.AckMessagePayload{
|
||||||
AuthData: &binary.AuthMessage{
|
AuthData: &binary.AuthMessage{
|
||||||
RequestID: uuid.NewString(),
|
RequestID: uuid.NewString(),
|
||||||
TachyonAuthToken: s.client.authData.TachyonAuthToken,
|
TachyonAuthToken: s.client.AuthData.TachyonAuthToken,
|
||||||
ConfigVersion: payload.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
EmptyArr: &binary.EmptyArr{},
|
EmptyArr: &binary.EmptyArr{},
|
||||||
Acks: ackMessages,
|
Acks: ackMessages,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package payload
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ConfigMessage = &binary.ConfigVersion{
|
var ConfigMessage = &binary.ConfigVersion{
|
||||||
|
@ -14,7 +13,7 @@ var ConfigMessage = &binary.ConfigVersion{
|
||||||
}
|
}
|
||||||
var Network = "Bugle"
|
var Network = "Bugle"
|
||||||
var BrowserDetailsMessage = &binary.BrowserDetails{
|
var BrowserDetailsMessage = &binary.BrowserDetails{
|
||||||
UserAgent: util.UserAgent,
|
UserAgent: UserAgent,
|
||||||
BrowserType: binary.BrowserTypes_OTHER,
|
BrowserType: binary.BrowserTypes_OTHER,
|
||||||
Os: "libgm",
|
Os: "libgm",
|
||||||
SomeBool: true,
|
SomeBool: true,
|
6
main.go
6
main.go
|
@ -29,7 +29,7 @@ import (
|
||||||
"go.mau.fi/mautrix-gmessages/config"
|
"go.mau.fi/mautrix-gmessages/config"
|
||||||
"go.mau.fi/mautrix-gmessages/database"
|
"go.mau.fi/mautrix-gmessages/database"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/payload"
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Information to find out exactly which commit the bridge was built from.
|
// Information to find out exactly which commit the bridge was built from.
|
||||||
|
@ -67,12 +67,12 @@ func (br *GMBridge) Init() {
|
||||||
br.CommandProcessor = commands.NewProcessor(&br.Bridge)
|
br.CommandProcessor = commands.NewProcessor(&br.Bridge)
|
||||||
br.RegisterCommands()
|
br.RegisterCommands()
|
||||||
|
|
||||||
payload.BrowserDetailsMessage.Os = br.Config.GoogleMessages.OS
|
util.BrowserDetailsMessage.Os = br.Config.GoogleMessages.OS
|
||||||
browserVal, ok := binary.BrowserTypes_value[br.Config.GoogleMessages.Browser]
|
browserVal, ok := binary.BrowserTypes_value[br.Config.GoogleMessages.Browser]
|
||||||
if !ok {
|
if !ok {
|
||||||
br.ZLog.Error().Str("browser_value", br.Config.GoogleMessages.Browser).Msg("Invalid browser value")
|
br.ZLog.Error().Str("browser_value", br.Config.GoogleMessages.Browser).Msg("Invalid browser value")
|
||||||
} else {
|
} else {
|
||||||
payload.BrowserDetailsMessage.BrowserType = binary.BrowserTypes(browserVal)
|
util.BrowserDetailsMessage.BrowserType = binary.BrowserTypes(browserVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
Segment.log = br.ZLog.With().Str("component", "segment").Logger()
|
Segment.log = br.ZLog.With().Str("component", "segment").Logger()
|
||||||
|
|
83
user.go
83
user.go
|
@ -71,10 +71,9 @@ type User struct {
|
||||||
batteryLow bool
|
batteryLow bool
|
||||||
mobileData bool
|
mobileData bool
|
||||||
|
|
||||||
DoublePuppetIntent *appservice.IntentAPI
|
pairSuccessChan chan struct{}
|
||||||
|
|
||||||
hackyLoginCommand *WrappedCommandEvent
|
DoublePuppetIntent *appservice.IntentAPI
|
||||||
hackyLoginCommandPrevEvent id.EventID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *GMBridge) getUserByMXID(userID id.UserID, onlyIfExists bool) *User {
|
func (br *GMBridge) getUserByMXID(userID id.UserID, onlyIfExists bool) *User {
|
||||||
|
@ -389,12 +388,11 @@ func (user *User) SetManagementRoom(roomID id.RoomID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrAlreadyLoggedIn = errors.New("already logged in")
|
var ErrAlreadyLoggedIn = errors.New("already logged in")
|
||||||
|
var ErrLoginInProgress = errors.New("login already in progress")
|
||||||
|
var ErrLoginTimeout = errors.New("login timed out")
|
||||||
|
|
||||||
func (user *User) createClient() {
|
func (user *User) createClient(sess *libgm.AuthData) {
|
||||||
if user.Session == nil {
|
user.Client = libgm.NewClient(sess, user.zlog.With().Str("component", "libgm").Logger())
|
||||||
user.Session = libgm.NewAuthData()
|
|
||||||
}
|
|
||||||
user.Client = libgm.NewClient(user.Session, user.zlog.With().Str("component", "libgm").Logger())
|
|
||||||
user.Client.SetEventHandler(user.syncHandleEvent)
|
user.Client.SetEventHandler(user.syncHandleEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,20 +400,66 @@ func (user *User) syncHandleEvent(ev any) {
|
||||||
go user.HandleEvent(ev)
|
go user.HandleEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) Login(ctx context.Context) (<-chan string, error) {
|
type qrChannelItem struct {
|
||||||
|
success bool
|
||||||
|
qr string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) Login(ctx context.Context, maxAttempts int) (<-chan qrChannelItem, error) {
|
||||||
user.connLock.Lock()
|
user.connLock.Lock()
|
||||||
defer user.connLock.Unlock()
|
defer user.connLock.Unlock()
|
||||||
if user.Session != nil {
|
if user.Session != nil {
|
||||||
return nil, ErrAlreadyLoggedIn
|
return nil, ErrAlreadyLoggedIn
|
||||||
} else if user.Client != nil {
|
} else if user.Client != nil {
|
||||||
user.unlockedDeleteConnection()
|
user.unlockedDeleteConnection()
|
||||||
|
} else if user.pairSuccessChan != nil {
|
||||||
|
return nil, ErrLoginInProgress
|
||||||
}
|
}
|
||||||
user.createClient()
|
pairSuccessChan := make(chan struct{})
|
||||||
err := user.Client.Connect()
|
user.pairSuccessChan = pairSuccessChan
|
||||||
|
user.createClient(libgm.NewAuthData())
|
||||||
|
qr, err := user.Client.StartLogin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
user.DeleteConnection()
|
||||||
|
user.pairSuccessChan = nil
|
||||||
return nil, fmt.Errorf("failed to connect to Google Messages: %w", err)
|
return nil, fmt.Errorf("failed to connect to Google Messages: %w", err)
|
||||||
}
|
}
|
||||||
return nil, nil
|
ch := make(chan qrChannelItem, maxAttempts+2)
|
||||||
|
ch <- qrChannelItem{qr: qr}
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
success := false
|
||||||
|
defer func() {
|
||||||
|
ticker.Stop()
|
||||||
|
if !success {
|
||||||
|
user.zlog.Debug().Msg("Deleting connection as login wasn't successful")
|
||||||
|
user.DeleteConnection()
|
||||||
|
}
|
||||||
|
user.pairSuccessChan = nil
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
for ; maxAttempts > 0; maxAttempts-- {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
user.zlog.Debug().Err(ctx.Err()).Msg("Login context cancelled")
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
qr, err := user.Client.RefreshPhoneRelay()
|
||||||
|
if err != nil {
|
||||||
|
ch <- qrChannelItem{err: fmt.Errorf("failed to refresh QR code: %w", err)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- qrChannelItem{qr: qr}
|
||||||
|
case <-pairSuccessChan:
|
||||||
|
ch <- qrChannelItem{success: true}
|
||||||
|
success = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch <- qrChannelItem{err: ErrLoginTimeout}
|
||||||
|
}()
|
||||||
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) Connect() bool {
|
func (user *User) Connect() bool {
|
||||||
|
@ -431,7 +475,7 @@ func (user *User) Connect() bool {
|
||||||
}
|
}
|
||||||
user.zlog.Debug().Msg("Connecting to Google Messages")
|
user.zlog.Debug().Msg("Connecting to Google Messages")
|
||||||
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting, Error: GMConnecting})
|
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting, Error: GMConnecting})
|
||||||
user.createClient()
|
user.createClient(user.Session)
|
||||||
err := user.Client.Connect()
|
err := user.Client.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user.zlog.Err(err).Msg("Error connecting to Google Messages")
|
user.zlog.Err(err).Msg("Error connecting to Google Messages")
|
||||||
|
@ -512,11 +556,6 @@ func (user *User) sendMarkdownBridgeAlert(formatString string, args ...interface
|
||||||
|
|
||||||
func (user *User) HandleEvent(event interface{}) {
|
func (user *User) HandleEvent(event interface{}) {
|
||||||
switch v := event.(type) {
|
switch v := event.(type) {
|
||||||
case *events.QR:
|
|
||||||
// This shouldn't be here
|
|
||||||
if user.hackyLoginCommand != nil {
|
|
||||||
user.hackyLoginCommandPrevEvent = user.sendQR(user.hackyLoginCommand, v.URL, user.hackyLoginCommandPrevEvent)
|
|
||||||
}
|
|
||||||
case *events.ListenFatalError:
|
case *events.ListenFatalError:
|
||||||
user.Logout(status.BridgeState{
|
user.Logout(status.BridgeState{
|
||||||
StateEvent: status.StateBadCredentials,
|
StateEvent: status.StateBadCredentials,
|
||||||
|
@ -534,15 +573,17 @@ func (user *User) HandleEvent(event interface{}) {
|
||||||
user.longPollingError = nil
|
user.longPollingError = nil
|
||||||
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
|
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
|
||||||
case *events.PairSuccessful:
|
case *events.PairSuccessful:
|
||||||
user.hackyLoginCommand = nil
|
user.Session = user.Client.AuthData
|
||||||
user.hackyLoginCommandPrevEvent = ""
|
|
||||||
user.tryAutomaticDoublePuppeting()
|
|
||||||
user.Phone = v.GetMobile().GetSourceID()
|
user.Phone = v.GetMobile().GetSourceID()
|
||||||
|
user.tryAutomaticDoublePuppeting()
|
||||||
user.addToPhoneMap()
|
user.addToPhoneMap()
|
||||||
err := user.Update(context.TODO())
|
err := user.Update(context.TODO())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user.zlog.Err(err).Msg("Failed to update session in database")
|
user.zlog.Err(err).Msg("Failed to update session in database")
|
||||||
}
|
}
|
||||||
|
if ch := user.pairSuccessChan; ch != nil {
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
case *binary.RevokePairData:
|
case *binary.RevokePairData:
|
||||||
user.zlog.Info().Any("revoked_device", v.GetRevokedDevice()).Msg("Got pair revoked event")
|
user.zlog.Info().Any("revoked_device", v.GetRevokedDevice()).Msg("Got pair revoked event")
|
||||||
user.Logout(status.BridgeState{
|
user.Logout(status.BridgeState{
|
||||||
|
|
Loading…
Reference in a new issue