Skip to content

Commit 8f87199

Browse files
authored
Add support for custom auth providers (authcode & authcode-pkce only) (#894)
* Add support for custom auth providers (authcode & authcode-pkce only) * fix authcode tests
1 parent 38289e4 commit 8f87199

File tree

9 files changed

+106
-85
lines changed

9 files changed

+106
-85
lines changed

packages/wallet/wdk/src/dbs/auth-commitments.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const TABLE_NAME = 'auth-commitments'
55

66
export type AuthCommitment = {
77
id: string
8-
kind: 'google-pkce' | 'apple'
8+
kind: 'google-pkce' | 'apple' | `custom-${string}`
99
metadata: { [key: string]: string }
1010
verifier?: string
1111
challenge?: string

packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import { AuthCodeHandler } from './authcode.js'
88

99
export class AuthCodePkceHandler extends AuthCodeHandler implements Handler {
1010
constructor(
11-
signupKind: 'google-pkce',
11+
signupKind: 'google-pkce' | `custom-${string}`,
1212
issuer: string,
13+
oauthUrl: string,
1314
audience: string,
1415
nitro: Identity.IdentityInstrument,
1516
signatures: Signatures,
1617
commitments: Db.AuthCommitments,
1718
authKeys: Db.AuthKeys,
1819
) {
19-
super(signupKind, issuer, audience, nitro, signatures, commitments, authKeys)
20+
super(signupKind, issuer, oauthUrl, audience, nitro, signatures, commitments, authKeys)
2021
}
2122

2223
public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) {
@@ -50,8 +51,7 @@ export class AuthCodePkceHandler extends AuthCodeHandler implements Handler {
5051
state,
5152
})
5253

53-
const oauthUrl = this.oauthUrl()
54-
return `${oauthUrl}?${searchParams.toString()}`
54+
return `${this.oauthUrl}?${searchParams.toString()}`
5555
}
5656

5757
public async completeAuth(

packages/wallet/wdk/src/sequence/handlers/authcode.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
1111
protected redirectUri: string = ''
1212

1313
constructor(
14-
public readonly signupKind: 'apple' | 'google-pkce',
14+
public readonly signupKind: 'apple' | 'google-pkce' | `custom-${string}`,
1515
public readonly issuer: string,
16+
protected readonly oauthUrl: string,
1617
public readonly audience: string,
1718
nitro: Identity.IdentityInstrument,
1819
signatures: Signatures,
@@ -48,12 +49,11 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
4849
client_id: this.audience,
4950
redirect_uri: this.redirectUri,
5051
response_type: 'code',
51-
scope: 'openid',
52+
scope: 'openid profile email',
5253
state,
5354
})
5455

55-
const oauthUrl = this.oauthUrl()
56-
return `${oauthUrl}?${searchParams.toString()}`
56+
return `${this.oauthUrl}?${searchParams.toString()}`
5757
}
5858

5959
public async completeAuth(
@@ -100,15 +100,4 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
100100
},
101101
}
102102
}
103-
104-
protected oauthUrl() {
105-
switch (this.issuer) {
106-
case 'https://accounts.google.com':
107-
return 'https://accounts.google.com/o/oauth2/v2/auth'
108-
case 'https://appleid.apple.com':
109-
return 'https://appleid.apple.com/auth/authorize'
110-
default:
111-
throw new Error('unsupported-issuer')
112-
}
113-
}
114103
}

packages/wallet/wdk/src/sequence/manager.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ export type ManagerOptions = {
8282
enabled: boolean
8383
clientId: string
8484
}
85+
customProviders?: {
86+
kind: `custom-${string}`
87+
authMethod: 'id-token' | 'authcode' | 'authcode-pkce'
88+
issuer: string
89+
oauthUrl: string
90+
clientId: string
91+
}[]
8592
}
8693
}
8794

@@ -484,6 +491,7 @@ export class Manager {
484491
new AuthCodePkceHandler(
485492
'google-pkce',
486493
'https://accounts.google.com',
494+
'https://accounts.google.com/o/oauth2/v2/auth',
487495
ops.identity.google.clientId,
488496
identityInstrument,
489497
modules.signatures,
@@ -498,6 +506,7 @@ export class Manager {
498506
new AuthCodeHandler(
499507
'apple',
500508
'https://appleid.apple.com',
509+
'https://appleid.apple.com/auth/authorize',
501510
ops.identity.apple.clientId,
502511
identityInstrument,
503512
modules.signatures,
@@ -506,6 +515,46 @@ export class Manager {
506515
),
507516
)
508517
}
518+
if (ops.identity.customProviders?.length) {
519+
for (const provider of ops.identity.customProviders) {
520+
switch (provider.authMethod) {
521+
case 'id-token':
522+
throw new Error('id-token is not supported yet')
523+
case 'authcode':
524+
shared.handlers.set(
525+
provider.kind,
526+
new AuthCodeHandler(
527+
provider.kind,
528+
provider.issuer,
529+
provider.oauthUrl,
530+
provider.clientId,
531+
identityInstrument,
532+
modules.signatures,
533+
shared.databases.authCommitments,
534+
shared.databases.authKeys,
535+
),
536+
)
537+
break
538+
case 'authcode-pkce':
539+
shared.handlers.set(
540+
provider.kind,
541+
new AuthCodePkceHandler(
542+
provider.kind,
543+
provider.issuer,
544+
provider.oauthUrl,
545+
provider.clientId,
546+
identityInstrument,
547+
modules.signatures,
548+
shared.databases.authCommitments,
549+
shared.databases.authKeys,
550+
),
551+
)
552+
break
553+
default:
554+
throw new Error('unsupported auth method')
555+
}
556+
}
557+
}
509558

510559
shared.modules = modules
511560
this.shared = shared

packages/wallet/wdk/src/sequence/signers.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ export function isWitnessExtraSignerKind(extra: any): extra is WitnessExtraSigne
88
}
99

1010
function toKnownKind(kind: string): Kind {
11-
if (Object.values(Kinds).includes(kind as Kind)) {
11+
if (kind.startsWith('custom-')) {
12+
return kind as Kind
13+
}
14+
15+
if (Object.values(Kinds).includes(kind as (typeof Kinds)[keyof typeof Kinds])) {
1216
return kind as Kind
1317
}
1418

packages/wallet/wdk/src/sequence/types/signer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const Kinds = {
1212
Unknown: 'unknown',
1313
} as const
1414

15-
export type Kind = (typeof Kinds)[keyof typeof Kinds]
15+
export type Kind = (typeof Kinds)[keyof typeof Kinds] | `custom-${string}`
1616

1717
export type WitnessExtraSignerKind = {
1818
signerKind: string

packages/wallet/wdk/src/sequence/wallets.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { PasskeysHandler } from './handlers/passkeys.js'
1414
import { GuardRole } from './guards.js'
1515

1616
export type StartSignUpWithRedirectArgs = {
17-
kind: 'google-pkce' | 'apple'
17+
kind: 'google-pkce' | 'apple' | `custom-${string}`
1818
target: string
1919
metadata: { [key: string]: string }
2020
}
@@ -55,7 +55,7 @@ export type CompleteRedirectArgs = CommonSignupArgs & {
5555
}
5656

5757
export type AuthCodeSignupArgs = CommonSignupArgs & {
58-
kind: 'google-pkce' | 'apple'
58+
kind: 'google-pkce' | 'apple' | `custom-${string}`
5959
commitment: AuthCommitment
6060
code: string
6161
target: string
@@ -692,10 +692,30 @@ export class Wallets implements WalletsInterface {
692692
}
693693
}
694694
}
695+
696+
if (args.kind.startsWith('custom-')) {
697+
// TODO: support other custom auth methods (e.g. id-token)
698+
const handler = this.shared.handlers.get(args.kind) as AuthCodeHandler
699+
if (!handler) {
700+
throw new Error('handler-not-registered')
701+
}
702+
703+
const [signer, metadata] = await handler.completeAuth(args.commitment, args.code)
704+
return {
705+
signer,
706+
extra: {
707+
signerKind: args.kind,
708+
},
709+
loginEmail: metadata.email,
710+
}
711+
}
712+
713+
throw new Error('invalid-signup-kind')
695714
}
696715

697716
async startSignUpWithRedirect(args: StartSignUpWithRedirectArgs) {
698-
const handler = this.shared.handlers.get('login-' + args.kind) as AuthCodeHandler
717+
const kind = args.kind.startsWith('custom-') ? args.kind : 'login-' + args.kind
718+
const handler = this.shared.handlers.get(kind) as AuthCodeHandler
699719
if (!handler) {
700720
throw new Error('handler-not-registered')
701721
}
@@ -720,7 +740,8 @@ export class Wallets implements WalletsInterface {
720740
use4337: args.use4337,
721741
})
722742
} else {
723-
const handler = this.shared.handlers.get('login-' + commitment.kind) as AuthCodeHandler
743+
const kind = commitment.kind.startsWith('custom-') ? commitment.kind : 'login-' + commitment.kind
744+
const handler = this.shared.handlers.get(kind) as AuthCodeHandler
724745
if (!handler) {
725746
throw new Error('handler-not-registered')
726747
}

packages/wallet/wdk/test/authcode-pkce.test.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ describe('AuthCodePkceHandler', () => {
5656
handler = new AuthCodePkceHandler(
5757
'google-pkce',
5858
'https://accounts.google.com',
59+
'https://accounts.google.com/o/oauth2/v2/auth',
5960
'test-google-client-id',
6061
mockNitroInstrument,
6162
mockSignatures,
@@ -81,8 +82,6 @@ describe('AuthCodePkceHandler', () => {
8182
8283
}
8384
})
84-
85-
vi.spyOn(handler as any, 'oauthUrl').mockReturnValue('https://accounts.google.com/oauth/authorize')
8685
})
8786

8887
afterEach(() => {
@@ -116,7 +115,7 @@ describe('AuthCodePkceHandler', () => {
116115
})
117116

118117
// Verify OAuth URL is constructed correctly
119-
expect(result).toMatch(/^https:\/\/accounts\.google\.com\/oauth\/authorize\?/)
118+
expect(result).toMatch(/^https:\/\/accounts\.google\.com\/o\/oauth2\/v2\/auth\?/)
120119
expect(result).toContain('code_challenge=mock-challenge-hash')
121120
expect(result).toContain('code_challenge_method=S256')
122121
expect(result).toContain('client_id=test-google-client-id')
@@ -335,19 +334,16 @@ describe('AuthCodePkceHandler', () => {
335334
const newRedirectUri = 'https://newdomain.com/callback'
336335
handler.setRedirectUri(newRedirectUri)
337336

338-
// Verify redirect URI is used in OAuth URL construction
339-
const mockUrl = 'https://accounts.google.com/oauth/authorize'
340-
vi.spyOn(handler as any, 'oauthUrl').mockReturnValue(mockUrl)
341-
342337
return handler.commitAuth('https://example.com/success', true).then((result) => {
343338
expect(result).toContain(`redirect_uri=${encodeURIComponent(newRedirectUri)}`)
344339
})
345340
})
346341

347342
it('Should work with different issuer and audience configurations', () => {
348343
const customHandler = new AuthCodePkceHandler(
349-
'google-pkce',
344+
'custom-provider',
350345
'https://custom-issuer.com',
346+
'https://custom-issuer.com/o/oauth2/v2/auth',
351347
'custom-client-id',
352348
mockNitroInstrument,
353349
mockSignatures,
@@ -357,7 +353,7 @@ describe('AuthCodePkceHandler', () => {
357353

358354
expect(customHandler['issuer']).toBe('https://custom-issuer.com')
359355
expect(customHandler['audience']).toBe('custom-client-id')
360-
expect(customHandler.signupKind).toBe('google-pkce')
356+
expect(customHandler.signupKind).toBe('custom-provider')
361357
})
362358
})
363359
})

0 commit comments

Comments
 (0)