Skip to content
Merged
43 changes: 30 additions & 13 deletions pkg/connector/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@ type MetaClient struct {
UserLogin *bridgev2.UserLogin
Ghost *bridgev2.Ghost

stopHandlingTables atomic.Pointer[context.CancelFunc]
initialTable atomic.Pointer[table.LSTable]
initialTableHandled atomic.Bool
parsedTables chan *parsedTable
backfillCollectors map[int64]*BackfillCollector
backfillLock sync.Mutex
connectLock sync.Mutex
stopConnectAttempt atomic.Pointer[context.CancelFunc]
permanentErrored atomic.Bool
stopHandlingTables atomic.Pointer[context.CancelFunc]
initialTable atomic.Pointer[table.LSTable]
initialTableHandled atomic.Bool
parsedTables chan *parsedTable
backfillCollectors map[int64]*BackfillCollector
backfillLock sync.Mutex
connectLock sync.Mutex
pushRegistrationLock sync.Mutex
stopConnectAttempt atomic.Pointer[context.CancelFunc]
permanentErrored atomic.Bool

editChannels *exsync.Map[string, chan *FBEditEvent]

Expand Down Expand Up @@ -81,6 +82,12 @@ func (m *MetaConnector) getMessagixConfig() *messagix.Config {

func (m *MetaConnector) LoadUserLogin(ctx context.Context, login *bridgev2.UserLogin) error {
loginMetadata := login.Metadata.(*metaid.UserLoginMetadata)
if err := validateNativeSession(loginMetadata.NativeSession); err != nil {
return err
}
if login.Client != nil {
login.Client.Disconnect()
}
c := &MetaClient{
Main: m,
LoginMeta: loginMetadata,
Expand Down Expand Up @@ -150,14 +157,22 @@ func (m *MetaClient) ensureMessagixClient() {
m.Main.getMessagixConfig(),
)
m.Client.SetEventHandler(m.handleMetaEvent)
m.Client.MessengerLite.SetNativeSession(m.LoginMeta.NativeSession)
}
}

func (m *MetaClient) ExportCredentials(ctx context.Context) any {
if m.Client == nil {
return nil
}
return m.Client.GetCookies()
if m.LoginMeta.NativeSession == nil {
return m.Client.GetCookies()
}
return &metaCredentials{
Platform: m.LoginMeta.Platform,
Cookies: m.Client.GetCookies(),
NativeSession: m.LoginMeta.NativeSession,
}
}

func (m *MetaClient) Connect(ctx context.Context) {
Expand Down Expand Up @@ -454,9 +469,11 @@ func (m *MetaClient) connectE2EE() error {
if m.WADevice == nil {
isNew = true
m.WADevice = m.Main.DeviceStore.NewDevice()
}
if suggested := m.Client.MessengerLite.GetSuggestedDeviceID(); suggested != uuid.Nil {
m.WADevice.FacebookUUID = suggested
if session := m.LoginMeta.NativeSession; session != nil {
m.WADevice.FacebookUUID = session.DeviceID
} else if suggested := m.Client.MessengerLite.GetSuggestedDeviceID(); suggested != uuid.Nil {
m.WADevice.FacebookUUID = suggested
}
}
m.Client.SetDevice(m.WADevice)

Expand Down
4 changes: 3 additions & 1 deletion pkg/connector/handlewhatsapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ func (m *MetaClient) e2eeEventHandler(rawEvt any) bool {
Any("ig_transport", evt.IGTransport).
Any("payload", evt.Message).
Msg("Received WhatsApp message")
m.Main.Bridge.QueueRemoteEvent(m.UserLogin, &EnsureWAChatStateEvent{JID: evt.Info.Chat, m: m})
if !m.Main.Bridge.QueueRemoteEvent(m.UserLogin, &EnsureWAChatStateEvent{JID: evt.Info.Chat, m: m}).Success {
return false
}
return m.Main.Bridge.QueueRemoteEvent(m.UserLogin, &WAMessageEvent{FBMessage: evt, m: m}).Success
case *events.ChatPresence:
m.handleWAChatPresence(m.Main.Bridge.BackgroundCtx, evt)
Expand Down
94 changes: 79 additions & 15 deletions pkg/connector/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package connector

import (
"context"
"encoding/json"
"errors"
"fmt"
"maps"
"net/http"
"slices"
"time"

"github.com/google/uuid"
"github.com/rs/zerolog"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/database"
Expand Down Expand Up @@ -73,24 +75,76 @@ func (m *MetaConnector) CreateLogin(ctx context.Context, user *bridgev2.User, fl
}, nil
}

// This creates a user login using credentials transferred from another instance of the meta bridge,
// via the `ExportCredentials` API.
type metaCredentials struct {
Platform types.Platform `json:"platform"`
Cookies *cookies.Cookies `json:"cookies"`
NativeSession *types.NativeSession `json:"native_session,omitempty"`
}

func validateNativeSession(session *types.NativeSession) error {
if session != nil && (session.AccessToken == "" || session.AppID != useragent.MessengerLiteAndroidAppID || session.DeviceID == uuid.Nil || session.FamilyDeviceID == uuid.Nil) {
return fmt.Errorf("invalid native Messenger session")
}
return nil
}

func (m *MetaConnector) CreateUserLoginFromCredentials(ctx context.Context, user *bridgev2.User, credentials any) error {
creds, ok := credentials.(map[string]any)
if !ok {
return fmt.Errorf("invalid credentials type: %T", credentials)
}
cleanCreds := make(map[string]string, len(creds))
for k, v := range creds {
cleanCreds[k] = v.(string)
var transferred metaCredentials
if _, structured := creds["cookies"]; structured {
data, err := json.Marshal(creds)
if err != nil {
return fmt.Errorf("failed to encode transferred credentials: %w", err)
} else if err = json.Unmarshal(data, &transferred); err != nil {
return fmt.Errorf("failed to decode transferred credentials: %w", err)
} else if transferred.Cookies == nil || !transferred.Platform.IsMessenger() {
return fmt.Errorf("invalid transferred Messenger credentials")
} else if err = validateNativeSession(transferred.NativeSession); err != nil {
return err
}
transferred.Cookies.Platform = transferred.Platform
} else {
transferred.Platform = types.Facebook
if m.Config.Tor {
transferred.Platform = types.FacebookTor
}
transferred.Cookies = &cookies.Cookies{Platform: transferred.Platform}
values := make(map[cookies.MetaCookieName]string, len(creds))
for key, value := range creds {
str, ok := value.(string)
if !ok {
return fmt.Errorf("invalid cookie value type for %s", key)
}
values[cookies.MetaCookieName(key)] = str
}
transferred.Cookies.UpdateValues(values)
}
log := zerolog.Ctx(ctx).With().Str("component", "messagix").Logger()
if session := transferred.NativeSession; session != nil {
authClient, err := getMessagixClient(log, m, &cookies.Cookies{Platform: types.MessengerLiteAndroid}, m.Config.ProxyMessengerLite)
if err != nil {
return err
}
session.DeviceID = uuid.New()
authClient.MessengerLite.SetNativeSession(session)
newCookies, err := authClient.MessengerLite.ExchangeTransientToken(ctx, session.AccessToken)
if err != nil {
return fmt.Errorf("failed to create transferred native session: %w", err)
} else if newCookies.GetUserID() != transferred.Cookies.GetUserID() {
return fmt.Errorf("exchanged native session account does not match login")
}
newCookies.Platform = transferred.Platform
transferred.Cookies = newCookies
transferred.NativeSession = authClient.MessengerLite.GetNativeSession()
}

login, err := m.CreateLogin(ctx, user, FlowIDFacebookCookies)
client, err := getMessagixClient(log, m, transferred.Cookies, m.Config.ProxyOther)
if err != nil {
return err
}

step, err := login.(bridgev2.LoginProcessCookies).SubmitCookies(ctx, cleanCreds)
step, err := loginWithCookies(ctx, log, client, user, m, transferred.Cookies, transferred.NativeSession)
if err != nil {
return err
} else if step.Type != bridgev2.LoginStepTypeComplete {
Expand Down Expand Up @@ -198,7 +252,14 @@ func loginWithCookies(
bridgeUser *bridgev2.User,
conn *MetaConnector,
c *cookies.Cookies,
nativeSession *types.NativeSession,
) (*bridgev2.LoginStep, error) {
if missing := c.GetMissingCookieNames(); len(missing) > 0 {
return nil, loginerrors.MissingCookies.AppendMessage(": %v", missing)
} else if err := validateNativeSession(nativeSession); err != nil {
return nil, err
}
client.MessengerLite.SetNativeSession(nativeSession)

log.Debug().
Strs("cookie_names", exslices.CastToString[string](slices.Collect(maps.Keys(c.GetAll())))).
Expand All @@ -221,21 +282,24 @@ func loginWithCookies(

id := user.GetFBID()
loginID := metaid.MakeUserLoginID(id)
if id != c.GetUserID() {
return nil, fmt.Errorf("logged-in account does not match cookies")
}
var loginUA string
if req, ok := ctx.Value("fi.mau.provision.request").(*http.Request); ok {
loginUA = req.Header.Get("User-Agent")
}

ul, err := bridgeUser.NewLogin(ctx, &database.UserLogin{
ID: loginID,
RemoteName: user.GetName(),
RemoteProfile: status.RemoteProfile{
Name: user.GetName(),
},
Metadata: &metaid.UserLoginMetadata{
Platform: c.Platform,
Cookies: c,
LoginUA: loginUA,
Platform: c.Platform,
Cookies: c,
LoginUA: loginUA,
NativeSession: nativeSession,
},
}, nil)
if err != nil {
Expand Down Expand Up @@ -281,7 +345,7 @@ func (m *MetaCookieLogin) SubmitCookies(ctx context.Context, strCookies map[stri
if err != nil {
return nil, err
}
return loginWithCookies(ctx, log, client, m.User, m.Main, c)
return loginWithCookies(ctx, log, client, m.User, m.Main, c, nil)
}

type MetaNativeLogin struct {
Expand Down Expand Up @@ -366,7 +430,7 @@ func (m *MetaNativeLogin) proceed(ctx context.Context, userInput map[string]stri

newClient.GetCookies().UpdateValues(newCookies.GetAll())

step, err = loginWithCookies(ctx, log, newClient, m.User, m.Main, newCookies)
step, err = loginWithCookies(ctx, log, newClient, m.User, m.Main, newCookies, m.SavedClient.MessengerLite.GetNativeSession())
if err != nil {
return nil, err
}
Expand Down
Loading
Loading