Add command for Google account login

This commit is contained in:
Tulir Asokan 2024-02-22 23:05:00 +02:00
parent 02f0b9e2ca
commit 20d05c90d3
2 changed files with 112 additions and 17 deletions

View file

@ -17,6 +17,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"strings" "strings"
@ -40,7 +41,8 @@ type WrappedCommandEvent struct {
func (br *GMBridge) RegisterCommands() { func (br *GMBridge) RegisterCommands() {
proc := br.CommandProcessor.(*commands.Processor) proc := br.CommandProcessor.(*commands.Processor)
proc.AddHandlers( proc.AddHandlers(
cmdLogin, cmdLoginQR,
cmdLoginGoogle,
cmdDeleteSession, cmdDeleteSession,
cmdLogout, cmdLogout,
cmdReconnect, cmdReconnect,
@ -70,16 +72,17 @@ var (
HelpSectionPortalManagement = commands.HelpSection{Name: "Portal management", Order: 20} HelpSectionPortalManagement = commands.HelpSection{Name: "Portal management", Order: 20}
) )
var cmdLogin = &commands.FullHandler{ var cmdLoginQR = &commands.FullHandler{
Func: wrapCommand(fnLogin), Func: wrapCommand(fnLoginQR),
Name: "login", Name: "login-qr",
Aliases: []string{"login"},
Help: commands.HelpMeta{ Help: commands.HelpMeta{
Section: commands.HelpSectionAuth, Section: commands.HelpSectionAuth,
Description: "Link the bridge to Google Messages on your Android phone as a web client.", Description: "Link the bridge to Google Messages on your Android phone by scanning a QR code.",
}, },
} }
func fnLogin(ce *WrappedCommandEvent) { func fnLoginQR(ce *WrappedCommandEvent) {
if ce.User.Session != nil { if ce.User.Session != nil {
if ce.User.IsConnected() { if ce.User.IsConnected() {
ce.Reply("You're already logged in") ce.Reply("You're already logged in")
@ -123,6 +126,64 @@ func fnLogin(ce *WrappedCommandEvent) {
ce.ZLog.Trace().Msg("Login command finished") ce.ZLog.Trace().Msg("Login command finished")
} }
var cmdLoginGoogle = &commands.FullHandler{
Func: wrapCommand(fnLoginGoogle),
Name: "login-google",
Aliases: []string{"login"},
Help: commands.HelpMeta{
Section: commands.HelpSectionAuth,
Description: "Link the bridge to Google Messages on your Android phone by logging in with your Google account.",
},
}
func fnLoginGoogle(ce *WrappedCommandEvent) {
if ce.User.Session != nil {
if ce.User.IsConnected() {
ce.Reply("You're already logged in")
} else {
ce.Reply("You're already logged in. Perhaps you wanted to `reconnect`?")
}
return
} else if ce.User.pairSuccessChan != nil {
ce.Reply("You already have a login in progress")
return
}
ce.User.CommandState = &commands.CommandState{
Next: commands.MinimalHandlerFunc(wrapCommand(fnLoginGoogleCookies)),
Action: "Login",
}
ce.Reply("Send your Google cookies here, formatted as a key-value JSON object (see <https://docs.mau.fi/bridges/go/gmessages/authentication.html> for details)")
}
func fnLoginGoogleCookies(ce *WrappedCommandEvent) {
ce.User.CommandState = nil
if ce.User.Session != nil {
if ce.User.IsConnected() {
ce.Reply("You're already logged in")
} else {
ce.Reply("You're already logged in. Perhaps you wanted to `reconnect`?")
}
return
} else if ce.User.pairSuccessChan != nil {
ce.Reply("You already have a login in progress")
return
}
var cookies map[string]string
err := json.Unmarshal([]byte(ce.RawArgs), &cookies)
if err != nil {
ce.Reply("Failed to parse cookies: %v", err)
return
}
err = ce.User.LoginGoogle(cookies, func(emoji string) {
ce.Reply(emoji)
})
if err != nil {
ce.Reply("Login failed: %v", err)
} else {
ce.Reply("Login successful")
}
}
func (user *User) sendQREdit(ce *WrappedCommandEvent, content *event.MessageEventContent, prevEvent id.EventID) id.EventID { func (user *User) sendQREdit(ce *WrappedCommandEvent, content *event.MessageEventContent, prevEvent id.EventID) id.EventID {
if len(prevEvent) != 0 { if len(prevEvent) != 0 {
content.SetEdit(prevEvent) content.SetEdit(prevEvent)

44
user.go
View file

@ -35,6 +35,7 @@ import (
"maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/bridge" "maunium.net/go/mautrix/bridge"
"maunium.net/go/mautrix/bridge/bridgeconfig" "maunium.net/go/mautrix/bridge/bridgeconfig"
"maunium.net/go/mautrix/bridge/commands"
"maunium.net/go/mautrix/bridge/status" "maunium.net/go/mautrix/bridge/status"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format" "maunium.net/go/mautrix/format"
@ -65,6 +66,7 @@ type User struct {
connLock sync.Mutex connLock sync.Mutex
BridgeState *bridge.BridgeStateQueue BridgeState *bridge.BridgeStateQueue
CommandState *commands.CommandState
spaceMembershipChecked bool spaceMembershipChecked bool
@ -83,13 +85,22 @@ type User struct {
loginInProgress atomic.Bool loginInProgress atomic.Bool
pairSuccessChan chan struct{} pairSuccessChan chan struct{}
ongoingLoginChan <-chan qrChannelItem ongoingLoginChan <-chan qrChannelItem
loginChanReadLock sync.Mutex
lastQRCode string lastQRCode string
cancelLogin func() cancelLogin func()
DoublePuppetIntent *appservice.IntentAPI DoublePuppetIntent *appservice.IntentAPI
} }
func (user *User) GetCommandState() *commands.CommandState {
return user.CommandState
}
func (user *User) SetCommandState(state *commands.CommandState) {
user.CommandState = state
}
var _ commands.CommandingUser = (*User)(nil)
func (br *GMBridge) getUserByMXID(userID id.UserID, onlyIfExists bool) *User { func (br *GMBridge) getUserByMXID(userID id.UserID, onlyIfExists bool) *User {
_, isPuppet := br.ParsePuppetMXID(userID) _, isPuppet := br.ParsePuppetMXID(userID)
if isPuppet || userID == br.Bot.UserID { if isPuppet || userID == br.Bot.UserID {
@ -155,10 +166,6 @@ func (user *User) GetMXID() id.UserID {
return user.MXID return user.MXID
} }
func (user *User) GetCommandState() map[string]interface{} {
return nil
}
func (br *GMBridge) GetUserByMXIDIfExists(userID id.UserID) *User { func (br *GMBridge) GetUserByMXIDIfExists(userID id.UserID) *User {
return br.getUserByMXID(userID, true) return br.getUserByMXID(userID, true)
} }
@ -456,6 +463,33 @@ func (user *User) Login(maxAttempts int) (<-chan qrChannelItem, error) {
return ch, nil return ch, nil
} }
func (user *User) LoginGoogle(cookies map[string]string, emojiCallback func(string)) error {
user.connLock.Lock()
defer user.connLock.Unlock()
if user.Session != nil {
return ErrAlreadyLoggedIn
} else if !user.loginInProgress.CompareAndSwap(false, true) {
return ErrLoginInProgress
}
if user.Client != nil {
user.unlockedDeleteConnection()
}
pairSuccessChan := make(chan struct{})
user.pairSuccessChan = pairSuccessChan
authData := libgm.NewAuthData()
authData.Cookies = cookies
user.createClient(authData)
Analytics.Track(user.MXID, "$login_start")
err := user.Client.DoGaiaPairing(emojiCallback)
if err != nil {
user.unlockedDeleteConnection()
user.pairSuccessChan = nil
user.loginInProgress.Store(false)
return fmt.Errorf("failed to connect to Google Messages: %w", err)
}
return nil
}
func (user *User) Connect() bool { func (user *User) Connect() bool {
user.connLock.Lock() user.connLock.Lock()
defer user.connLock.Unlock() defer user.connLock.Unlock()