From bf2181a9d72d76a7e6e50a683fc4c61ff2490507 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Mon, 6 Jul 2026 13:51:56 +0100 Subject: [PATCH] messagix: implement transient token exchange for cookies --- pkg/messagix/endpoints/facebook.go | 1 + pkg/messagix/messengerlite.go | 79 ++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/pkg/messagix/endpoints/facebook.go b/pkg/messagix/endpoints/facebook.go index 3ba5e5e0..2ecfc160 100644 --- a/pkg/messagix/endpoints/facebook.go +++ b/pkg/messagix/endpoints/facebook.go @@ -38,6 +38,7 @@ func makeMessengerLiteEndpoints(host string) map[string]string { endpoints := makeFacebookEndpoints(host) endpoints["graph_graphql"] = "https://graph.facebook.com/graphql" endpoints["pwd_key"] = "https://graph.facebook.com/pwd_key_fetch" + endpoints["get_session_for_app"] = "https://api.facebook.com/method/auth.getSessionForApp" endpoints["v2.10"] = "https://graph.facebook.com/v2.10" endpoints["cat"] = "https://web.facebook.com/messaging/lightspeed/cat" endpoints["icdc_fetch"] = "https://v.whatsapp.net/v2/fb_icdc_fetch" diff --git a/pkg/messagix/messengerlite.go b/pkg/messagix/messengerlite.go index 6473f8e2..9d604870 100644 --- a/pkg/messagix/messengerlite.go +++ b/pkg/messagix/messengerlite.go @@ -150,14 +150,72 @@ type BloksLoginActionResponsePayload struct { } func convertCookies(payload *BloksLoginActionResponsePayload) *cookies.Cookies { + return cookiesFromRaw(payload.SessionCookies) +} + +func cookiesFromRaw(rawCookies []RawCookie) *cookies.Cookies { newCookies := &cookies.Cookies{Platform: types.MessengerLite} newCookies.UpdateValues(make(map[cookies.MetaCookieName]string)) - for _, raw := range payload.SessionCookies { + for _, raw := range rawCookies { newCookies.Set(cookies.MetaCookieName(raw.Name), raw.Value) } return newCookies } +type getSessionForAppResponse struct { + SessionCookies []RawCookie `json:"session_cookies"` + UID int64 `json:"uid"` + + // Present when the request fails. + ErrorCode int `json:"error_code"` + ErrorMsg string `json:"error_msg"` + ErrorSubcode int `json:"error_subcode"` +} + +func (c *Client) ExchangeTransientToken(ctx context.Context, transientToken string) (*cookies.Cookies, error) { + endpoint := c.GetEndpoint("get_session_for_app") + + query := url.Values{} + query.Set("access_token", transientToken) + query.Set("new_app_id", useragent.MessengerLiteAppID) + query.Set("generate_session_cookies", "1") + query.Set("format", "json") + fullURL := endpoint + "?" + query.Encode() + + analyticsTags, err := httpclient.MakeRequestAnalyticsHeader() + if err != nil { + return nil, err + } + httpHeaders := http.Header{} + for k, v := range map[string]string{ + "accept": "*/*", + "x-fb-appid": useragent.MessengerLiteAppID, + "x-fb-request-analytics-tags": analyticsTags, + "user-agent": useragent.MessengerLiteUserAgent, + "accept-language": "en-US,en;q=0.9", + "request_token": uuid.New().String(), + } { + httpHeaders.Set(k, v) + } + + _, responseBytes, err := c.http.MakeRequest(ctx, fullURL, "GET", httpHeaders, nil, types.NONE) + if err != nil { + return nil, fmt.Errorf("requesting session for transient token: %w", err) + } + + var response getSessionForAppResponse + if err = json.Unmarshal(responseBytes, &response); err != nil { + return nil, fmt.Errorf("parsing session-for-app response: %w", err) + } + if response.ErrorCode != 0 { + return nil, fmt.Errorf("session-for-app error %d/%d: %s", response.ErrorCode, response.ErrorSubcode, response.ErrorMsg) + } + if len(response.SessionCookies) == 0 { + return nil, fmt.Errorf("session-for-app returned no cookies for transient token") + } + return cookiesFromRaw(response.SessionCookies), nil +} + // For testing only func (m *MessengerLiteMethods) SetDeviceIdentifiers(deviceID uuid.UUID) { m.deviceID = deviceID @@ -196,14 +254,17 @@ func (m *MessengerLiteMethods) DoLoginSteps(ctx context.Context, userInput map[s return nil, nil, fmt.Errorf("parsing login response data: %w", err) } - if loginRespPayload.CredentialType == "transient_token" { - // There is an extra step for getting cookies when the credential_type - // field is transient_token, which the bridge does not currently - // implement. Until then, add a warning log. Normally the credential_type - // is two_factor. It depends on the account, not on the MFA method - // selected. - m.client.Logger.Warn().Msg("Got credential_type transient_token, login will fail") - return nil, nil, ErrTransientTokenLogin + if loginRespPayload.CredentialType == "transient_token" && len(loginRespPayload.SessionCookies) == 0 { + if loginRespPayload.AccessToken == "" { + return nil, nil, ErrTransientTokenLogin + } + m.client.Logger.Debug().Msg("Got credential_type transient_token, exchanging for session cookies") + newCookies, err := m.client.ExchangeTransientToken(ctx, loginRespPayload.AccessToken) + if err != nil { + m.client.Logger.Warn().Err(err).Msg("Failed to exchange transient token for session cookies") + return nil, nil, fmt.Errorf("%w: %w", ErrTransientTokenLogin, err) + } + return nil, newCookies, nil } if len(loginRespPayload.SessionCookies) == 0 {