@seams/wallet-server 0.20.0 → 0.22.0

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 (71) hide show
  1. package/artifact-manifest.json +22 -21
  2. package/cloudflare-router-ab/build/deriver-a/build-stamp.json +1 -1
  3. package/cloudflare-router-ab/build/deriver-a/index_bg.wasm +0 -0
  4. package/cloudflare-router-ab/build/deriver-b/build-stamp.json +1 -1
  5. package/cloudflare-router-ab/build/deriver-b/index_bg.wasm +0 -0
  6. package/cloudflare-router-ab/build/local-tools/linux-x64/router_ab_local_init +0 -0
  7. package/cloudflare-router-ab/build/router/build-stamp.json +1 -1
  8. package/cloudflare-router-ab/build/router/index_bg.wasm +0 -0
  9. package/cloudflare-router-ab/build/signing-worker/build-stamp.json +1 -1
  10. package/cloudflare-router-ab/build/signing-worker/index_bg.wasm +0 -0
  11. package/cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json +1 -1
  12. package/cloudflare-router-ab/build/tenant-root-control-plane/index_bg.wasm +0 -0
  13. package/cloudflare-router-ab/scripts/prepare-local-runtime-config.mjs +16 -2
  14. package/dist/esm/core/deviceLinking/linkedDeviceManagement.js +11 -30
  15. package/dist/esm/core/emailOtpD1Statements.js +14 -2
  16. package/dist/esm/core/nearRelayerAccountProvisioning.js +2 -9
  17. package/dist/esm/hosted-wallet-gateway.js +10 -7
  18. package/dist/esm/router/auth/webAuthnCredentialCodecs.js +16 -1
  19. package/dist/esm/router/cloudflare/d1/auth/d1AuthorizationAssembly.js +1 -0
  20. package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +0 -1
  21. package/dist/esm/router/cloudflare/d1/deviceLinking/d1LinkedDeviceEmailOtpTargetFactor.js +4 -1
  22. package/dist/esm/router/cloudflare/d1/deviceLinking/d1LinkedDeviceManagementService.js +6 -11
  23. package/dist/esm/router/cloudflare/d1/deviceLinking/d1LinkedDeviceRevocation.js +7 -0
  24. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpGrantStore.js +5 -2
  25. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpRateLimitStore.js +18 -0
  26. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpRecoveryService.js +28 -8
  27. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpServerSealRuntime.js +12 -8
  28. package/dist/esm/router/cloudflare/d1/oidc/d1OidcBoundary.js +1 -0
  29. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyRouteService.js +19 -5
  30. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1WalletCustodyCommitStore.js +26 -10
  31. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1WalletRecoveryGoogleEmailOtpService.js +4 -1
  32. package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthMethodService.js +6 -0
  33. package/dist/esm/router/cloudflare/d1/wallet/d1WalletRelocationApprovals.js +12 -0
  34. package/dist/esm/router/cloudflare.js +252 -150
  35. package/dist/esm/router/domains/ecdsa/routerAbEcdsaStrictRegistration.js +1 -1
  36. package/dist/esm/router/domains/passkeyCustody/passkeyCustodyEnvelopeRetrieval.js +4 -22
  37. package/dist/esm/router/domains/passkeyCustody/registrationCustodyOutcome.js +8 -2
  38. package/dist/esm/router/framework/routeDefinitions.js +3 -2
  39. package/dist/esm/router/transport/fetch/routes/auth.js +5 -0
  40. package/dist/esm/router/transport/fetch/routes/passkeyCustody.js +78 -88
  41. package/dist/esm/router/transport/fetch/routes/sessions.js +4 -1
  42. package/dist/esm/router/transport/fetch/routes/thresholdEd25519.js +4 -1
  43. package/dist/esm/router/transport/fetch/routes/walletCustodyRequestBoundary.js +80 -0
  44. package/dist/esm/shared-ts/src/passkey-custody/ceremonyCommitPayload.js +5 -1
  45. package/dist/esm/shared-ts/src/utils/evmRpcResults.js +5 -3
  46. package/dist/esm/shared-ts/src/utils/routerAbEcdsaDerivation.js +2 -1
  47. package/dist/esm/threshold/session/signingSessionSeal/crypto/cipher.js +160 -40
  48. package/dist/types/shared-ts/src/authorization/walletCustodyOperation.d.ts +1 -1
  49. package/dist/types/shared-ts/src/device-linking/contracts.d.ts +1 -7
  50. package/dist/types/shared-ts/src/passkey-custody/ceremonyCommitPayload.d.ts +5 -0
  51. package/dist/types/shared-ts/src/utils/evmRpcResults.d.ts +3 -3
  52. package/dist/types/shared-ts/src/utils/routerAbEcdsaDerivation.d.ts +1 -1
  53. package/dist/types/wallet-server/src/core/authService/emailOtpSeal.d.ts +5 -1
  54. package/dist/types/wallet-server/src/core/deviceLinking/linkedDeviceManagement.d.ts +1 -9
  55. package/dist/types/wallet-server/src/core/emailOtpD1Statements.d.ts +2 -1
  56. package/dist/types/wallet-server/src/hosted-wallet-gateway.d.ts +1 -0
  57. package/dist/types/wallet-server/src/router/auth/webAuthnCredentialCodecs.d.ts +1 -0
  58. package/dist/types/wallet-server/src/router/cloudflare/d1/deviceLinking/d1LinkedDeviceManagementService.d.ts +0 -3
  59. package/dist/types/wallet-server/src/router/cloudflare/d1/deviceLinking/d1LinkedDeviceRevocation.d.ts +1 -1
  60. package/dist/types/wallet-server/src/router/cloudflare/d1/emailOtp/d1EmailOtpGrantStore.d.ts +3 -1
  61. package/dist/types/wallet-server/src/router/cloudflare/d1/emailOtp/d1EmailOtpRateLimitStore.d.ts +15 -2
  62. package/dist/types/wallet-server/src/router/cloudflare/d1/emailOtp/d1EmailOtpServerSealRuntime.d.ts +3 -3
  63. package/dist/types/wallet-server/src/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyRouteService.d.ts +3 -0
  64. package/dist/types/wallet-server/src/router/cloudflare/d1/passkeyCustody/d1WalletCustodyCommitStore.d.ts +5 -6
  65. package/dist/types/wallet-server/src/router/domains/passkeyCustody/walletCustodyRegistrationCommit.d.ts +4 -6
  66. package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +7 -1
  67. package/dist/types/wallet-server/src/router/transport/fetch/routes/passkeyCustody.d.ts +1 -10
  68. package/dist/types/wallet-server/src/router/transport/fetch/routes/walletCustodyRequestBoundary.d.ts +28 -0
  69. package/dist/types/wallet-server/src/threshold/session/signingSessionSeal/crypto/cipher.d.ts +3 -0
  70. package/package.json +1 -1
  71. package/src/README.md +11 -4
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "schemaVersion": "seams_wallet_server_artifact_manifest_v1",
3
+ "walletSecurityPolicy": "scoped_seals_and_owner_proofs_v1",
3
4
  "package": {
4
5
  "name": "@seams/wallet-server",
5
- "version": "0.20.0"
6
+ "version": "0.22.0"
6
7
  },
7
8
  "workers": {
8
9
  "router": {
9
10
  "entry": "cloudflare-router-ab/build/router/worker/shim.mjs",
10
- "fingerprint": "f49233b4fb230aa6e45e9fe850d61d9bbc29472d9c7cebac48b528c652627e18",
11
+ "fingerprint": "74fd3407337c12574566cad2d68e834cc62384a0afbeedf74c94820e9151b827",
11
12
  "files": [
12
13
  {
13
14
  "path": "cloudflare-router-ab/build/router/build-stamp.json",
14
15
  "bytes": 119,
15
- "sha256": "8f2c54ccc6ff47d9581a41dbc44b31cd85a46e06dfbdcb22fe4244f68182a2d3"
16
+ "sha256": "b32b3d7182ed9c00a4d9677c35604e2ece2607a01032d49956df19a89850660f"
16
17
  },
17
18
  {
18
19
  "path": "cloudflare-router-ab/build/router/index.js",
@@ -21,8 +22,8 @@
21
22
  },
22
23
  {
23
24
  "path": "cloudflare-router-ab/build/router/index_bg.wasm",
24
- "bytes": 7105252,
25
- "sha256": "b7194f2c339ef9ce9b276b3f9b17cdd9dfc37d101011c8676f785ae5e07c35c9"
25
+ "bytes": 7109033,
26
+ "sha256": "a791cedbd5df0cb84c0680c08339f58ebdeb76845759437d81f675cf1d92539a"
26
27
  },
27
28
  {
28
29
  "path": "cloudflare-router-ab/build/router/package.json",
@@ -38,12 +39,12 @@
38
39
  },
39
40
  "deriver-a": {
40
41
  "entry": "cloudflare-router-ab/build/deriver-a/worker/shim.mjs",
41
- "fingerprint": "26d51766dedd5f959011d096ea205470f94699f7e7ad9725157b5a2bd0d217e6",
42
+ "fingerprint": "cd913ea58e44a8f91ec9e8bee6bb06372943e9eb519abb366fbf36bdf5b710fe",
42
43
  "files": [
43
44
  {
44
45
  "path": "cloudflare-router-ab/build/deriver-a/build-stamp.json",
45
46
  "bytes": 122,
46
- "sha256": "93650745aee0bac07d27a59e20bf858a2c71bc01264ca0f5267fbca032f125ab"
47
+ "sha256": "747923ca2224aa97fd46d6051dc1085a2e8f4f7da54d3b7190d4b674e6dd3c0a"
47
48
  },
48
49
  {
49
50
  "path": "cloudflare-router-ab/build/deriver-a/index.js",
@@ -52,8 +53,8 @@
52
53
  },
53
54
  {
54
55
  "path": "cloudflare-router-ab/build/deriver-a/index_bg.wasm",
55
- "bytes": 11184414,
56
- "sha256": "ce0be6d5dbe4dd3e6ce2090f68c21b6dcd348b844d270f4bff413e4d2b10fa53"
56
+ "bytes": 11184432,
57
+ "sha256": "5e7510c28ca56ca06022f09f1be153aef591c3bdad52cde837294c2974c220a1"
57
58
  },
58
59
  {
59
60
  "path": "cloudflare-router-ab/build/deriver-a/package.json",
@@ -69,12 +70,12 @@
69
70
  },
70
71
  "deriver-b": {
71
72
  "entry": "cloudflare-router-ab/build/deriver-b/worker/shim.mjs",
72
- "fingerprint": "a043319fd7b348529679fe5053b2e7dab1fb511ab5f63e3e517dd51fa7d08822",
73
+ "fingerprint": "92af4092ea8fe179e5fb1f9f945c51df56d7eedef04e14d6b4d629b1234c5246",
73
74
  "files": [
74
75
  {
75
76
  "path": "cloudflare-router-ab/build/deriver-b/build-stamp.json",
76
77
  "bytes": 122,
77
- "sha256": "00b08f7e90a221885e4466c3fae1084494a6fa9908ee6ec695c2fd28112dc731"
78
+ "sha256": "bff31f4d458314d638759bc92209b8ceca4a8eb5529f2380fc99456a5c979ef9"
78
79
  },
79
80
  {
80
81
  "path": "cloudflare-router-ab/build/deriver-b/index.js",
@@ -83,8 +84,8 @@
83
84
  },
84
85
  {
85
86
  "path": "cloudflare-router-ab/build/deriver-b/index_bg.wasm",
86
- "bytes": 11217095,
87
- "sha256": "edcfad6f13f3777ddcb1e4b4fc3e6ba70c4f8030c76cdfa47504386a5d81ad2f"
87
+ "bytes": 11217121,
88
+ "sha256": "275f543ea48ab9e16b781cc76f888b0a5e850d42e4fba5e7f95480de38f7b151"
88
89
  },
89
90
  {
90
91
  "path": "cloudflare-router-ab/build/deriver-b/package.json",
@@ -100,12 +101,12 @@
100
101
  },
101
102
  "signing-worker": {
102
103
  "entry": "cloudflare-router-ab/build/signing-worker/worker/shim.mjs",
103
- "fingerprint": "b30f7566b3ba99145995cbf06766b950e0a8eb1617c7ef714cbf5ff16f273a08",
104
+ "fingerprint": "193f7af99585cabd0d9dc5266bd0df3fc0868869d7f9a91662f163a73f05e735",
104
105
  "files": [
105
106
  {
106
107
  "path": "cloudflare-router-ab/build/signing-worker/build-stamp.json",
107
108
  "bytes": 127,
108
- "sha256": "75879e99bac1ee3008e9b4acfa85137884bd4a5876e9630135daf266a4abde41"
109
+ "sha256": "8e4ce1fe79a9cdd6c3679d20f288ea37afeaa7c5b3937df936c0abf424e2c145"
109
110
  },
110
111
  {
111
112
  "path": "cloudflare-router-ab/build/signing-worker/index.js",
@@ -114,8 +115,8 @@
114
115
  },
115
116
  {
116
117
  "path": "cloudflare-router-ab/build/signing-worker/index_bg.wasm",
117
- "bytes": 6664587,
118
- "sha256": "4be982ddafb0546e2884009c0c53e56eb656441cc24f9fd794be148291b4059a"
118
+ "bytes": 6778203,
119
+ "sha256": "878a8ae2a4df685eebf0a5445bc57279183f5683b98abf541f31d40591904dab"
119
120
  },
120
121
  {
121
122
  "path": "cloudflare-router-ab/build/signing-worker/package.json",
@@ -131,12 +132,12 @@
131
132
  },
132
133
  "tenant-root-control-plane": {
133
134
  "entry": "cloudflare-router-ab/build/tenant-root-control-plane/worker/shim.mjs",
134
- "fingerprint": "02ef927532ddf330fa6744dbc964f6213020a6bc03e3d16560a1a3b38b9eb756",
135
+ "fingerprint": "bcbca7dead054e5637575fa5935f819cc1eb8d5b9ac21d80d22e646a8f5941ed",
135
136
  "files": [
136
137
  {
137
138
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json",
138
139
  "bytes": 139,
139
- "sha256": "f3ce6924af3f43de8ef76af8fe4c088e69be134627423ba8a090cf7327b4cf79"
140
+ "sha256": "a8b459e0a878d51fade909dbaa4d825b2195a17af0c2e931c75244eef93121e4"
140
141
  },
141
142
  {
142
143
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/index.js",
@@ -145,8 +146,8 @@
145
146
  },
146
147
  {
147
148
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/index_bg.wasm",
148
- "bytes": 2041210,
149
- "sha256": "095bd8881fe3fa153bdd35e2de1451c2021f9d4b292d5c59180a2a883af63d9a"
149
+ "bytes": 2041194,
150
+ "sha256": "a0aca19c356849e5c4b6ae9d6231130341e48222b258e2c4430cd4fc38450c0a"
150
151
  },
151
152
  {
152
153
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/package.json",
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-a-entrypoint","started_at_ms":1792679818312}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-a-entrypoint","started_at_ms":1792904733881}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-b-entrypoint","started_at_ms":1792680013063}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-b-entrypoint","started_at_ms":1792904884404}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-router-entrypoint","started_at_ms":1792680188833}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-router-entrypoint","started_at_ms":1792905019915}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-signing-worker-entrypoint","started_at_ms":1792679536171}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-signing-worker-entrypoint","started_at_ms":1792904505860}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":false,"features":"strict-worker-tenant-root-control-plane-entrypoint","started_at_ms":1792680386474}
1
+ {"profile":"release","wallet_objects":false,"features":"strict-worker-tenant-root-control-plane-entrypoint","started_at_ms":1792905174415}
@@ -120,8 +120,10 @@ export function prepareLocalHostedWalletGatewayConfig(input) {
120
120
  SEAMS_STAGING_ENV_ID: deployment.deployment.environmentId,
121
121
  SEAMS_STAGING_ORG_ID: deployment.deployment.orgId,
122
122
  SEAMS_STAGING_PROJECT_ID: deployment.deployment.projectId,
123
- SIGNING_SESSION_SEAL_ACCEPTED_WARM_KEY_VERSIONS: 'signing-session-seal-local-r2',
124
- SIGNING_SESSION_SEAL_CURRENT_KEY_VERSION: 'signing-session-seal-local-r2',
123
+ EMAIL_OTP_SERVER_SEAL_ACCEPTED_WARM_KEY_VERSIONS:
124
+ 'signing-session-seal-local-r2,signing-session-seal-local-r3',
125
+ SIGNING_SESSION_SEAL_ACCEPTED_WARM_KEY_VERSIONS: 'signing-session-seal-local-r3',
126
+ SIGNING_SESSION_SEAL_CURRENT_KEY_VERSION: 'signing-session-seal-local-r3',
125
127
  SIGNING_SESSION_SEAL_ROOT_SECRET_B64U: localSecret(internalAuthSecret, 'signing-session-seal'),
126
128
  SIGNING_WORKER_ID: requiredEnv(routerEnv, 'SIGNING_WORKER_ID'),
127
129
  WALLET_LOCAL_DEPLOYMENT_JSON: JSON.stringify(deployment),
@@ -524,6 +526,18 @@ function applyRoleVars(source, role, env) {
524
526
  'SIGNING_WORKER_SERVER_OUTPUT_HPKE_PUBLIC_KEY',
525
527
  requiredEnv(env.signingWorkerEnv, 'SIGNING_WORKER_SERVER_OUTPUT_HPKE_PUBLIC_KEY'),
526
528
  );
529
+ config = setTomlSectionAssignment(
530
+ config,
531
+ 'vars',
532
+ 'TENANT_ROOT_CONTROL_PLANE_ISSUER_VERIFYING_KEYS_JSON',
533
+ env.tenantRootKeys.issuer.verifyingKeysJson,
534
+ );
535
+ config = setTomlSectionAssignment(
536
+ config,
537
+ 'vars',
538
+ 'ROUTER_TENANT_ROOT_CREATION_ROLE_VERIFYING_KEYS_JSON',
539
+ env.tenantRootKeys.roleVerifyingKeysJson,
540
+ );
527
541
  return setPrivateD1Vars(config, 'SIGNING_WORKER_PRIVATE_D1', env.privateD1Keys.signingWorker);
528
542
  case 'tenant-root-control-plane':
529
543
  config = setTomlSectionAssignment(
@@ -1,4 +1,4 @@
1
- import { parseLinkedDeviceId, parseVerifiedEmailAddress, parseWalletAuthorityId } from "../../shared-ts/src/utils/domainIds.js";
1
+ import { parseLinkedDeviceId, parseWalletAuthorityId } from "../../shared-ts/src/utils/domainIds.js";
2
2
  import { buildDelegatedWalletAuthorityV1, buildFullOwnerPermissionsV1, hasDelegatedWalletPermissionV1, sameDelegatedWalletAuthorityV1 } from "../../shared-ts/src/authorization/delegatedAuthority.js";
3
3
  import { base64UrlDecode, base64UrlEncode } from "../../shared-ts/src/utils/base64.js";
4
4
  //#region src/core/deviceLinking/linkedDeviceManagement.ts
@@ -22,14 +22,13 @@ var LinkedDeviceManagementServiceV1 = class {
22
22
  });
23
23
  const devices = [];
24
24
  const ownerDevices = [];
25
- const emailOtpAddress = emailOtpAddressOnceV1(this.options.credentials, request.walletId);
26
25
  for (const authority of page.records) {
27
26
  const activeMethods = (await this.options.authMethod.listForAuthorityV1({
28
27
  walletId: authority.walletId,
29
28
  authorityId: authority.authorityId
30
29
  })).filter(isActiveAuthMethod);
31
- if (authority.provenance.kind === "device_link") for (const activeMethod of activeMethods) devices.push(await this.buildLinkedDeviceSummaryV1(authority, activeMethod, emailOtpAddress));
32
- else if (request.cursor === null) for (const activeMethod of activeMethods) ownerDevices.push(await this.buildOwnerDeviceSummaryV1(authority, activeMethod, emailOtpAddress));
30
+ if (authority.provenance.kind === "device_link") for (const activeMethod of activeMethods) devices.push(await this.buildLinkedDeviceSummaryV1(authority, activeMethod));
31
+ else if (request.cursor === null) for (const activeMethod of activeMethods) ownerDevices.push(await this.buildOwnerDeviceSummaryV1(authority, activeMethod));
33
32
  }
34
33
  return {
35
34
  devices,
@@ -42,7 +41,7 @@ var LinkedDeviceManagementServiceV1 = class {
42
41
  if (!resolved || !hasFullOwnerPermissionsV1(resolved.authority)) return { kind: "unauthorized" };
43
42
  if (request.walletId !== resolved.session.walletId) return { kind: "unauthorized" };
44
43
  const freshProof = source.freshProof;
45
- if (!freshProof || freshProof.verifiedAtMs > request.requestedAtMs || request.requestedAtMs - freshProof.verifiedAtMs > MAX_FRESH_REVOCATION_PROOF_AGE_MS_V1) return { kind: "unauthorized" };
44
+ if (!freshProof || freshProof.verifiedAtMs > request.requestedAtMs || request.requestedAtMs - freshProof.verifiedAtMs > 3e5) return { kind: "unauthorized" };
46
45
  const freshSourceMethod = await this.options.authMethod.readByIdV1({ walletAuthMethodId: freshProof.walletAuthMethodId });
47
46
  if (!freshSourceMethod || freshSourceMethod.status !== "active" || freshSourceMethod.walletId !== request.walletId) return { kind: "unauthorized" };
48
47
  const freshSourceAuthority = await this.options.authority.readByIdV1(freshSourceMethod.walletAuthorityId);
@@ -142,13 +141,13 @@ var LinkedDeviceManagementServiceV1 = class {
142
141
  requestedAtMs
143
142
  });
144
143
  }
145
- async buildLinkedDeviceSummaryV1(authority, authMethod, emailOtpAddress) {
144
+ async buildLinkedDeviceSummaryV1(authority, authMethod) {
146
145
  if (authority.provenance.kind !== "device_link") throw new Error("linked authority provenance is invalid");
147
146
  return {
148
147
  deviceId: parseLinkedDeviceIdValue(authority.principal.deviceId),
149
148
  enrollmentId: authority.provenance.enrollmentId,
150
149
  walletId: authority.walletId,
151
- credential: await credentialMetadataV1(this.options.credentials, authority.walletId, authMethod, emailOtpAddress),
150
+ credential: await credentialMetadataV1(this.options.credentials, authority.walletId, authMethod),
152
151
  permission: buildDelegatedWalletAuthorityV1({ permissions: authority.permissions }),
153
152
  keyManifestDigestB64u: authority.signerActivationSetDigestB64u,
154
153
  coveredWalletKeys: walletKeysFromAuthority(authority),
@@ -158,11 +157,11 @@ var LinkedDeviceManagementServiceV1 = class {
158
157
  revocationEpoch: authority.revocationEpoch
159
158
  };
160
159
  }
161
- async buildOwnerDeviceSummaryV1(authority, authMethod, emailOtpAddress) {
160
+ async buildOwnerDeviceSummaryV1(authority, authMethod) {
162
161
  return {
163
162
  walletId: authority.walletId,
164
163
  walletAuthorityId: authority.authorityId,
165
- credential: await credentialMetadataV1(this.options.credentials, authority.walletId, authMethod, emailOtpAddress),
164
+ credential: await credentialMetadataV1(this.options.credentials, authority.walletId, authMethod),
166
165
  createdAtMs: authority.createdAtMs,
167
166
  lastActivityAtMs: Math.max(authority.updatedAtMs, authMethod.updatedAtMs)
168
167
  };
@@ -181,28 +180,10 @@ function hasFullOwnerPermissionsV1(authority) {
181
180
  function isActiveAuthMethod(record) {
182
181
  return record.status === "active";
183
182
  }
184
- /**
185
- * One wallet has one verified Email OTP address (its canonical enrollment), so
186
- * the list path reads it at most once regardless of how many methods share it.
187
- */
188
- function emailOtpAddressOnceV1(credentials, walletId) {
189
- let pending = null;
190
- return () => {
191
- pending ??= (async () => {
192
- const raw = await credentials.readEmailOtpAddressV1({ walletId });
193
- if (raw === null) throw new Error("active email OTP enrollment address is missing");
194
- const parsed = parseVerifiedEmailAddress(raw);
195
- if (!parsed.ok) throw new Error("active email OTP enrollment address is invalid");
196
- return parsed.value;
197
- })();
198
- return pending;
199
- };
200
- }
201
- async function credentialMetadataV1(credentials, walletId, authMethod, emailOtpAddress) {
183
+ async function credentialMetadataV1(credentials, walletId, authMethod) {
202
184
  if (authMethod.kind === "email_otp") return {
203
185
  kind: "email_otp",
204
- walletAuthMethodId: authMethod.walletAuthMethodId,
205
- email: await emailOtpAddress()
186
+ walletAuthMethodId: authMethod.walletAuthMethodId
206
187
  };
207
188
  const device = await credentials.readPasskeyDeviceInfoV1({
208
189
  walletId,
@@ -293,6 +274,6 @@ function isCursorRecordV1(value) {
293
274
  return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).sort().join(",") === "authorityId,kind,updatedAtMs";
294
275
  }
295
276
  //#endregion
296
- export { LinkedDeviceListCursorError, LinkedDeviceManagementServiceV1, MAX_LINKED_DEVICE_LIST_LIMIT_V1 };
277
+ export { LinkedDeviceListCursorError, LinkedDeviceManagementServiceV1, MAX_FRESH_REVOCATION_PROOF_AGE_MS_V1, MAX_LINKED_DEVICE_LIST_LIMIT_V1 };
297
278
 
298
279
  //# sourceMappingURL=linkedDeviceManagement.js.map
@@ -79,13 +79,25 @@ function grantValues(record) {
79
79
  /** Statements on `email_otp_grants`. */
80
80
  const emailOtpGrantRows = {
81
81
  insert: (prepare, record) => prepare(INSERT_GRANT_SQL, grantValues(record)),
82
- consume: (prepare, grantToken) => prepare(`DELETE FROM email_otp_grants
82
+ read: (prepare, grantToken) => prepare(`SELECT record_json, expires_at_ms FROM email_otp_grants
83
83
  WHERE namespace = ?
84
84
  AND org_id = ?
85
85
  AND project_id = ?
86
86
  AND env_id = ?
87
87
  AND grant_token = ?
88
- RETURNING record_json, expires_at_ms`, [grantToken])
88
+ LIMIT 1`, [grantToken]),
89
+ consumeMatching: (prepare, record, nowMs) => prepare(`DELETE FROM email_otp_grants
90
+ WHERE namespace = ?
91
+ AND org_id = ?
92
+ AND project_id = ?
93
+ AND env_id = ?
94
+ AND grant_token = ?
95
+ AND record_json = ?
96
+ AND expires_at_ms > ?`, [
97
+ record.grantToken,
98
+ JSON.stringify(record),
99
+ nowMs
100
+ ])
89
101
  };
90
102
  const INSERT_WALLET_ENROLLMENT_SQL = `INSERT INTO email_otp_wallet_enrollments (
91
103
  namespace,
@@ -140,16 +140,9 @@ function validateNamedAccountCreationInput(input) {
140
140
  };
141
141
  }
142
142
  async function fetchRelayerTxContext(input) {
143
- let nonce = 0n;
144
- try {
145
- const accessKey = await input.nearClient.viewAccessKey(input.relayerAccount, input.relayerPublicKey);
146
- nonce = BigInt(accessKey?.nonce ?? 0);
147
- } catch {
148
- nonce = 0n;
149
- }
150
- const block = await input.nearClient.viewBlock({ finality: "final" });
143
+ const [accessKey, block] = await Promise.all([input.nearClient.viewAccessKey(input.relayerAccount, input.relayerPublicKey), input.nearClient.viewBlock({ finality: "final" })]);
151
144
  return {
152
- nextNonce: (nonce + 1n).toString(),
145
+ nextNonce: (BigInt(accessKey.nonce) + 1n).toString(),
153
146
  blockHash: block.header.hash
154
147
  };
155
148
  }
@@ -584,12 +584,15 @@ function routerAbCeremonyJwksResponse(env) {
584
584
  let cachedStagingSigningSessionSealOptions = null;
585
585
  function stagingSigningSessionSealOptions(env) {
586
586
  if (cachedStagingSigningSessionSealOptions) return cachedStagingSigningSessionSealOptions;
587
- const seal = stagingEmailOtpServerSealConfig(env);
588
- if (!seal) return void 0;
587
+ const rootSecretB64u = readEnvironmentString(env, "SIGNING_SESSION_SEAL_ROOT_SECRET_B64U");
588
+ const currentKeyVersion = readEnvironmentString(env, "SIGNING_SESSION_SEAL_CURRENT_KEY_VERSION");
589
+ const acceptedWarmKeyVersions = readEnvironmentString(env, "SIGNING_SESSION_SEAL_ACCEPTED_WARM_KEY_VERSIONS").split(",").map((value) => value.trim()).filter(Boolean);
590
+ if (!rootSecretB64u && !currentKeyVersion) return void 0;
591
+ if (!rootSecretB64u || !currentKeyVersion || acceptedWarmKeyVersions.length === 0) throw new Error("Signing-session seal requires the root secret, current key version, and accepted warm key versions");
589
592
  cachedStagingSigningSessionSealOptions = createSigningSessionSealOptions({
590
- rootSecretB64u: seal.rootSecretB64u,
591
- currentKeyVersion: seal.currentKeyVersion,
592
- acceptedWarmKeyVersions: seal.acceptedWarmKeyVersions
593
+ rootSecretB64u,
594
+ currentKeyVersion,
595
+ acceptedWarmKeyVersions
593
596
  });
594
597
  return cachedStagingSigningSessionSealOptions;
595
598
  }
@@ -626,8 +629,8 @@ async function assertD1Tables(input) {
626
629
  function stagingEmailOtpServerSealConfig(env) {
627
630
  const rootSecretB64u = readEnvironmentString(env, "SIGNING_SESSION_SEAL_ROOT_SECRET_B64U");
628
631
  const currentKeyVersion = readEnvironmentString(env, "SIGNING_SESSION_SEAL_CURRENT_KEY_VERSION");
629
- const acceptedWarmKeyVersions = readEnvironmentString(env, "SIGNING_SESSION_SEAL_ACCEPTED_WARM_KEY_VERSIONS").split(",").map((value) => value.trim()).filter(Boolean);
630
- if (!rootSecretB64u && !currentKeyVersion) return;
632
+ const acceptedWarmKeyVersions = readEnvironmentString(env, "EMAIL_OTP_SERVER_SEAL_ACCEPTED_WARM_KEY_VERSIONS").split(",").map((value) => value.trim()).filter(Boolean);
633
+ if (!rootSecretB64u && !currentKeyVersion && acceptedWarmKeyVersions.length === 0) return;
631
634
  if (!rootSecretB64u || !currentKeyVersion || acceptedWarmKeyVersions.length === 0) throw new Error("Email OTP server seal requires the root secret, current key version, and accepted warm key versions");
632
635
  return {
633
636
  rootSecretB64u,
@@ -1,9 +1,24 @@
1
1
  import { normalizeOptionalTrimmedString } from "../../shared-ts/src/utils/normalize.js";
2
+ import { isPlainObject } from "../../shared-ts/src/utils/validation.js";
2
3
  import { base64Decode, base64UrlDecode, base64UrlEncode } from "../../shared-ts/src/utils/base64.js";
3
4
  import "../../shared-ts/src/utils/encoders.js";
4
5
  import { failure } from "../../shared-ts/src/utils/failure.js";
5
6
  import { errorMessage } from "../../shared-ts/src/utils/errors.js";
6
7
  //#region src/router/auth/webAuthnCredentialCodecs.ts
8
+ function containsWebAuthnExtensionOutput(value) {
9
+ if (value == null) return false;
10
+ if (!isPlainObject(value)) return true;
11
+ for (const nested of Object.values(value)) if (containsWebAuthnExtensionOutput(nested)) return true;
12
+ return false;
13
+ }
14
+ function unredactedWebAuthnExtensionField(input) {
15
+ if (!isPlainObject(input)) return null;
16
+ if (containsWebAuthnExtensionOutput(input.clientExtensionResults)) return "clientExtensionResults";
17
+ const response = input.response;
18
+ if (!isPlainObject(response)) return null;
19
+ if (containsWebAuthnExtensionOutput(response.clientExtensionResults)) return "response.clientExtensionResults";
20
+ return null;
21
+ }
7
22
  function decodeWebAuthnBase64UrlOrBase64(input, fieldName) {
8
23
  try {
9
24
  return base64UrlDecode(input);
@@ -90,6 +105,6 @@ function parseWebAuthnAuthenticationCredential(input) {
90
105
  };
91
106
  }
92
107
  //#endregion
93
- export { decodeWebAuthnBase64UrlOrBase64, parseWebAuthnAuthenticationCredential, parseWebAuthnClientDataJsonBase64url, webAuthnCredentialIdB64uFromCredential, webAuthnOriginHostnameOrEmpty };
108
+ export { decodeWebAuthnBase64UrlOrBase64, parseWebAuthnAuthenticationCredential, parseWebAuthnClientDataJsonBase64url, unredactedWebAuthnExtensionField, webAuthnCredentialIdB64uFromCredential, webAuthnOriginHostnameOrEmpty };
94
109
 
95
110
  //# sourceMappingURL=webAuthnCredentialCodecs.js.map
@@ -77,6 +77,7 @@ function createD1GoogleRegistrationAttempts(options, prepare) {
77
77
  }
78
78
  function createD1EmailOtpRateLimits(options, prepare) {
79
79
  return new CloudflareD1EmailOtpRateLimitStore({
80
+ database: options.database,
80
81
  prepare,
81
82
  batch: options.database.batch.bind(options.database),
82
83
  rateLimits: options.emailOtp.rateLimits
@@ -115,7 +115,6 @@ function createD1LinkedDeviceComposition(input) {
115
115
  authorizationService: input.authorizationService,
116
116
  walletSessionAuthorizations: input.authorizationStore,
117
117
  webAuthnStore: input.webAuthnStore,
118
- ...input.emailOtpLinkedDevice === void 0 ? {} : { emailOtpEnrollments: input.emailOtpLinkedDevice.enrollments },
119
118
  materialDeactivation: createCloudflareOrdinaryInactiveSignerMaterialDeactivationPortV1({ endpoint: deactivationEndpoint })
120
119
  });
121
120
  const revokeLinkedDeviceWithFreshProof = createD1LinkedDeviceRevocationV1({
@@ -156,7 +156,10 @@ var D1LinkedDeviceEmailOtpTargetFactorV1 = class {
156
156
  issuedAtMs,
157
157
  expiresAtMs
158
158
  });
159
- const unsealed = await this.options.serverSeal.removeEmailOtpServerSeal({ wrappedCiphertext: context.resolved.enrollment.serverSealedFactorCiphertextB64u });
159
+ const unsealed = await this.options.serverSeal.removeEmailOtpServerSeal({
160
+ wrappedCiphertext: context.resolved.enrollment.serverSealedFactorCiphertextB64u,
161
+ enrollmentSealKeyVersion: context.resolved.enrollment.enrollmentSealKeyVersion
162
+ });
160
163
  if (!unsealed.ok) return {
161
164
  kind: "refused",
162
165
  code: unsealed.code,
@@ -11,17 +11,12 @@ function createD1LinkedDeviceManagementServiceV1(input) {
11
11
  }),
12
12
  authMethod: authMethodPortV1(input.authMethodStore),
13
13
  sessions: input.authorizationService,
14
- credentials: {
15
- readPasskeyDeviceInfoV1: async ({ walletId, credentialIdB64u }) => {
16
- return (await input.webAuthnStore.readAuthenticator({
17
- userId: String(walletId),
18
- credentialIdB64u
19
- }))?.deviceInfo ?? null;
20
- },
21
- readEmailOtpAddressV1: async ({ walletId }) => {
22
- return (await input.emailOtpEnrollments?.readEnrollment(String(walletId)))?.verifiedEmail ?? null;
23
- }
24
- },
14
+ credentials: { readPasskeyDeviceInfoV1: async ({ walletId, credentialIdB64u }) => {
15
+ return (await input.webAuthnStore.readAuthenticator({
16
+ userId: String(walletId),
17
+ credentialIdB64u
18
+ }))?.deviceInfo ?? null;
19
+ } },
25
20
  ...input.materialDeactivation === void 0 ? {} : { materialDeactivation: input.materialDeactivation }
26
21
  });
27
22
  }
@@ -1,4 +1,5 @@
1
1
  import { computeWalletAuthMethodRevokeOperationFingerprintV1 } from "../../../../shared-ts/src/utils/walletAuthMethodRecord.js";
2
+ import "../../../../core/deviceLinking/linkedDeviceManagement.js";
2
3
  import { computeWalletAuthMethodRevocationProofDigestV1 } from "../wallet/d1WalletAuthMethodRevocationReplayStore.js";
3
4
  //#region src/router/cloudflare/d1/deviceLinking/d1LinkedDeviceRevocation.ts
4
5
  /**
@@ -42,6 +43,12 @@ function createD1LinkedDeviceRevocationV1(deps) {
42
43
  };
43
44
  const committed = await answeredFromRecord();
44
45
  if (committed) return committed;
46
+ const nowMs = deps.nowV1();
47
+ if (requestedAtMs > nowMs || nowMs - requestedAtMs > 3e5) return {
48
+ kind: "denied",
49
+ code: "invalid",
50
+ message: "Fresh revocation proof request time is invalid"
51
+ };
45
52
  const verified = await deps.verifyProofForBatch({
46
53
  walletId,
47
54
  targetWalletAuthMethodId: walletAuthMethodId,
@@ -9,8 +9,11 @@ var CloudflareD1EmailOtpGrantStore = class {
9
9
  async put(record) {
10
10
  await emailOtpGrantRows.insert(this.prepare, record).run();
11
11
  }
12
- async consume(grantToken) {
13
- return parseEmailOtpGrantRow(await emailOtpGrantRows.consume(this.prepare, grantToken).first());
12
+ async read(grantToken) {
13
+ return parseEmailOtpGrantRow(await emailOtpGrantRows.read(this.prepare, grantToken).first());
14
+ }
15
+ prepareConsumeMatching(record, nowMs) {
16
+ return emailOtpGrantRows.consumeMatching(this.prepare, record, nowMs);
14
17
  }
15
18
  };
16
19
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { D1_BATCH_CAS_GUARD_SQL } from "../../../../storage/d1Sql.js";
1
2
  import { emailOtpRateLimitExceeded, emailOtpRateLimitKeys } from "./d1EmailOtpRecords.js";
2
3
  //#region src/router/cloudflare/d1/emailOtp/d1EmailOtpRateLimitStore.ts
3
4
  var CloudflareD1EmailOtpRateLimitStore = class {
@@ -20,6 +21,23 @@ var CloudflareD1EmailOtpRateLimitStore = class {
20
21
  }
21
22
  return { ok: true };
22
23
  }
24
+ async consumeVerifiedGrant(input) {
25
+ const policy = this.input.rateLimits.grant;
26
+ const keys = emailOtpRateLimitKeys(input.rateLimit, policy);
27
+ const statements = [input.grantDelete, this.input.database.prepare(D1_BATCH_CAS_GUARD_SQL)];
28
+ for (const counter of keys) statements.push(this.prepareConsume(counter, policy, input.nowMs, true), this.input.database.prepare(D1_BATCH_CAS_GUARD_SQL));
29
+ const results = await this.input.batch(statements);
30
+ if (results.length !== statements.length) throw new Error("OTP grant counter batch failed");
31
+ for (const result of results) if (!result.success) throw new Error("OTP grant counter batch failed");
32
+ }
33
+ async readExceededGrantRateLimit(input, nowMs) {
34
+ const policy = this.input.rateLimits.grant;
35
+ for (const counter of emailOtpRateLimitKeys(input, policy)) {
36
+ const row = await this.readCounter(counter);
37
+ if (typeof row?.consumed_count === "number" && Number.isSafeInteger(row.consumed_count) && row.consumed_count >= policy.limit && typeof row.reset_at_ms === "number" && Number.isSafeInteger(row.reset_at_ms) && row.reset_at_ms > nowMs) return emailOtpRateLimitExceeded(row);
38
+ }
39
+ return null;
40
+ }
23
41
  prepareConsume(counter, policy, nowMs, first) {
24
42
  const resetAtMs = nowMs + policy.windowMs;
25
43
  let walletId;
@@ -232,19 +232,39 @@ var CloudflareD1EmailOtpRecoveryService = class {
232
232
  try {
233
233
  const parsed = normalizeGrantConsumptionInput(input);
234
234
  if (!parsed.ok) return parsed.result;
235
- const rateLimit = await this.emailOtpRateLimits.consume({
235
+ const nowMs = Date.now();
236
+ const record = await this.emailOtpGrants.read(parsed.value.loginGrant);
237
+ if (!record || nowMs >= record.expiresAtMs || record.action !== WALLET_EMAIL_OTP_ACTIONS.unseal || emailOtpGrantBindingMismatch(record, parsed.value)) {
238
+ const invalidGrantRateLimit = await this.emailOtpRateLimits.consume({
239
+ kind: "regional",
240
+ scope: "grant",
241
+ action: "invalid_grant",
242
+ clientIp: parsed.value.clientIp || "unknown"
243
+ });
244
+ if (!invalidGrantRateLimit.ok) return invalidGrantRateLimit;
245
+ return emailOtpGrantInvalidOrExpired();
246
+ }
247
+ const grantRateLimitInput = {
236
248
  kind: "wallet",
237
249
  scope: "grant",
238
- userId: parsed.value.userId,
239
250
  walletId: parsed.value.walletId,
251
+ userId: parsed.value.userId,
240
252
  orgId: parsed.value.orgId,
241
253
  clientIp: parsed.value.clientIp
242
- });
243
- if (!rateLimit.ok) return rateLimit;
244
- const record = await this.emailOtpGrants.consume(parsed.value.loginGrant);
245
- if (!record || Date.now() > record.expiresAtMs) return emailOtpGrantInvalidOrExpired();
246
- if (record.action !== WALLET_EMAIL_OTP_ACTIONS.unseal) return emailOtpGrantInvalidOrExpired();
247
- if (emailOtpGrantBindingMismatch(record, parsed.value)) return emailOtpGrantInvalidOrExpired();
254
+ };
255
+ try {
256
+ await this.emailOtpRateLimits.consumeVerifiedGrant({
257
+ grantDelete: this.emailOtpGrants.prepareConsumeMatching(record, nowMs),
258
+ rateLimit: grantRateLimitInput,
259
+ nowMs
260
+ });
261
+ } catch (error) {
262
+ const current = await this.emailOtpGrants.read(parsed.value.loginGrant);
263
+ if (!current || Date.now() >= current.expiresAtMs) return emailOtpGrantInvalidOrExpired();
264
+ const rateLimit = await this.emailOtpRateLimits.readExceededGrantRateLimit(grantRateLimitInput, nowMs);
265
+ if (rateLimit) return rateLimit;
266
+ throw error;
267
+ }
248
268
  return {
249
269
  ok: true,
250
270
  challengeId: record.challengeId,