@0xsequence/wallet-wdk 3.0.4 → 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 (34) 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 +12 -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/index.d.ts +1 -1
  16. package/dist/sequence/index.d.ts.map +1 -1
  17. package/dist/sequence/types/signature-request.d.ts +4 -0
  18. package/dist/sequence/types/signature-request.d.ts.map +1 -1
  19. package/dist/sequence/types/signature-request.js +2 -0
  20. package/dist/sequence/wallets.d.ts +82 -0
  21. package/dist/sequence/wallets.d.ts.map +1 -1
  22. package/dist/sequence/wallets.js +173 -21
  23. package/package.json +8 -8
  24. package/src/dbs/auth-commitments.ts +6 -3
  25. package/src/dbs/index.ts +1 -1
  26. package/src/sequence/handlers/authcode-pkce.ts +16 -10
  27. package/src/sequence/handlers/authcode.ts +20 -11
  28. package/src/sequence/index.ts +3 -0
  29. package/src/sequence/types/signature-request.ts +4 -0
  30. package/src/sequence/wallets.ts +299 -21
  31. package/test/authcode-pkce.test.ts +27 -18
  32. package/test/authcode.test.ts +24 -19
  33. package/test/identity-auth-dbs.test.ts +8 -7
  34. package/test/wallets.test.ts +1 -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'
@@ -57,6 +57,12 @@ export type StartSignUpWithRedirectArgs = {
57
57
  metadata: { [key: string]: string }
58
58
  }
59
59
 
60
+ export type StartAddLoginSignerWithRedirectArgs = {
61
+ wallet: Address.Address
62
+ kind: 'google-pkce' | 'apple' | `custom-${string}`
63
+ target: string
64
+ }
65
+
60
66
  export type SignupStatus =
61
67
  | { type: 'login-signer-created'; address: Address.Address }
62
68
  | { type: 'device-signer-created'; address: Address.Address }
@@ -112,6 +118,19 @@ export type SignupArgs =
112
118
  | IdTokenSignupArgs
113
119
  | AuthCodeSignupArgs
114
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
+
115
134
  export type LoginToWalletArgs = {
116
135
  wallet: Address.Address
117
136
  }
@@ -292,6 +311,66 @@ export interface WalletsInterface {
292
311
  */
293
312
  completeLogin(requestId: string): Promise<void>
294
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
+
295
374
  /**
296
375
  * Logs out from a given wallet, ending the current session.
297
376
  *
@@ -417,6 +496,20 @@ export function isIdTokenArgs(args: SignupArgs): args is IdTokenSignupArgs {
417
496
  return 'idToken' in args
418
497
  }
419
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
+
420
513
  function buildCappedTree(members: { address: Address.Address; imageHash?: Hex.Hex }[]): Config.Topology {
421
514
  const loginMemberWeight = 1n
422
515
 
@@ -807,7 +900,27 @@ export class Wallets implements WalletsInterface {
807
900
  if (!(handler instanceof AuthCodeHandler)) {
808
901
  throw new Error('handler-does-not-support-redirect')
809
902
  }
810
- 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 })
811
924
  }
812
925
 
813
926
  async completeRedirect(args: CompleteRedirectArgs): Promise<string> {
@@ -816,28 +929,62 @@ export class Wallets implements WalletsInterface {
816
929
  throw new Error('invalid-state')
817
930
  }
818
931
 
819
- // commitment.isSignUp and signUp also mean 'signIn' from wallet's perspective
820
- if (commitment.isSignUp) {
821
- await this.signUp({
822
- kind: commitment.kind,
823
- commitment,
824
- code: args.code,
825
- noGuard: args.noGuard,
826
- target: commitment.target,
827
- isRedirect: true,
828
- use4337: args.use4337,
829
- })
830
- } else {
831
- const handlerKind = getSignupHandlerKey(commitment.kind)
832
- const handler = this.shared.handlers.get(handlerKind)
833
- if (!handler) {
834
- 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
835
960
  }
836
- if (!(handler instanceof AuthCodeHandler)) {
837
- 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
838
973
  }
839
974
 
840
- 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
+ }
841
988
  }
842
989
 
843
990
  if (!commitment.target) {
@@ -1273,6 +1420,87 @@ export class Wallets implements WalletsInterface {
1273
1420
  })
1274
1421
  }
1275
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
+
1276
1504
  async logout<T extends { skipRemoveDevice?: boolean } | undefined = undefined>(
1277
1505
  wallet: Address.Address,
1278
1506
  options?: T,
@@ -1503,4 +1731,54 @@ export class Wallets implements WalletsInterface {
1503
1731
 
1504
1732
  return requestId
1505
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
+ }
1506
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
  })
@@ -113,7 +113,7 @@ describe('AuthCodeHandler', () => {
113
113
  kind: 'google-pkce',
114
114
  metadata: {},
115
115
  target: '/test-target',
116
- isSignUp: false,
116
+ type: 'reauth',
117
117
  signer: testWallet,
118
118
  }
119
119
 
@@ -247,20 +247,17 @@ describe('AuthCodeHandler', () => {
247
247
 
248
248
  it('Should create auth commitment and return OAuth URL', async () => {
249
249
  const target = '/test-target'
250
- const isSignUp = true
251
- const signer = testWallet
252
250
 
253
- const result = await authCodeHandler.commitAuth(target, isSignUp, undefined, signer)
251
+ const result = await authCodeHandler.commitAuth(target, { type: 'auth' })
254
252
 
255
253
  // Verify commitment was saved
256
254
  expect(mockAuthCommitmentsSet).toHaveBeenCalledOnce()
257
255
  const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]!
258
256
 
259
257
  expect(commitmentCall.kind).toBe('google-pkce')
260
- expect(commitmentCall.signer).toBe(signer)
261
258
  expect(commitmentCall.target).toBe(target)
262
259
  expect(commitmentCall.metadata).toEqual({})
263
- expect(commitmentCall.isSignUp).toBe(isSignUp)
260
+ expect(commitmentCall.type).toBe('auth')
264
261
  expect(commitmentCall.id).toBeDefined()
265
262
  expect(typeof commitmentCall.id).toBe('string')
266
263
 
@@ -276,7 +273,11 @@ describe('AuthCodeHandler', () => {
276
273
  it('Should use provided state parameter', async () => {
277
274
  const customState = 'custom-state-123'
278
275
 
279
- const result = await authCodeHandler.commitAuth('/target', false, customState)
276
+ const result = await authCodeHandler.commitAuth('/target', {
277
+ type: 'reauth',
278
+ state: customState,
279
+ signer: testWallet,
280
+ })
280
281
 
281
282
  // Verify commitment uses custom state
282
283
  const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]!
@@ -285,7 +286,7 @@ describe('AuthCodeHandler', () => {
285
286
  })
286
287
 
287
288
  it('Should generate random state when not provided', async () => {
288
- await authCodeHandler.commitAuth('/target', false)
289
+ await authCodeHandler.commitAuth('/target', { type: 'auth' })
289
290
  const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]!
290
291
  expect(commitmentCall.id).toBeDefined()
291
292
  expect(typeof commitmentCall.id).toBe('string')
@@ -306,7 +307,7 @@ describe('AuthCodeHandler', () => {
306
307
  )
307
308
  appleHandler.setRedirectUri('https://example.com/callback')
308
309
 
309
- const result = await appleHandler.commitAuth('/target', false)
310
+ const result = await appleHandler.commitAuth('/target', { type: 'auth' })
310
311
 
311
312
  expect(result).toContain('https://appleid.apple.com/auth/authorize?')
312
313
  expect(result).toContain('client_id=apple-client-id')
@@ -315,10 +316,10 @@ describe('AuthCodeHandler', () => {
315
316
  })
316
317
 
317
318
  it('Should create commitment without signer', async () => {
318
- await authCodeHandler.commitAuth('/target', true)
319
+ await authCodeHandler.commitAuth('/target', { type: 'auth' })
319
320
  const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]!
320
321
  expect(commitmentCall.signer).toBeUndefined()
321
- expect(commitmentCall.isSignUp).toBe(true)
322
+ expect(commitmentCall.type).toBe('auth')
322
323
  })
323
324
  })
324
325
 
@@ -492,7 +493,7 @@ describe('AuthCodeHandler', () => {
492
493
 
493
494
  const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]!
494
495
  expect(commitmentCall.target).toBe(window.location.pathname)
495
- expect(commitmentCall.isSignUp).toBe(false)
496
+ expect(commitmentCall.type).toBe('reauth')
496
497
  expect(commitmentCall.signer).toBe(testWallet)
497
498
  })
498
499
  })
@@ -654,7 +655,7 @@ describe('AuthCodeHandler', () => {
654
655
  it('Should handle auth commitments database errors', async () => {
655
656
  mockAuthCommitmentsSet.mockRejectedValueOnce(new Error('Database error'))
656
657
 
657
- await expect(authCodeHandler.commitAuth('/target', false)).rejects.toThrow('Database error')
658
+ await expect(authCodeHandler.commitAuth('/target', { type: 'auth' })).rejects.toThrow('Database error')
658
659
  })
659
660
 
660
661
  it('Should handle auth keys database errors', async () => {
@@ -671,7 +672,11 @@ describe('AuthCodeHandler', () => {
671
672
  authCodeHandler.setRedirectUri('https://example.com/callback')
672
673
 
673
674
  // Step 1: Commit auth
674
- const commitUrl = await authCodeHandler.commitAuth('/test-target', false, 'test-state', testWallet)
675
+ const commitUrl = await authCodeHandler.commitAuth('/test-target', {
676
+ type: 'reauth',
677
+ state: 'test-state',
678
+ signer: testWallet,
679
+ })
675
680
 
676
681
  expect(commitUrl).toContain('state=test-state')
677
682
  expect(mockAuthCommitmentsSet).toHaveBeenCalledWith(
@@ -679,7 +684,7 @@ describe('AuthCodeHandler', () => {
679
684
  id: 'test-state',
680
685
  kind: 'google-pkce',
681
686
  target: '/test-target',
682
- isSignUp: false,
687
+ type: 'reauth',
683
688
  signer: testWallet,
684
689
  }),
685
690
  )
@@ -709,17 +714,17 @@ describe('AuthCodeHandler', () => {
709
714
  authCodeHandler.setRedirectUri('https://example.com/callback')
710
715
 
711
716
  // Test signup flow
712
- await authCodeHandler.commitAuth('/signup-target', true, 'signup-state')
717
+ await authCodeHandler.commitAuth('/signup-target', { type: 'auth', state: 'signup-state' })
713
718
 
714
719
  const signupCall = mockAuthCommitmentsSet.mock.calls[0]![0]!
715
- expect(signupCall.isSignUp).toBe(true)
720
+ expect(signupCall.type).toBe('auth')
716
721
  expect(signupCall.target).toBe('/signup-target')
717
722
 
718
723
  // Test login flow
719
- await authCodeHandler.commitAuth('/login-target', false, 'login-state')
724
+ await authCodeHandler.commitAuth('/login-target', { type: 'reauth', state: 'login-state', signer: testWallet })
720
725
 
721
726
  const loginCall = mockAuthCommitmentsSet.mock.calls[1]![0]!
722
- expect(loginCall.isSignUp).toBe(false)
727
+ expect(loginCall.type).toBe('reauth')
723
728
  expect(loginCall.target).toBe('/login-target')
724
729
  })
725
730
  })