@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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/dbs/auth-commitments.d.ts +21 -3
- package/dist/dbs/auth-commitments.d.ts.map +1 -1
- package/dist/dbs/index.d.ts +1 -1
- package/dist/dbs/index.d.ts.map +1 -1
- package/dist/sequence/handlers/authcode-pkce.d.ts +2 -1
- package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -1
- package/dist/sequence/handlers/authcode-pkce.js +15 -9
- package/dist/sequence/handlers/authcode.d.ts +2 -1
- package/dist/sequence/handlers/authcode.d.ts.map +1 -1
- package/dist/sequence/handlers/authcode.js +19 -10
- package/dist/sequence/index.d.ts +1 -1
- package/dist/sequence/index.d.ts.map +1 -1
- package/dist/sequence/types/signature-request.d.ts +4 -0
- package/dist/sequence/types/signature-request.d.ts.map +1 -1
- package/dist/sequence/types/signature-request.js +2 -0
- package/dist/sequence/wallets.d.ts +82 -0
- package/dist/sequence/wallets.d.ts.map +1 -1
- package/dist/sequence/wallets.js +173 -21
- package/package.json +8 -8
- package/src/dbs/auth-commitments.ts +6 -3
- package/src/dbs/index.ts +1 -1
- package/src/sequence/handlers/authcode-pkce.ts +16 -10
- package/src/sequence/handlers/authcode.ts +20 -11
- package/src/sequence/index.ts +3 -0
- package/src/sequence/types/signature-request.ts +4 -0
- package/src/sequence/wallets.ts +299 -21
- package/test/authcode-pkce.test.ts +27 -18
- package/test/authcode.test.ts +24 -19
- package/test/identity-auth-dbs.test.ts +8 -7
- package/test/wallets.test.ts +1 -1
package/src/sequence/wallets.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
837
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
185
|
+
await handler.commitAuth(target, { type: 'auth' })
|
|
185
186
|
expect(mockCommitments.set).toHaveBeenLastCalledWith(
|
|
186
187
|
expect.objectContaining({
|
|
187
|
-
|
|
188
|
+
type: 'auth',
|
|
188
189
|
}),
|
|
189
190
|
)
|
|
190
191
|
|
|
191
192
|
// Test login
|
|
192
|
-
await handler.commitAuth(target,
|
|
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
|
-
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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',
|
|
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
|
})
|
package/test/authcode.test.ts
CHANGED
|
@@ -113,7 +113,7 @@ describe('AuthCodeHandler', () => {
|
|
|
113
113
|
kind: 'google-pkce',
|
|
114
114
|
metadata: {},
|
|
115
115
|
target: '/test-target',
|
|
116
|
-
|
|
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,
|
|
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.
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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.
|
|
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.
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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',
|
|
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.
|
|
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',
|
|
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.
|
|
727
|
+
expect(loginCall.type).toBe('reauth')
|
|
723
728
|
expect(loginCall.target).toBe('/login-target')
|
|
724
729
|
})
|
|
725
730
|
})
|