@seams/wallet-server 0.54.0 → 0.56.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.
package/README.md CHANGED
@@ -4,17 +4,17 @@ This package owns the reusable signer server surface: route adapters,
4
4
  WebAuthn verification policy, threshold signing services, signer storage
5
5
  adapters, and server wasm bindings.
6
6
 
7
- The package version is `@seams/wallet-server@0.54.0`. Pair it with
8
- `@seams/wallet@0.54.0` and matching hosted assets. Use `@seams/wallet-cli@0.29.0`.
7
+ The package version is `@seams/wallet-server@0.56.0`. Pair it with
8
+ `@seams/wallet@0.56.0` and matching hosted assets. Use `@seams/wallet-cli@0.29.0`.
9
9
  The wallet iframe protocol is **23.0.0**.
10
10
 
11
11
  ```sh
12
- pnpm add @seams/wallet-server@0.54.0
12
+ pnpm add @seams/wallet-server@0.56.0
13
13
  ```
14
14
 
15
15
  ## Google BYO authentication
16
16
 
17
- Version 0.54.0 verifies a configured application's signed session assertion or the app's Google ID token or exchanges it for a browser-bound handoff.
17
+ Version 0.56.0 verifies a configured application's signed session assertion or the app's Google ID token or exchanges it for a browser-bound handoff.
18
18
  An environment-specific Google client configuration and an owner-approved wallet association are required.
19
19
  Apply the packaged signer migrations before enabling the feature.
20
20
  External sessions grant settings reads and remain separate from owner and signing credentials.
@@ -3,17 +3,17 @@
3
3
  "walletSecurityPolicy": "scoped_seals_and_owner_proofs_v1",
4
4
  "package": {
5
5
  "name": "@seams/wallet-server",
6
- "version": "0.54.0"
6
+ "version": "0.56.0"
7
7
  },
8
8
  "workers": {
9
9
  "router": {
10
10
  "entry": "cloudflare-router-ab/build/router/worker/shim.mjs",
11
- "fingerprint": "22e885129a9dbc5ad00599cc4dc3b4ce71a833fa3fa26993530200abda4a47d1",
11
+ "fingerprint": "2bce0668c0ea0ebe987780665679d355755c54dba199037d2888b2f45618bd9f",
12
12
  "files": [
13
13
  {
14
14
  "path": "cloudflare-router-ab/build/router/build-stamp.json",
15
15
  "bytes": 119,
16
- "sha256": "8a75df14351fb43abefdd49c9660727594a5b751ad61d8916aabffe8492f78ff"
16
+ "sha256": "e52ab10f7f5ccbc3489faa7dedbe99411a921f3a337c02a9dbc6157c270b5837"
17
17
  },
18
18
  {
19
19
  "path": "cloudflare-router-ab/build/router/index.js",
@@ -39,12 +39,12 @@
39
39
  },
40
40
  "deriver-a": {
41
41
  "entry": "cloudflare-router-ab/build/deriver-a/worker/shim.mjs",
42
- "fingerprint": "bdd26687d013f61d6e6638fe29b0d457813fd4ee7bc731c6a9f2d4ba1b0faa28",
42
+ "fingerprint": "64b21a5543d049faf93a8c811df01b3189f237a78525d794ab8fed4d363d5c77",
43
43
  "files": [
44
44
  {
45
45
  "path": "cloudflare-router-ab/build/deriver-a/build-stamp.json",
46
46
  "bytes": 122,
47
- "sha256": "06905532466e1015afdd6c5ae77945cc5fd117548e10769f3c26027d82d94a0c"
47
+ "sha256": "12a004595c4e75fc0c60eb4a86c1d097b60219a12a87e72629fec88de3c5c9aa"
48
48
  },
49
49
  {
50
50
  "path": "cloudflare-router-ab/build/deriver-a/index.js",
@@ -70,12 +70,12 @@
70
70
  },
71
71
  "deriver-b": {
72
72
  "entry": "cloudflare-router-ab/build/deriver-b/worker/shim.mjs",
73
- "fingerprint": "d998751bf0efb8861236a8b8f6b0314dd8d41eaaca2f542497085e9d8b62e504",
73
+ "fingerprint": "c5176d3f8dccc72bb1dc577d975470b4213f1a72ded5e66bab8ff7f34c190724",
74
74
  "files": [
75
75
  {
76
76
  "path": "cloudflare-router-ab/build/deriver-b/build-stamp.json",
77
77
  "bytes": 122,
78
- "sha256": "fdec4e26b25e217da83b6f6c82c014d4689a95dbb47fd14333cd01f4f870e97b"
78
+ "sha256": "740681c46eb3400e69130059ef4a9423e9e06b3da129e8b834e0b82227003861"
79
79
  },
80
80
  {
81
81
  "path": "cloudflare-router-ab/build/deriver-b/index.js",
@@ -101,12 +101,12 @@
101
101
  },
102
102
  "signing-worker": {
103
103
  "entry": "cloudflare-router-ab/build/signing-worker/worker/shim.mjs",
104
- "fingerprint": "2d666492dbaa78826cedd663257c2eab86fe23ec5552a1b59b683717aebfbe3e",
104
+ "fingerprint": "4ea33a63250bfbf073102ceafb22193d3f300a562bba4537d067ef2a8de07b44",
105
105
  "files": [
106
106
  {
107
107
  "path": "cloudflare-router-ab/build/signing-worker/build-stamp.json",
108
108
  "bytes": 127,
109
- "sha256": "1ace62b6964ee4b8b9ac30bb49f315890e2742be54e14c1b8ee78237f3e808c3"
109
+ "sha256": "b0dacdc6a53ebd644b08f9f186ba0eed59a3a4a5f3a14936fc2197138c13e20a"
110
110
  },
111
111
  {
112
112
  "path": "cloudflare-router-ab/build/signing-worker/index.js",
@@ -132,12 +132,12 @@
132
132
  },
133
133
  "tenant-root-control-plane": {
134
134
  "entry": "cloudflare-router-ab/build/tenant-root-control-plane/worker/shim.mjs",
135
- "fingerprint": "9fa94f9d652e8e36e68f318ff2a44be844ff398f6317d80467d0e0fa836d522f",
135
+ "fingerprint": "e79512250e1c788de74dcb29fdb56c004ab511ad58b41be8bc7af14bc777ad2a",
136
136
  "files": [
137
137
  {
138
138
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json",
139
139
  "bytes": 139,
140
- "sha256": "2e7804f6e031ad965621682f581c0540069c2a25a182dd763c80d7f4d75dd98a"
140
+ "sha256": "c375a2555ade8c3bff20b13436430e608bfdc2388e709f4e02f959722723c679"
141
141
  },
142
142
  {
143
143
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/index.js",
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-a-entrypoint","started_at_ms":1794125422353}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-a-entrypoint","started_at_ms":1794146490393}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-b-entrypoint","started_at_ms":1794125619132}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-b-entrypoint","started_at_ms":1794146682144}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-router-entrypoint","started_at_ms":1794125797895}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-router-entrypoint","started_at_ms":1794146860888}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-signing-worker-entrypoint","started_at_ms":1794125116180}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-signing-worker-entrypoint","started_at_ms":1794146191911}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":false,"features":"strict-worker-tenant-root-control-plane-entrypoint","started_at_ms":1794126001644}
1
+ {"profile":"release","wallet_objects":false,"features":"strict-worker-tenant-root-control-plane-entrypoint","started_at_ms":1794147071629}
@@ -77,6 +77,8 @@ import { CloudflareD1WebAuthnStore, prepareD1WebAuthnAuthenticatorInsertStatemen
77
77
  import { CloudflareD1WebAuthnAuthService } from "./router/cloudflare/d1/webauthn/d1WebAuthnAuthService.js";
78
78
  import { admitD1EstablishedWalletHome, resumeRegistrationDirectoryPublicationsV1 } from "./router/cloudflare/d1/registration/d1WalletExecutionAuthority.js";
79
79
  import { parseLinkedDeviceSessionRecordV1 } from "./core/deviceLinking/linkedDeviceSessionRecord.js";
80
+ import { parseD1LinkedDeviceSessionRowV1, parseD1LinkedDeviceSessionTranscriptRowV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceSessionRecords.js";
81
+ import { D1LinkedDeviceSessionStoreV1, SESSION_CAS_GUARD_SQL } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceSessionStore.js";
80
82
  import { D1LinkedDeviceSourceContributionPreparationPlannerV1, createD1LinkedDeviceSourceContributionPreparationPlannerV1, linkedDeviceX25519RecipientPublicKeyB64uV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceSourceContributionPreparationPlanner.js";
81
83
  import { D1LinkedDeviceAuthorityInstallServiceV1, parseInstalledEcdsaCustodySnapshotV1, readD1LinkedDeviceEcdsaSourceV1, readD1LinkedDeviceEd25519SourceV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceAuthorityInstallService.js";
82
84
  import { CloudflareD1AuthorizationStore } from "./router/cloudflare/d1/authorization/d1AuthorizationStore.js";
@@ -113,8 +115,6 @@ import { D1IdentityStore } from "./core/d1IdentityStore.js";
113
115
  import { D1LinkedDeviceEd25519ExportRootStoreV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceEd25519ExportRootStore.js";
114
116
  import { D1LinkedDeviceRequestProofNonceStoreV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceRequestProofNonceStore.js";
115
117
  import { createD1LinkedDeviceSessionServiceV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceSessionService.js";
116
- import { parseD1LinkedDeviceSessionRowV1, parseD1LinkedDeviceSessionTranscriptRowV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceSessionRecords.js";
117
- import { D1LinkedDeviceSessionStoreV1, SESSION_CAS_GUARD_SQL } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceSessionStore.js";
118
118
  import { createD1LinkedDeviceRouteServiceV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceRouteService.js";
119
119
  import { LinkedDeviceSourceFamilyUnavailableErrorV1, createD1LinkedDeviceVerifiedLinkSourceReaderV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceVerifiedLinkSourceReader.js";
120
120
  import { buildVerifiedLinkInputV1, buildVerifiedTargetFactorV1, computeVerifiedTargetFactorVerificationDigestV1, linkedDeviceSignerManifestsEqualV1 } from "./router/cloudflare/d1/deviceLinking/d1LinkedDeviceVerifiedLinkBuilder.js";
@@ -16,6 +16,7 @@ import { resolveActiveOwnerWalletExecutionLane, resolveWalletAuthMethodIdForAuth
16
16
  import { DEFAULT_WALLET_SESSION_TTL_MS, LINKED_DEVICE_WALLET_SESSION_TTL_MS } from "../../../../shared-ts/src/threshold/sessionPolicy.js";
17
17
  import { createDeviceLinkingOwnerRequestAuthenticatorV1 } from "../../../transport/fetch/routes/deviceLinkingOwnerAuthorization.js";
18
18
  import { verifyD1LinkedDeviceFreshRevokeProofV1 } from "../wallet/d1WalletAuthMethodBoundary.js";
19
+ import { D1LinkedDeviceSessionStoreV1 } from "../deviceLinking/d1LinkedDeviceSessionStore.js";
19
20
  import { D1LinkedDeviceAuthorityInstallServiceV1 } from "../deviceLinking/d1LinkedDeviceAuthorityInstallService.js";
20
21
  import { D1WalletAuthorityStore } from "../wallet/d1WalletAuthorityStore.js";
21
22
  import { createD1Ed25519YaoLifecycleRecords } from "../ed25519Yao/d1Ed25519YaoProductRegistrationPartitionedStateStore.js";
@@ -68,7 +69,6 @@ import { CloudflareD1WalletRecoveryGoogleEmailOtpService } from "../passkeyCusto
68
69
  import { CloudflareD1WalletAddSignerService, parseD1WalletAddSignerStartSideEffectRecord } from "../wallet/d1WalletAddSignerService.js";
69
70
  import { CloudflareD1RegistrationIntentService } from "../registration/d1RegistrationIntentService.js";
70
71
  import { createD1LinkedDeviceSessionServiceV1 } from "../deviceLinking/d1LinkedDeviceSessionService.js";
71
- import { D1LinkedDeviceSessionStoreV1 } from "../deviceLinking/d1LinkedDeviceSessionStore.js";
72
72
  import { createD1LinkedDeviceRouteServiceV1 } from "../deviceLinking/d1LinkedDeviceRouteService.js";
73
73
  import { D1LinkedDeviceEmailOtpGrantStoreV1 } from "../deviceLinking/d1LinkedDeviceEmailOtpGrantStore.js";
74
74
  import { createD1LinkedDeviceManagementServiceV1 } from "../deviceLinking/d1LinkedDeviceManagementService.js";
@@ -28,6 +28,7 @@ import { prepareD1WebAuthnAuthenticatorInsertStatement } from "../webauthn/d1Web
28
28
  import { admitD1EstablishedWalletHome } from "../registration/d1WalletExecutionAuthority.js";
29
29
  import { findInstalledEcdsaAuthority, installedEcdsaAuthorityChain, projectInstalledEcdsaAuthority } from "./installedEcdsaAuthority.js";
30
30
  import { buildAuthorityActiveSessionRecordV1, buildAuthorityPendingLocalInstallSessionRecordV1 } from "../../../../core/deviceLinking/linkedDeviceSessionRecord.js";
31
+ import { SESSION_CAS_GUARD_SQL } from "./d1LinkedDeviceSessionStore.js";
31
32
  import { linkedDeviceX25519RecipientPublicKeyB64uV1 } from "./d1LinkedDeviceSourceContributionPreparationPlanner.js";
32
33
  //#region src/router/cloudflare/d1/deviceLinking/d1LinkedDeviceAuthorityInstallService.ts
33
34
  var LinkedDeviceActivationIntegrityErrorV1 = class extends Error {
@@ -580,9 +581,10 @@ var D1LinkedDeviceAuthorityInstallServiceV1 = class {
580
581
  });
581
582
  }
582
583
  async commitAcknowledgementCleanupV1(input) {
584
+ const allocationDelete = this.buildAuthorityAllocationDeleteStatement(input.prepared.acknowledgement.linkSessionId);
583
585
  const beforeDeleteStatements = [
584
586
  this.buildCredentialDeliveryAcknowledgementStatement(input.prepared),
585
- this.buildAuthorityAllocationDeleteStatement(input.prepared.acknowledgement.linkSessionId),
587
+ allocationDelete,
586
588
  this.buildCredentialDeliveryCleanupStateStatement({
587
589
  linkSessionId: input.prepared.acknowledgement.linkSessionId,
588
590
  from: "pending",
@@ -609,7 +611,7 @@ var D1LinkedDeviceAuthorityInstallServiceV1 = class {
609
611
  const detail = "message" in deleted && deleted.message ? `: ${deleted.message}` : "";
610
612
  throw new Error(`active authority cleanup failed: ${deleted.outcome}${detail}`);
611
613
  }
612
- } else await this.options.database.batch([...beforeDeleteStatements, ...afterDeleteStatements]);
614
+ } else await this.options.database.batch([...beforeDeleteStatements, ...afterDeleteStatements].flatMap((statement) => statement === allocationDelete ? [statement] : [statement, this.options.database.prepare(SESSION_CAS_GUARD_SQL)]));
613
615
  } catch (error) {
614
616
  const replay = await this.readActivationCleanupReceiptV1({
615
617
  linkSessionId: input.prepared.acknowledgement.linkSessionId,
@@ -1,10 +1,10 @@
1
1
  import { parseLinkDeviceSessionId } from "../../../../shared-ts/src/utils/domainIds.js";
2
2
  import { readJson } from "../../../framework/http.js";
3
3
  import { LinkedDeviceRequestProofVerifierV1 } from "../../../../core/deviceLinking/requestProof.js";
4
+ import { D1LinkedDeviceSessionStoreV1 } from "./d1LinkedDeviceSessionStore.js";
4
5
  import { D1LinkedDeviceEd25519ExportRootStoreV1 } from "./d1LinkedDeviceEd25519ExportRootStore.js";
5
6
  import { D1LinkedDeviceRequestProofNonceStoreV1 } from "./d1LinkedDeviceRequestProofNonceStore.js";
6
7
  import { createD1LinkedDeviceSessionServiceV1 } from "./d1LinkedDeviceSessionService.js";
7
- import { D1LinkedDeviceSessionStoreV1 } from "./d1LinkedDeviceSessionStore.js";
8
8
  //#region src/router/cloudflare/d1/deviceLinking/d1LinkedDeviceRouteService.ts
9
9
  function createD1LinkedDeviceRouteServiceV1(options) {
10
10
  const nowV1 = options.nowV1 ?? Date.now;
@@ -10,9 +10,9 @@ import { assertLinkedDeviceTargetCredentialRegistrationMatchesPreparationV1, com
10
10
  import { d1ChangedRows } from "../../../../storage/d1Sql.js";
11
11
  import { errorMessage } from "../../../../shared-ts/src/utils/errors.js";
12
12
  import { parseClientDataJsonBase64url } from "../../../../core/authService/webauthnOidcHelpers.js";
13
+ import { SESSION_CAS_GUARD_SQL } from "./d1LinkedDeviceSessionStore.js";
13
14
  import { linkedDeviceX25519RecipientPublicKeyB64uV1 } from "./d1LinkedDeviceSourceContributionPreparationPlanner.js";
14
15
  import { verifyWebAuthnRegistrationCredentialForIntent } from "../../../../core/authService/webauthn.js";
15
- import { SESSION_CAS_GUARD_SQL } from "./d1LinkedDeviceSessionStore.js";
16
16
  import { linkedDeviceEmailOtpDescriptorCredentialIdV1 } from "../../../../core/deviceLinking/linkedDeviceEmailOtpGrant.js";
17
17
  import { buildVerifiedLinkInputV1, buildVerifiedTargetFactorV1 } from "./d1LinkedDeviceVerifiedLinkBuilder.js";
18
18
  //#region src/router/cloudflare/d1/deviceLinking/d1LinkedDeviceTargetCredentialProvider.ts
@@ -1,10 +1,10 @@
1
+ import { parseD1LinkedDeviceSessionRowV1, parseD1LinkedDeviceSessionTranscriptRowV1 } from "./d1LinkedDeviceSessionRecords.js";
2
+ import { D1LinkedDeviceSessionStoreV1, SESSION_CAS_GUARD_SQL } from "./d1LinkedDeviceSessionStore.js";
1
3
  import { D1LinkedDeviceSourceContributionPreparationPlannerV1, createD1LinkedDeviceSourceContributionPreparationPlannerV1, linkedDeviceX25519RecipientPublicKeyB64uV1 } from "./d1LinkedDeviceSourceContributionPreparationPlanner.js";
2
4
  import { D1LinkedDeviceAuthorityInstallServiceV1, parseInstalledEcdsaCustodySnapshotV1, readD1LinkedDeviceEcdsaSourceV1, readD1LinkedDeviceEd25519SourceV1 } from "./d1LinkedDeviceAuthorityInstallService.js";
3
5
  import { D1LinkedDeviceEd25519ExportRootStoreV1 } from "./d1LinkedDeviceEd25519ExportRootStore.js";
4
6
  import { D1LinkedDeviceRequestProofNonceStoreV1 } from "./d1LinkedDeviceRequestProofNonceStore.js";
5
7
  import { createD1LinkedDeviceSessionServiceV1 } from "./d1LinkedDeviceSessionService.js";
6
- import { parseD1LinkedDeviceSessionRowV1, parseD1LinkedDeviceSessionTranscriptRowV1 } from "./d1LinkedDeviceSessionRecords.js";
7
- import { D1LinkedDeviceSessionStoreV1, SESSION_CAS_GUARD_SQL } from "./d1LinkedDeviceSessionStore.js";
8
8
  import { createD1LinkedDeviceRouteServiceV1 } from "./d1LinkedDeviceRouteService.js";
9
9
  import { LinkedDeviceSourceFamilyUnavailableErrorV1, createD1LinkedDeviceVerifiedLinkSourceReaderV1 } from "./d1LinkedDeviceVerifiedLinkSourceReader.js";
10
10
  import { buildVerifiedLinkInputV1, buildVerifiedTargetFactorV1, computeVerifiedTargetFactorVerificationDigestV1, linkedDeviceSignerManifestsEqualV1 } from "./d1LinkedDeviceVerifiedLinkBuilder.js";
@@ -82,10 +82,7 @@ var D1WalletExecutionAuthority = class {
82
82
  outcome: "cancelled"
83
83
  };
84
84
  await this.fixCancellation(input);
85
- const completed = await this.registration.complete(completion);
86
- if (!completed.ok) return completed;
87
- await this.database.batch(this.prepareState(input, "retired"));
88
- return completed;
85
+ return await this.registration.complete(completion);
89
86
  }
90
87
  prepareEstablished(input) {
91
88
  if (input.registration.state === "active") return null;
@@ -96,7 +93,7 @@ var D1WalletExecutionAuthority = class {
96
93
  attempts: 0
97
94
  };
98
95
  return {
99
- statements: [...this.prepareState(input.registration, "active"), prepareD1TenantStatement(this.database, this.scope, `INSERT INTO wallet_registration_directory_publications
96
+ statements: [...this.prepareEstablishedState(input.registration), prepareD1TenantStatement(this.database, this.scope, `INSERT INTO wallet_registration_directory_publications
100
97
  (namespace, org_id, project_id, env_id, wallet_id, ceremony_id,
101
98
  session_json, next_attempt_at_ms)
102
99
  VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
@@ -185,16 +182,12 @@ var D1WalletExecutionAuthority = class {
185
182
  AND registration_completion IN ('pending', 'cancelled')
186
183
  AND state IN ('registering', 'retired')`, [input.walletId, input.ceremonyId]), this.database.prepare(D1_BATCH_CAS_GUARD_SQL)]);
187
184
  }
188
- /** The state update with its guard. The guard reads the row count of the update before it. */
189
- prepareState(input, state) {
190
- return [prepareD1TenantStatement(this.database, this.scope, `UPDATE wallet_execution_generations SET state = ?7, registration_completion = CASE WHEN ?7 = 'active' THEN 'established' ELSE registration_completion END
185
+ /** The established-state update with its guard. The guard reads the row count of the update before it. */
186
+ prepareEstablishedState(input) {
187
+ return [prepareD1TenantStatement(this.database, this.scope, `UPDATE wallet_execution_generations SET state = 'active', registration_completion = 'established'
191
188
  WHERE namespace = ?1 AND org_id = ?2 AND project_id = ?3 AND env_id = ?4
192
189
  AND wallet_id = ?5 AND origin = 'registration' AND origin_id = ?6
193
- AND state IN ('registering', ?7)`, [
194
- input.walletId,
195
- input.ceremonyId,
196
- state
197
- ]), this.database.prepare(D1_BATCH_CAS_GUARD_SQL)];
190
+ AND state IN ('registering', 'active')`, [input.walletId, input.ceremonyId]), this.database.prepare(D1_BATCH_CAS_GUARD_SQL)];
198
191
  }
199
192
  async admitEstablishedHome(input) {
200
193
  return admitD1EstablishedWalletHome(this.database, this.scope, input);
@@ -4,11 +4,14 @@ import { routerAbMpcMaterialActivationRefToWire } from "../../../shared-ts/src/u
4
4
  import { hasExactKeys } from "../../../shared-ts/src/utils/exactKeys.js";
5
5
  import { extractBearerCredential } from "../../auth/routerApiKeyAuth.js";
6
6
  import { defineRoute } from "../../framework/routeDefinitions.js";
7
- import { resolveWalletSessionOperationCredentialAdmissionFromContext } from "../../auth/commonRouterUtils.js";
7
+ import { resolveWalletSessionAuthorizationV2Admission } from "../signingOperations/walletExecutionAdmission.js";
8
8
  //#region src/router/domains/speakToSign/speakToSignVerify.ts
9
9
  const SPEAK_TO_SIGN_VERIFY_PATH = "/speak-to-sign/v1/verify";
10
- /** 8 s of 16 kHz PCM16 is 256,000 bytes, about 341 KB in base64, with room for the fields. */
11
- const SPEAK_TO_SIGN_VERIFY_MAX_BODY_BYTES = 400 * 1024;
10
+ /**
11
+ * An enrollment carries three clips. 8 s of 16 kHz PCM16 is 256,000 bytes, about 341 KB in
12
+ * base64, so three clips are about 1,000 KB, with room for the fields.
13
+ */
14
+ const SPEAK_TO_SIGN_VERIFY_MAX_BODY_BYTES = 1100 * 1024;
12
15
  /** The verifier calls two remote models; the whole flow has a 10 s budget. */
13
16
  const OUTLAYER_TIMEOUT_MS = 2e4;
14
17
  const OUTLAYER_RESOURCE_LIMITS = Object.freeze({
@@ -81,10 +84,10 @@ var SpeakToSignVerifyRoutes = class {
81
84
  path: SPEAK_TO_SIGN_VERIFY_PATH,
82
85
  auth: {
83
86
  plane: "public",
84
- rationale: "The handler requires an active Wallet Session operation credential."
87
+ rationale: "The handler requires a live Wallet Session operation credential of the owner."
85
88
  },
86
89
  metering: { kind: "none" },
87
- summary: "Run the pinned OutLayer voice verifier for a spoken payment"
90
+ summary: "Run the pinned OutLayer voice verifier for a spoken payment or an enrollment"
88
91
  })];
89
92
  now;
90
93
  constructor(config, dependencies) {
@@ -94,8 +97,10 @@ var SpeakToSignVerifyRoutes = class {
94
97
  }
95
98
  async handleFetchRoute(input) {
96
99
  const startedAtMs = this.now();
97
- const subject = await this.authenticate(input.request, startedAtMs);
98
- if (!subject) return failure(401, "unauthorized");
100
+ const authentication = await this.authenticate(input.request, startedAtMs, input.logger);
101
+ if (authentication.kind === "unauthorized") return failure(401, "unauthorized");
102
+ if (authentication.kind === "unavailable") return failure(503, "session_unavailable");
103
+ const { subject } = authentication;
99
104
  const { walletId } = subject;
100
105
  if (this.dependencies.rateLimit) {
101
106
  if (!(await this.dependencies.rateLimit({ walletId })).allowed) return failure(429, "rate_limited");
@@ -109,32 +114,53 @@ var SpeakToSignVerifyRoutes = class {
109
114
  if (verifierInput.owner !== owner) return failure(403, "owner_mismatch");
110
115
  return await this.callVerifier(verifierInput, input.logger, startedAtMs);
111
116
  }
112
- async authenticate(request, nowMs) {
117
+ /**
118
+ * The route needs the owner's identity, not a signing allowance: the vault transfer is signed
119
+ * later through the Wallet's own signing path, which spends the quota. An exhausted session
120
+ * still names its owner, so the identity read admits it.
121
+ */
122
+ async authenticate(request, nowMs, logger) {
113
123
  const token = extractBearerCredential(request.headers);
114
- if (!token) return null;
124
+ if (!token) return { kind: "unauthorized" };
115
125
  const sessions = this.dependencies.authorizationSessions;
116
- const context = await sessions.readWalletSessionAuthorizationV2ByOperationCredential({
117
- tenantId: sessions.tenantId,
118
- token,
119
- nowMs
120
- });
121
- if (!context) return null;
122
- if (resolveWalletSessionOperationCredentialAdmissionFromContext({
123
- context,
126
+ let context;
127
+ try {
128
+ context = await sessions.readWalletSessionExactOperationContextByCredential({
129
+ tenantId: sessions.tenantId,
130
+ token,
131
+ nowMs
132
+ });
133
+ } catch (error) {
134
+ logger.warn("[speak-to-sign] Wallet Session read failed", { error: error instanceof Error ? error.message : "unknown" });
135
+ return { kind: "unavailable" };
136
+ }
137
+ if (!context) return { kind: "unauthorized" };
138
+ const { session } = context;
139
+ const admission = resolveWalletSessionAuthorizationV2Admission({
140
+ authorization: session,
141
+ authority: context.authority,
142
+ authMethod: context.authMethod,
143
+ retiredAtMs: context.retiredAtMs,
124
144
  nowMs,
125
145
  operation: {
146
+ tenantId: session.tenantId,
147
+ principalId: session.principalId,
148
+ walletId: session.walletId,
126
149
  keyFamily: "ed25519",
127
150
  operationKind: NEAR_ED25519_MPC_OPERATION_KINDS.signTransaction
128
151
  }
129
- }).kind !== "admitted") return null;
152
+ });
153
+ if (!admission.ok || admission.keyFamily !== "ed25519") return { kind: "unauthorized" };
130
154
  return {
131
- walletId: context.authorization.session.walletId,
132
- ed25519: context.authority.signerActivations.ed25519?.materialActivation ?? null
155
+ kind: "admitted",
156
+ subject: {
157
+ walletId: session.walletId,
158
+ ed25519: admission.materialActivation
159
+ }
133
160
  };
134
161
  }
135
- /** The NEAR account of the session's Ed25519 signer, or null when the wallet has none. */
162
+ /** The NEAR account of the session's Ed25519 signer, or null when it has none. */
136
163
  async sessionNearAccount(subject) {
137
- if (!subject.ed25519) return null;
138
164
  const resolved = await this.dependencies.walletRegistration.resolveEd25519MaterialActivation({
139
165
  walletId: String(subject.walletId),
140
166
  materialActivation: routerAbMpcMaterialActivationRefToWire(subject.ed25519)
@@ -17,7 +17,7 @@ import type { LinkedDeviceEcdsaSourcePreservingActivationReceiptV1 } from '../..
17
17
  import type { D1DatabaseLike, D1PreparedStatementLike } from '../../../../storage/tenantRoute';
18
18
  import { type RouterAbEd25519YaoActivationPublicReceiptV1, type RouterAbEd25519YaoApplicationBindingFactsV1, type RouterAbEd25519YaoCeremonyBindingV1 } from '../../../../../../shared-ts/src/utils/routerAbEd25519Yao';
19
19
  import { D1WalletAuthorityStore, type D1WalletAuthorityStoreScope } from '../wallet/d1WalletAuthorityStore';
20
- import type { D1LinkedDeviceSessionStoreV1 } from './d1LinkedDeviceSessionStore';
20
+ import { type D1LinkedDeviceSessionStoreV1 } from './d1LinkedDeviceSessionStore';
21
21
  import type { D1WalletAuthMethodStore } from '../../../../core/d1WalletAuthMethodStore';
22
22
  import type { WalletEd25519SignerRecord } from '../../../../core/WalletStore';
23
23
  import type { AuthorizationService } from '../../../../authorization/service';
@@ -36,8 +36,8 @@ export declare class D1WalletExecutionAuthority implements WalletRegistrationHom
36
36
  private deliver;
37
37
  private preparePublicationUpdate;
38
38
  private fixCancellation;
39
- /** The state update with its guard. The guard reads the row count of the update before it. */
40
- private prepareState;
39
+ /** The established-state update with its guard. The guard reads the row count of the update before it. */
40
+ private prepareEstablishedState;
41
41
  admitEstablishedHome(input: Parameters<WalletExecutionAuthority['admitEstablishedHome']>[0]): Promise<OrdinaryAdmission>;
42
42
  }
43
43
  /** Records the authenticated reservation beside setup without reopening a retired home. */
@@ -2,7 +2,10 @@ import type { WalletId } from '../../../../../shared-ts/src/utils/domainIds';
2
2
  import type { RouterApiAuthorizationSessionService, RouterApiWalletRegistrationService } from '../../framework/authServicePort';
3
3
  import type { RouterApiRouteExtension } from '../../framework/routeExtensions';
4
4
  export declare const SPEAK_TO_SIGN_VERIFY_PATH = "/speak-to-sign/v1/verify";
5
- /** 8 s of 16 kHz PCM16 is 256,000 bytes, about 341 KB in base64, with room for the fields. */
5
+ /**
6
+ * An enrollment carries three clips. 8 s of 16 kHz PCM16 is 256,000 bytes, about 341 KB in
7
+ * base64, so three clips are about 1,000 KB, with room for the fields.
8
+ */
6
9
  export declare const SPEAK_TO_SIGN_VERIFY_MAX_BODY_BYTES: number;
7
10
  /** The route exists only when the server configuration holds every field. */
8
11
  export type SpeakToSignVerifyConfig = Readonly<{
@@ -25,7 +28,8 @@ export type SpeakToSignVerifyRateLimit = (input: {
25
28
  readonly allowed: boolean;
26
29
  }>;
27
30
  export type SpeakToSignVerifyDependencies = Readonly<{
28
- authorizationSessions: Pick<RouterApiAuthorizationSessionService, 'tenantId' | 'readWalletSessionAuthorizationV2ByOperationCredential'>;
31
+ /** The identity read: it admits an exhausted session, because the route spends no quota. */
32
+ authorizationSessions: Pick<RouterApiAuthorizationSessionService, 'tenantId' | 'readWalletSessionExactOperationContextByCredential'>;
29
33
  /** Resolves the NEAR account of the session's Ed25519 signer for the owner check. */
30
34
  walletRegistration: Pick<RouterApiWalletRegistrationService, 'resolveEd25519MaterialActivation'>;
31
35
  fetch: typeof fetch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seams/wallet-server",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "type": "module",
5
5
  "description": "Seams server TypeScript SDK for NEAR and EVM-family wallets",
6
6
  "main": "./dist/esm/index.js",