@@ -24,6 +24,8 @@ import {
24
24
Env ,
25
25
JwtBearerAuth ,
26
26
} from '../../sdk' ;
27
+ import { authorizeOIDC , getNonce , PAIR_SOCIAL_IDENTIFIER } from '../../sdk/authentication-jwt-bearer/services' ;
28
+ import { PairError } from '../../sdk/errors' ;
27
29
import type { MetaMetricsAuth } from '../../shared/types/services' ;
28
30
29
31
const controllerName = 'AuthenticationController' ;
@@ -32,6 +34,8 @@ const controllerName = 'AuthenticationController';
32
34
export type AuthenticationControllerState = {
33
35
isSignedIn : boolean ;
34
36
srpSessionData ?: Record < string , LoginResponse > ;
37
+ socialPairingDone ?: boolean ;
38
+ pairingInProgress ?: boolean ;
35
39
} ;
36
40
export const defaultState : AuthenticationControllerState = {
37
41
isSignedIn : false ,
@@ -45,6 +49,14 @@ const metadata: StateMetadata<AuthenticationControllerState> = {
45
49
persist : true ,
46
50
anonymous : false ,
47
51
} ,
52
+ socialPairingDone : {
53
+ persist : true ,
54
+ anonymous : true ,
55
+ } ,
56
+ pairingInProgress : {
57
+ persist : false ,
58
+ anonymous : true ,
59
+ } ,
48
60
} ;
49
61
50
62
// Messenger Actions
@@ -88,7 +100,11 @@ export type Events = AuthenticationControllerStateChangeEvent;
88
100
// Allowed Actions
89
101
export type AllowedActions =
90
102
| HandleSnapRequest
91
- | KeyringControllerGetStateAction ;
103
+ | KeyringControllerGetStateAction
104
+ | {
105
+ type : 'SeedlessOnboardingController:getState' ;
106
+ handler : ( ) => { accessToken ?: string } ;
107
+ } ;
92
108
93
109
export type AllowedEvents =
94
110
| KeyringControllerLockEvent
@@ -277,13 +293,19 @@ export default class AuthenticationController extends BaseController<
277
293
accessTokens . push ( accessToken ) ;
278
294
}
279
295
296
+ // don't await for the pairing to finish
297
+ this . #tryPairingWithSeedlessAccessToken( ) . catch ( ( ) => {
298
+ // don't care
299
+ } ) ;
300
+
280
301
return accessTokens ;
281
302
}
282
303
283
304
public performSignOut ( ) : void {
284
305
this . update ( ( state ) => {
285
306
state . isSignedIn = false ;
286
307
state . srpSessionData = undefined ;
308
+ state . socialPairingDone = false ;
287
309
} ) ;
288
310
}
289
311
@@ -318,6 +340,95 @@ export default class AuthenticationController extends BaseController<
318
340
return this . state . isSignedIn ;
319
341
}
320
342
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
+
321
432
/**
322
433
* Returns the auth snap public key.
323
434
*
0 commit comments