@0xsequence/wallet-wdk 3.0.3 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/CHANGELOG.md +24 -0
  5. package/dist/dbs/auth-commitments.d.ts +21 -3
  6. package/dist/dbs/auth-commitments.d.ts.map +1 -1
  7. package/dist/dbs/index.d.ts +1 -1
  8. package/dist/dbs/index.d.ts.map +1 -1
  9. package/dist/sequence/handlers/authcode-pkce.d.ts +2 -1
  10. package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -1
  11. package/dist/sequence/handlers/authcode-pkce.js +15 -9
  12. package/dist/sequence/handlers/authcode.d.ts +2 -1
  13. package/dist/sequence/handlers/authcode.d.ts.map +1 -1
  14. package/dist/sequence/handlers/authcode.js +19 -10
  15. package/dist/sequence/handlers/idtoken.d.ts +3 -3
  16. package/dist/sequence/handlers/idtoken.d.ts.map +1 -1
  17. package/dist/sequence/handlers/idtoken.js +3 -0
  18. package/dist/sequence/index.d.ts +1 -1
  19. package/dist/sequence/index.d.ts.map +1 -1
  20. package/dist/sequence/manager.d.ts +3 -0
  21. package/dist/sequence/manager.d.ts.map +1 -1
  22. package/dist/sequence/manager.js +7 -1
  23. package/dist/sequence/types/signature-request.d.ts +4 -0
  24. package/dist/sequence/types/signature-request.d.ts.map +1 -1
  25. package/dist/sequence/types/signature-request.js +2 -0
  26. package/dist/sequence/wallets.d.ts +84 -2
  27. package/dist/sequence/wallets.d.ts.map +1 -1
  28. package/dist/sequence/wallets.js +180 -24
  29. package/package.json +8 -8
  30. package/src/dbs/auth-commitments.ts +6 -3
  31. package/src/dbs/index.ts +1 -1
  32. package/src/sequence/handlers/authcode-pkce.ts +16 -10
  33. package/src/sequence/handlers/authcode.ts +20 -11
  34. package/src/sequence/handlers/idtoken.ts +8 -2
  35. package/src/sequence/index.ts +3 -0
  36. package/src/sequence/manager.ts +32 -14
  37. package/src/sequence/types/signature-request.ts +4 -0
  38. package/src/sequence/wallets.ts +315 -27
  39. package/test/authcode-pkce.test.ts +27 -18
  40. package/test/authcode.test.ts +24 -19
  41. package/test/identity-auth-dbs.test.ts +44 -7
  42. package/test/idtoken.test.ts +16 -0
  43. package/test/sessions-idtoken.test.ts +1 -0
  44. package/test/wallets.test.ts +62 -1
@@ -8,7 +8,7 @@ import { MnemonicHandler } from './handlers/mnemonic.js'
8
8
  import { OtpHandler } from './handlers/otp.js'
9
9
  import { Shared } from './manager.js'
10
10
  import { Device } from './types/device.js'
11
- import { Action, Module } from './types/index.js'
11
+ import { Action, Actions, Module } from './types/index.js'
12
12
  import { Kinds, SignerWithKind, WitnessExtraSignerKind } from './types/signer.js'
13
13
  import { Wallet, WalletSelectionUiHandler } from './types/wallet.js'
14
14
  import { PasskeysHandler } from './handlers/passkeys.js'
@@ -28,13 +28,19 @@ function getSignerKindForSignup(kind: SignupArgs['kind'] | AuthCommitment['kind'
28
28
  if (kind === 'google-id-token' || kind === 'google-pkce') {
29
29
  return Kinds.LoginGoogle
30
30
  }
31
+ if (kind === 'apple-id-token' || kind === 'apple') {
32
+ return Kinds.LoginApple
33
+ }
31
34
  if (kind.startsWith('custom-')) {
32
35
  return kind
33
36
  }
34
37
  return ('login-' + kind) as string
35
38
  }
36
39
 
37
- function getIdTokenSignupHandler(shared: Shared, kind: typeof Kinds.LoginGoogle | `custom-${string}`): IdTokenHandler {
40
+ function getIdTokenSignupHandler(
41
+ shared: Shared,
42
+ kind: typeof Kinds.LoginGoogle | typeof Kinds.LoginApple | `custom-${string}`,
43
+ ): IdTokenHandler {
38
44
  const handler = shared.handlers.get(kind)
39
45
  if (!handler) {
40
46
  throw new Error('handler-not-registered')
@@ -51,6 +57,12 @@ export type StartSignUpWithRedirectArgs = {
51
57
  metadata: { [key: string]: string }
52
58
  }
53
59
 
60
+ export type StartAddLoginSignerWithRedirectArgs = {
61
+ wallet: Address.Address
62
+ kind: 'google-pkce' | 'apple' | `custom-${string}`
63
+ target: string
64
+ }
65
+
54
66
  export type SignupStatus =
55
67
  | { type: 'login-signer-created'; address: Address.Address }
56
68
  | { type: 'device-signer-created'; address: Address.Address }
@@ -82,7 +94,7 @@ export type EmailOtpSignupArgs = CommonSignupArgs & {
82
94
  }
83
95
 
84
96
  export type IdTokenSignupArgs = CommonSignupArgs & {
85
- kind: 'google-id-token' | `custom-${string}`
97
+ kind: 'google-id-token' | 'apple-id-token' | `custom-${string}`
86
98
  idToken: string
87
99
  }
88
100
 
@@ -106,6 +118,19 @@ export type SignupArgs =
106
118
  | IdTokenSignupArgs
107
119
  | AuthCodeSignupArgs
108
120
 
121
+ export type AddLoginSignerArgs = {
122
+ wallet: Address.Address
123
+ } & (
124
+ | { kind: 'mnemonic'; mnemonic: string }
125
+ | { kind: 'email-otp'; email: string }
126
+ | { kind: 'google-id-token' | 'apple-id-token' | `custom-${string}`; idToken: string }
127
+ )
128
+
129
+ export type RemoveLoginSignerArgs = {
130
+ wallet: Address.Address
131
+ signerAddress: Address.Address
132
+ }
133
+
109
134
  export type LoginToWalletArgs = {
110
135
  wallet: Address.Address
111
136
  }
@@ -222,7 +247,7 @@ export interface WalletsInterface {
222
247
  * - `kind: 'mnemonic'`: Uses a mnemonic phrase as the login credential.
223
248
  * - `kind: 'passkey'`: Prompts the user to create a WebAuthn passkey.
224
249
  * - `kind: 'email-otp'`: Initiates an OTP flow to the user's email.
225
- * - `kind: 'google-id-token'`: Completes an OIDC ID token flow when Google is configured with `authMethod: 'id-token'`.
250
+ * - `kind: 'google-id-token' | 'apple-id-token'`: Completes an OIDC ID token flow when the provider is configured with `authMethod: 'id-token'`.
226
251
  * - `kind: 'google-pkce' | 'apple'`: Completes an OAuth redirect flow.
227
252
  * Common options like `noGuard` or `noRecovery` can customize the wallet's security features.
228
253
  * @returns A promise that resolves to the address of the newly created wallet, or `undefined` if the sign-up was aborted.
@@ -286,6 +311,66 @@ export interface WalletsInterface {
286
311
  */
287
312
  completeLogin(requestId: string): Promise<void>
288
313
 
314
+ /**
315
+ * Adds a new login signer to an existing wallet, enabling account federation.
316
+ *
317
+ * This allows a user to link a new login method (e.g., Google, email OTP, mnemonic) to a wallet
318
+ * that was originally created with a different credential. After federation, the wallet can be
319
+ * discovered and accessed via any of its linked login methods.
320
+ *
321
+ * @param args The arguments specifying the wallet and the new login credential to add.
322
+ * @returns A promise that resolves to a `requestId` for the configuration update signature request.
323
+ * @see {completeAddLoginSigner}
324
+ */
325
+ addLoginSigner(args: AddLoginSignerArgs): Promise<string>
326
+
327
+ /**
328
+ * Completes the add-login-signer process after the configuration update has been signed.
329
+ *
330
+ * @param requestId The ID of the completed signature request returned by `addLoginSigner`.
331
+ * @returns A promise that resolves when the configuration update has been submitted.
332
+ */
333
+ completeAddLoginSigner(requestId: string): Promise<void>
334
+
335
+ /**
336
+ * Initiates an add-login-signer process that involves an OAuth redirect.
337
+ *
338
+ * This is the first step for adding a social login signer (e.g., Google, Apple) to an existing wallet
339
+ * via a redirect-based OAuth flow. It validates the wallet, generates the necessary challenges and state,
340
+ * stores them locally, and returns a URL. Your application should redirect the user to this URL.
341
+ *
342
+ * After the redirect callback, call `completeRedirect` with the returned state and code. This will
343
+ * create a pending `AddLoginSigner` signature request internally. The caller can then discover
344
+ * the pending request through the signatures module and call `completeAddLoginSigner` with the requestId
345
+ * once it has been signed.
346
+ *
347
+ * @param args Arguments specifying the wallet, provider (`kind`), and the `target` URL for the redirect callback.
348
+ * @returns A promise that resolves to the full OAuth URL to which the user should be redirected.
349
+ * @see {completeRedirect} for the second step of this flow.
350
+ * @see {completeAddLoginSigner} to finalize after signing.
351
+ */
352
+ startAddLoginSignerWithRedirect(args: StartAddLoginSignerWithRedirectArgs): Promise<string>
353
+
354
+ /**
355
+ * Removes a login signer from an existing wallet, enabling account defederation.
356
+ *
357
+ * This allows a user to unlink a login method from a wallet. A safety guard ensures
358
+ * at least one login signer always remains.
359
+ *
360
+ * @param args The arguments specifying the wallet and the signer address to remove.
361
+ * @returns A promise that resolves to a `requestId` for the configuration update signature request.
362
+ * @see {completeRemoveLoginSigner}
363
+ */
364
+ removeLoginSigner(args: RemoveLoginSignerArgs): Promise<string>
365
+
366
+ /**
367
+ * Completes the remove-login-signer process after the configuration update has been signed.
368
+ *
369
+ * @param requestId The ID of the completed signature request returned by `removeLoginSigner`.
370
+ * @returns A promise that resolves when the configuration update has been submitted.
371
+ */
372
+ completeRemoveLoginSigner(requestId: string): Promise<void>
373
+
289
374
  /**
290
375
  * Logs out from a given wallet, ending the current session.
291
376
  *
@@ -411,6 +496,20 @@ export function isIdTokenArgs(args: SignupArgs): args is IdTokenSignupArgs {
411
496
  return 'idToken' in args
412
497
  }
413
498
 
499
+ function addLoginSignerToSignupArgs(args: AddLoginSignerArgs): SignupArgs {
500
+ switch (args.kind) {
501
+ case 'mnemonic':
502
+ return { kind: 'mnemonic', mnemonic: args.mnemonic }
503
+ case 'email-otp':
504
+ return { kind: 'email-otp', email: args.email }
505
+ default: {
506
+ // google-id-token, apple-id-token, custom-*
507
+ const _args = args as { kind: string; idToken: string }
508
+ return { kind: _args.kind as IdTokenSignupArgs['kind'], idToken: _args.idToken }
509
+ }
510
+ }
511
+ }
512
+
414
513
  function buildCappedTree(members: { address: Address.Address; imageHash?: Hex.Hex }[]): Config.Topology {
415
514
  const loginMemberWeight = 1n
416
515
 
@@ -721,8 +820,12 @@ export class Wallets implements WalletsInterface {
721
820
  }
722
821
  }
723
822
 
724
- case 'google-id-token': {
725
- const handler = getIdTokenSignupHandler(this.shared, Kinds.LoginGoogle)
823
+ case 'google-id-token':
824
+ case 'apple-id-token': {
825
+ const handler = getIdTokenSignupHandler(
826
+ this.shared,
827
+ args.kind === 'google-id-token' ? Kinds.LoginGoogle : Kinds.LoginApple,
828
+ )
726
829
  const [signer, metadata] = await handler.completeAuth(args.idToken)
727
830
  const loginEmail = metadata.email
728
831
  this.shared.modules.logger.log('Created new id token signer:', signer.address)
@@ -730,7 +833,7 @@ export class Wallets implements WalletsInterface {
730
833
  return {
731
834
  signer,
732
835
  extra: {
733
- signerKind: Kinds.LoginGoogle,
836
+ signerKind: getSignerKindForSignup(args.kind),
734
837
  },
735
838
  loginEmail,
736
839
  }
@@ -797,7 +900,27 @@ export class Wallets implements WalletsInterface {
797
900
  if (!(handler instanceof AuthCodeHandler)) {
798
901
  throw new Error('handler-does-not-support-redirect')
799
902
  }
800
- return handler.commitAuth(args.target, true)
903
+ return handler.commitAuth(args.target, { type: 'auth' })
904
+ }
905
+
906
+ async startAddLoginSignerWithRedirect(args: StartAddLoginSignerWithRedirectArgs) {
907
+ const walletEntry = await this.get(args.wallet)
908
+ if (!walletEntry) {
909
+ throw new Error('wallet-not-found')
910
+ }
911
+ if (walletEntry.status !== 'ready') {
912
+ throw new Error('wallet-not-ready')
913
+ }
914
+
915
+ const kind = getSignupHandlerKey(args.kind)
916
+ const handler = this.shared.handlers.get(kind)
917
+ if (!handler) {
918
+ throw new Error('handler-not-registered')
919
+ }
920
+ if (!(handler instanceof AuthCodeHandler)) {
921
+ throw new Error('handler-does-not-support-redirect')
922
+ }
923
+ return handler.commitAuth(args.target, { type: 'add-signer', wallet: args.wallet })
801
924
  }
802
925
 
803
926
  async completeRedirect(args: CompleteRedirectArgs): Promise<string> {
@@ -806,28 +929,62 @@ export class Wallets implements WalletsInterface {
806
929
  throw new Error('invalid-state')
807
930
  }
808
931
 
809
- // commitment.isSignUp and signUp also mean 'signIn' from wallet's perspective
810
- if (commitment.isSignUp) {
811
- await this.signUp({
812
- kind: commitment.kind,
813
- commitment,
814
- code: args.code,
815
- noGuard: args.noGuard,
816
- target: commitment.target,
817
- isRedirect: true,
818
- use4337: args.use4337,
819
- })
820
- } else {
821
- const handlerKind = getSignupHandlerKey(commitment.kind)
822
- const handler = this.shared.handlers.get(handlerKind)
823
- if (!handler) {
824
- throw new Error('handler-not-registered')
932
+ switch (commitment.type) {
933
+ case 'add-signer': {
934
+ const handlerKind = getSignupHandlerKey(commitment.kind)
935
+ const handler = this.shared.handlers.get(handlerKind)
936
+ if (!handler) {
937
+ throw new Error('handler-not-registered')
938
+ }
939
+ if (!(handler instanceof AuthCodeHandler)) {
940
+ throw new Error('handler-does-not-support-redirect')
941
+ }
942
+
943
+ const walletAddress = commitment.wallet as Address.Address
944
+ const walletEntry = await this.get(walletAddress)
945
+ if (!walletEntry) {
946
+ throw new Error('wallet-not-found')
947
+ }
948
+ if (walletEntry.status !== 'ready') {
949
+ throw new Error('wallet-not-ready')
950
+ }
951
+
952
+ const [signer] = await handler.completeAuth(commitment, args.code)
953
+ const signerKind = getSignerKindForSignup(commitment.kind)
954
+
955
+ await this.addLoginSignerFromPrepared(walletAddress, {
956
+ signer,
957
+ extra: { signerKind },
958
+ })
959
+ break
825
960
  }
826
- if (!(handler instanceof AuthCodeHandler)) {
827
- throw new Error('handler-does-not-support-redirect')
961
+
962
+ case 'auth': {
963
+ await this.signUp({
964
+ kind: commitment.kind,
965
+ commitment,
966
+ code: args.code,
967
+ noGuard: args.noGuard,
968
+ target: commitment.target,
969
+ isRedirect: true,
970
+ use4337: args.use4337,
971
+ })
972
+ break
828
973
  }
829
974
 
830
- await handler.completeAuth(commitment, args.code)
975
+ case 'reauth': {
976
+ const handlerKind = getSignupHandlerKey(commitment.kind)
977
+ const handler = this.shared.handlers.get(handlerKind)
978
+ if (!handler) {
979
+ throw new Error('handler-not-registered')
980
+ }
981
+ if (!(handler instanceof AuthCodeHandler)) {
982
+ throw new Error('handler-does-not-support-redirect')
983
+ }
984
+
985
+ await handler.completeAuth(commitment, args.code)
986
+ break
987
+ }
831
988
  }
832
989
 
833
990
  if (!commitment.target) {
@@ -1263,6 +1420,87 @@ export class Wallets implements WalletsInterface {
1263
1420
  })
1264
1421
  }
1265
1422
 
1423
+ async addLoginSigner(args: AddLoginSignerArgs): Promise<string> {
1424
+ const walletEntry = await this.get(args.wallet)
1425
+ if (!walletEntry) {
1426
+ throw new Error('wallet-not-found')
1427
+ }
1428
+ if (walletEntry.status !== 'ready') {
1429
+ throw new Error('wallet-not-ready')
1430
+ }
1431
+
1432
+ const signupArgs = addLoginSignerToSignupArgs(args)
1433
+ const loginSigner = await this.prepareSignUp(signupArgs)
1434
+ return this.addLoginSignerFromPrepared(args.wallet, loginSigner)
1435
+ }
1436
+
1437
+ async completeAddLoginSigner(requestId: string): Promise<void> {
1438
+ const request = await this.shared.modules.signatures.get(requestId)
1439
+ if (request.action !== Actions.AddLoginSigner) {
1440
+ throw new Error('invalid-request-action')
1441
+ }
1442
+ await this.completeConfigurationUpdate(requestId)
1443
+ }
1444
+
1445
+ async removeLoginSigner(args: RemoveLoginSignerArgs): Promise<string> {
1446
+ const walletEntry = await this.get(args.wallet)
1447
+ if (!walletEntry) {
1448
+ throw new Error('wallet-not-found')
1449
+ }
1450
+ if (walletEntry.status !== 'ready') {
1451
+ throw new Error('wallet-not-ready')
1452
+ }
1453
+
1454
+ const { loginTopology, modules } = await this.getConfigurationParts(args.wallet)
1455
+
1456
+ const existingSigners = Config.getSigners(loginTopology)
1457
+ const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)]
1458
+
1459
+ if (!allExistingAddresses.some((addr) => Address.isEqual(addr, args.signerAddress))) {
1460
+ throw new Error('signer-not-found')
1461
+ }
1462
+
1463
+ const remainingMembers = [
1464
+ ...existingSigners.signers
1465
+ .filter((x) => x !== Constants.ZeroAddress && !Address.isEqual(x, args.signerAddress))
1466
+ .map((x) => ({ address: x })),
1467
+ ...existingSigners.sapientSigners
1468
+ .filter((x) => !Address.isEqual(x.address, args.signerAddress))
1469
+ .map((x) => ({ address: x.address, imageHash: x.imageHash })),
1470
+ ]
1471
+
1472
+ if (remainingMembers.length < 1) {
1473
+ throw new Error('cannot-remove-last-login-signer')
1474
+ }
1475
+
1476
+ const nextLoginTopology = buildCappedTree(remainingMembers)
1477
+
1478
+ if (this.shared.modules.sessions.hasSessionModule(modules)) {
1479
+ await this.shared.modules.sessions.removeIdentitySignerFromModules(modules, args.signerAddress)
1480
+ }
1481
+
1482
+ if (this.shared.modules.recovery.hasRecoveryModule(modules)) {
1483
+ await this.shared.modules.recovery.removeRecoverySignerFromModules(modules, args.signerAddress)
1484
+ }
1485
+
1486
+ const requestId = await this.requestConfigurationUpdate(
1487
+ args.wallet,
1488
+ { loginTopology: nextLoginTopology, modules },
1489
+ Actions.RemoveLoginSigner,
1490
+ 'wallet-webapp',
1491
+ )
1492
+
1493
+ return requestId
1494
+ }
1495
+
1496
+ async completeRemoveLoginSigner(requestId: string): Promise<void> {
1497
+ const request = await this.shared.modules.signatures.get(requestId)
1498
+ if (request.action !== Actions.RemoveLoginSigner) {
1499
+ throw new Error('invalid-request-action')
1500
+ }
1501
+ await this.completeConfigurationUpdate(requestId)
1502
+ }
1503
+
1266
1504
  async logout<T extends { skipRemoveDevice?: boolean } | undefined = undefined>(
1267
1505
  wallet: Address.Address,
1268
1506
  options?: T,
@@ -1493,4 +1731,54 @@ export class Wallets implements WalletsInterface {
1493
1731
 
1494
1732
  return requestId
1495
1733
  }
1734
+
1735
+ private async addLoginSignerFromPrepared(
1736
+ wallet: Address.Address,
1737
+ loginSigner: {
1738
+ signer: (Signers.Signer | Signers.SapientSigner) & Signers.Witnessable
1739
+ extra: WitnessExtraSignerKind
1740
+ },
1741
+ ): Promise<string> {
1742
+ const newSignerAddress = await loginSigner.signer.address
1743
+
1744
+ const { loginTopology, modules } = await this.getConfigurationParts(wallet)
1745
+
1746
+ // Check for duplicate signer
1747
+ const existingSigners = Config.getSigners(loginTopology)
1748
+ const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)]
1749
+ if (allExistingAddresses.some((addr) => Address.isEqual(addr, newSignerAddress))) {
1750
+ throw new Error('signer-already-exists')
1751
+ }
1752
+
1753
+ // Build new login topology with the additional signer
1754
+ const existingMembers = [
1755
+ ...existingSigners.signers.filter((x) => x !== Constants.ZeroAddress).map((x) => ({ address: x })),
1756
+ ...existingSigners.sapientSigners.map((x) => ({ address: x.address, imageHash: x.imageHash })),
1757
+ ]
1758
+ const newMember = {
1759
+ address: newSignerAddress,
1760
+ imageHash: Signers.isSapientSigner(loginSigner.signer) ? await loginSigner.signer.imageHash : undefined,
1761
+ }
1762
+ const nextLoginTopology = buildCappedTree([...existingMembers, newMember])
1763
+
1764
+ // Add non-sapient login signer to sessions module identity signers
1765
+ if (!Signers.isSapientSigner(loginSigner.signer) && this.shared.modules.sessions.hasSessionModule(modules)) {
1766
+ await this.shared.modules.sessions.addIdentitySignerToModules(modules, newSignerAddress)
1767
+ }
1768
+
1769
+ // Add to recovery module if present
1770
+ if (this.shared.modules.recovery.hasRecoveryModule(modules)) {
1771
+ await this.shared.modules.recovery.addRecoverySignerToModules(modules, newSignerAddress)
1772
+ }
1773
+
1774
+ // Witness so the wallet becomes discoverable via the new credential
1775
+ await loginSigner.signer.witness(this.shared.sequence.stateProvider, wallet, loginSigner.extra)
1776
+
1777
+ return this.requestConfigurationUpdate(
1778
+ wallet,
1779
+ { loginTopology: nextLoginTopology, modules },
1780
+ Actions.AddLoginSigner,
1781
+ 'wallet-webapp',
1782
+ )
1783
+ }
1496
1784
  }
@@ -90,9 +90,8 @@ describe('AuthCodePkceHandler', () => {
90
90
  describe('commitAuth', () => {
91
91
  it('Should create Google PKCE auth commitment and return OAuth URL', async () => {
92
92
  const target = 'https://example.com/success'
93
- const isSignUp = true
94
93
 
95
- const result = await handler.commitAuth(target, isSignUp)
94
+ const result = await handler.commitAuth(target, { type: 'auth' })
96
95
 
97
96
  // Verify nitroCommitVerifier was called with correct challenge
98
97
  expect(handler['nitroCommitVerifier']).toHaveBeenCalledWith(
@@ -110,7 +109,7 @@ describe('AuthCodePkceHandler', () => {
110
109
  challenge: 'mock-challenge-hash',
111
110
  target,
112
111
  metadata: {},
113
- isSignUp,
112
+ type: 'auth',
114
113
  })
115
114
 
116
115
  // Verify OAuth URL is constructed correctly
@@ -127,10 +126,13 @@ describe('AuthCodePkceHandler', () => {
127
126
 
128
127
  it('Should use provided state instead of generating random one', async () => {
129
128
  const target = 'https://example.com/success'
130
- const isSignUp = false
131
129
  const customState = 'custom-state-123'
132
130
 
133
- const result = await handler.commitAuth(target, isSignUp, customState)
131
+ const result = await handler.commitAuth(target, {
132
+ type: 'reauth',
133
+ state: customState,
134
+ signer: '0x1234567890123456789012345678901234567890',
135
+ })
134
136
 
135
137
  // Verify commitment was saved with custom state
136
138
  expect(mockCommitments.set).toHaveBeenCalledWith({
@@ -140,7 +142,8 @@ describe('AuthCodePkceHandler', () => {
140
142
  challenge: 'mock-challenge-hash',
141
143
  target,
142
144
  metadata: {},
143
- isSignUp,
145
+ type: 'reauth',
146
+ signer: '0x1234567890123456789012345678901234567890',
144
147
  })
145
148
 
146
149
  // Verify URL contains custom state
@@ -149,10 +152,9 @@ describe('AuthCodePkceHandler', () => {
149
152
 
150
153
  it('Should include signer in challenge when provided', async () => {
151
154
  const target = 'https://example.com/success'
152
- const isSignUp = true
153
155
  const signer = '0x9876543210987654321098765432109876543210'
154
156
 
155
- await handler.commitAuth(target, isSignUp, undefined, signer)
157
+ await handler.commitAuth(target, { type: 'reauth', state: 'test-state', signer })
156
158
 
157
159
  // Verify nitroCommitVerifier was called with signer in challenge
158
160
  expect(handler['nitroCommitVerifier']).toHaveBeenCalledWith(
@@ -164,9 +166,8 @@ describe('AuthCodePkceHandler', () => {
164
166
 
165
167
  it('Should generate random state when not provided', async () => {
166
168
  const target = 'https://example.com/success'
167
- const isSignUp = true
168
169
 
169
- const result = await handler.commitAuth(target, isSignUp)
170
+ const result = await handler.commitAuth(target, { type: 'auth' })
170
171
 
171
172
  // Verify that a state parameter is present and looks like a hex string
172
173
  expect(result).toMatch(/state=0x[a-f0-9]+/)
@@ -181,18 +182,22 @@ describe('AuthCodePkceHandler', () => {
181
182
  const target = 'https://example.com/success'
182
183
 
183
184
  // Test signup
184
- await handler.commitAuth(target, true)
185
+ await handler.commitAuth(target, { type: 'auth' })
185
186
  expect(mockCommitments.set).toHaveBeenLastCalledWith(
186
187
  expect.objectContaining({
187
- isSignUp: true,
188
+ type: 'auth',
188
189
  }),
189
190
  )
190
191
 
191
192
  // Test login
192
- await handler.commitAuth(target, false)
193
+ await handler.commitAuth(target, {
194
+ type: 'reauth',
195
+ state: 'test-state',
196
+ signer: '0x1234567890123456789012345678901234567890',
197
+ })
193
198
  expect(mockCommitments.set).toHaveBeenLastCalledWith(
194
199
  expect.objectContaining({
195
- isSignUp: false,
200
+ type: 'reauth',
196
201
  }),
197
202
  )
198
203
  })
@@ -200,13 +205,17 @@ describe('AuthCodePkceHandler', () => {
200
205
  it('Should handle errors from nitroCommitVerifier', async () => {
201
206
  vi.spyOn(handler as any, 'nitroCommitVerifier').mockRejectedValue(new Error('Nitro service unavailable'))
202
207
 
203
- await expect(handler.commitAuth('https://example.com/success', true)).rejects.toThrow('Nitro service unavailable')
208
+ await expect(handler.commitAuth('https://example.com/success', { type: 'auth' })).rejects.toThrow(
209
+ 'Nitro service unavailable',
210
+ )
204
211
  })
205
212
 
206
213
  it('Should handle database errors during commitment storage', async () => {
207
214
  vi.mocked(mockCommitments.set).mockRejectedValue(new Error('Database write failed'))
208
215
 
209
- await expect(handler.commitAuth('https://example.com/success', true)).rejects.toThrow('Database write failed')
216
+ await expect(handler.commitAuth('https://example.com/success', { type: 'auth' })).rejects.toThrow(
217
+ 'Database write failed',
218
+ )
210
219
  })
211
220
  })
212
221
 
@@ -221,7 +230,7 @@ describe('AuthCodePkceHandler', () => {
221
230
  challenge: 'test-challenge-hash',
222
231
  target: 'https://example.com/success',
223
232
  metadata: { scope: 'openid profile email' },
224
- isSignUp: true,
233
+ type: 'auth',
225
234
  }
226
235
  })
227
236
 
@@ -333,7 +342,7 @@ describe('AuthCodePkceHandler', () => {
333
342
  const newRedirectUri = 'https://newdomain.com/callback'
334
343
  handler.setRedirectUri(newRedirectUri)
335
344
 
336
- return handler.commitAuth('https://example.com/success', true).then((result) => {
345
+ return handler.commitAuth('https://example.com/success', { type: 'auth' }).then((result) => {
337
346
  expect(result).toContain(`redirect_uri=${encodeURIComponent(newRedirectUri)}`)
338
347
  })
339
348
  })