Skip to content

Commit f85ebca

Browse files
committed
feat: use SeedlessOnboardingController accessToken for social pairing
- Modify AuthenticationController to get accessToken from SeedlessOnboardingController state - Add pairing logic that runs non-blocking during signIn - Add state tracking for pairing status to prevent duplicate attempts - Add PAIR_SOCIAL_IDENTIFIER endpoint for social pairing API - Ensure pairing failures don't affect other authentication flows Based on PR #6048 but using accessToken from SeedlessOnboardingController (PR #6060) instead of injecting a separate socialPairingToken.
1 parent 62be127 commit f85ebca

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

packages/profile-sync-controller/src/controllers/authentication/AuthenticationController.ts

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
Env,
2525
JwtBearerAuth,
2626
} from '../../sdk';
27+
import { authorizeOIDC, getNonce, PAIR_SOCIAL_IDENTIFIER } from '../../sdk/authentication-jwt-bearer/services';
28+
import { PairError } from '../../sdk/errors';
2729
import type { MetaMetricsAuth } from '../../shared/types/services';
2830

2931
const controllerName = 'AuthenticationController';
@@ -32,6 +34,8 @@ const controllerName = 'AuthenticationController';
3234
export type AuthenticationControllerState = {
3335
isSignedIn: boolean;
3436
srpSessionData?: Record<string, LoginResponse>;
37+
socialPairingDone?: boolean;
38+
pairingInProgress?: boolean;
3539
};
3640
export const defaultState: AuthenticationControllerState = {
3741
isSignedIn: false,
@@ -45,6 +49,14 @@ const metadata: StateMetadata<AuthenticationControllerState> = {
4549
persist: true,
4650
anonymous: false,
4751
},
52+
socialPairingDone: {
53+
persist: true,
54+
anonymous: true,
55+
},
56+
pairingInProgress: {
57+
persist: false,
58+
anonymous: true,
59+
},
4860
};
4961

5062
// Messenger Actions
@@ -88,7 +100,11 @@ export type Events = AuthenticationControllerStateChangeEvent;
88100
// Allowed Actions
89101
export type AllowedActions =
90102
| HandleSnapRequest
91-
| KeyringControllerGetStateAction;
103+
| KeyringControllerGetStateAction
104+
| {
105+
type: 'SeedlessOnboardingController:getState';
106+
handler: () => { accessToken?: string };
107+
};
92108

93109
export type AllowedEvents =
94110
| KeyringControllerLockEvent
@@ -277,13 +293,19 @@ export default class AuthenticationController extends BaseController<
277293
accessTokens.push(accessToken);
278294
}
279295

296+
// don't await for the pairing to finish
297+
this.#tryPairingWithSeedlessAccessToken().catch(() => {
298+
// don't care
299+
});
300+
280301
return accessTokens;
281302
}
282303

283304
public performSignOut(): void {
284305
this.update((state) => {
285306
state.isSignedIn = false;
286307
state.srpSessionData = undefined;
308+
state.socialPairingDone = false;
287309
});
288310
}
289311

@@ -318,6 +340,95 @@ export default class AuthenticationController extends BaseController<
318340
return this.state.isSignedIn;
319341
}
320342

343+
async #tryPairingWithSeedlessAccessToken(): Promise<void> {
344+
const { socialPairingDone, pairingInProgress } = this.state;
345+
if (socialPairingDone || pairingInProgress) {
346+
return;
347+
}
348+
349+
// Get accessToken from SeedlessOnboardingController
350+
let seedlessState;
351+
try {
352+
seedlessState = this.messagingSystem.call('SeedlessOnboardingController:getState');
353+
} catch (error) {
354+
// SeedlessOnboardingController might not be available
355+
return;
356+
}
357+
358+
const accessToken = seedlessState?.accessToken;
359+
if (!accessToken) {
360+
return;
361+
}
362+
363+
try {
364+
this.update((state) => {
365+
state.pairingInProgress = true;
366+
});
367+
368+
if (await this.#pairSocialIdentifier(accessToken)) {
369+
this.update((state) => {
370+
state.socialPairingDone = true;
371+
});
372+
}
373+
} catch (error) {
374+
// ignore the error - pairing failure should not affect other flows
375+
} finally {
376+
this.update((state) => {
377+
state.pairingInProgress = false;
378+
});
379+
}
380+
}
381+
382+
async #pairSocialIdentifier(accessToken: string): Promise<boolean> {
383+
// TODO: need to hardcode the env as web3auth prod is not available.
384+
const env = Env.DEV;
385+
386+
// Exchange the social token with an access token
387+
const tokenResponse = await authorizeOIDC(accessToken, env, this.#metametrics.agent);
388+
389+
// Prepare the SRP signature
390+
const identifier = await this.#snapGetPublicKey();
391+
const profile = await this.#auth.getUserProfile();
392+
const n = await getNonce(profile.profileId, env);
393+
const raw = `metamask:${n.nonce}:${identifier}`;
394+
const sig = await this.#snapSignMessage(raw);
395+
const primaryIdentifierSignature = {
396+
signature: sig,
397+
raw_message: raw,
398+
identifier_type: AuthType.SRP,
399+
encrypted_storage_key: '', // Not yet part of this flow, so we leave it empty
400+
};
401+
402+
const pairUrl = new URL(PAIR_SOCIAL_IDENTIFIER(env));
403+
404+
try {
405+
const response = await fetch(pairUrl, {
406+
method: 'POST',
407+
headers: {
408+
'Content-Type': 'application/json',
409+
Authorization: `Bearer ${tokenResponse.accessToken}`,
410+
},
411+
body: JSON.stringify({
412+
nonce: n.nonce,
413+
login: primaryIdentifierSignature,
414+
}),
415+
});
416+
417+
if (!response.ok) {
418+
const responseBody = (await response.json()) as { message: string; error: string };
419+
throw new Error(
420+
`HTTP error message: ${responseBody.message}, error: ${responseBody.error}`,
421+
);
422+
}
423+
424+
return true;
425+
} catch (e) {
426+
const errorMessage =
427+
e instanceof Error ? e.message : JSON.stringify(e ?? '');
428+
throw new PairError(`unable to pair identifiers: ${errorMessage}`);
429+
}
430+
}
431+
321432
/**
322433
* Returns the auth snap public key.
323434
*

packages/profile-sync-controller/src/sdk/authentication-jwt-bearer/services.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export const NONCE_URL = (env: Env) =>
1616
export const PAIR_IDENTIFIERS = (env: Env) =>
1717
`${getEnvUrls(env).authApiUrl}/api/v2/identifiers/pair`;
1818

19+
export const PAIR_SOCIAL_IDENTIFIER = (env: Env) =>
20+
`${getEnvUrls(env).authApiUrl}/api/v2/identifiers/pair/social`;
21+
1922
export const OIDC_TOKEN_URL = (env: Env) =>
2023
`${getEnvUrls(env).oidcApiUrl}/oauth2/token`;
2124

0 commit comments

Comments
 (0)