Skip to content

Commit a63c009

Browse files
committed
chore: twitter x google account linking scenarios coverage
1 parent e385c60 commit a63c009

4 files changed

Lines changed: 170 additions & 9 deletions

File tree

packages/wallets/wallet-turnkey/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './strategy/types.js'
12
export * from './strategy/strategy.js'
23

34
export * from './strategy/turnkey/turnkey.js'

packages/wallets/wallet-turnkey/src/strategy/turnkey/oauth.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ export class TurnkeyOauthWallet {
4242
providerName: TurnkeyOAuthProvider
4343
indexedDbClient: TurnkeyIndexedDbClient
4444
expirationSeconds?: number
45-
}): Promise<
46-
{ organizationId: string; credentialBundle: string } | undefined
47-
> {
45+
}): Promise<TurnkeyOauthLoginResponse | undefined> {
4846
const { client, indexedDbClient, expirationSeconds } = args
4947

5048
const path = args.oauthLoginPath || TURNKEY_OAUTH_PATH

packages/wallets/wallet-turnkey/src/strategy/turnkey/turnkey.ts

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ import type {
2929
TurnkeyMetadata,
3030
TurnkeyOAuthProvider,
3131
} from '@injectivelabs/wallet-base'
32+
import type {
33+
TurnkeyOauthProvider,
34+
TurnkeyOAuth2Response,
35+
TurnkeyOAuthConfirmResponse,
36+
TurnkeyOAuth2ConfirmResponse,
37+
TurnkeyLinkOAuthProviderResponse,
38+
} from '../types.js'
3239

3340
export class TurnkeyWallet {
3441
private otpId?: string
@@ -359,7 +366,10 @@ export class TurnkeyWallet {
359366
)
360367
}
361368

362-
public async confirmOAuth(provider: TurnkeyOAuthProvider, oidcToken: string) {
369+
public async confirmOAuth(
370+
provider: TurnkeyOAuthProvider,
371+
oidcToken: string,
372+
): Promise<TurnkeyOAuthConfirmResponse> {
363373
if (provider === TurnkeyProvider.Apple) {
364374
throw new WalletException(
365375
new Error('Apple sign in option is currently not supported'),
@@ -376,7 +386,16 @@ export class TurnkeyWallet {
376386
oauthLoginPath: this.metadata.oauthLoginPath || TURNKEY_OAUTH_PATH,
377387
})
378388

379-
if (!oauthResult || !oauthResult.credentialBundle) {
389+
if (
390+
oauthResult &&
391+
'status' in oauthResult &&
392+
(oauthResult.status === 'link_required' ||
393+
oauthResult.status === 'existing_account_detected')
394+
) {
395+
return oauthResult
396+
}
397+
398+
if (!oauthResult?.credentialBundle) {
380399
throw new WalletException(new Error('Unexpected OAuth result'))
381400
}
382401

@@ -398,14 +417,18 @@ export class TurnkeyWallet {
398417
codeVerifier: string
399418
targetPublicKey: string
400419
providerName: TurnkeyOAuthProvider
401-
}) {
420+
}): Promise<TurnkeyOAuth2ConfirmResponse> {
402421
const indexedDbClient = await this.getIndexedDbClient()
403422
const path = this.metadata.oauth2ExchangePath || 'turnkey/oauth2'
404423

405424
const response = await this.client.post<{
406-
data: { credentialBundle: string; organizationId: string; email?: string }
425+
data: TurnkeyOAuth2Response
407426
}>(path, { nonce, authCode, codeVerifier, targetPublicKey, providerName })
408427

428+
if (response?.data?.status === 'link_required') {
429+
return response.data
430+
}
431+
409432
if (!response?.data?.credentialBundle || !response?.data?.organizationId) {
410433
throw new WalletException(
411434
new Error(`${providerName} OAuth2 exchange failed`),
@@ -421,6 +444,79 @@ export class TurnkeyWallet {
421444
return { session: credentialBundle, email }
422445
}
423446

447+
public async linkOAuthProvider({
448+
oidcToken,
449+
providerName,
450+
}: {
451+
oidcToken: string
452+
providerName: TurnkeyOAuthProvider
453+
}): Promise<TurnkeyLinkOAuthProviderResponse> {
454+
const indexedDbClient = await this.getIndexedDbClient()
455+
const { organizationId } = await this.getSession()
456+
457+
if (!organizationId) {
458+
throw new WalletException(
459+
new Error('Turnkey organization not found. Please login again.'),
460+
)
461+
}
462+
463+
const user = await indexedDbClient.getWhoami({ organizationId })
464+
let userId = user?.userId
465+
466+
if (!userId) {
467+
const { users } = await indexedDbClient.getUsers({ organizationId })
468+
469+
if (users.length !== 1) {
470+
throw new WalletException(
471+
new Error('Unable to resolve current Turnkey user.'),
472+
)
473+
}
474+
475+
userId = users[0].userId
476+
}
477+
478+
await indexedDbClient.createOauthProviders({
479+
userId,
480+
organizationId,
481+
oauthProviders: [{ providerName, oidcToken }],
482+
})
483+
484+
return { organizationId }
485+
}
486+
487+
public async getCurrentOauthProviders(): Promise<TurnkeyOauthProvider[]> {
488+
const indexedDbClient = await this.getIndexedDbClient()
489+
const { organizationId } = await this.getSession()
490+
491+
if (!organizationId) {
492+
throw new WalletException(
493+
new Error('Turnkey organization not found. Please login again.'),
494+
)
495+
}
496+
497+
const user = await indexedDbClient.getWhoami({ organizationId })
498+
let userId = user?.userId
499+
500+
if (!userId) {
501+
const { users } = await indexedDbClient.getUsers({ organizationId })
502+
503+
if (users.length !== 1) {
504+
throw new WalletException(
505+
new Error('Unable to resolve current Turnkey user.'),
506+
)
507+
}
508+
509+
userId = users[0].userId
510+
}
511+
512+
const { oauthProviders } = await indexedDbClient.getOauthProviders({
513+
organizationId,
514+
userId,
515+
})
516+
517+
return oauthProviders
518+
}
519+
424520
public async refreshSession() {
425521
const session = await this.getSession()
426522
const indexedDbClient = await this.getIndexedDbClient()

packages/wallets/wallet-turnkey/src/strategy/types.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { TurnkeyOAuthProvider } from '@injectivelabs/wallet-base'
2+
13
export const TurnkeyErrorCodes = {
24
UserLoggedOut: 7,
35
} as const
@@ -30,16 +32,80 @@ export type TurnkeyEnableArgs =
3032

3133
export type TurnkeyOTPCredentialsResponse = {
3234
otpId: string
35+
email?: string
36+
userName?: string
3337
organizationId: string
38+
status?: 'existing_account_detected'
3439
}
3540

3641
export type TurnkeyConfirmEmailOTPResponse = {
3742
session: string
3843
organizationId: string
3944
}
4045

41-
export type TurnkeyOauthLoginResponse = {
46+
export type TurnkeyOauthAuthenticatedResponse = {
47+
message?: string
48+
organizationId: string
49+
credentialBundle: string
50+
}
51+
52+
export type TurnkeyExistingAccountDetectedResponse = {
53+
email?: string
54+
message?: string
55+
userName?: string
56+
organizationId: string
57+
credentialBundle: string
58+
status: 'existing_account_detected'
59+
}
60+
61+
export type TurnkeyOAuth2AuthenticatedResponse = {
62+
email?: string
4263
organizationId: string
64+
status?: 'authenticated'
4365
credentialBundle: string
44-
message: string
66+
}
67+
68+
export type TurnkeyOAuth2LinkRequiredResponse = {
69+
email: string
70+
oidcToken: string
71+
expiresAt?: number
72+
organizationId: string
73+
status: 'link_required'
74+
providerName: TurnkeyOAuthProvider
75+
}
76+
77+
export type TurnkeyOAuthLinkRequiredResponse = TurnkeyOAuth2LinkRequiredResponse
78+
79+
export type TurnkeyOAuth2Response =
80+
| TurnkeyOAuth2AuthenticatedResponse
81+
| TurnkeyOAuth2LinkRequiredResponse
82+
| TurnkeyExistingAccountDetectedResponse
83+
84+
export type TurnkeyOauthLoginResponse =
85+
| TurnkeyOauthAuthenticatedResponse
86+
| TurnkeyOAuth2LinkRequiredResponse
87+
| TurnkeyExistingAccountDetectedResponse
88+
89+
export type TurnkeyOAuthConfirmResponse =
90+
| string
91+
| TurnkeyOAuthLinkRequiredResponse
92+
| TurnkeyExistingAccountDetectedResponse
93+
94+
export type TurnkeyOAuth2ConfirmResponse =
95+
| {
96+
email?: string
97+
session: string
98+
}
99+
| TurnkeyOAuthLinkRequiredResponse
100+
101+
export type TurnkeyLinkOAuthProviderResponse = {
102+
organizationId: string
103+
}
104+
105+
export type TurnkeyOauthProvider = {
106+
issuer: string
107+
subject: string
108+
audience: string
109+
providerId: string
110+
providerName: string
45111
}

0 commit comments

Comments
 (0)