@seams/wallet-server 0.28.0 → 0.29.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 (80) hide show
  1. package/README.md +19 -4
  2. package/artifact-manifest.json +23 -13
  3. package/cloudflare-router-ab/build/deriver-a/build-stamp.json +1 -1
  4. package/cloudflare-router-ab/build/deriver-b/build-stamp.json +1 -1
  5. package/cloudflare-router-ab/build/router/build-stamp.json +1 -1
  6. package/cloudflare-router-ab/build/signing-worker/build-stamp.json +1 -1
  7. package/cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json +1 -1
  8. package/dist/esm/application-auth.js +2 -0
  9. package/dist/esm/auth/applicationAuth.js +85 -0
  10. package/dist/esm/auth/externalAuthService.js +58 -7
  11. package/dist/esm/auth/externalAuthStore.js +157 -21
  12. package/dist/esm/auth/externalOwnerOperations.js +82 -0
  13. package/dist/esm/cloud-host.js +4 -4
  14. package/dist/esm/core/nearRelayerAccountProvisioning.js +1 -1
  15. package/dist/esm/router/cloudflare/d1/auth/d1AuthorizedOperationRouteService.js +2 -0
  16. package/dist/esm/router/cloudflare/d1/auth/d1RecordedOwnerOperation.js +43 -0
  17. package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +22 -1
  18. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpRecoveryService.js +1 -1
  19. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpRegistrationEnrollmentFinalizer.js +1 -1
  20. package/dist/esm/router/cloudflare/d1/emailOtp/d1GoogleEmailOtpSessionResolver.js +1 -1
  21. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyRouteService.js +2 -2
  22. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1WalletCustodyCommitStore.js +2 -2
  23. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1WalletRecoveryGoogleEmailOtpService.js +2 -2
  24. package/dist/esm/router/cloudflare/d1/registration/d1RegistrationCeremonyRecords.js +9 -0
  25. package/dist/esm/router/cloudflare/d1/registration/d1WalletExecutionAuthority.js +9 -16
  26. package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationService.js +43 -1
  27. package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationSetup.js +12 -0
  28. package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationSetupExecution.js +1 -0
  29. package/dist/esm/router/cloudflare/d1/registration/registrationCompletionWrite.js +1 -0
  30. package/dist/esm/router/cloudflare/d1/registration/walletRegistrationSessionCommitReceipt.js +35 -0
  31. package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthMethodBoundary.js +1 -1
  32. package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthMethodService.js +4 -3
  33. package/dist/esm/router/cloudflare.js +1785 -1489
  34. package/dist/esm/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.js +2 -2
  35. package/dist/esm/router/domains/emailOtp/emailOtpRouteHandlers.js +1 -1
  36. package/dist/esm/router/domains/passkeyCustody/walletCustodyRegistrationCommit.js +1 -1
  37. package/dist/esm/router/domains/signingOperations/routerAbEd25519NormalSigningRoute.js +35 -1
  38. package/dist/esm/router/domains/walletRegistration/walletRegistrationReservation.js +17 -2
  39. package/dist/esm/router/domains/walletRegistration/walletRegistrationRoutes.js +9 -1
  40. package/dist/esm/router/domains/walletUnlock/walletUnlockRouteHandlers.js +1 -1
  41. package/dist/esm/router/transport/fetch/routes/ecdsaOperationStepUpFactor.js +2 -2
  42. package/dist/esm/router/transport/fetch/routes/externalAuth.js +139 -32
  43. package/dist/esm/router/transport/fetch/routes/passkeyCustody.js +44 -304
  44. package/dist/esm/router/transport/fetch/routes/sessions.js +2 -2
  45. package/dist/esm/router/transport/fetch/routes/thresholdEd25519.js +2 -2
  46. package/dist/esm/router/transport/fetch/routes/walletRegistration.js +2 -2
  47. package/dist/esm/router/transport/fetch/walletOwnerOperationAuthorization.js +306 -0
  48. package/dist/esm/shared-ts/src/auth/externalAuth.js +87 -14
  49. package/dist/esm/shared-ts/src/authorization/index.js +1 -1
  50. package/dist/esm/shared-ts/src/authorization/{walletCustodyOperation.js → walletOwnerOperation.js} +4 -4
  51. package/dist/esm/shared-ts/src/wallet-recovery/index.js +3 -3
  52. package/dist/types/shared-ts/src/auth/externalAuth.d.ts +51 -8
  53. package/dist/types/shared-ts/src/authorization/index.d.ts +1 -1
  54. package/dist/types/shared-ts/src/authorization/walletOwnerOperation.d.ts +10 -0
  55. package/dist/types/shared-ts/src/utils/registrationEstablishedSession.d.ts +4 -0
  56. package/dist/types/shared-ts/src/utils/registrationIntent.d.ts +2 -0
  57. package/dist/types/shared-ts/src/utils/walletManagementProtocol.d.ts +1 -1
  58. package/dist/types/wallet-server/src/application-auth.d.ts +3 -0
  59. package/dist/types/wallet-server/src/auth/applicationAuth.d.ts +29 -0
  60. package/dist/types/wallet-server/src/auth/externalAuthService.d.ts +29 -10
  61. package/dist/types/wallet-server/src/auth/externalAuthStore.d.ts +60 -6
  62. package/dist/types/wallet-server/src/auth/externalOwnerOperations.d.ts +18 -0
  63. package/dist/types/wallet-server/src/cloud-host.d.ts +1 -1
  64. package/dist/types/wallet-server/src/core/threeRouteRegistrationContracts.d.ts +12 -0
  65. package/dist/types/wallet-server/src/router/cloudflare/d1/auth/d1AuthorizedOperationRouteService.d.ts +5 -1
  66. package/dist/types/wallet-server/src/router/cloudflare/d1/auth/d1RecordedOwnerOperation.d.ts +16 -0
  67. package/dist/types/wallet-server/src/router/cloudflare/d1/registration/d1RegistrationCeremonyRecords.d.ts +1 -0
  68. package/dist/types/wallet-server/src/router/cloudflare/d1/registration/d1WalletRegistrationSetup.d.ts +2 -0
  69. package/dist/types/wallet-server/src/router/cloudflare/d1/registration/walletRegistrationSessionCommitReceipt.d.ts +7 -1
  70. package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1WalletAuthMethodService.d.ts +1 -0
  71. package/dist/types/wallet-server/src/router/domains/signingOperations/routerAbEd25519NormalSigningRoute.d.ts +2 -2
  72. package/dist/types/wallet-server/src/router/domains/signingOperations/routerAbOperationStepUp.d.ts +1 -1
  73. package/dist/types/wallet-server/src/router/domains/walletRegistration/walletRegistrationInputs.d.ts +1 -0
  74. package/dist/types/wallet-server/src/router/domains/walletRegistration/walletRegistrationReservation.d.ts +11 -6
  75. package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +11 -0
  76. package/dist/types/wallet-server/src/router/transport/fetch/walletOwnerOperationAuthorization.d.ts +23 -0
  77. package/migrations/d1-signer/0086_application_auth.sql +67 -0
  78. package/migrations/d1-signer/0087_registration_without_session.sql +43 -0
  79. package/package.json +7 -2
  80. package/dist/types/shared-ts/src/authorization/walletCustodyOperation.d.ts +0 -10
@@ -226,7 +226,7 @@ var DefaultRouterAbNormalSigningAdmissionAdapter = class {
226
226
  case "project_policy_rejected": return admissionFailure(403, "project_policy_rejected", "Router A/B normal-signing project policy rejected the request");
227
227
  case "rate_limited": return admissionFailure(429, "rate_limited", "Router A/B normal-signing request is rate limited");
228
228
  case "abuse_rejected": return admissionFailure(403, "abuse_rejected", "Router A/B normal-signing abuse policy rejected the request");
229
- default: return assertNever$14(policy);
229
+ default: return assertNever$16(policy);
230
230
  }
231
231
  }
232
232
  };
@@ -283,7 +283,7 @@ function abusePrincipalKey(input) {
283
283
  input.curve
284
284
  ].join("");
285
285
  }
286
- function assertNever$14(value) {
286
+ function assertNever$16(value) {
287
287
  throw new Error(`Unexpected Router A/B normal-signing admission branch: ${String(value)}`);
288
288
  }
289
289
  //#endregion
@@ -4957,7 +4957,7 @@ function normalizeProjectPolicyDecision(decision) {
4957
4957
  kind: "rejected",
4958
4958
  retryAfterMs: requirePositiveInteger$2("retryAfterMs", decision.retryAfterMs)
4959
4959
  };
4960
- default: return assertNever$13(decision);
4960
+ default: return assertNever$15(decision);
4961
4961
  }
4962
4962
  }
4963
4963
  function normalizeAbuseDecision(decision) {
@@ -4971,7 +4971,7 @@ function normalizeAbuseDecision(decision) {
4971
4971
  kind: "rejected",
4972
4972
  retryAfterMs: requirePositiveInteger$2("retryAfterMs", decision.retryAfterMs)
4973
4973
  };
4974
- default: return assertNever$13(decision);
4974
+ default: return assertNever$15(decision);
4975
4975
  }
4976
4976
  }
4977
4977
  function parseProjectPolicyDecision(row) {
@@ -5015,7 +5015,7 @@ function requirePositiveInteger$2(label, value) {
5015
5015
  }
5016
5016
  throw new Error(`${label} must be a positive integer`);
5017
5017
  }
5018
- function assertNever$13(value) {
5018
+ function assertNever$15(value) {
5019
5019
  throw new Error(`Unexpected Router A/B normal-signing admission branch: ${String(value)}`);
5020
5020
  }
5021
5021
  function parsePolicyRow(row) {
@@ -5190,19 +5190,61 @@ const ED25519_CREDENTIAL_POLICY_PROJECTION = credentialPolicyProjection({
5190
5190
  //#endregion
5191
5191
  //#region ../shared-ts/src/auth/externalAuth.ts
5192
5192
  const EXTERNAL_AUTH_PATH = "/wallet/external-auth";
5193
+ function parseExternalAuthProvider(raw) {
5194
+ const value = requireRecord$1(raw, "external authentication provider");
5195
+ switch (value.kind) {
5196
+ case "disabled": return { kind: "disabled" };
5197
+ case "google": return {
5198
+ kind: "google",
5199
+ clientId: requireCanonicalString(value.clientId, "clientId")
5200
+ };
5201
+ case "application": {
5202
+ const issuer = requireCanonicalString(value.issuer, "issuer");
5203
+ const url = new URL(issuer);
5204
+ if (url.protocol !== "https:" || url.origin !== issuer) throw new Error("Application issuer must be a canonical HTTPS origin");
5205
+ return {
5206
+ kind: "application",
5207
+ issuer,
5208
+ publicKeyB64u: parseEd25519PublicKeyB64u(value.publicKeyB64u, "publicKeyB64u")
5209
+ };
5210
+ }
5211
+ default: throw new Error("Unsupported external authentication provider");
5212
+ }
5213
+ }
5214
+ function externalAuthConfiguration(provider, revision) {
5215
+ switch (provider.kind) {
5216
+ case "disabled": return {
5217
+ kind: "disabled",
5218
+ revision
5219
+ };
5220
+ case "google": return {
5221
+ kind: "google",
5222
+ revision,
5223
+ clientId: provider.clientId
5224
+ };
5225
+ case "application": return {
5226
+ kind: "application",
5227
+ revision,
5228
+ issuer: provider.issuer,
5229
+ publicKeyB64u: provider.publicKeyB64u
5230
+ };
5231
+ default: return assertNever$14(provider);
5232
+ }
5233
+ }
5193
5234
  function parseExternalAuthConfiguration(raw) {
5194
5235
  const value = requireRecord$1(raw, "external authentication configuration");
5195
5236
  if (typeof value.revision !== "number" || !Number.isSafeInteger(value.revision) || value.revision < 0) throw new Error("Invalid external authentication revision");
5196
- if (value.kind === "disabled") return {
5197
- kind: "disabled",
5198
- revision: value.revision
5199
- };
5200
- if (value.kind === "google") return {
5201
- kind: "google",
5202
- revision: value.revision,
5203
- clientId: requireCanonicalString(value.clientId, "clientId")
5204
- };
5205
- throw new Error("Unsupported external authentication configuration");
5237
+ return externalAuthConfiguration(parseExternalAuthProvider(value), value.revision);
5238
+ }
5239
+ function serializeExternalIdentity(identity) {
5240
+ return JSON.stringify([
5241
+ identity.provider,
5242
+ identity.issuer,
5243
+ identity.subject
5244
+ ]);
5245
+ }
5246
+ function assertNever$14(value) {
5247
+ throw new Error(`Unsupported external authentication provider: ${String(value)}`);
5206
5248
  }
5207
5249
  function externalAuthFailure(code) {
5208
5250
  return {
@@ -5212,105 +5254,194 @@ function externalAuthFailure(code) {
5212
5254
  };
5213
5255
  }
5214
5256
  //#endregion
5215
- //#region src/router/auth/routerApiKeyAuth.ts
5216
- function readHeader(headers, name) {
5217
- if (headers instanceof Headers) return String(headers.get(name) || "").trim();
5218
- const direct = headers[name];
5219
- const lower = headers[name.toLowerCase()];
5220
- const upper = headers[name.toUpperCase()];
5221
- const value = direct ?? lower ?? upper;
5222
- if (typeof value === "string") return value.trim();
5223
- if (Array.isArray(value)) return String(value[0] || "").trim();
5224
- return "";
5257
+ //#region ../shared-ts/src/authorization/walletOwnerOperation.ts
5258
+ /** Public challenge digest for a single wallet-administration factor proof. */
5259
+ async function computeWalletOwnerAdminChallengeDigest(input) {
5260
+ return sha256Utf8DigestB64u(`seams:wallet-custody:challenge:v1|${alphabetizeStringify({
5261
+ walletId: input.walletId,
5262
+ operation: input.operation,
5263
+ payload: input.payload,
5264
+ requestOrigin: input.requestOrigin
5265
+ })}`);
5225
5266
  }
5226
- function extractBearerCredential(headers) {
5227
- const authHeader = readHeader(headers, "authorization");
5228
- if (authHeader) {
5229
- if (authHeader.toLowerCase().startsWith("bearer ")) {
5230
- const bearerValue = authHeader.slice(7).trim();
5231
- if (bearerValue) return bearerValue;
5267
+ //#endregion
5268
+ //#region src/auth/externalOwnerOperations.ts
5269
+ async function readExternalOwnerOperation(input) {
5270
+ const attempt = input.phase === "prepare" ? await input.store.prepareOwnerChallenge(input.binding) : await input.store.readOwnerAttempt(input.binding);
5271
+ if (!attempt) return externalAuthFailure("access_denied");
5272
+ if (attempt.challenge.kind !== "ready") return externalAuthFailure("access_denied");
5273
+ if (attempt.status === "verified" && attempt.challenge.expiresAtMs <= input.binding.nowMs) return externalAuthFailure("expired");
5274
+ const identity = serializeExternalIdentity(attempt.identity);
5275
+ let operation;
5276
+ let payload;
5277
+ switch (attempt.purpose) {
5278
+ case "associate":
5279
+ operation = "external_identity_associate";
5280
+ payload = {
5281
+ attemptId: attempt.attemptId,
5282
+ identity,
5283
+ originBinding: attempt.originBinding,
5284
+ revision: attempt.trustRevision,
5285
+ expiresAtMs: attempt.challenge.expiresAtMs
5286
+ };
5287
+ break;
5288
+ case "unlink":
5289
+ operation = "external_identity_unlink";
5290
+ payload = {
5291
+ attemptId: attempt.attemptId,
5292
+ associationId: attempt.associationId,
5293
+ identity,
5294
+ originBinding: attempt.originBinding,
5295
+ revision: attempt.trustRevision,
5296
+ expiresAtMs: attempt.challenge.expiresAtMs
5297
+ };
5298
+ break;
5299
+ default: return assertNever$13(attempt);
5300
+ }
5301
+ const challengeB64u = await computeWalletOwnerAdminChallengeDigest({
5302
+ walletId: attempt.walletId,
5303
+ operation,
5304
+ payload,
5305
+ requestOrigin: input.requestOrigin
5306
+ });
5307
+ return {
5308
+ ok: true,
5309
+ value: {
5310
+ attempt,
5311
+ operation,
5312
+ payload,
5313
+ challengeB64u
5232
5314
  }
5315
+ };
5316
+ }
5317
+ function externalOwnerChallenge(operation) {
5318
+ const attempt = operation.attempt;
5319
+ if (attempt.challenge.kind !== "ready") throw new Error("Owner challenge is not prepared");
5320
+ switch (attempt.purpose) {
5321
+ case "associate": return {
5322
+ kind: "associate",
5323
+ walletId: attempt.walletId,
5324
+ attemptId: attempt.attemptId,
5325
+ identity: attempt.identity,
5326
+ challengeB64u: operation.challengeB64u,
5327
+ expiresAtMs: attempt.challenge.expiresAtMs
5328
+ };
5329
+ case "unlink": return {
5330
+ kind: "unlink",
5331
+ walletId: attempt.walletId,
5332
+ attemptId: attempt.attemptId,
5333
+ associationId: attempt.associationId,
5334
+ identity: attempt.identity,
5335
+ challengeB64u: operation.challengeB64u,
5336
+ expiresAtMs: attempt.challenge.expiresAtMs
5337
+ };
5338
+ default: return assertNever$13(attempt);
5233
5339
  }
5234
- return null;
5235
5340
  }
5236
- function normalizeSourceIp(raw) {
5237
- const value = String(raw || "").trim();
5238
- if (!value) return null;
5239
- const maybeForwarded = value.includes(",") ? value.split(",")[0] : value;
5240
- return String(maybeForwarded || "").trim() || null;
5341
+ function assertNever$13(value) {
5342
+ throw new Error(`Unsupported external owner operation: ${String(value)}`);
5241
5343
  }
5242
- function extractRouterApiEnvironmentId(headers) {
5243
- const preferred = readHeader(headers, "x-seams-environment-id");
5244
- if (preferred) return preferred;
5245
- const fallback = readHeader(headers, "x-environment-id");
5246
- if (fallback) return fallback;
5247
- return null;
5344
+ //#endregion
5345
+ //#region ../shared-ts/src/authorization/operationFingerprint.ts
5346
+ const CAPABILITY_OPERATION_FINGERPRINT_DOMAIN_V1 = "seams:authorization:capability-operation-fingerprint:v1";
5347
+ var CapabilityOperationEnvelopeProof = class {
5348
+ tenantId;
5349
+ principalId;
5350
+ capabilityId;
5351
+ operationId;
5352
+ operation;
5353
+ digests;
5354
+ retainProof() {
5355
+ return true;
5356
+ }
5357
+ constructor(fields) {
5358
+ this.retainProof();
5359
+ this.tenantId = fields.tenantId;
5360
+ this.principalId = fields.principalId;
5361
+ this.capabilityId = fields.capabilityId;
5362
+ this.operationId = fields.operationId;
5363
+ this.operation = fields.operation;
5364
+ this.digests = fields.digests;
5365
+ }
5366
+ };
5367
+ function buildCapabilityOperationEnvelope(fields) {
5368
+ return new CapabilityOperationEnvelopeProof({
5369
+ tenantId: fields.tenantId,
5370
+ principalId: fields.principalId,
5371
+ capabilityId: fields.capabilityId,
5372
+ operationId: fields.operationId,
5373
+ operation: fields.operation,
5374
+ digests: {
5375
+ laneDigest: fields.digests.laneDigest,
5376
+ intentDigest: fields.digests.intentDigest,
5377
+ displayDigest: fields.digests.displayDigest
5378
+ }
5379
+ });
5248
5380
  }
5249
- function resolveSourceIpFromFetchHeaders(headers) {
5250
- const cfConnecting = readHeader(headers, "cf-connecting-ip");
5251
- const forwarded = readHeader(headers, "x-forwarded-for");
5252
- const realIp = readHeader(headers, "x-real-ip");
5253
- return normalizeSourceIp(cfConnecting || forwarded || realIp);
5381
+ function canonicalCapabilityOperationFingerprintPreimageV1(envelope) {
5382
+ return `${CAPABILITY_OPERATION_FINGERPRINT_DOMAIN_V1}|${alphabetizeStringify({
5383
+ tenantId: envelope.tenantId,
5384
+ principalId: envelope.principalId,
5385
+ capabilityId: envelope.capabilityId,
5386
+ operationId: envelope.operationId,
5387
+ operation: envelope.operation,
5388
+ digests: envelope.digests
5389
+ })}`;
5254
5390
  }
5255
- //#endregion
5256
- //#region ../shared-ts/src/utils/failure.ts
5257
- function failure$6(code, message) {
5258
- return {
5259
- ok: false,
5260
- code,
5261
- message
5262
- };
5391
+ async function computeCapabilityOperationFingerprintDigest(envelope) {
5392
+ return parseCapabilityOperationFingerprintDigest(base64UrlEncode(await sha256BytesUtf8(canonicalCapabilityOperationFingerprintPreimageV1(envelope))));
5263
5393
  }
5264
- function failedVerification(code, message) {
5265
- return {
5266
- ok: false,
5267
- verified: false,
5268
- code,
5269
- message
5270
- };
5394
+ function parseCapabilityOperationFingerprintDigest(value) {
5395
+ return parseDigestB64u(value);
5396
+ }
5397
+ function parseSigningOperationFingerprintDigest(value) {
5398
+ if (typeof value !== "string" || !value.startsWith("sha256:")) throw new Error("signing operation fingerprint must use the sha256: prefix");
5399
+ return parseDigestB64u(value.slice(7));
5271
5400
  }
5272
5401
  //#endregion
5273
- //#region src/core/SessionService.ts
5274
- function normalizeParsedCorsOrigin(url) {
5275
- const host = url.hostname.toLowerCase();
5276
- const proto = url.protocol === "http:" || url.protocol === "https:" ? url.protocol : "https:";
5277
- const rawPort = String(url.port || "").trim();
5278
- return `${proto}//${host}${rawPort && !(proto === "https:" && rawPort === "443") && !(proto === "http:" && rawPort === "80") ? `:${rawPort}` : ""}`;
5402
+ //#region ../shared-ts/src/utils/emailOtpDomain.ts
5403
+ const EMAIL_OTP_CHANNEL = "email_otp";
5404
+ /** Domain label that opens the AAD binding a released Email OTP factor to its challenge. */
5405
+ const EMAIL_OTP_FACTOR_RELEASE_AAD_DOMAIN_V1 = "seams/email-otp/factor-release/v1";
5406
+ const WALLET_UNLOCK_BACKENDS = ["passkey", EMAIL_OTP_CHANNEL];
5407
+ const WALLET_EMAIL_OTP_LOGIN_OPERATIONS = [
5408
+ "wallet_unlock",
5409
+ "transaction_sign",
5410
+ "export_key"
5411
+ ];
5412
+ const WALLET_EMAIL_OTP_EXPORT_OPERATION = "export_key";
5413
+ const WALLET_EMAIL_OTP_UNLOCK_OPERATION = "wallet_unlock";
5414
+ const WALLET_EMAIL_OTP_TRANSACTION_SIGN_OPERATION = "transaction_sign";
5415
+ function isWalletUnlockBackend(value) {
5416
+ return WALLET_UNLOCK_BACKENDS.includes(value);
5279
5417
  }
5280
- function parseCsvList(input) {
5281
- const out = /* @__PURE__ */ new Set();
5282
- for (const raw of String(input || "").split(",")) {
5283
- const s = raw.trim();
5284
- if (!s) continue;
5285
- try {
5286
- const u = new URL(s);
5287
- out.add(normalizeParsedCorsOrigin(u));
5288
- } catch {
5289
- const stripped = s.replace(/\/$/, "");
5290
- if (stripped) out.add(stripped);
5291
- }
5292
- }
5293
- return Array.from(out);
5418
+ function isWalletEmailOtpLoginOperation(value) {
5419
+ return WALLET_EMAIL_OTP_LOGIN_OPERATIONS.includes(value);
5294
5420
  }
5421
+ //#endregion
5422
+ //#region ../shared-ts/src/utils/base58.ts
5295
5423
  /**
5296
- * Normalize a single origin for CORS comparisons.
5297
- * Returns null when the input is empty or not URL-like.
5424
+ * Encodes binary data to base58 string using Bitcoin's base58 alphabet.
5425
+ *
5426
+ * @param data - Binary data to encode (Uint8Array, ArrayBuffer, or number[])
5427
+ * @returns Base58-encoded string
5298
5428
  */
5299
- function normalizeCorsOrigin(input) {
5300
- const raw = String(input || "").trim();
5301
- if (!raw) return null;
5302
- try {
5303
- return normalizeParsedCorsOrigin(new URL(raw));
5304
- } catch {
5305
- return null;
5306
- }
5307
- }
5308
- function buildCorsOrigins(...inputs) {
5309
- const merged = /* @__PURE__ */ new Set();
5310
- for (const input of inputs) for (const origin of parseCsvList(input)) merged.add(origin);
5311
- const list = Array.from(merged);
5312
- return list.length > 0 ? list : "*";
5313
- }
5429
+ const base58Encode = (data) => {
5430
+ if (data instanceof ArrayBuffer) return bs58.encode(new Uint8Array(data));
5431
+ if (Array.isArray(data)) return bs58.encode(new Uint8Array(data));
5432
+ return bs58.encode(data);
5433
+ };
5434
+ /**
5435
+ * Decodes a base58 string to binary data using Bitcoin's base58 alphabet.
5436
+ * Returns a Uint8Array containing the decoded bytes.
5437
+ *
5438
+ * @param base58String - Base58-encoded string to decode
5439
+ * @returns Uint8Array containing the decoded bytes
5440
+ * @throws Error if input contains invalid base58 characters
5441
+ */
5442
+ const base58Decode = (base58String) => {
5443
+ return bs58.decode(base58String);
5444
+ };
5314
5445
  //#endregion
5315
5446
  //#region ../shared-ts/src/device-linking/contracts.ts
5316
5447
  function isLinkSessionTerminalFailureV1(state) {
@@ -8419,170 +8550,1318 @@ function parseFixedB64u$1(value, expectedBytes, label) {
8419
8550
  return bytes;
8420
8551
  }
8421
8552
  //#endregion
8422
- //#region ../shared-ts/src/utils/routerAbTraceContext.ts
8423
- const ROUTER_AB_TRACE_ID_HEADER_V1 = "x-seams-trace-id";
8424
- const TRACE_ID_PATTERN = /^[0-9a-f]{32}$/;
8425
- function parseRouterAbTraceContextV1(raw) {
8426
- if (raw === null || raw === void 0 || raw === "") return {
8427
- ok: false,
8428
- reason: "missing",
8429
- message: "Router trace ID is missing"
8430
- };
8431
- if (typeof raw !== "string" || !TRACE_ID_PATTERN.test(raw)) return {
8432
- ok: false,
8433
- reason: "invalid",
8434
- message: "Router trace ID must be 32 lowercase hexadecimal characters"
8435
- };
8553
+ //#region src/authorization/domain.ts
8554
+ function buildExactWalletSessionQuotaProjectionV1(fields) {
8555
+ requireNonnegativeInteger(fields.remainingUses, "Wallet Session quota remaining uses");
8556
+ requirePositiveTime(fields.expiresAtMs, "Wallet Session quota expiry");
8557
+ if (fields.lifecycle === "exhausted" !== (fields.remainingUses === 0)) throw new Error("Wallet Session quota lifecycle disagrees with its remaining uses");
8436
8558
  return {
8437
- ok: true,
8438
- value: {
8439
- kind: "router_ab_trace_context_v1",
8440
- value: raw
8441
- }
8559
+ kind: "exact_wallet_session_quota_projection_v1",
8560
+ lifecycle: fields.lifecycle,
8561
+ tenantId: fields.tenantId,
8562
+ principalId: fields.principalId,
8563
+ walletSessionId: fields.walletSessionId,
8564
+ quotaId: fields.quotaId,
8565
+ remainingUses: fields.remainingUses,
8566
+ expiresAtMs: fields.expiresAtMs
8442
8567
  };
8443
8568
  }
8444
- function createRouterAbTraceContextV1() {
8445
- const bytes = new Uint8Array(16);
8446
- globalThis.crypto.getRandomValues(bytes);
8447
- let value = "";
8448
- for (const byte of bytes) value += byte.toString(16).padStart(2, "0");
8449
- return {
8450
- kind: "router_ab_trace_context_v1",
8451
- value
8452
- };
8569
+ /**
8570
+ * Every capability subject must resolve through the authoritative active
8571
+ * authority that the authorization names. A signing or export subject resolves
8572
+ * only when its key family is activated and names the same material the
8573
+ * authority currently holds; an administration subject resolves only when it
8574
+ * names that same authority. Anything else is a capability the caller can no
8575
+ * longer exercise, not a corrupt row.
8576
+ */
8577
+ function exactWalletSessionCapabilitySubjectsResolveAuthority(input) {
8578
+ return input.session.capabilitySubjects.every((subject) => {
8579
+ switch (subject.kind) {
8580
+ case "sign":
8581
+ case "export_keys": {
8582
+ const activation = subject.keyFamily === "ed25519" ? input.signerActivations.ed25519 : input.signerActivations.ecdsa;
8583
+ return activation !== void 0 && mpcMaterialActivationRefsEqual(activation.materialActivation, subject.materialActivation);
8584
+ }
8585
+ case "link_devices":
8586
+ case "revoke_devices": return subject.authorityId === input.session.authorityId;
8587
+ default: return false;
8588
+ }
8589
+ });
8453
8590
  }
8454
- //#endregion
8455
- //#region ../shared-ts/src/utils/walletManagementProtocol.ts
8456
- const WALLET_MANAGEMENT_PROTOCOL_HEADER = "X-Seams-Wallet-Protocol";
8457
- //#endregion
8458
- //#region src/router/framework/http.ts
8459
- const CORS_ALLOW_METHODS = "GET,POST,PUT,PATCH,DELETE,OPTIONS";
8460
- const CORS_PREFLIGHT_MAX_AGE_SECONDS = 7200;
8461
- const CORS_ALLOW_HEADERS = [
8462
- "Content-Type",
8463
- WALLET_MANAGEMENT_PROTOCOL_HEADER,
8464
- "Authorization",
8465
- "X-Seams-Wallet-Region",
8466
- "X-Seams-Benchmark-Diagnostics",
8467
- LINKED_DEVICE_REQUEST_PROOF_HEADER_V1,
8468
- ROUTER_AB_ED25519_YAO_RECOVERY_CHALLENGE_ID_HEADER_V1,
8469
- ROUTER_AB_TRACE_ID_HEADER_V1,
8470
- "X-Seams-Environment-Id",
8471
- "X-Environment-Id"
8472
- ].join(",");
8473
- function json$3(body, init, extraHeaders) {
8474
- const headers = new Headers({ "Content-Type": "application/json; charset=utf-8" });
8475
- const initHeaders = init?.headers;
8476
- if (initHeaders) try {
8477
- new Headers(initHeaders).forEach((v, k) => headers.set(k, v));
8478
- } catch {}
8479
- if (extraHeaders) for (const [k, v] of Object.entries(extraHeaders)) headers.set(k, v);
8480
- const { headers: _omit, ...rest } = init || {};
8481
- return new Response(JSON.stringify(body), {
8482
- status: 200,
8483
- ...rest,
8484
- headers
8591
+ /**
8592
+ * The digest-free authorization projection published to the browser. It carries
8593
+ * the exact wallet, authority, method, capability, and lifetime identity a
8594
+ * reader needs to reconcile its own record, and never the credential digest.
8595
+ */
8596
+ function projectExactWalletSessionAuthorizationV1(session) {
8597
+ const capabilitySubjects = session.capabilitySubjects.map((subject) => {
8598
+ switch (subject.kind) {
8599
+ case "sign":
8600
+ case "export_keys": return {
8601
+ kind: subject.kind,
8602
+ keyFamily: subject.keyFamily,
8603
+ materialActivation: subject.materialActivation
8604
+ };
8605
+ case "link_devices":
8606
+ case "revoke_devices": return { kind: subject.kind };
8607
+ default: throw new Error("Issued linked-device Wallet Session subject is invalid");
8608
+ }
8609
+ });
8610
+ const first = capabilitySubjects[0];
8611
+ if (!first) throw new Error("Issued linked-device Wallet Session has no subjects");
8612
+ return parseActiveWalletSessionV1({
8613
+ kind: "active_wallet_session_v1",
8614
+ walletId: session.walletId,
8615
+ authorityId: session.authorityId,
8616
+ authMethodId: session.walletAuthMethodId,
8617
+ authorizationId: session.authorizationId,
8618
+ quotaId: session.quotaId,
8619
+ authorityDigestB64u: session.authorityDigestB64u,
8620
+ authorityRevocationEpoch: session.authorityRevocationEpoch,
8621
+ capabilitySubjects: [first, ...capabilitySubjects.slice(1)],
8622
+ issuedAtMs: session.createdAtMs,
8623
+ expiresAtMs: session.expiresAtMs
8485
8624
  });
8486
8625
  }
8487
- function jsonFailure(status, code, message) {
8488
- return json$3(failure$6(code, message), { status });
8626
+ function projectActiveWalletSession(issued) {
8627
+ if (issued.quota.quotaId !== issued.session.quotaId) throw new Error("Issued Wallet Session quota does not belong to its authorization");
8628
+ return projectExactWalletSessionAuthorizationV1(issued.session);
8489
8629
  }
8490
- function withCors(headers, opts, request) {
8491
- const pathname = request ? new URL(request.url).pathname : "";
8492
- if (pathname === "/healthz" || pathname === "/readyz") {
8493
- headers.set("Access-Control-Allow-Origin", "*");
8494
- headers.delete("Access-Control-Allow-Credentials");
8495
- headers.set("Access-Control-Allow-Methods", CORS_ALLOW_METHODS);
8496
- headers.set("Access-Control-Allow-Headers", CORS_ALLOW_HEADERS);
8497
- if (request?.method === "OPTIONS") headers.set("Access-Control-Max-Age", String(CORS_PREFLIGHT_MAX_AGE_SECONDS));
8630
+ function appendWalletSessionSignerSubjects(subjects, kind, signerActivations) {
8631
+ if (signerActivations.keyFamilies.length === 1 && signerActivations.keyFamilies[0] === "ed25519") {
8632
+ if (!signerActivations.ed25519) throw new Error("Ed25519 signer activation is missing");
8633
+ subjects.push({
8634
+ kind,
8635
+ keyFamily: "ed25519",
8636
+ materialActivation: signerActivations.ed25519.materialActivation
8637
+ });
8498
8638
  return;
8499
8639
  }
8500
- if (!opts?.corsOrigins) return;
8501
- let allowedOrigin;
8502
- const normalized = buildCorsOrigins(...opts.corsOrigins || []);
8503
- if (normalized === "*") {
8504
- allowedOrigin = "*";
8505
- headers.set("Access-Control-Allow-Origin", "*");
8506
- } else if (Array.isArray(normalized)) {
8507
- const originRaw = String(request?.headers.get("Origin") || "").trim();
8508
- const originNormalized = normalizeCorsOrigin(originRaw);
8509
- if (originRaw && originNormalized && normalized.includes(originNormalized)) {
8510
- allowedOrigin = originRaw;
8511
- headers.set("Access-Control-Allow-Origin", originRaw);
8512
- headers.append("Vary", "Origin");
8513
- }
8514
- }
8515
- headers.set("Access-Control-Allow-Methods", CORS_ALLOW_METHODS);
8516
- headers.set("Access-Control-Allow-Headers", CORS_ALLOW_HEADERS);
8517
- if (allowedOrigin && request?.method === "OPTIONS") headers.set("Access-Control-Max-Age", String(CORS_PREFLIGHT_MAX_AGE_SECONDS));
8518
- headers.set("Access-Control-Expose-Headers", "Server-Timing, X-Seams-Wallet-Region");
8519
- if (allowedOrigin && allowedOrigin !== "*") headers.set("Access-Control-Allow-Credentials", "true");
8520
- }
8521
- async function readJson(request) {
8522
- try {
8523
- return await request.json();
8524
- } catch {
8525
- return null;
8640
+ if (signerActivations.keyFamilies.length === 1 && signerActivations.keyFamilies[0] === "ecdsa_secp256k1") {
8641
+ if (!signerActivations.ecdsa) throw new Error("ECDSA signer activation is missing");
8642
+ subjects.push({
8643
+ kind,
8644
+ keyFamily: "ecdsa_secp256k1",
8645
+ materialActivation: signerActivations.ecdsa.materialActivation
8646
+ });
8647
+ return;
8526
8648
  }
8649
+ if (!signerActivations.ed25519 || !signerActivations.ecdsa) throw new Error("both-family signer activations are incomplete");
8650
+ subjects.push({
8651
+ kind,
8652
+ keyFamily: "ed25519",
8653
+ materialActivation: signerActivations.ed25519.materialActivation
8654
+ }, {
8655
+ kind,
8656
+ keyFamily: "ecdsa_secp256k1",
8657
+ materialActivation: signerActivations.ecdsa.materialActivation
8658
+ });
8527
8659
  }
8528
- function headersToRecord(headers) {
8529
- const out = {};
8530
- headers.forEach((v, k) => {
8531
- out[k] = v;
8660
+ function buildWalletSessionCapabilitySubjectsV1(authority) {
8661
+ const subjects = [];
8662
+ if (authority.permissions.includes("sign")) appendWalletSessionSignerSubjects(subjects, "sign", authority.signerActivations);
8663
+ if (authority.permissions.includes("export_keys")) appendWalletSessionSignerSubjects(subjects, "export_keys", authority.signerActivations);
8664
+ if (authority.permissions.includes("link_devices")) subjects.push({
8665
+ kind: "link_devices",
8666
+ authorityId: authority.authorityId
8532
8667
  });
8533
- return out;
8668
+ if (authority.permissions.includes("revoke_devices")) subjects.push({
8669
+ kind: "revoke_devices",
8670
+ authorityId: authority.authorityId
8671
+ });
8672
+ const [firstSubject, ...remainingSubjects] = subjects;
8673
+ if (!firstSubject) throw new Error("active wallet authority must produce capability subjects");
8674
+ return [firstSubject, ...remainingSubjects];
8534
8675
  }
8535
- //#endregion
8536
- //#region src/router/transport/fetch/routes/externalAuth.ts
8537
- const ACTIONS = [
8538
- "associate",
8539
- "unlink",
8540
- "prepare",
8541
- "exchange",
8542
- "backend-exchange",
8543
- "redeem",
8544
- "settings",
8545
- "logout"
8546
- ];
8547
- function parseCommand(action, body) {
8548
- const wallet = parseWalletId(body.walletId);
8549
- if (!wallet.ok) throw new Error("Wallet ID is invalid");
8550
- const walletId = wallet.value;
8551
- if (action === "backend-exchange") return {
8552
- action,
8553
- walletId,
8554
- attemptId: requireCanonicalString(body.attemptId, "attemptId"),
8555
- idToken: requireCanonicalString(body.idToken, "idToken")
8676
+ function buildWalletSessionAuthorizationV2(fields) {
8677
+ requireNonnegativeInteger(fields.authorityRevocationEpoch, "Wallet Session authority revocation epoch");
8678
+ parseDigestB64u(fields.authorityDigestB64u);
8679
+ if (fields.capabilitySubjects.length === 0) throw new Error("Wallet Session capability subjects are required");
8680
+ requireOrderedTimes(fields.createdAtMs, fields.expiresAtMs, "Wallet Session authorization");
8681
+ const identityValues = [
8682
+ String(fields.authorizationId),
8683
+ String(fields.walletSessionId),
8684
+ String(fields.quotaId)
8685
+ ];
8686
+ if (new Set(identityValues).size !== identityValues.length) throw new Error("Wallet Session authorization identities must be pairwise distinct");
8687
+ return {
8688
+ kind: "wallet_session_authorization_v2",
8689
+ tenantId: fields.tenantId,
8690
+ principalId: fields.principalId,
8691
+ walletId: fields.walletId,
8692
+ authorityId: fields.authorityId,
8693
+ walletAuthMethodId: fields.walletAuthMethodId,
8694
+ authorityDigestB64u: fields.authorityDigestB64u,
8695
+ authorityRevocationEpoch: fields.authorityRevocationEpoch,
8696
+ mintId: fields.mintId,
8697
+ authorizationId: fields.authorizationId,
8698
+ walletSessionId: fields.walletSessionId,
8699
+ quotaId: fields.quotaId,
8700
+ capabilitySubjects: fields.capabilitySubjects,
8701
+ createdAtMs: fields.createdAtMs,
8702
+ expiresAtMs: fields.expiresAtMs
8556
8703
  };
8557
- const appOrigin = requireCanonicalString(body.appOrigin, "appOrigin");
8558
- if (new URL(appOrigin).origin !== appOrigin) throw new Error("Application origin is invalid");
8559
- switch (action) {
8560
- case "associate": return {
8561
- action,
8562
- walletId,
8563
- appOrigin,
8564
- ownerCredential: requireCanonicalString(body.ownerCredential, "ownerCredential"),
8565
- idToken: requireCanonicalString(body.idToken, "idToken")
8566
- };
8567
- case "unlink": return {
8568
- action,
8569
- walletId,
8570
- appOrigin,
8571
- ownerCredential: requireCanonicalString(body.ownerCredential, "ownerCredential"),
8572
- associationId: requireCanonicalString(body.associationId, "associationId")
8573
- };
8574
- case "prepare": return {
8575
- action,
8576
- walletId,
8577
- appOrigin,
8578
- binding: secret(body.binding, "binding"),
8579
- sessionToken: secret(body.sessionToken, "sessionToken")
8580
- };
8581
- case "exchange": return {
8582
- action,
8583
- walletId,
8584
- appOrigin,
8585
- attemptId: requireCanonicalString(body.attemptId, "attemptId"),
8704
+ }
8705
+ function walletSessionCapabilitySubjectsV1Equal(left, right) {
8706
+ if (left.length !== right.length) return false;
8707
+ for (let index = 0; index < left.length; index += 1) {
8708
+ const leftSubject = left[index];
8709
+ const rightSubject = right[index];
8710
+ if (!leftSubject || !rightSubject || leftSubject.kind !== rightSubject.kind) return false;
8711
+ switch (leftSubject.kind) {
8712
+ case "sign":
8713
+ case "export_keys": break;
8714
+ case "link_devices":
8715
+ case "revoke_devices":
8716
+ if (rightSubject.kind !== leftSubject.kind || leftSubject.authorityId !== rightSubject.authorityId) return false;
8717
+ break;
8718
+ }
8719
+ if (leftSubject.kind === "sign" || leftSubject.kind === "export_keys") {
8720
+ if (rightSubject.kind !== leftSubject.kind || leftSubject.keyFamily !== rightSubject.keyFamily || !mpcMaterialActivationRefsEqual(leftSubject.materialActivation, rightSubject.materialActivation)) return false;
8721
+ }
8722
+ }
8723
+ return true;
8724
+ }
8725
+ function walletSessionAuthorizationV2RecordsEqual(left, right) {
8726
+ return left.kind === right.kind && left.tenantId === right.tenantId && left.principalId === right.principalId && left.walletId === right.walletId && left.authorityId === right.authorityId && left.walletAuthMethodId === right.walletAuthMethodId && left.authorityDigestB64u === right.authorityDigestB64u && left.authorityRevocationEpoch === right.authorityRevocationEpoch && left.mintId === right.mintId && left.authorizationId === right.authorizationId && left.walletSessionId === right.walletSessionId && left.quotaId === right.quotaId && walletSessionCapabilitySubjectsV1Equal(left.capabilitySubjects, right.capabilitySubjects) && left.createdAtMs === right.createdAtMs && left.expiresAtMs === right.expiresAtMs;
8727
+ }
8728
+ function parseMpcMaterialActivationRefRequired(value) {
8729
+ const parsed = parseMpcMaterialActivationRef(value);
8730
+ if (!parsed.ok) throw new Error(parsed.error.message);
8731
+ return parsed.value;
8732
+ }
8733
+ function parseWalletAuthorityIdRequired(value) {
8734
+ const parsed = parseWalletAuthorityId(value);
8735
+ if (!parsed.ok) throw new Error(parsed.error.message);
8736
+ return parsed.value;
8737
+ }
8738
+ function parseWalletAuthMethodIdRequired(value) {
8739
+ const parsed = parseWalletAuthMethodId(value);
8740
+ if (!parsed.ok) throw new Error(parsed.error.message);
8741
+ return parsed.value;
8742
+ }
8743
+ function parseWalletSessionCapabilitySubject(value) {
8744
+ if (!isWalletSessionCapabilitySubjectRecordV1(value) || typeof value.kind !== "string") throw new Error("Wallet Session capability subject must be an object");
8745
+ switch (value.kind) {
8746
+ case "sign":
8747
+ case "export_keys":
8748
+ if (Object.keys(value).sort().join("|") !== "keyFamily|kind|materialActivation") throw new Error("Wallet Session signer capability subject contains unexpected fields");
8749
+ if (value.keyFamily !== "ed25519" && value.keyFamily !== "ecdsa_secp256k1") throw new Error("Wallet Session signer capability subject key family is invalid");
8750
+ return {
8751
+ kind: value.kind,
8752
+ keyFamily: value.keyFamily,
8753
+ materialActivation: parseMpcMaterialActivationRefRequired(value.materialActivation)
8754
+ };
8755
+ case "link_devices":
8756
+ case "revoke_devices":
8757
+ if (Object.keys(value).sort().join("|") !== "authorityId|kind") throw new Error("Wallet Session administration capability subject contains unexpected fields");
8758
+ return {
8759
+ kind: value.kind,
8760
+ authorityId: parseWalletAuthorityIdRequired(value.authorityId)
8761
+ };
8762
+ default: throw new Error("Wallet Session capability subject kind is unsupported");
8763
+ }
8764
+ }
8765
+ function isWalletSessionCapabilitySubjectRecordV1(value) {
8766
+ return value !== null && typeof value === "object" && !Array.isArray(value);
8767
+ }
8768
+ function parseWalletSessionCapabilitySubjects(value) {
8769
+ if (!Array.isArray(value) || value.length === 0) throw new Error("Wallet Session capability subjects must be non-empty");
8770
+ const subjects = [];
8771
+ for (const subject of value) subjects.push(parseWalletSessionCapabilitySubject(subject));
8772
+ const [firstSubject, ...remainingSubjects] = subjects;
8773
+ if (!firstSubject) throw new Error("Wallet Session capability subjects must be non-empty");
8774
+ return [firstSubject, ...remainingSubjects];
8775
+ }
8776
+ function parseWalletSessionAuthorizationV2(value) {
8777
+ if (!isWalletSessionAuthorizationRecordV2(value)) throw new Error("Wallet Session authorization V2 contains unexpected fields");
8778
+ if (value.kind !== "wallet_session_authorization_v2") throw new Error("Wallet Session authorization V2 kind is invalid");
8779
+ const tenantId = parseTenantIdRequired(value.tenantId);
8780
+ const principalId = parsePrincipalIdRequired(value.principalId);
8781
+ const walletId = parseWalletIdRequired(value.walletId);
8782
+ const authorityId = parseWalletAuthorityIdRequired(value.authorityId);
8783
+ const walletAuthMethodId = parseWalletAuthMethodIdRequired(value.walletAuthMethodId);
8784
+ const authorityDigestB64u = parseDigestB64u(value.authorityDigestB64u);
8785
+ const mintIdResult = parseWalletSessionMintId(value.mintId);
8786
+ if (!mintIdResult.ok) throw new Error(mintIdResult.error.message);
8787
+ const authorizationIdResult = parseWalletSessionAuthorizationId(value.authorizationId);
8788
+ if (!authorizationIdResult.ok) throw new Error(authorizationIdResult.error.message);
8789
+ const walletSessionId = parseWalletSessionIdRequired(value.walletSessionId);
8790
+ const quotaId = parseMpcWalletSigningQuotaIdRequired(value.quotaId);
8791
+ return buildWalletSessionAuthorizationV2({
8792
+ tenantId,
8793
+ principalId,
8794
+ walletId,
8795
+ authorityId,
8796
+ walletAuthMethodId,
8797
+ authorityDigestB64u,
8798
+ authorityRevocationEpoch: requireNonnegativeInteger(value.authorityRevocationEpoch, "Wallet Session authority revocation epoch"),
8799
+ mintId: mintIdResult.value,
8800
+ authorizationId: authorizationIdResult.value,
8801
+ walletSessionId,
8802
+ quotaId,
8803
+ capabilitySubjects: parseWalletSessionCapabilitySubjects(value.capabilitySubjects),
8804
+ createdAtMs: requirePositiveTimestamp(value.createdAtMs, "Wallet Session createdAtMs"),
8805
+ expiresAtMs: requirePositiveTimestamp(value.expiresAtMs, "Wallet Session expiresAtMs")
8806
+ });
8807
+ }
8808
+ function isWalletSessionAuthorizationRecordV2(value) {
8809
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
8810
+ return Object.keys(value).sort().join("|") === "authorityDigestB64u|authorityId|authorityRevocationEpoch|authorizationId|capabilitySubjects|createdAtMs|expiresAtMs|kind|mintId|principalId|quotaId|tenantId|walletAuthMethodId|walletId|walletSessionId";
8811
+ }
8812
+ async function buildAuthorizedOperation(input) {
8813
+ if (input.tenantId !== input.operation.tenantId) throw new Error("authorized operation tenant must match its operation envelope");
8814
+ const operationFingerprintDigest = await computeCapabilityOperationFingerprintDigest(input.operation);
8815
+ return {
8816
+ ...input,
8817
+ kind: "authorized_operation",
8818
+ operationFingerprintDigest,
8819
+ lifecycle: "claimed"
8820
+ };
8821
+ }
8822
+ const AUTHORIZED_OPERATION_RESULT_DIGEST_DOMAIN_V1 = "seams:authorization:authorized-operation-result:v1";
8823
+ function parseAuthorizedOperationReplayResponse(value) {
8824
+ if (!isAuthorizedOperationReplayResponseRecordV1(value)) throw new Error("authorized operation replay response must be an object");
8825
+ const keys = Object.keys(value).sort();
8826
+ if (keys.length !== 3 || keys.join("|") !== "bodyText|contentType|status") throw new Error("authorized operation replay response must contain only status, contentType, and bodyText");
8827
+ const status = value.status;
8828
+ if (typeof status !== "number" || !Number.isSafeInteger(status) || status < 200 || status > 599) throw new Error("authorized operation replay response status must be an HTTP status");
8829
+ const contentType = value.contentType;
8830
+ if (typeof contentType !== "string" || contentType.length === 0 || contentType.trim() !== contentType || contentType.length > 255 || /[\u0000-\u001f\u007f]/.test(contentType)) throw new Error("authorized operation replay response contentType is invalid");
8831
+ const bodyText = value.bodyText;
8832
+ if (typeof bodyText !== "string") throw new Error("authorized operation replay response bodyText must be text");
8833
+ if (new TextEncoder().encode(bodyText).byteLength > 65536) throw new Error("authorized operation replay response bodyText is too large");
8834
+ if (responseStatusHasNoBody(status) && bodyText.length !== 0) throw new Error("authorized operation replay response status cannot carry a body");
8835
+ return {
8836
+ status,
8837
+ contentType,
8838
+ bodyText
8839
+ };
8840
+ }
8841
+ function authorizedOperationReplayBodyInit(response) {
8842
+ const parsed = parseAuthorizedOperationReplayResponse(response);
8843
+ return responseStatusHasNoBody(parsed.status) ? null : parsed.bodyText;
8844
+ }
8845
+ function responseStatusHasNoBody(status) {
8846
+ return status === 204 || status === 205 || status === 304;
8847
+ }
8848
+ function isAuthorizedOperationReplayResponseRecordV1(value) {
8849
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
8850
+ return Object.keys(value).sort().join("|") === "bodyText|contentType|status";
8851
+ }
8852
+ async function computeAuthorizedOperationResultDigest(response) {
8853
+ return parseDigestB64u(base64UrlEncode(await sha256BytesUtf8(`${AUTHORIZED_OPERATION_RESULT_DIGEST_DOMAIN_V1}|${alphabetizeStringify(parseAuthorizedOperationReplayResponse(response))}`)));
8854
+ }
8855
+ function parseHostedWalletSeamsSessionExchangeCode(value) {
8856
+ return parseAuthorizationDomainId(value, "hostedWalletSessionExchangeCode");
8857
+ }
8858
+ function parseVerifiedOwnerProofId(value) {
8859
+ return parseAuthorizationDomainId(value, "verifiedOwnerProofId");
8860
+ }
8861
+ function parseHostedWalletSeamsSessionExchangeNonce(value) {
8862
+ return parseAuthorizationDomainId(value, "hostedWalletSessionExchangeNonce");
8863
+ }
8864
+ function parseHostedWalletSessionCredentialId(value) {
8865
+ return parseAuthorizationDomainId(value, "hostedWalletSessionCredentialId");
8866
+ }
8867
+ function parsePrimaryWalletSessionOperationCredentialToken(value) {
8868
+ if (typeof value !== "string" || !/^wst_[A-Za-z0-9_-]{43}$/.test(value)) throw new Error("primary Wallet Session credential is invalid");
8869
+ return parseAuthorizationDomainId(value, "primaryWalletSessionOperationCredentialToken");
8870
+ }
8871
+ function parseHostedWalletSessionOperationCredentialToken(value) {
8872
+ if (typeof value !== "string" || !/^wsh_[A-Za-z0-9_-]{43}$/.test(value)) throw new Error("HostedWalletSessionOperationCredentialV1.token is invalid");
8873
+ return parseAuthorizationDomainId(value, "hostedWalletSessionOperationCredentialToken");
8874
+ }
8875
+ function parseSessionOrigin(value) {
8876
+ if (typeof value !== "string" || value.trim() !== value) throw new Error("session origin must be a canonical HTTP origin");
8877
+ let url;
8878
+ try {
8879
+ url = new URL(value);
8880
+ } catch {
8881
+ throw new Error("session origin must be a canonical HTTP origin");
8882
+ }
8883
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.origin !== value || url.pathname !== "/" || url.search || url.hash) throw new Error("session origin must be a canonical HTTP origin");
8884
+ return value;
8885
+ }
8886
+ function buildActiveWalletSessionQuota(fields) {
8887
+ requirePositiveCount(fields.remainingUses, "Wallet Session quota remaining uses");
8888
+ requirePositiveTime(fields.expiresAtMs, "Wallet Session quota expiry");
8889
+ return {
8890
+ kind: "active_wallet_session_quota",
8891
+ tenantId: fields.tenantId,
8892
+ principalId: fields.principalId,
8893
+ walletSessionId: fields.walletSessionId,
8894
+ quotaId: fields.quotaId,
8895
+ remainingUses: fields.remainingUses,
8896
+ expiresAtMs: fields.expiresAtMs
8897
+ };
8898
+ }
8899
+ function parseAuthorizationDomainId(value, fieldName) {
8900
+ if (typeof value !== "string" || value.trim() !== value || value.length === 0 || value.length > 512 || /[\s\u0000-\u001f\u007f]/.test(value)) throw new Error(`${fieldName} must be a compact opaque identifier`);
8901
+ return value;
8902
+ }
8903
+ function parseTenantIdRequired(value) {
8904
+ const parsed = parseTenantId(value);
8905
+ if (!parsed.ok) throw new Error(`linked-device authorization tenantId: ${parsed.error.message}`);
8906
+ return parsed.value;
8907
+ }
8908
+ function parsePrincipalIdRequired(value) {
8909
+ const parsed = parsePrincipalId(value);
8910
+ if (!parsed.ok) throw new Error(`linked-device authorization principalId: ${parsed.error.message}`);
8911
+ return parsed.value;
8912
+ }
8913
+ function parseWalletIdRequired(value) {
8914
+ const parsed = parseWalletId(value);
8915
+ if (!parsed.ok) throw new Error(`linked-device authorization walletId: ${parsed.error.message}`);
8916
+ return parsed.value;
8917
+ }
8918
+ function parseWalletSessionIdRequired(value) {
8919
+ const parsed = parseWalletSessionId(value);
8920
+ if (!parsed.ok) throw new Error(`linked-device authorization walletSessionId: ${parsed.error.message}`);
8921
+ return parsed.value;
8922
+ }
8923
+ function parseMpcWalletSigningQuotaIdRequired(value) {
8924
+ const parsed = parseMpcWalletSigningQuotaId(value);
8925
+ if (!parsed.ok) throw new Error(`linked-device authorization quotaId: ${parsed.error.message}`);
8926
+ return parsed.value;
8927
+ }
8928
+ function requirePositiveCount(value, label) {
8929
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`);
8930
+ }
8931
+ function requireNonnegativeInteger(value, label) {
8932
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error(`${label} must be a nonnegative safe integer`);
8933
+ return value;
8934
+ }
8935
+ function requirePositiveTimestamp(value, label) {
8936
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`);
8937
+ return value;
8938
+ }
8939
+ function requirePositiveTime(value, label) {
8940
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive timestamp`);
8941
+ }
8942
+ function requireOrderedTimes(createdAtMs, expiresAtMs, label) {
8943
+ requirePositiveTime(createdAtMs, `${label} creation`);
8944
+ requirePositiveTime(expiresAtMs, `${label} expiry`);
8945
+ if (expiresAtMs <= createdAtMs) throw new Error(`${label} expiry must follow creation`);
8946
+ }
8947
+ //#endregion
8948
+ //#region src/authorization/factorEvidence.ts
8949
+ function buildVerifiedWalletOperationPasskeyFactorResult(fields) {
8950
+ requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
8951
+ return {
8952
+ kind: "verified_wallet_operation_passkey_factor",
8953
+ ...fields
8954
+ };
8955
+ }
8956
+ function buildVerifiedWalletOperationEmailOtpFactorResult(fields) {
8957
+ requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
8958
+ return {
8959
+ kind: "verified_wallet_operation_email_otp_factor",
8960
+ ...fields
8961
+ };
8962
+ }
8963
+ function buildVerifiedWalletSessionPasskeyFactorResult(fields) {
8964
+ requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
8965
+ return {
8966
+ kind: "verified_wallet_session_passkey_factor",
8967
+ tenantId: fields.tenantId,
8968
+ principalId: fields.principalId,
8969
+ walletId: fields.walletId,
8970
+ authorityRef: fields.authorityRef,
8971
+ requestOrigin: fields.requestOrigin,
8972
+ audience: fields.audience,
8973
+ factorId: fields.factorId,
8974
+ credentialIdB64u: fields.credentialIdB64u,
8975
+ assertionDigest: fields.assertionDigest,
8976
+ verifiedAtMs: fields.verifiedAtMs,
8977
+ expiresAtMs: fields.expiresAtMs
8978
+ };
8979
+ }
8980
+ function buildVerifiedWalletSessionEmailOtpFactorResult(fields) {
8981
+ requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
8982
+ return {
8983
+ kind: "verified_wallet_session_email_otp_factor",
8984
+ tenantId: fields.tenantId,
8985
+ principalId: fields.principalId,
8986
+ walletId: fields.walletId,
8987
+ authorityRef: fields.authorityRef,
8988
+ requestOrigin: fields.requestOrigin,
8989
+ audience: fields.audience,
8990
+ factorId: fields.factorId,
8991
+ challengeId: fields.challengeId,
8992
+ verificationReceiptDigest: fields.verificationReceiptDigest,
8993
+ verifiedAtMs: fields.verifiedAtMs,
8994
+ expiresAtMs: fields.expiresAtMs
8995
+ };
8996
+ }
8997
+ function requireVerificationWindow(verifiedAtMs, expiresAtMs) {
8998
+ if (!Number.isSafeInteger(verifiedAtMs) || !Number.isSafeInteger(expiresAtMs) || verifiedAtMs <= 0 || expiresAtMs <= verifiedAtMs) throw new Error("verified factor expiry must follow verification");
8999
+ }
9000
+ //#endregion
9001
+ //#region ../shared-ts/src/utils/errors.ts
9002
+ /**
9003
+ * Best-effort error message extractor without relying on `any`.
9004
+ * Always returns a string (may be empty when nothing usable can be derived).
9005
+ */
9006
+ function errorMessage$3(err) {
9007
+ try {
9008
+ if (typeof err === "string") return err;
9009
+ if (err && typeof err.message === "string") return err.message;
9010
+ return String(err ?? "");
9011
+ } catch {
9012
+ return "";
9013
+ }
9014
+ }
9015
+ function b64uField(prefix) {
9016
+ return `${prefix}B64u`;
9017
+ }
9018
+ b64uField("seed"), b64uField("canonicalSeed"), b64uField("xClientBase"), b64uField("clientOutputMask"), b64uField("clientRecoverableSecret"), b64uField("prfFirst"), b64uField("prfSecond"), b64uField("signingShare32"), b64uField("clientNonceHandle");
9019
+ //#endregion
9020
+ //#region ../shared-ts/src/utils/failure.ts
9021
+ function failure$6(code, message) {
9022
+ return {
9023
+ ok: false,
9024
+ code,
9025
+ message
9026
+ };
9027
+ }
9028
+ function failedVerification(code, message) {
9029
+ return {
9030
+ ok: false,
9031
+ verified: false,
9032
+ code,
9033
+ message
9034
+ };
9035
+ }
9036
+ //#endregion
9037
+ //#region src/router/auth/webAuthnCredentialCodecs.ts
9038
+ function containsWebAuthnExtensionOutput(value) {
9039
+ if (value == null) return false;
9040
+ if (!isPlainObject(value)) return true;
9041
+ for (const nested of Object.values(value)) if (containsWebAuthnExtensionOutput(nested)) return true;
9042
+ return false;
9043
+ }
9044
+ function unredactedWebAuthnExtensionField(input) {
9045
+ if (!isPlainObject(input)) return null;
9046
+ if (containsWebAuthnExtensionOutput(input.clientExtensionResults)) return "clientExtensionResults";
9047
+ const response = input.response;
9048
+ if (!isPlainObject(response)) return null;
9049
+ if (containsWebAuthnExtensionOutput(response.clientExtensionResults)) return "response.clientExtensionResults";
9050
+ return null;
9051
+ }
9052
+ function decodeWebAuthnBase64UrlOrBase64(input, fieldName) {
9053
+ try {
9054
+ return base64UrlDecode(input);
9055
+ } catch {
9056
+ try {
9057
+ return base64Decode(input);
9058
+ } catch (error) {
9059
+ throw new Error(`Invalid ${fieldName}: expected base64url/base64 string (${errorMessage$3(error) || "decode failed"})`);
9060
+ }
9061
+ }
9062
+ }
9063
+ function parseWebAuthnClientDataJsonBase64url(clientDataJSONB64u) {
9064
+ const bytes = decodeWebAuthnBase64UrlOrBase64(clientDataJSONB64u, "webauthn_authentication.response.clientDataJSON");
9065
+ const json = new TextDecoder().decode(bytes);
9066
+ let parsed;
9067
+ try {
9068
+ parsed = JSON.parse(json);
9069
+ } catch {
9070
+ throw new Error("Invalid clientDataJSON: expected object");
9071
+ }
9072
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Invalid clientDataJSON: expected object");
9073
+ const challenge = normalizeOptionalTrimmedString(Reflect.get(parsed, "challenge"));
9074
+ const origin = normalizeOptionalTrimmedString(Reflect.get(parsed, "origin"));
9075
+ const type = normalizeOptionalTrimmedString(Reflect.get(parsed, "type"));
9076
+ if (!challenge) throw new Error("Invalid clientDataJSON.challenge");
9077
+ if (!origin) throw new Error("Invalid clientDataJSON.origin");
9078
+ if (!type) throw new Error("Invalid clientDataJSON.type");
9079
+ return {
9080
+ challenge,
9081
+ origin,
9082
+ type
9083
+ };
9084
+ }
9085
+ function webAuthnOriginHostnameOrEmpty(origin) {
9086
+ try {
9087
+ return new URL(origin).hostname.toLowerCase();
9088
+ } catch {
9089
+ return "";
9090
+ }
9091
+ }
9092
+ function webAuthnCredentialIdB64uFromCredential(input) {
9093
+ if (input === null || typeof input !== "object" || Array.isArray(input)) return failure$6("invalid_body", "Missing webauthn_authentication.id/rawId");
9094
+ const rawId = normalizeOptionalTrimmedString(Reflect.get(input, "rawId"));
9095
+ const id = normalizeOptionalTrimmedString(Reflect.get(input, "id"));
9096
+ const selected = rawId || id;
9097
+ if (!selected) return failure$6("invalid_body", "Missing webauthn_authentication.id/rawId");
9098
+ try {
9099
+ return {
9100
+ ok: true,
9101
+ credentialIdB64u: base64UrlEncode(decodeWebAuthnBase64UrlOrBase64(selected, "webauthn_authentication.rawId"))
9102
+ };
9103
+ } catch (error) {
9104
+ return failure$6("invalid_body", errorMessage$3(error) || "Invalid credential rawId");
9105
+ }
9106
+ }
9107
+ function parseWebAuthnAuthenticationCredential$2(input) {
9108
+ if (input === null || typeof input !== "object" || Array.isArray(input)) return null;
9109
+ const responseValue = Reflect.get(input, "response");
9110
+ if (responseValue === null || typeof responseValue !== "object" || Array.isArray(responseValue)) return null;
9111
+ const id = normalizeOptionalTrimmedString(Reflect.get(input, "id"));
9112
+ const rawId = normalizeOptionalTrimmedString(Reflect.get(input, "rawId"));
9113
+ const type = normalizeOptionalTrimmedString(Reflect.get(input, "type"));
9114
+ const clientDataJSON = normalizeOptionalTrimmedString(Reflect.get(responseValue, "clientDataJSON"));
9115
+ const authenticatorData = normalizeOptionalTrimmedString(Reflect.get(responseValue, "authenticatorData"));
9116
+ const signature = normalizeOptionalTrimmedString(Reflect.get(responseValue, "signature"));
9117
+ const rawUserHandle = Reflect.get(responseValue, "userHandle");
9118
+ const userHandle = rawUserHandle === null ? null : normalizeOptionalTrimmedString(rawUserHandle) || null;
9119
+ const rawAuthenticatorAttachment = Reflect.get(input, "authenticatorAttachment");
9120
+ const authenticatorAttachment = rawAuthenticatorAttachment === null ? null : normalizeOptionalTrimmedString(rawAuthenticatorAttachment) || null;
9121
+ if (!id || !rawId || type !== "public-key") return null;
9122
+ if (!clientDataJSON || !authenticatorData || !signature) return null;
9123
+ return {
9124
+ id,
9125
+ rawId,
9126
+ type,
9127
+ authenticatorAttachment,
9128
+ response: {
9129
+ clientDataJSON,
9130
+ authenticatorData,
9131
+ signature,
9132
+ userHandle
9133
+ },
9134
+ clientExtensionResults: Reflect.get(input, "clientExtensionResults") ?? null
9135
+ };
9136
+ }
9137
+ //#endregion
9138
+ //#region src/router/domains/emailOtp/emailOtpSessionRouteHelpers.ts
9139
+ function emailOtpStatusCode(code) {
9140
+ if (code === "internal") return 500;
9141
+ if (code === "not_configured") return 503;
9142
+ if (code === "not_found") return 404;
9143
+ if (code === "rate_limited") return 429;
9144
+ if (code === "stronger_auth_required") return 403;
9145
+ if (code === "reenrollment_required" || code === "registration_attempt_missing" || code === "registration_attempt_expired") return 409;
9146
+ if (code === "challenge_id_mismatch" || code === "challenge_purpose_mismatch" || code === "challenge_subject_mismatch" || code === "challenge_email_mismatch" || code === "challenge_wallet_mismatch" || code === "challenge_session_mismatch" || code === "challenge_org_mismatch" || code === "challenge_channel_mismatch" || code === "registration_reroll_disallowed" || code === "challenge_expired_or_invalid" || code === "invalid_otp") return 401;
9147
+ return 400;
9148
+ }
9149
+ function emailOtpFailureAuditPayload(input) {
9150
+ return {
9151
+ source: input.source,
9152
+ code: input.code,
9153
+ message: input.message,
9154
+ ...input.challengeId ? { challengeId: input.challengeId } : {},
9155
+ ...input.otpChannel ? { otpChannel: input.otpChannel } : {},
9156
+ ...input.operation ? { operation: input.operation } : {},
9157
+ ...typeof input.lockedUntilMs === "number" ? { lockedUntilMs: input.lockedUntilMs } : {}
9158
+ };
9159
+ }
9160
+ function shouldEmitEmailOtpLockedWebhook(code) {
9161
+ return code === "otp_locked_out" || code === "otp_attempts_exhausted";
9162
+ }
9163
+ function emailOtpFailureWebhookEventDescriptors(input) {
9164
+ const payload = emailOtpFailureAuditPayload(input);
9165
+ const event = {
9166
+ eventType: "wallet.email_otp.failed",
9167
+ ...input.challengeId ? { eventId: input.challengeId } : {},
9168
+ payload
9169
+ };
9170
+ if (!shouldEmitEmailOtpLockedWebhook(input.code)) return [event];
9171
+ return [event, {
9172
+ eventType: "wallet.email_otp.locked",
9173
+ ...input.challengeId ? { eventId: input.challengeId } : {},
9174
+ payload
9175
+ }];
9176
+ }
9177
+ function emailOtpLoggedInWebhookEventDescriptor(input) {
9178
+ return {
9179
+ eventType: "wallet.email_otp.logged_in",
9180
+ eventId: input.challengeId,
9181
+ payload: {
9182
+ otpChannel: input.otpChannel,
9183
+ unlockBackend: input.unlockBackend,
9184
+ challengeId: input.challengeId
9185
+ }
9186
+ };
9187
+ }
9188
+ async function hashEmailOtpOperationBinding(input) {
9189
+ return base64UrlEncode(await sha256BytesUtf8(alphabetizeStringify({
9190
+ walletId: input.walletId,
9191
+ providerUserId: input.providerUserId,
9192
+ orgId: input.orgId,
9193
+ operation: input.operation,
9194
+ requestOrigin: input.requestOrigin || "",
9195
+ audience: input.audience || "",
9196
+ ...input.authorityRef !== void 0 ? { authorityRef: input.authorityRef } : {},
9197
+ ...input.operationFingerprintDigest ? { operationFingerprintDigest: input.operationFingerprintDigest } : {}
9198
+ })));
9199
+ }
9200
+ //#endregion
9201
+ //#region src/router/framework/routeResponses.ts
9202
+ function routeJson(status, body, options) {
9203
+ return {
9204
+ status,
9205
+ body,
9206
+ ...options?.headers ? { headers: options.headers } : {},
9207
+ ...options?.usage ? { usage: options.usage } : {}
9208
+ };
9209
+ }
9210
+ function routeError(status, code, message) {
9211
+ return routeJson(status, {
9212
+ ok: false,
9213
+ code,
9214
+ message
9215
+ });
9216
+ }
9217
+ function toFetchRouteResponse(response) {
9218
+ return new Response(JSON.stringify(response.body), {
9219
+ status: response.status,
9220
+ headers: {
9221
+ "Content-Type": "application/json; charset=utf-8",
9222
+ ...response.headers || {}
9223
+ }
9224
+ });
9225
+ }
9226
+ //#endregion
9227
+ //#region src/router/transport/fetch/routes/walletCustodyRequestBoundary.ts
9228
+ function requireExactObjectFields(value, fields, label) {
9229
+ const keys = Object.keys(value).sort();
9230
+ const expected = [...fields].sort();
9231
+ if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) throw new Error(`${label} contains unsupported fields`);
9232
+ }
9233
+ function parseCustodyOwnerProof(value) {
9234
+ if (!isPlainObject(value)) throw new Error("factorProof is required");
9235
+ const kind = normalizeOptionalTrimmedString(value.kind);
9236
+ if (kind === "passkey") {
9237
+ requireExactObjectFields(value, [
9238
+ "kind",
9239
+ "walletId",
9240
+ "rpId",
9241
+ "credentialIdB64u",
9242
+ "challenge_digest",
9243
+ "webauthn_authentication"
9244
+ ], "factorProof");
9245
+ const extensionField = unredactedWebAuthnExtensionField(value.webauthn_authentication);
9246
+ if (extensionField) throw new Error(`factorProof.webauthn_authentication.${extensionField} must be redacted`);
9247
+ const webauthnAuthentication = parseWebAuthnAuthenticationCredential$2(value.webauthn_authentication);
9248
+ if (!webauthnAuthentication) throw new Error("factorProof.webauthn_authentication is invalid");
9249
+ return {
9250
+ kind,
9251
+ walletId: requireTrimmedString(value.walletId, "factorProof.walletId"),
9252
+ rpId: requireTrimmedString(value.rpId, "factorProof.rpId"),
9253
+ credentialIdB64u: requireTrimmedString(value.credentialIdB64u, "factorProof.credentialIdB64u"),
9254
+ challenge_digest: requireTrimmedString(value.challenge_digest, "factorProof.challenge_digest"),
9255
+ webauthn_authentication: webauthnAuthentication
9256
+ };
9257
+ }
9258
+ if (kind === "email_otp") {
9259
+ requireExactObjectFields(value, [
9260
+ "kind",
9261
+ "provider_subject_id",
9262
+ "challenge_id",
9263
+ "otp_code",
9264
+ "challenge_digest"
9265
+ ], "factorProof");
9266
+ return {
9267
+ kind,
9268
+ provider_subject_id: requireTrimmedString(value.provider_subject_id, "factorProof.provider_subject_id"),
9269
+ challenge_id: requireTrimmedString(value.challenge_id, "factorProof.challenge_id"),
9270
+ otp_code: requireTrimmedString(value.otp_code, "factorProof.otp_code"),
9271
+ challenge_digest: requireTrimmedString(value.challenge_digest, "factorProof.challenge_digest")
9272
+ };
9273
+ }
9274
+ throw new Error("factorProof.kind must be passkey or email_otp");
9275
+ }
9276
+ function parseRecoveryBackupAcknowledgementRequest(body) {
9277
+ if (!isPlainObject(body)) throw new Error("acknowledgement body is required");
9278
+ requireExactObjectFields(body, [
9279
+ "expectedIssuedAtMs",
9280
+ "factorProof",
9281
+ "requestId",
9282
+ "walletId"
9283
+ ], "recovery backup acknowledgement");
9284
+ return {
9285
+ walletId: requireTrimmedString(body.walletId, "walletId"),
9286
+ requestId: requireTrimmedString(body.requestId, "requestId"),
9287
+ expectedIssuedAtMs: parseUnixMs$1(body.expectedIssuedAtMs, "expectedIssuedAtMs"),
9288
+ factorProof: body.factorProof
9289
+ };
9290
+ }
9291
+ function parseRecoveryStatusRequest(body) {
9292
+ if (!isPlainObject(body)) throw new Error("status body is required");
9293
+ requireExactObjectFields(body, ["factorProof", "requestId"], "recovery status");
9294
+ return {
9295
+ requestId: requireTrimmedString(body.requestId, "requestId"),
9296
+ factorProof: body.factorProof
9297
+ };
9298
+ }
9299
+ //#endregion
9300
+ //#region src/router/transport/fetch/walletOwnerOperationAuthorization.ts
9301
+ const CUSTODY_OPERATION_CAPABILITY_ID = "wallet-custody-admin";
9302
+ const CUSTODY_PROOF_TTL_MS = 6e4;
9303
+ function walletOwnerOperationAuthorizationId(operation, challenge) {
9304
+ return requireParsed$4(parseAuthorizedOperationId(`wallet-custody:authorized:wallet-custody:${operation}:${challenge}`), "owner operation");
9305
+ }
9306
+ function hasFullOwnerAuthority$1(authority) {
9307
+ return sameDelegatedWalletAuthorityV1(buildDelegatedWalletAuthorityV1({ permissions: authority.permissions }), buildDelegatedWalletAuthorityV1({ permissions: buildFullOwnerPermissionsV1() }));
9308
+ }
9309
+ async function custodyDigest(domain, value) {
9310
+ return sha256Utf8DigestB64u(`${domain}|${alphabetizeStringify(value)}`);
9311
+ }
9312
+ async function buildWalletCustodyOperation(input) {
9313
+ const tenantId = requireParsed$4(parseTenantId(input.tenantId), "authorization");
9314
+ const principalId = requireParsed$4(parsePrincipalId(input.principalId), "authorization");
9315
+ const capabilityId = requireParsed$4(parseCapabilityId(CUSTODY_OPERATION_CAPABILITY_ID), "authorization");
9316
+ const operationRef = buildVaultOperationRef("vault.reveal");
9317
+ const laneDigest = await custodyDigest("seams:wallet-custody:lane:v1", {
9318
+ capabilityId,
9319
+ operation: input.operation,
9320
+ operationRef
9321
+ });
9322
+ const challengeDigest = await computeWalletOwnerAdminChallengeDigest({
9323
+ walletId: input.walletId,
9324
+ operation: input.operation,
9325
+ payload: input.payload,
9326
+ requestOrigin: input.requestOrigin
9327
+ });
9328
+ const intentDigest = challengeDigest;
9329
+ const displayDigest = await custodyDigest("seams:wallet-custody:display:v1", {
9330
+ walletId: input.walletId,
9331
+ operation: input.operation
9332
+ });
9333
+ return {
9334
+ operation: buildCapabilityOperationEnvelope({
9335
+ tenantId,
9336
+ principalId,
9337
+ capabilityId,
9338
+ operationId: requireParsed$4(parseCapabilityOperationId(`wallet-custody:${input.operation}:${intentDigest}`), "authorization"),
9339
+ operation: operationRef,
9340
+ digests: {
9341
+ laneDigest,
9342
+ intentDigest,
9343
+ displayDigest
9344
+ }
9345
+ }),
9346
+ challengeDigest
9347
+ };
9348
+ }
9349
+ async function authorizeWalletOwnerOperation(input) {
9350
+ const requestOrigin = normalizeOptionalTrimmedString(input.ctx.request.headers.get("origin"));
9351
+ if (!requestOrigin) return toCustodyAuthorizationFailure(400, "invalid_origin", "request Origin is required");
9352
+ let origin;
9353
+ let proof;
9354
+ let walletId;
9355
+ try {
9356
+ origin = parseSessionOrigin(requestOrigin);
9357
+ walletId = requireParsed$4(parseWalletId(input.walletId), "authorization");
9358
+ proof = parseCustodyOwnerProof(input.factorProof);
9359
+ } catch (error) {
9360
+ return toCustodyAuthorizationFailure(400, "invalid_owner_proof", error instanceof Error ? error.message : "owner proof is invalid");
9361
+ }
9362
+ if (proof.kind === "passkey" && proof.walletId !== input.walletId) return toCustodyAuthorizationFailure(403, "scope_mismatch", "owner proof wallet differs");
9363
+ let principalId;
9364
+ let factor;
9365
+ let ownerAuthorityRef;
9366
+ let ownerAuthority;
9367
+ let operation;
9368
+ let challengeDigest;
9369
+ try {
9370
+ principalId = proof.kind === "passkey" ? input.walletId : proof.provider_subject_id;
9371
+ const built = await buildWalletCustodyOperation({
9372
+ tenantId: input.ctx.service.authorizedOperations.tenantId,
9373
+ principalId,
9374
+ walletId: input.walletId,
9375
+ operation: input.operation,
9376
+ payload: input.payload,
9377
+ requestOrigin
9378
+ });
9379
+ operation = built.operation;
9380
+ challengeDigest = built.challengeDigest;
9381
+ } catch (error) {
9382
+ return toCustodyAuthorizationFailure(400, "invalid_owner_proof", error instanceof Error ? error.message : "owner proof binding is invalid");
9383
+ }
9384
+ const operationFingerprintDigest = await computeCapabilityOperationFingerprintDigest(operation);
9385
+ const nowMs = Date.now();
9386
+ if (proof.challenge_digest !== String(challengeDigest)) return toCustodyAuthorizationFailure(403, "scope_mismatch", "factor proof challenge does not match the requested operation");
9387
+ if (proof.kind === "passkey") {
9388
+ let authority;
9389
+ try {
9390
+ authority = buildPasskeyWalletAuthAuthority({
9391
+ walletId,
9392
+ rpId: proof.rpId,
9393
+ credentialIdB64u: proof.credentialIdB64u
9394
+ });
9395
+ } catch (error) {
9396
+ return toCustodyAuthorizationFailure(400, "invalid_owner_proof", error instanceof Error ? error.message : "passkey authority is invalid");
9397
+ }
9398
+ const active = await input.ctx.service.walletAuthMethods.resolveActivePasskeyAuthorityForVerifiedCredential({
9399
+ walletId,
9400
+ rpId: authority.verifier.rpId,
9401
+ credentialIdB64u: authority.factor.credentialIdB64u
9402
+ });
9403
+ if (!active.ok) return toCustodyAuthorizationFailure(403, active.code, active.message);
9404
+ if (!hasFullOwnerAuthority$1(active.walletAuthority)) return toCustodyAuthorizationFailure(403, "forbidden", "Full owner authority is required");
9405
+ ownerAuthority = active.walletAuthority;
9406
+ const credential = parseWebAuthnAuthenticationCredential$2(proof.webauthn_authentication);
9407
+ if (!credential) return toCustodyAuthorizationFailure(400, "invalid_owner_proof", "passkey assertion is invalid");
9408
+ if (String(credential.rawId || credential.id).trim() !== authority.factor.credentialIdB64u) return toCustodyAuthorizationFailure(401, "unauthorized", "passkey credential changed");
9409
+ const verified = await input.ctx.service.webAuthn.verifyWebAuthnAuthenticationLite({
9410
+ userId: input.walletId,
9411
+ rpId: authority.verifier.rpId,
9412
+ expectedChallenge: challengeDigest,
9413
+ expected_origin: requestOrigin,
9414
+ webauthn_authentication: credential
9415
+ });
9416
+ if (!verified.success || !verified.verified) return toCustodyAuthorizationFailure(401, verified.code || "not_verified", verified.message || "WebAuthn authentication verification failed");
9417
+ ownerAuthorityRef = await walletAuthAuthorityRef({ authority: active.authority });
9418
+ factor = buildVerifiedWalletOperationPasskeyFactorResult({
9419
+ tenantId: operation.tenantId,
9420
+ principalId: operation.principalId,
9421
+ walletId,
9422
+ authorityRef: ownerAuthorityRef,
9423
+ requestOrigin: origin,
9424
+ audience: origin,
9425
+ factorId: requireParsed$4(parseAuthFactorId(`passkey:${authority.factor.credentialIdB64u}`), "authorization"),
9426
+ credentialIdB64u: requireParsed$4(parseWebAuthnCredentialIdB64u(authority.factor.credentialIdB64u), "authorization"),
9427
+ assertionDigest: await custodyDigest("seams:wallet-custody:assertion:v1", { credential }),
9428
+ operation,
9429
+ verifiedAtMs: nowMs,
9430
+ expiresAtMs: nowMs + CUSTODY_PROOF_TTL_MS
9431
+ });
9432
+ } else {
9433
+ const providerSubject = requireParsed$4(parseProviderSubject(proof.provider_subject_id), "authorization");
9434
+ const orgId = requireParsed$4(parseOrgId(input.ctx.service.authorizedOperations.tenantId), "authorization");
9435
+ const active = await input.ctx.service.walletAuthMethods.resolveActiveEmailOtpAuthorityForVerifiedSubject({
9436
+ walletId: input.walletId,
9437
+ providerUserId: proof.provider_subject_id
9438
+ });
9439
+ if (!active.ok) return toCustodyAuthorizationFailure(403, active.code, active.message);
9440
+ const activeAuthorityRef = await walletAuthAuthorityRef({ authority: active.authority });
9441
+ ownerAuthorityRef = activeAuthorityRef;
9442
+ ownerAuthority = active.walletAuthority;
9443
+ const ownerProofBindingDigest = await hashEmailOtpOperationBinding({
9444
+ walletId: input.walletId,
9445
+ providerUserId: proof.provider_subject_id,
9446
+ orgId,
9447
+ operation: `wallet_custody:${input.operation}`,
9448
+ requestOrigin,
9449
+ audience: requestOrigin,
9450
+ authorityRef: activeAuthorityRef,
9451
+ operationFingerprintDigest: challengeDigest
9452
+ });
9453
+ const verified = await input.ctx.service.emailOtp.verifyEmailOtpChallenge({
9454
+ userId: proof.provider_subject_id,
9455
+ walletId: input.walletId,
9456
+ orgId,
9457
+ challengeId: proof.challenge_id,
9458
+ otpCode: proof.otp_code,
9459
+ otpChannel: EMAIL_OTP_CHANNEL,
9460
+ ownerProofBindingDigest,
9461
+ operation: WALLET_EMAIL_OTP_UNLOCK_OPERATION
9462
+ });
9463
+ if (!verified.ok) return toCustodyAuthorizationFailure(verified.code === "invalid_body" ? 400 : 401, verified.code, verified.message);
9464
+ const consumed = await input.ctx.service.emailOtp.consumeEmailOtpGrant({
9465
+ subject: {
9466
+ kind: "provider_identity",
9467
+ orgId,
9468
+ providerSubject,
9469
+ walletId
9470
+ },
9471
+ loginGrant: verified.loginGrant,
9472
+ otpChannel: EMAIL_OTP_CHANNEL
9473
+ });
9474
+ if (!consumed.ok) return toCustodyAuthorizationFailure(emailOtpStatusCode(consumed.code), consumed.code, consumed.message);
9475
+ factor = buildVerifiedWalletOperationEmailOtpFactorResult({
9476
+ tenantId: operation.tenantId,
9477
+ principalId: operation.principalId,
9478
+ walletId,
9479
+ authorityRef: activeAuthorityRef,
9480
+ requestOrigin: origin,
9481
+ audience: origin,
9482
+ factorId: requireParsed$4(parseAuthFactorId(`email_otp:${active.authority.factor.provider}:${proof.provider_subject_id}`), "authorization"),
9483
+ operation,
9484
+ challengeId: requireParsed$4(parseEmailOtpChallengeId(consumed.challengeId), "authorization"),
9485
+ verificationReceiptDigest: await custodyDigest("seams:wallet-custody:otp-receipt:v1", {
9486
+ challengeId: consumed.challengeId,
9487
+ operationFingerprintDigest
9488
+ }),
9489
+ verifiedAtMs: nowMs,
9490
+ expiresAtMs: Math.min(nowMs + CUSTODY_PROOF_TTL_MS, verified.grantExpiresAtMs)
9491
+ });
9492
+ }
9493
+ const ownerProof = await input.ctx.service.authorizedOperations.buildVerifiedOwnerProof({
9494
+ purpose: "operation",
9495
+ proofId: parseVerifiedOwnerProofId(`wallet-custody:${operation.operationId}`),
9496
+ factor
9497
+ });
9498
+ if (ownerProof.purpose !== "operation" || ownerProof.operation.operationFingerprintDigest !== operationFingerprintDigest) return toCustodyAuthorizationFailure(403, "owner_proof_rejected", "owner proof binding is invalid");
9499
+ const existing = await input.ctx.service.authorizedOperations.readAuthorizedOperation({
9500
+ tenantId: operation.tenantId,
9501
+ operationFingerprintDigest
9502
+ });
9503
+ if (existing) {
9504
+ if (existing.authorization.kind !== "verified_step_up" || existing.quota.kind !== "quota_neutral") return toCustodyAuthorizationFailure(403, "owner_proof_rejected", "Owner operation authorization differs");
9505
+ return {
9506
+ ok: false,
9507
+ response: authorizedOperationReplay(existing)
9508
+ };
9509
+ }
9510
+ const evidenceSet = await input.ctx.service.authorizedOperations.recordVerifiedWalletOperationFactorEvidenceSet({
9511
+ operation,
9512
+ evidenceId: requireParsed$4(parseAuthorizationEvidenceId(`wallet-custody:evidence:${operation.operationId}`), "authorization"),
9513
+ evidenceSetId: requireParsed$4(parseAuthorizationEvidenceSetId(`wallet-custody:evidence-set:${operation.operationId}`), "authorization"),
9514
+ factor
9515
+ });
9516
+ const admission = await input.ctx.service.authorizedOperations.admitAuthorizedOperation({ operation: {
9517
+ tenantId: operation.tenantId,
9518
+ authorizedOperationId: walletOwnerOperationAuthorizationId(input.operation, challengeDigest),
9519
+ auditEventId: requireParsed$4(parseAuthorizationAuditEventId(`wallet-custody:audit:${operation.operationId}`), "authorization"),
9520
+ operation,
9521
+ authorization: {
9522
+ kind: "verified_step_up",
9523
+ evidenceSetDigest: evidenceSet.evidenceSetDigest
9524
+ },
9525
+ quota: { kind: "quota_neutral" },
9526
+ claimedAtMs: nowMs
9527
+ } });
9528
+ switch (admission.kind) {
9529
+ case "claimed": return {
9530
+ ok: true,
9531
+ operation: admission.operation,
9532
+ ownerAuthority
9533
+ };
9534
+ case "replayed":
9535
+ case "operation_in_progress": return {
9536
+ ok: false,
9537
+ response: authorizedOperationReplay(admission.operation)
9538
+ };
9539
+ case "authorization_grant_rejected":
9540
+ case "verified_step_up_rejected":
9541
+ case "wallet_session_quota_exhausted":
9542
+ case "material_mismatch": return toCustodyAuthorizationFailure(403, "owner_proof_rejected", "owner proof was rejected");
9543
+ }
9544
+ }
9545
+ function toCustodyAuthorizationFailure(status, code, message) {
9546
+ return {
9547
+ ok: false,
9548
+ response: routeErrorResponse$1(status, code, message)
9549
+ };
9550
+ }
9551
+ function routeErrorResponse$1(status, code, message) {
9552
+ return toFetchRouteResponse({
9553
+ status,
9554
+ body: {
9555
+ ok: false,
9556
+ code,
9557
+ message
9558
+ }
9559
+ });
9560
+ }
9561
+ function authorizedOperationReplay(operation) {
9562
+ if (operation.lifecycle !== "completed") return routeErrorResponse$1(409, "recovery_in_progress", "wallet recovery is still in progress");
9563
+ return new Response(operation.response.bodyText, {
9564
+ status: operation.response.status,
9565
+ headers: { "content-type": operation.response.contentType }
9566
+ });
9567
+ }
9568
+ async function completeWalletOwnerOperation(ctx, operation, status, body, result = "succeeded") {
9569
+ const bodyText = JSON.stringify(body);
9570
+ await ctx.service.authorizedOperations.completeAuthorizedOperation({
9571
+ operation,
9572
+ result,
9573
+ response: {
9574
+ status,
9575
+ contentType: "application/json",
9576
+ bodyText
9577
+ },
9578
+ completedAtMs: Date.now()
9579
+ });
9580
+ return toFetchRouteResponse({
9581
+ status,
9582
+ body
9583
+ });
9584
+ }
9585
+ //#endregion
9586
+ //#region src/router/auth/routerApiKeyAuth.ts
9587
+ function readHeader(headers, name) {
9588
+ if (headers instanceof Headers) return String(headers.get(name) || "").trim();
9589
+ const direct = headers[name];
9590
+ const lower = headers[name.toLowerCase()];
9591
+ const upper = headers[name.toUpperCase()];
9592
+ const value = direct ?? lower ?? upper;
9593
+ if (typeof value === "string") return value.trim();
9594
+ if (Array.isArray(value)) return String(value[0] || "").trim();
9595
+ return "";
9596
+ }
9597
+ function extractBearerCredential(headers) {
9598
+ const authHeader = readHeader(headers, "authorization");
9599
+ if (authHeader) {
9600
+ if (authHeader.toLowerCase().startsWith("bearer ")) {
9601
+ const bearerValue = authHeader.slice(7).trim();
9602
+ if (bearerValue) return bearerValue;
9603
+ }
9604
+ }
9605
+ return null;
9606
+ }
9607
+ function normalizeSourceIp(raw) {
9608
+ const value = String(raw || "").trim();
9609
+ if (!value) return null;
9610
+ const maybeForwarded = value.includes(",") ? value.split(",")[0] : value;
9611
+ return String(maybeForwarded || "").trim() || null;
9612
+ }
9613
+ function extractRouterApiEnvironmentId(headers) {
9614
+ const preferred = readHeader(headers, "x-seams-environment-id");
9615
+ if (preferred) return preferred;
9616
+ const fallback = readHeader(headers, "x-environment-id");
9617
+ if (fallback) return fallback;
9618
+ return null;
9619
+ }
9620
+ function resolveSourceIpFromFetchHeaders(headers) {
9621
+ const cfConnecting = readHeader(headers, "cf-connecting-ip");
9622
+ const forwarded = readHeader(headers, "x-forwarded-for");
9623
+ const realIp = readHeader(headers, "x-real-ip");
9624
+ return normalizeSourceIp(cfConnecting || forwarded || realIp);
9625
+ }
9626
+ //#endregion
9627
+ //#region src/core/SessionService.ts
9628
+ function normalizeParsedCorsOrigin(url) {
9629
+ const host = url.hostname.toLowerCase();
9630
+ const proto = url.protocol === "http:" || url.protocol === "https:" ? url.protocol : "https:";
9631
+ const rawPort = String(url.port || "").trim();
9632
+ return `${proto}//${host}${rawPort && !(proto === "https:" && rawPort === "443") && !(proto === "http:" && rawPort === "80") ? `:${rawPort}` : ""}`;
9633
+ }
9634
+ function parseCsvList(input) {
9635
+ const out = /* @__PURE__ */ new Set();
9636
+ for (const raw of String(input || "").split(",")) {
9637
+ const s = raw.trim();
9638
+ if (!s) continue;
9639
+ try {
9640
+ const u = new URL(s);
9641
+ out.add(normalizeParsedCorsOrigin(u));
9642
+ } catch {
9643
+ const stripped = s.replace(/\/$/, "");
9644
+ if (stripped) out.add(stripped);
9645
+ }
9646
+ }
9647
+ return Array.from(out);
9648
+ }
9649
+ /**
9650
+ * Normalize a single origin for CORS comparisons.
9651
+ * Returns null when the input is empty or not URL-like.
9652
+ */
9653
+ function normalizeCorsOrigin(input) {
9654
+ const raw = String(input || "").trim();
9655
+ if (!raw) return null;
9656
+ try {
9657
+ return normalizeParsedCorsOrigin(new URL(raw));
9658
+ } catch {
9659
+ return null;
9660
+ }
9661
+ }
9662
+ function buildCorsOrigins(...inputs) {
9663
+ const merged = /* @__PURE__ */ new Set();
9664
+ for (const input of inputs) for (const origin of parseCsvList(input)) merged.add(origin);
9665
+ const list = Array.from(merged);
9666
+ return list.length > 0 ? list : "*";
9667
+ }
9668
+ //#endregion
9669
+ //#region ../shared-ts/src/utils/routerAbTraceContext.ts
9670
+ const ROUTER_AB_TRACE_ID_HEADER_V1 = "x-seams-trace-id";
9671
+ const TRACE_ID_PATTERN = /^[0-9a-f]{32}$/;
9672
+ function parseRouterAbTraceContextV1(raw) {
9673
+ if (raw === null || raw === void 0 || raw === "") return {
9674
+ ok: false,
9675
+ reason: "missing",
9676
+ message: "Router trace ID is missing"
9677
+ };
9678
+ if (typeof raw !== "string" || !TRACE_ID_PATTERN.test(raw)) return {
9679
+ ok: false,
9680
+ reason: "invalid",
9681
+ message: "Router trace ID must be 32 lowercase hexadecimal characters"
9682
+ };
9683
+ return {
9684
+ ok: true,
9685
+ value: {
9686
+ kind: "router_ab_trace_context_v1",
9687
+ value: raw
9688
+ }
9689
+ };
9690
+ }
9691
+ function createRouterAbTraceContextV1() {
9692
+ const bytes = new Uint8Array(16);
9693
+ globalThis.crypto.getRandomValues(bytes);
9694
+ let value = "";
9695
+ for (const byte of bytes) value += byte.toString(16).padStart(2, "0");
9696
+ return {
9697
+ kind: "router_ab_trace_context_v1",
9698
+ value
9699
+ };
9700
+ }
9701
+ //#endregion
9702
+ //#region ../shared-ts/src/utils/walletManagementProtocol.ts
9703
+ const WALLET_MANAGEMENT_PROTOCOL_HEADER = "X-Seams-Wallet-Protocol";
9704
+ //#endregion
9705
+ //#region src/router/framework/http.ts
9706
+ const CORS_ALLOW_METHODS = "GET,POST,PUT,PATCH,DELETE,OPTIONS";
9707
+ const CORS_PREFLIGHT_MAX_AGE_SECONDS = 7200;
9708
+ const CORS_ALLOW_HEADERS = [
9709
+ "Content-Type",
9710
+ WALLET_MANAGEMENT_PROTOCOL_HEADER,
9711
+ "Authorization",
9712
+ "X-Seams-Wallet-Region",
9713
+ "X-Seams-Benchmark-Diagnostics",
9714
+ LINKED_DEVICE_REQUEST_PROOF_HEADER_V1,
9715
+ ROUTER_AB_ED25519_YAO_RECOVERY_CHALLENGE_ID_HEADER_V1,
9716
+ ROUTER_AB_TRACE_ID_HEADER_V1,
9717
+ "X-Seams-Environment-Id",
9718
+ "X-Environment-Id"
9719
+ ].join(",");
9720
+ function json$3(body, init, extraHeaders) {
9721
+ const headers = new Headers({ "Content-Type": "application/json; charset=utf-8" });
9722
+ const initHeaders = init?.headers;
9723
+ if (initHeaders) try {
9724
+ new Headers(initHeaders).forEach((v, k) => headers.set(k, v));
9725
+ } catch {}
9726
+ if (extraHeaders) for (const [k, v] of Object.entries(extraHeaders)) headers.set(k, v);
9727
+ const { headers: _omit, ...rest } = init || {};
9728
+ return new Response(JSON.stringify(body), {
9729
+ status: 200,
9730
+ ...rest,
9731
+ headers
9732
+ });
9733
+ }
9734
+ function jsonFailure(status, code, message) {
9735
+ return json$3(failure$6(code, message), { status });
9736
+ }
9737
+ function withCors(headers, opts, request) {
9738
+ const pathname = request ? new URL(request.url).pathname : "";
9739
+ if (pathname === "/healthz" || pathname === "/readyz") {
9740
+ headers.set("Access-Control-Allow-Origin", "*");
9741
+ headers.delete("Access-Control-Allow-Credentials");
9742
+ headers.set("Access-Control-Allow-Methods", CORS_ALLOW_METHODS);
9743
+ headers.set("Access-Control-Allow-Headers", CORS_ALLOW_HEADERS);
9744
+ if (request?.method === "OPTIONS") headers.set("Access-Control-Max-Age", String(CORS_PREFLIGHT_MAX_AGE_SECONDS));
9745
+ return;
9746
+ }
9747
+ if (!opts?.corsOrigins) return;
9748
+ let allowedOrigin;
9749
+ const normalized = buildCorsOrigins(...opts.corsOrigins || []);
9750
+ if (normalized === "*") {
9751
+ allowedOrigin = "*";
9752
+ headers.set("Access-Control-Allow-Origin", "*");
9753
+ } else if (Array.isArray(normalized)) {
9754
+ const originRaw = String(request?.headers.get("Origin") || "").trim();
9755
+ const originNormalized = normalizeCorsOrigin(originRaw);
9756
+ if (originRaw && originNormalized && normalized.includes(originNormalized)) {
9757
+ allowedOrigin = originRaw;
9758
+ headers.set("Access-Control-Allow-Origin", originRaw);
9759
+ headers.append("Vary", "Origin");
9760
+ }
9761
+ }
9762
+ headers.set("Access-Control-Allow-Methods", CORS_ALLOW_METHODS);
9763
+ headers.set("Access-Control-Allow-Headers", CORS_ALLOW_HEADERS);
9764
+ if (allowedOrigin && request?.method === "OPTIONS") headers.set("Access-Control-Max-Age", String(CORS_PREFLIGHT_MAX_AGE_SECONDS));
9765
+ headers.set("Access-Control-Expose-Headers", "Server-Timing, X-Seams-Wallet-Region");
9766
+ if (allowedOrigin && allowedOrigin !== "*") headers.set("Access-Control-Allow-Credentials", "true");
9767
+ }
9768
+ async function readJson(request) {
9769
+ try {
9770
+ return await request.json();
9771
+ } catch {
9772
+ return null;
9773
+ }
9774
+ }
9775
+ function headersToRecord(headers) {
9776
+ const out = {};
9777
+ headers.forEach((v, k) => {
9778
+ out[k] = v;
9779
+ });
9780
+ return out;
9781
+ }
9782
+ //#endregion
9783
+ //#region src/router/transport/fetch/routes/externalAuth.ts
9784
+ const ACTIONS = [
9785
+ "associate",
9786
+ "unlink",
9787
+ "prepare",
9788
+ "prepare-association",
9789
+ "prepare-unlink",
9790
+ "owner-challenge",
9791
+ "exchange",
9792
+ "backend-exchange",
9793
+ "redeem",
9794
+ "settings",
9795
+ "logout"
9796
+ ];
9797
+ function parseIdentityProof(value) {
9798
+ const proof = requireRecord$1(value, "external identity proof");
9799
+ switch (proof.kind) {
9800
+ case "google": return {
9801
+ kind: "google",
9802
+ idToken: requireCanonicalString(proof.idToken, "idToken")
9803
+ };
9804
+ case "application": return {
9805
+ kind: "application",
9806
+ assertion: requireCanonicalString(proof.assertion, "assertion")
9807
+ };
9808
+ default: throw new Error("Unsupported external identity proof");
9809
+ }
9810
+ }
9811
+ function parseCommand(action, body) {
9812
+ const wallet = parseWalletId(body.walletId);
9813
+ if (!wallet.ok) throw new Error("Wallet ID is invalid");
9814
+ const walletId = wallet.value;
9815
+ if (action === "backend-exchange") return {
9816
+ action,
9817
+ walletId,
9818
+ attemptId: requireCanonicalString(body.attemptId, "attemptId"),
9819
+ proof: parseIdentityProof(body.proof)
9820
+ };
9821
+ const appOrigin = requireCanonicalString(body.appOrigin, "appOrigin");
9822
+ if (new URL(appOrigin).origin !== appOrigin) throw new Error("Application origin is invalid");
9823
+ switch (action) {
9824
+ case "associate":
9825
+ case "unlink": return {
9826
+ action,
9827
+ walletId,
9828
+ appOrigin,
9829
+ attemptId: requireCanonicalString(body.attemptId, "attemptId"),
9830
+ binding: secret(body.binding, "binding"),
9831
+ sessionToken: secret(body.sessionToken, "sessionToken"),
9832
+ code: secret(body.code, "code"),
9833
+ factorProof: body.factorProof
9834
+ };
9835
+ case "owner-challenge": return {
9836
+ action,
9837
+ walletId,
9838
+ appOrigin,
9839
+ attemptId: requireCanonicalString(body.attemptId, "attemptId"),
9840
+ binding: secret(body.binding, "binding"),
9841
+ sessionToken: secret(body.sessionToken, "sessionToken"),
9842
+ code: secret(body.code, "code")
9843
+ };
9844
+ case "prepare-unlink": return {
9845
+ action,
9846
+ walletId,
9847
+ appOrigin,
9848
+ associationId: requireCanonicalString(body.associationId, "associationId"),
9849
+ binding: secret(body.binding, "binding"),
9850
+ sessionToken: secret(body.sessionToken, "sessionToken")
9851
+ };
9852
+ case "prepare":
9853
+ case "prepare-association": return {
9854
+ action,
9855
+ walletId,
9856
+ appOrigin,
9857
+ binding: secret(body.binding, "binding"),
9858
+ sessionToken: secret(body.sessionToken, "sessionToken")
9859
+ };
9860
+ case "exchange": return {
9861
+ action,
9862
+ walletId,
9863
+ appOrigin,
9864
+ attemptId: requireCanonicalString(body.attemptId, "attemptId"),
8586
9865
  idToken: requireCanonicalString(body.idToken, "idToken")
8587
9866
  };
8588
9867
  case "redeem": return {
@@ -8671,11 +9950,10 @@ async function resolveApplicationTrust(ctx, service, command) {
8671
9950
  actorUserId: result.principal.apiKeyId,
8672
9951
  roles: []
8673
9952
  }, { status: "ACTIVE" }))?.find(matchesEnvironment.bind(null, scope.envId));
8674
- const configuration = parseExternalAuthConfiguration(environment?.externalAuth ?? {
9953
+ return parseExternalAuthConfiguration(environment?.externalAuth ?? {
8675
9954
  kind: "disabled",
8676
9955
  revision: 0
8677
9956
  });
8678
- return configuration.kind === "google" ? configuration : null;
8679
9957
  }
8680
9958
  function matchesEnvironment(environmentId, environment) {
8681
9959
  return environment.id === environmentId;
@@ -8702,43 +9980,118 @@ async function handleExternalAuth(ctx) {
8702
9980
  }
8703
9981
  async function dispatch(ctx, service, trust, command) {
8704
9982
  const walletId = command.walletId;
8705
- const originBinding = JSON.stringify([ctx.request.headers.get("origin") || "", command.appOrigin]);
8706
- if (command.action === "prepare" || command.action === "exchange" || command.action === "backend-exchange" || command.action === "associate") {
9983
+ if (command.action === "prepare" || command.action === "prepare-association" || command.action === "prepare-unlink" || command.action === "exchange" || command.action === "backend-exchange" || command.action === "associate") {
8707
9984
  const rateKey = await sha256HexUtf8(resolveSourceIpFromFetchHeaders(ctx.request.headers) || "unknown");
8708
9985
  if (!await service.store.consumeAuthenticationAllowance(rateKey, Date.now())) return failure$5(429, "rate_limited");
8709
9986
  }
8710
- if (command.action === "associate" || command.action === "unlink") {
8711
- const token = command.ownerCredential;
8712
- const owner = await ctx.service.authorizationSessions.readWalletSessionAuthorizationV2ByOperationCredential({
8713
- tenantId: ctx.service.authorizationSessions.tenantId,
8714
- token,
9987
+ if (command.action === "backend-exchange") {
9988
+ if (trust.kind === "disabled") return failure$5(403, "access_denied");
9989
+ return json$3(await service.exchange({
9990
+ walletId,
9991
+ attemptId: command.attemptId,
9992
+ proof: command.proof,
9993
+ trust
9994
+ }));
9995
+ }
9996
+ const originBinding = JSON.stringify([ctx.request.headers.get("origin") || "", command.appOrigin]);
9997
+ if (command.action === "prepare-unlink") return json$3(await service.prepareUnlink({
9998
+ walletId,
9999
+ associationId: command.associationId,
10000
+ originBinding,
10001
+ binding: command.binding,
10002
+ sessionToken: command.sessionToken,
10003
+ trustRevision: trust.revision
10004
+ }));
10005
+ if (command.action === "owner-challenge" || command.action === "associate" || command.action === "unlink") {
10006
+ const binding = {
10007
+ walletId,
10008
+ attemptId: command.attemptId,
10009
+ originBinding,
10010
+ bindingHash: await sha256HexUtf8(command.binding),
10011
+ sessionTokenHash: await sha256HexUtf8(command.sessionToken),
10012
+ codeHash: await sha256HexUtf8(command.code),
10013
+ trustRevision: trust.revision,
8715
10014
  nowMs: Date.now()
10015
+ };
10016
+ const result = await readExternalOwnerOperation({
10017
+ store: service.store,
10018
+ binding,
10019
+ requestOrigin: ctx.request.headers.get("origin") || "",
10020
+ phase: command.action === "owner-challenge" ? "prepare" : "complete"
8716
10021
  });
8717
- if (!owner || owner.authorization.session.walletId !== walletId || !sameDelegatedWalletAuthorityV1(buildDelegatedWalletAuthorityV1({ permissions: owner.authority.permissions }), buildDelegatedWalletAuthorityV1({ permissions: buildFullOwnerPermissionsV1() }))) return failure$5(403, "access_denied");
8718
- if (command.action === "unlink") {
8719
- await service.store.revokeAssociation(walletId, command.associationId);
8720
- return json$3({ ok: true });
10022
+ if (!result.ok) return json$3(result, { status: 403 });
10023
+ const ownerOperation = result.value;
10024
+ if (command.action === "owner-challenge") return json$3({
10025
+ ok: true,
10026
+ value: externalOwnerChallenge(ownerOperation)
10027
+ });
10028
+ if (command.action !== ownerOperation.attempt.purpose) return failure$5(403, "access_denied");
10029
+ if (ownerOperation.attempt.status === "consumed") {
10030
+ const stored = await ctx.service.authorizedOperations.readAuthorizedOperationById({
10031
+ tenantId: ctx.service.authorizedOperations.tenantId,
10032
+ authorizedOperationId: walletOwnerOperationAuthorizationId(ownerOperation.operation, ownerOperation.challengeB64u)
10033
+ });
10034
+ if (!stored || stored.authorization.kind !== "verified_step_up" || stored.quota.kind !== "quota_neutral" || stored.lifecycle !== "completed") return failure$5(503, "unavailable");
10035
+ return new Response(stored.response.bodyText, {
10036
+ status: stored.response.status,
10037
+ headers: {
10038
+ "Content-Type": stored.response.contentType,
10039
+ "Cache-Control": "no-store"
10040
+ }
10041
+ });
8721
10042
  }
8722
- const proof = await service.verifyGoogle(trust, command.idToken);
8723
- if (!proof.ok) return json$3(proof, { status: 403 });
8724
- return await service.store.associate({
10043
+ const authorized = await authorizeWalletOwnerOperation({
10044
+ ctx,
10045
+ walletId,
10046
+ operation: ownerOperation.operation,
10047
+ payload: ownerOperation.payload,
10048
+ factorProof: command.factorProof
10049
+ });
10050
+ if (!authorized.ok) return authorized.response;
10051
+ if (!await service.store.consumeOwnerAttempt({
10052
+ walletId,
10053
+ attemptId: binding.attemptId,
10054
+ originBinding,
10055
+ bindingHash: binding.bindingHash,
10056
+ sessionTokenHash: binding.sessionTokenHash,
10057
+ codeHash: binding.codeHash,
10058
+ trustRevision: binding.trustRevision,
10059
+ nowMs: Date.now()
10060
+ }, authorized.ownerAuthority)) return completeWalletOwnerOperation(ctx, authorized.operation, 403, externalAuthFailure("access_denied"), "failed_before_side_effect");
10061
+ const attempt = ownerOperation.attempt;
10062
+ if (attempt.purpose === "associate") {
10063
+ if (!await service.store.associate({
10064
+ walletId,
10065
+ identity: attempt.identity,
10066
+ authorityId: authorized.ownerAuthority.authorityId,
10067
+ authorityEpoch: authorized.ownerAuthority.revocationEpoch
10068
+ })) return completeWalletOwnerOperation(ctx, authorized.operation, 403, externalAuthFailure("access_denied"), "failed_before_side_effect");
10069
+ } else await service.store.revokeAssociation({
8725
10070
  walletId,
8726
- subject: proof.value.subject,
8727
- authorityId: owner.authority.authorityId,
8728
- authorityEpoch: owner.authority.revocationEpoch
8729
- }) ? json$3({ ok: true }) : failure$5(409, "access_denied");
10071
+ associationId: attempt.associationId,
10072
+ owner: authorized.ownerAuthority
10073
+ });
10074
+ return completeWalletOwnerOperation(ctx, authorized.operation, 200, {
10075
+ ok: true,
10076
+ value: null
10077
+ });
8730
10078
  }
8731
- if (command.action === "prepare") return json$3(await service.prepare({
10079
+ if (trust.kind === "disabled") return failure$5(403, "access_denied");
10080
+ if (command.action === "prepare" || command.action === "prepare-association") return json$3(await service.prepare({
10081
+ purpose: command.action === "prepare" ? "authenticate" : "associate",
8732
10082
  walletId,
8733
10083
  originBinding,
8734
10084
  binding: command.binding,
8735
10085
  sessionToken: command.sessionToken
8736
10086
  }));
8737
- if (command.action === "exchange" || command.action === "backend-exchange") return json$3(await service.exchange({
10087
+ if (command.action === "exchange") return json$3(await service.exchange({
8738
10088
  trust,
8739
10089
  walletId,
8740
10090
  attemptId: command.attemptId,
8741
- idToken: command.idToken
10091
+ proof: {
10092
+ kind: "google",
10093
+ idToken: command.idToken
10094
+ }
8742
10095
  }));
8743
10096
  const tokenHash = await sha256HexUtf8(command.sessionToken);
8744
10097
  if (command.action === "logout") {
@@ -10517,482 +11870,6 @@ async function computeOwnerLaneParticipantBindingDigestV1(input) {
10517
11870
  return requireParsed$4(parseLaneParticipantBindingDigestB64u(base64UrlEncode(await sha256Bytes(ownerLaneParticipantContinuityCanonicalBytesV1(input)))), "owner participant digest");
10518
11871
  }
10519
11872
  //#endregion
10520
- //#region ../shared-ts/src/authorization/operationFingerprint.ts
10521
- const CAPABILITY_OPERATION_FINGERPRINT_DOMAIN_V1 = "seams:authorization:capability-operation-fingerprint:v1";
10522
- var CapabilityOperationEnvelopeProof = class {
10523
- tenantId;
10524
- principalId;
10525
- capabilityId;
10526
- operationId;
10527
- operation;
10528
- digests;
10529
- retainProof() {
10530
- return true;
10531
- }
10532
- constructor(fields) {
10533
- this.retainProof();
10534
- this.tenantId = fields.tenantId;
10535
- this.principalId = fields.principalId;
10536
- this.capabilityId = fields.capabilityId;
10537
- this.operationId = fields.operationId;
10538
- this.operation = fields.operation;
10539
- this.digests = fields.digests;
10540
- }
10541
- };
10542
- function buildCapabilityOperationEnvelope(fields) {
10543
- return new CapabilityOperationEnvelopeProof({
10544
- tenantId: fields.tenantId,
10545
- principalId: fields.principalId,
10546
- capabilityId: fields.capabilityId,
10547
- operationId: fields.operationId,
10548
- operation: fields.operation,
10549
- digests: {
10550
- laneDigest: fields.digests.laneDigest,
10551
- intentDigest: fields.digests.intentDigest,
10552
- displayDigest: fields.digests.displayDigest
10553
- }
10554
- });
10555
- }
10556
- function canonicalCapabilityOperationFingerprintPreimageV1(envelope) {
10557
- return `${CAPABILITY_OPERATION_FINGERPRINT_DOMAIN_V1}|${alphabetizeStringify({
10558
- tenantId: envelope.tenantId,
10559
- principalId: envelope.principalId,
10560
- capabilityId: envelope.capabilityId,
10561
- operationId: envelope.operationId,
10562
- operation: envelope.operation,
10563
- digests: envelope.digests
10564
- })}`;
10565
- }
10566
- async function computeCapabilityOperationFingerprintDigest(envelope) {
10567
- return parseCapabilityOperationFingerprintDigest(base64UrlEncode(await sha256BytesUtf8(canonicalCapabilityOperationFingerprintPreimageV1(envelope))));
10568
- }
10569
- function parseCapabilityOperationFingerprintDigest(value) {
10570
- return parseDigestB64u(value);
10571
- }
10572
- function parseSigningOperationFingerprintDigest(value) {
10573
- if (typeof value !== "string" || !value.startsWith("sha256:")) throw new Error("signing operation fingerprint must use the sha256: prefix");
10574
- return parseDigestB64u(value.slice(7));
10575
- }
10576
- //#endregion
10577
- //#region ../shared-ts/src/utils/base58.ts
10578
- /**
10579
- * Encodes binary data to base58 string using Bitcoin's base58 alphabet.
10580
- *
10581
- * @param data - Binary data to encode (Uint8Array, ArrayBuffer, or number[])
10582
- * @returns Base58-encoded string
10583
- */
10584
- const base58Encode = (data) => {
10585
- if (data instanceof ArrayBuffer) return bs58.encode(new Uint8Array(data));
10586
- if (Array.isArray(data)) return bs58.encode(new Uint8Array(data));
10587
- return bs58.encode(data);
10588
- };
10589
- /**
10590
- * Decodes a base58 string to binary data using Bitcoin's base58 alphabet.
10591
- * Returns a Uint8Array containing the decoded bytes.
10592
- *
10593
- * @param base58String - Base58-encoded string to decode
10594
- * @returns Uint8Array containing the decoded bytes
10595
- * @throws Error if input contains invalid base58 characters
10596
- */
10597
- const base58Decode = (base58String) => {
10598
- return bs58.decode(base58String);
10599
- };
10600
- //#endregion
10601
- //#region src/authorization/domain.ts
10602
- function buildExactWalletSessionQuotaProjectionV1(fields) {
10603
- requireNonnegativeInteger(fields.remainingUses, "Wallet Session quota remaining uses");
10604
- requirePositiveTime(fields.expiresAtMs, "Wallet Session quota expiry");
10605
- if (fields.lifecycle === "exhausted" !== (fields.remainingUses === 0)) throw new Error("Wallet Session quota lifecycle disagrees with its remaining uses");
10606
- return {
10607
- kind: "exact_wallet_session_quota_projection_v1",
10608
- lifecycle: fields.lifecycle,
10609
- tenantId: fields.tenantId,
10610
- principalId: fields.principalId,
10611
- walletSessionId: fields.walletSessionId,
10612
- quotaId: fields.quotaId,
10613
- remainingUses: fields.remainingUses,
10614
- expiresAtMs: fields.expiresAtMs
10615
- };
10616
- }
10617
- /**
10618
- * Every capability subject must resolve through the authoritative active
10619
- * authority that the authorization names. A signing or export subject resolves
10620
- * only when its key family is activated and names the same material the
10621
- * authority currently holds; an administration subject resolves only when it
10622
- * names that same authority. Anything else is a capability the caller can no
10623
- * longer exercise, not a corrupt row.
10624
- */
10625
- function exactWalletSessionCapabilitySubjectsResolveAuthority(input) {
10626
- return input.session.capabilitySubjects.every((subject) => {
10627
- switch (subject.kind) {
10628
- case "sign":
10629
- case "export_keys": {
10630
- const activation = subject.keyFamily === "ed25519" ? input.signerActivations.ed25519 : input.signerActivations.ecdsa;
10631
- return activation !== void 0 && mpcMaterialActivationRefsEqual(activation.materialActivation, subject.materialActivation);
10632
- }
10633
- case "link_devices":
10634
- case "revoke_devices": return subject.authorityId === input.session.authorityId;
10635
- default: return false;
10636
- }
10637
- });
10638
- }
10639
- /**
10640
- * The digest-free authorization projection published to the browser. It carries
10641
- * the exact wallet, authority, method, capability, and lifetime identity a
10642
- * reader needs to reconcile its own record, and never the credential digest.
10643
- */
10644
- function projectExactWalletSessionAuthorizationV1(session) {
10645
- const capabilitySubjects = session.capabilitySubjects.map((subject) => {
10646
- switch (subject.kind) {
10647
- case "sign":
10648
- case "export_keys": return {
10649
- kind: subject.kind,
10650
- keyFamily: subject.keyFamily,
10651
- materialActivation: subject.materialActivation
10652
- };
10653
- case "link_devices":
10654
- case "revoke_devices": return { kind: subject.kind };
10655
- default: throw new Error("Issued linked-device Wallet Session subject is invalid");
10656
- }
10657
- });
10658
- const first = capabilitySubjects[0];
10659
- if (!first) throw new Error("Issued linked-device Wallet Session has no subjects");
10660
- return parseActiveWalletSessionV1({
10661
- kind: "active_wallet_session_v1",
10662
- walletId: session.walletId,
10663
- authorityId: session.authorityId,
10664
- authMethodId: session.walletAuthMethodId,
10665
- authorizationId: session.authorizationId,
10666
- quotaId: session.quotaId,
10667
- authorityDigestB64u: session.authorityDigestB64u,
10668
- authorityRevocationEpoch: session.authorityRevocationEpoch,
10669
- capabilitySubjects: [first, ...capabilitySubjects.slice(1)],
10670
- issuedAtMs: session.createdAtMs,
10671
- expiresAtMs: session.expiresAtMs
10672
- });
10673
- }
10674
- function projectActiveWalletSession(issued) {
10675
- if (issued.quota.quotaId !== issued.session.quotaId) throw new Error("Issued Wallet Session quota does not belong to its authorization");
10676
- return projectExactWalletSessionAuthorizationV1(issued.session);
10677
- }
10678
- function appendWalletSessionSignerSubjects(subjects, kind, signerActivations) {
10679
- if (signerActivations.keyFamilies.length === 1 && signerActivations.keyFamilies[0] === "ed25519") {
10680
- if (!signerActivations.ed25519) throw new Error("Ed25519 signer activation is missing");
10681
- subjects.push({
10682
- kind,
10683
- keyFamily: "ed25519",
10684
- materialActivation: signerActivations.ed25519.materialActivation
10685
- });
10686
- return;
10687
- }
10688
- if (signerActivations.keyFamilies.length === 1 && signerActivations.keyFamilies[0] === "ecdsa_secp256k1") {
10689
- if (!signerActivations.ecdsa) throw new Error("ECDSA signer activation is missing");
10690
- subjects.push({
10691
- kind,
10692
- keyFamily: "ecdsa_secp256k1",
10693
- materialActivation: signerActivations.ecdsa.materialActivation
10694
- });
10695
- return;
10696
- }
10697
- if (!signerActivations.ed25519 || !signerActivations.ecdsa) throw new Error("both-family signer activations are incomplete");
10698
- subjects.push({
10699
- kind,
10700
- keyFamily: "ed25519",
10701
- materialActivation: signerActivations.ed25519.materialActivation
10702
- }, {
10703
- kind,
10704
- keyFamily: "ecdsa_secp256k1",
10705
- materialActivation: signerActivations.ecdsa.materialActivation
10706
- });
10707
- }
10708
- function buildWalletSessionCapabilitySubjectsV1(authority) {
10709
- const subjects = [];
10710
- if (authority.permissions.includes("sign")) appendWalletSessionSignerSubjects(subjects, "sign", authority.signerActivations);
10711
- if (authority.permissions.includes("export_keys")) appendWalletSessionSignerSubjects(subjects, "export_keys", authority.signerActivations);
10712
- if (authority.permissions.includes("link_devices")) subjects.push({
10713
- kind: "link_devices",
10714
- authorityId: authority.authorityId
10715
- });
10716
- if (authority.permissions.includes("revoke_devices")) subjects.push({
10717
- kind: "revoke_devices",
10718
- authorityId: authority.authorityId
10719
- });
10720
- const [firstSubject, ...remainingSubjects] = subjects;
10721
- if (!firstSubject) throw new Error("active wallet authority must produce capability subjects");
10722
- return [firstSubject, ...remainingSubjects];
10723
- }
10724
- function buildWalletSessionAuthorizationV2(fields) {
10725
- requireNonnegativeInteger(fields.authorityRevocationEpoch, "Wallet Session authority revocation epoch");
10726
- parseDigestB64u(fields.authorityDigestB64u);
10727
- if (fields.capabilitySubjects.length === 0) throw new Error("Wallet Session capability subjects are required");
10728
- requireOrderedTimes(fields.createdAtMs, fields.expiresAtMs, "Wallet Session authorization");
10729
- const identityValues = [
10730
- String(fields.authorizationId),
10731
- String(fields.walletSessionId),
10732
- String(fields.quotaId)
10733
- ];
10734
- if (new Set(identityValues).size !== identityValues.length) throw new Error("Wallet Session authorization identities must be pairwise distinct");
10735
- return {
10736
- kind: "wallet_session_authorization_v2",
10737
- tenantId: fields.tenantId,
10738
- principalId: fields.principalId,
10739
- walletId: fields.walletId,
10740
- authorityId: fields.authorityId,
10741
- walletAuthMethodId: fields.walletAuthMethodId,
10742
- authorityDigestB64u: fields.authorityDigestB64u,
10743
- authorityRevocationEpoch: fields.authorityRevocationEpoch,
10744
- mintId: fields.mintId,
10745
- authorizationId: fields.authorizationId,
10746
- walletSessionId: fields.walletSessionId,
10747
- quotaId: fields.quotaId,
10748
- capabilitySubjects: fields.capabilitySubjects,
10749
- createdAtMs: fields.createdAtMs,
10750
- expiresAtMs: fields.expiresAtMs
10751
- };
10752
- }
10753
- function walletSessionCapabilitySubjectsV1Equal(left, right) {
10754
- if (left.length !== right.length) return false;
10755
- for (let index = 0; index < left.length; index += 1) {
10756
- const leftSubject = left[index];
10757
- const rightSubject = right[index];
10758
- if (!leftSubject || !rightSubject || leftSubject.kind !== rightSubject.kind) return false;
10759
- switch (leftSubject.kind) {
10760
- case "sign":
10761
- case "export_keys": break;
10762
- case "link_devices":
10763
- case "revoke_devices":
10764
- if (rightSubject.kind !== leftSubject.kind || leftSubject.authorityId !== rightSubject.authorityId) return false;
10765
- break;
10766
- }
10767
- if (leftSubject.kind === "sign" || leftSubject.kind === "export_keys") {
10768
- if (rightSubject.kind !== leftSubject.kind || leftSubject.keyFamily !== rightSubject.keyFamily || !mpcMaterialActivationRefsEqual(leftSubject.materialActivation, rightSubject.materialActivation)) return false;
10769
- }
10770
- }
10771
- return true;
10772
- }
10773
- function walletSessionAuthorizationV2RecordsEqual(left, right) {
10774
- return left.kind === right.kind && left.tenantId === right.tenantId && left.principalId === right.principalId && left.walletId === right.walletId && left.authorityId === right.authorityId && left.walletAuthMethodId === right.walletAuthMethodId && left.authorityDigestB64u === right.authorityDigestB64u && left.authorityRevocationEpoch === right.authorityRevocationEpoch && left.mintId === right.mintId && left.authorizationId === right.authorizationId && left.walletSessionId === right.walletSessionId && left.quotaId === right.quotaId && walletSessionCapabilitySubjectsV1Equal(left.capabilitySubjects, right.capabilitySubjects) && left.createdAtMs === right.createdAtMs && left.expiresAtMs === right.expiresAtMs;
10775
- }
10776
- function parseMpcMaterialActivationRefRequired(value) {
10777
- const parsed = parseMpcMaterialActivationRef(value);
10778
- if (!parsed.ok) throw new Error(parsed.error.message);
10779
- return parsed.value;
10780
- }
10781
- function parseWalletAuthorityIdRequired(value) {
10782
- const parsed = parseWalletAuthorityId(value);
10783
- if (!parsed.ok) throw new Error(parsed.error.message);
10784
- return parsed.value;
10785
- }
10786
- function parseWalletAuthMethodIdRequired(value) {
10787
- const parsed = parseWalletAuthMethodId(value);
10788
- if (!parsed.ok) throw new Error(parsed.error.message);
10789
- return parsed.value;
10790
- }
10791
- function parseWalletSessionCapabilitySubject(value) {
10792
- if (!isWalletSessionCapabilitySubjectRecordV1(value) || typeof value.kind !== "string") throw new Error("Wallet Session capability subject must be an object");
10793
- switch (value.kind) {
10794
- case "sign":
10795
- case "export_keys":
10796
- if (Object.keys(value).sort().join("|") !== "keyFamily|kind|materialActivation") throw new Error("Wallet Session signer capability subject contains unexpected fields");
10797
- if (value.keyFamily !== "ed25519" && value.keyFamily !== "ecdsa_secp256k1") throw new Error("Wallet Session signer capability subject key family is invalid");
10798
- return {
10799
- kind: value.kind,
10800
- keyFamily: value.keyFamily,
10801
- materialActivation: parseMpcMaterialActivationRefRequired(value.materialActivation)
10802
- };
10803
- case "link_devices":
10804
- case "revoke_devices":
10805
- if (Object.keys(value).sort().join("|") !== "authorityId|kind") throw new Error("Wallet Session administration capability subject contains unexpected fields");
10806
- return {
10807
- kind: value.kind,
10808
- authorityId: parseWalletAuthorityIdRequired(value.authorityId)
10809
- };
10810
- default: throw new Error("Wallet Session capability subject kind is unsupported");
10811
- }
10812
- }
10813
- function isWalletSessionCapabilitySubjectRecordV1(value) {
10814
- return value !== null && typeof value === "object" && !Array.isArray(value);
10815
- }
10816
- function parseWalletSessionCapabilitySubjects(value) {
10817
- if (!Array.isArray(value) || value.length === 0) throw new Error("Wallet Session capability subjects must be non-empty");
10818
- const subjects = [];
10819
- for (const subject of value) subjects.push(parseWalletSessionCapabilitySubject(subject));
10820
- const [firstSubject, ...remainingSubjects] = subjects;
10821
- if (!firstSubject) throw new Error("Wallet Session capability subjects must be non-empty");
10822
- return [firstSubject, ...remainingSubjects];
10823
- }
10824
- function parseWalletSessionAuthorizationV2(value) {
10825
- if (!isWalletSessionAuthorizationRecordV2(value)) throw new Error("Wallet Session authorization V2 contains unexpected fields");
10826
- if (value.kind !== "wallet_session_authorization_v2") throw new Error("Wallet Session authorization V2 kind is invalid");
10827
- const tenantId = parseTenantIdRequired(value.tenantId);
10828
- const principalId = parsePrincipalIdRequired(value.principalId);
10829
- const walletId = parseWalletIdRequired(value.walletId);
10830
- const authorityId = parseWalletAuthorityIdRequired(value.authorityId);
10831
- const walletAuthMethodId = parseWalletAuthMethodIdRequired(value.walletAuthMethodId);
10832
- const authorityDigestB64u = parseDigestB64u(value.authorityDigestB64u);
10833
- const mintIdResult = parseWalletSessionMintId(value.mintId);
10834
- if (!mintIdResult.ok) throw new Error(mintIdResult.error.message);
10835
- const authorizationIdResult = parseWalletSessionAuthorizationId(value.authorizationId);
10836
- if (!authorizationIdResult.ok) throw new Error(authorizationIdResult.error.message);
10837
- const walletSessionId = parseWalletSessionIdRequired(value.walletSessionId);
10838
- const quotaId = parseMpcWalletSigningQuotaIdRequired(value.quotaId);
10839
- return buildWalletSessionAuthorizationV2({
10840
- tenantId,
10841
- principalId,
10842
- walletId,
10843
- authorityId,
10844
- walletAuthMethodId,
10845
- authorityDigestB64u,
10846
- authorityRevocationEpoch: requireNonnegativeInteger(value.authorityRevocationEpoch, "Wallet Session authority revocation epoch"),
10847
- mintId: mintIdResult.value,
10848
- authorizationId: authorizationIdResult.value,
10849
- walletSessionId,
10850
- quotaId,
10851
- capabilitySubjects: parseWalletSessionCapabilitySubjects(value.capabilitySubjects),
10852
- createdAtMs: requirePositiveTimestamp(value.createdAtMs, "Wallet Session createdAtMs"),
10853
- expiresAtMs: requirePositiveTimestamp(value.expiresAtMs, "Wallet Session expiresAtMs")
10854
- });
10855
- }
10856
- function isWalletSessionAuthorizationRecordV2(value) {
10857
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
10858
- return Object.keys(value).sort().join("|") === "authorityDigestB64u|authorityId|authorityRevocationEpoch|authorizationId|capabilitySubjects|createdAtMs|expiresAtMs|kind|mintId|principalId|quotaId|tenantId|walletAuthMethodId|walletId|walletSessionId";
10859
- }
10860
- async function buildAuthorizedOperation(input) {
10861
- if (input.tenantId !== input.operation.tenantId) throw new Error("authorized operation tenant must match its operation envelope");
10862
- const operationFingerprintDigest = await computeCapabilityOperationFingerprintDigest(input.operation);
10863
- return {
10864
- ...input,
10865
- kind: "authorized_operation",
10866
- operationFingerprintDigest,
10867
- lifecycle: "claimed"
10868
- };
10869
- }
10870
- const AUTHORIZED_OPERATION_RESULT_DIGEST_DOMAIN_V1 = "seams:authorization:authorized-operation-result:v1";
10871
- function parseAuthorizedOperationReplayResponse(value) {
10872
- if (!isAuthorizedOperationReplayResponseRecordV1(value)) throw new Error("authorized operation replay response must be an object");
10873
- const keys = Object.keys(value).sort();
10874
- if (keys.length !== 3 || keys.join("|") !== "bodyText|contentType|status") throw new Error("authorized operation replay response must contain only status, contentType, and bodyText");
10875
- const status = value.status;
10876
- if (typeof status !== "number" || !Number.isSafeInteger(status) || status < 200 || status > 599) throw new Error("authorized operation replay response status must be an HTTP status");
10877
- const contentType = value.contentType;
10878
- if (typeof contentType !== "string" || contentType.length === 0 || contentType.trim() !== contentType || contentType.length > 255 || /[\u0000-\u001f\u007f]/.test(contentType)) throw new Error("authorized operation replay response contentType is invalid");
10879
- const bodyText = value.bodyText;
10880
- if (typeof bodyText !== "string") throw new Error("authorized operation replay response bodyText must be text");
10881
- if (new TextEncoder().encode(bodyText).byteLength > 65536) throw new Error("authorized operation replay response bodyText is too large");
10882
- if (responseStatusHasNoBody(status) && bodyText.length !== 0) throw new Error("authorized operation replay response status cannot carry a body");
10883
- return {
10884
- status,
10885
- contentType,
10886
- bodyText
10887
- };
10888
- }
10889
- function authorizedOperationReplayBodyInit(response) {
10890
- const parsed = parseAuthorizedOperationReplayResponse(response);
10891
- return responseStatusHasNoBody(parsed.status) ? null : parsed.bodyText;
10892
- }
10893
- function responseStatusHasNoBody(status) {
10894
- return status === 204 || status === 205 || status === 304;
10895
- }
10896
- function isAuthorizedOperationReplayResponseRecordV1(value) {
10897
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
10898
- return Object.keys(value).sort().join("|") === "bodyText|contentType|status";
10899
- }
10900
- async function computeAuthorizedOperationResultDigest(response) {
10901
- return parseDigestB64u(base64UrlEncode(await sha256BytesUtf8(`${AUTHORIZED_OPERATION_RESULT_DIGEST_DOMAIN_V1}|${alphabetizeStringify(parseAuthorizedOperationReplayResponse(response))}`)));
10902
- }
10903
- function parseHostedWalletSeamsSessionExchangeCode(value) {
10904
- return parseAuthorizationDomainId(value, "hostedWalletSessionExchangeCode");
10905
- }
10906
- function parseVerifiedOwnerProofId(value) {
10907
- return parseAuthorizationDomainId(value, "verifiedOwnerProofId");
10908
- }
10909
- function parseHostedWalletSeamsSessionExchangeNonce(value) {
10910
- return parseAuthorizationDomainId(value, "hostedWalletSessionExchangeNonce");
10911
- }
10912
- function parseHostedWalletSessionCredentialId(value) {
10913
- return parseAuthorizationDomainId(value, "hostedWalletSessionCredentialId");
10914
- }
10915
- function parsePrimaryWalletSessionOperationCredentialToken(value) {
10916
- if (typeof value !== "string" || !/^wst_[A-Za-z0-9_-]{43}$/.test(value)) throw new Error("primary Wallet Session credential is invalid");
10917
- return parseAuthorizationDomainId(value, "primaryWalletSessionOperationCredentialToken");
10918
- }
10919
- function parseHostedWalletSessionOperationCredentialToken(value) {
10920
- if (typeof value !== "string" || !/^wsh_[A-Za-z0-9_-]{43}$/.test(value)) throw new Error("HostedWalletSessionOperationCredentialV1.token is invalid");
10921
- return parseAuthorizationDomainId(value, "hostedWalletSessionOperationCredentialToken");
10922
- }
10923
- function parseSessionOrigin(value) {
10924
- if (typeof value !== "string" || value.trim() !== value) throw new Error("session origin must be a canonical HTTP origin");
10925
- let url;
10926
- try {
10927
- url = new URL(value);
10928
- } catch {
10929
- throw new Error("session origin must be a canonical HTTP origin");
10930
- }
10931
- if (url.protocol !== "https:" && url.protocol !== "http:" || url.origin !== value || url.pathname !== "/" || url.search || url.hash) throw new Error("session origin must be a canonical HTTP origin");
10932
- return value;
10933
- }
10934
- function buildActiveWalletSessionQuota(fields) {
10935
- requirePositiveCount(fields.remainingUses, "Wallet Session quota remaining uses");
10936
- requirePositiveTime(fields.expiresAtMs, "Wallet Session quota expiry");
10937
- return {
10938
- kind: "active_wallet_session_quota",
10939
- tenantId: fields.tenantId,
10940
- principalId: fields.principalId,
10941
- walletSessionId: fields.walletSessionId,
10942
- quotaId: fields.quotaId,
10943
- remainingUses: fields.remainingUses,
10944
- expiresAtMs: fields.expiresAtMs
10945
- };
10946
- }
10947
- function parseAuthorizationDomainId(value, fieldName) {
10948
- if (typeof value !== "string" || value.trim() !== value || value.length === 0 || value.length > 512 || /[\s\u0000-\u001f\u007f]/.test(value)) throw new Error(`${fieldName} must be a compact opaque identifier`);
10949
- return value;
10950
- }
10951
- function parseTenantIdRequired(value) {
10952
- const parsed = parseTenantId(value);
10953
- if (!parsed.ok) throw new Error(`linked-device authorization tenantId: ${parsed.error.message}`);
10954
- return parsed.value;
10955
- }
10956
- function parsePrincipalIdRequired(value) {
10957
- const parsed = parsePrincipalId(value);
10958
- if (!parsed.ok) throw new Error(`linked-device authorization principalId: ${parsed.error.message}`);
10959
- return parsed.value;
10960
- }
10961
- function parseWalletIdRequired(value) {
10962
- const parsed = parseWalletId(value);
10963
- if (!parsed.ok) throw new Error(`linked-device authorization walletId: ${parsed.error.message}`);
10964
- return parsed.value;
10965
- }
10966
- function parseWalletSessionIdRequired(value) {
10967
- const parsed = parseWalletSessionId(value);
10968
- if (!parsed.ok) throw new Error(`linked-device authorization walletSessionId: ${parsed.error.message}`);
10969
- return parsed.value;
10970
- }
10971
- function parseMpcWalletSigningQuotaIdRequired(value) {
10972
- const parsed = parseMpcWalletSigningQuotaId(value);
10973
- if (!parsed.ok) throw new Error(`linked-device authorization quotaId: ${parsed.error.message}`);
10974
- return parsed.value;
10975
- }
10976
- function requirePositiveCount(value, label) {
10977
- if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`);
10978
- }
10979
- function requireNonnegativeInteger(value, label) {
10980
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error(`${label} must be a nonnegative safe integer`);
10981
- return value;
10982
- }
10983
- function requirePositiveTimestamp(value, label) {
10984
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`);
10985
- return value;
10986
- }
10987
- function requirePositiveTime(value, label) {
10988
- if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive timestamp`);
10989
- }
10990
- function requireOrderedTimes(createdAtMs, expiresAtMs, label) {
10991
- requirePositiveTime(createdAtMs, `${label} creation`);
10992
- requirePositiveTime(expiresAtMs, `${label} expiry`);
10993
- if (expiresAtMs <= createdAtMs) throw new Error(`${label} expiry must follow creation`);
10994
- }
10995
- //#endregion
10996
11873
  //#region src/router/domains/signingOperations/walletExecutionAdmission.ts
10997
11874
  function validateWalletSessionAuthorizationV2Context(input) {
10998
11875
  if (!isPositiveSafeInteger(input.nowMs)) return "invalid_time";
@@ -11785,32 +12662,6 @@ function parseRegistrationPreparedSessionSeal(raw, service) {
11785
12662
  };
11786
12663
  }
11787
12664
  //#endregion
11788
- //#region src/router/framework/routeResponses.ts
11789
- function routeJson(status, body, options) {
11790
- return {
11791
- status,
11792
- body,
11793
- ...options?.headers ? { headers: options.headers } : {},
11794
- ...options?.usage ? { usage: options.usage } : {}
11795
- };
11796
- }
11797
- function routeError(status, code, message) {
11798
- return routeJson(status, {
11799
- ok: false,
11800
- code,
11801
- message
11802
- });
11803
- }
11804
- function toFetchRouteResponse(response) {
11805
- return new Response(JSON.stringify(response.body), {
11806
- status: response.status,
11807
- headers: {
11808
- "Content-Type": "application/json; charset=utf-8",
11809
- ...response.headers || {}
11810
- }
11811
- });
11812
- }
11813
- //#endregion
11814
12665
  //#region ../shared-ts/src/utils/registrationIds.ts
11815
12666
  function walletIdFromString(value) {
11816
12667
  const parsed = parseWalletId(value);
@@ -13246,12 +14097,19 @@ function parseWalletRegistrationSetupRequest(body) {
13246
14097
  code: "invalid_body",
13247
14098
  message: "authMethod is required"
13248
14099
  };
14100
+ const completion = body.completion ?? "wallet_session";
14101
+ if (completion !== "wallet_session" && completion !== "owner_operations") return {
14102
+ ok: false,
14103
+ code: "invalid_body",
14104
+ message: "Invalid registration completion"
14105
+ };
13249
14106
  const wallet = parseWalletRegistrationSetupWallet(body.wallet);
13250
14107
  if (!wallet.ok) return wallet;
13251
14108
  return {
13252
14109
  ok: true,
13253
14110
  value: {
13254
14111
  registrationOperationId,
14112
+ completion,
13255
14113
  ...wallet.value ? { wallet: wallet.value } : {},
13256
14114
  signerSelection: signerSelection.value.selection,
13257
14115
  authMethod
@@ -13321,7 +14179,7 @@ function parseInventoryKeyTargets(raw) {
13321
14179
  value: targets
13322
14180
  };
13323
14181
  }
13324
- function parseWebAuthnAuthenticationCredential$2(raw) {
14182
+ function parseWebAuthnAuthenticationCredential$1(raw) {
13325
14183
  if (!isPlainObject(raw)) return {
13326
14184
  ok: false,
13327
14185
  code: "invalid_body",
@@ -13400,7 +14258,7 @@ async function parseWalletEcdsaInventoryBody(body, walletId) {
13400
14258
  message: "auth is required"
13401
14259
  };
13402
14260
  if (auth.kind === "webauthn_assertion") {
13403
- const credential = parseWebAuthnAuthenticationCredential$2(auth.credential);
14261
+ const credential = parseWebAuthnAuthenticationCredential$1(auth.credential);
13404
14262
  if (!credential.ok) return credential;
13405
14263
  const expectedChallengeDigestB64u = typeof auth.expectedChallengeDigestB64u === "string" ? auth.expectedChallengeDigestB64u.trim() : "";
13406
14264
  const serverNonceB64u = typeof auth.serverNonceB64u === "string" ? auth.serverNonceB64u.trim() : "";
@@ -13634,7 +14492,7 @@ async function parseWalletAddSignerStartBody(body, walletId) {
13634
14492
  code: "invalid_body",
13635
14493
  message: authRpId.error.message
13636
14494
  };
13637
- const credential = parseWebAuthnAuthenticationCredential$2(auth.credential);
14495
+ const credential = parseWebAuthnAuthenticationCredential$1(auth.credential);
13638
14496
  if (!credential.ok) return credential;
13639
14497
  const expectedChallengeDigestB64u = typeof auth.expectedChallengeDigestB64u === "string" ? auth.expectedChallengeDigestB64u.trim() : "";
13640
14498
  if (expectedChallengeDigestB64u !== expectedDigest) return {
@@ -13745,7 +14603,7 @@ async function parseWalletAddAuthMethodStartBody(body, walletId) {
13745
14603
  code: "invalid_body",
13746
14604
  message: authRpId.error.message
13747
14605
  };
13748
- const credential = parseWebAuthnAuthenticationCredential$2(auth.credential);
14606
+ const credential = parseWebAuthnAuthenticationCredential$1(auth.credential);
13749
14607
  if (!credential.ok) return credential;
13750
14608
  const expectedChallengeDigestB64u = typeof auth.expectedChallengeDigestB64u === "string" ? auth.expectedChallengeDigestB64u.trim() : "";
13751
14609
  if (expectedChallengeDigestB64u !== expectedDigest) return {
@@ -14349,7 +15207,7 @@ function parseWalletRevokeAuthMethodSourceProof(raw) {
14349
15207
  code: "invalid_body",
14350
15208
  message: rpId.error.message
14351
15209
  };
14352
- const credential = parseWebAuthnAuthenticationCredential$2(raw.credential);
15210
+ const credential = parseWebAuthnAuthenticationCredential$1(raw.credential);
14353
15211
  if (!credential.ok) return credential;
14354
15212
  const expectedChallengeDigestB64u = typeof raw.expectedChallengeDigestB64u === "string" ? raw.expectedChallengeDigestB64u.trim() : "";
14355
15213
  if (!expectedChallengeDigestB64u) return {
@@ -14831,6 +15689,7 @@ async function handleRouterApiWalletRegistrationNearProvisioning(input) {
14831
15689
  if (sessionSeal.value) {
14832
15690
  if (!isPasskeyWalletRegistrationNearProvisioningSuccessV2(result)) return routeError(400, "invalid_body", "Registration session sealing requires a passkey authority");
14833
15691
  const sealStartedAt = Date.now();
15692
+ if (result.registrationEstablishedSession.kind === "owner_operations") return routeError(400, "invalid_body", "Owner registration has no session seal");
14834
15693
  const sealed = await sessionSeal.value.finish(result.registrationEstablishedSession.session);
14835
15694
  serverTiming.push(["near_finalize_session_seal", Math.max(0, Date.now() - sealStartedAt)]);
14836
15695
  if (!sealed.ok) return routeJson(503, {
@@ -15230,7 +16089,7 @@ function resolveWalletRegistrationRoute(ctx) {
15230
16089
  async function handleWalletRegistration(ctx) {
15231
16090
  const route = resolveWalletRegistrationRoute(ctx);
15232
16091
  if (!route) return null;
15233
- if (ctx.request.headers.get("X-Seams-Wallet-Protocol") !== "4") return jsonFailure(409, "wallet_protocol_mismatch", "Your wallet application needs an update. Reload this page and try again. If the problem continues, ask the application developer to upgrade the Wallet SDK.");
16092
+ if (ctx.request.headers.get("X-Seams-Wallet-Protocol") !== "5") return jsonFailure(409, "wallet_protocol_mismatch", "Your wallet application needs an update. Reload this page and try again. If the problem continues, ask the application developer to upgrade the Wallet SDK.");
15234
16093
  const common = {
15235
16094
  body: await readJson(ctx.request),
15236
16095
  headers: Object.fromEntries(ctx.request.headers.entries()),
@@ -15597,215 +16456,19 @@ async function handleVoiceIdVerifyRequest(request, deps) {
15597
16456
  }, { status: 503 });
15598
16457
  }
15599
16458
  }
16459
+ Object.freeze(Array.from("0123456789ABCDEFGHJKMNPQRSTVWXYZ").reduce((acc, char, index) => {
16460
+ acc[char] = index;
16461
+ return acc;
16462
+ }, {}));
15600
16463
  //#endregion
15601
- //#region ../shared-ts/src/utils/errors.ts
15602
- /**
15603
- * Best-effort error message extractor without relying on `any`.
15604
- * Always returns a string (may be empty when nothing usable can be derived).
15605
- */
15606
- function errorMessage$3(err) {
15607
- try {
15608
- if (typeof err === "string") return err;
15609
- if (err && typeof err.message === "string") return err.message;
15610
- return String(err ?? "");
15611
- } catch {
15612
- return "";
15613
- }
15614
- }
15615
- function b64uField(prefix) {
15616
- return `${prefix}B64u`;
15617
- }
15618
- b64uField("seed"), b64uField("canonicalSeed"), b64uField("xClientBase"), b64uField("clientOutputMask"), b64uField("clientRecoverableSecret"), b64uField("prfFirst"), b64uField("prfSecond"), b64uField("signingShare32"), b64uField("clientNonceHandle");
15619
- //#endregion
15620
- //#region src/router/auth/webAuthnCredentialCodecs.ts
15621
- function containsWebAuthnExtensionOutput(value) {
15622
- if (value == null) return false;
15623
- if (!isPlainObject(value)) return true;
15624
- for (const nested of Object.values(value)) if (containsWebAuthnExtensionOutput(nested)) return true;
15625
- return false;
15626
- }
15627
- function unredactedWebAuthnExtensionField(input) {
15628
- if (!isPlainObject(input)) return null;
15629
- if (containsWebAuthnExtensionOutput(input.clientExtensionResults)) return "clientExtensionResults";
15630
- const response = input.response;
15631
- if (!isPlainObject(response)) return null;
15632
- if (containsWebAuthnExtensionOutput(response.clientExtensionResults)) return "response.clientExtensionResults";
15633
- return null;
15634
- }
15635
- function decodeWebAuthnBase64UrlOrBase64(input, fieldName) {
15636
- try {
15637
- return base64UrlDecode(input);
15638
- } catch {
15639
- try {
15640
- return base64Decode(input);
15641
- } catch (error) {
15642
- throw new Error(`Invalid ${fieldName}: expected base64url/base64 string (${errorMessage$3(error) || "decode failed"})`);
15643
- }
15644
- }
15645
- }
15646
- function parseWebAuthnClientDataJsonBase64url(clientDataJSONB64u) {
15647
- const bytes = decodeWebAuthnBase64UrlOrBase64(clientDataJSONB64u, "webauthn_authentication.response.clientDataJSON");
15648
- const json = new TextDecoder().decode(bytes);
15649
- let parsed;
15650
- try {
15651
- parsed = JSON.parse(json);
15652
- } catch {
15653
- throw new Error("Invalid clientDataJSON: expected object");
15654
- }
15655
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Invalid clientDataJSON: expected object");
15656
- const challenge = normalizeOptionalTrimmedString(Reflect.get(parsed, "challenge"));
15657
- const origin = normalizeOptionalTrimmedString(Reflect.get(parsed, "origin"));
15658
- const type = normalizeOptionalTrimmedString(Reflect.get(parsed, "type"));
15659
- if (!challenge) throw new Error("Invalid clientDataJSON.challenge");
15660
- if (!origin) throw new Error("Invalid clientDataJSON.origin");
15661
- if (!type) throw new Error("Invalid clientDataJSON.type");
15662
- return {
15663
- challenge,
15664
- origin,
15665
- type
15666
- };
15667
- }
15668
- function webAuthnOriginHostnameOrEmpty(origin) {
15669
- try {
15670
- return new URL(origin).hostname.toLowerCase();
15671
- } catch {
15672
- return "";
15673
- }
15674
- }
15675
- function webAuthnCredentialIdB64uFromCredential(input) {
15676
- if (input === null || typeof input !== "object" || Array.isArray(input)) return failure$6("invalid_body", "Missing webauthn_authentication.id/rawId");
15677
- const rawId = normalizeOptionalTrimmedString(Reflect.get(input, "rawId"));
15678
- const id = normalizeOptionalTrimmedString(Reflect.get(input, "id"));
15679
- const selected = rawId || id;
15680
- if (!selected) return failure$6("invalid_body", "Missing webauthn_authentication.id/rawId");
16464
+ //#region ../shared-ts/src/wallet-recovery/recoveryCodeLocator.ts
16465
+ function parseRecoveryCodeLocatorV1(value, label = "recoveryCodeLocator") {
15681
16466
  try {
15682
- return {
15683
- ok: true,
15684
- credentialIdB64u: base64UrlEncode(decodeWebAuthnBase64UrlOrBase64(selected, "webauthn_authentication.rawId"))
15685
- };
16467
+ return parseDigestB64u(value);
15686
16468
  } catch (error) {
15687
- return failure$6("invalid_body", errorMessage$3(error) || "Invalid credential rawId");
15688
- }
15689
- }
15690
- function parseWebAuthnAuthenticationCredential$1(input) {
15691
- if (input === null || typeof input !== "object" || Array.isArray(input)) return null;
15692
- const responseValue = Reflect.get(input, "response");
15693
- if (responseValue === null || typeof responseValue !== "object" || Array.isArray(responseValue)) return null;
15694
- const id = normalizeOptionalTrimmedString(Reflect.get(input, "id"));
15695
- const rawId = normalizeOptionalTrimmedString(Reflect.get(input, "rawId"));
15696
- const type = normalizeOptionalTrimmedString(Reflect.get(input, "type"));
15697
- const clientDataJSON = normalizeOptionalTrimmedString(Reflect.get(responseValue, "clientDataJSON"));
15698
- const authenticatorData = normalizeOptionalTrimmedString(Reflect.get(responseValue, "authenticatorData"));
15699
- const signature = normalizeOptionalTrimmedString(Reflect.get(responseValue, "signature"));
15700
- const rawUserHandle = Reflect.get(responseValue, "userHandle");
15701
- const userHandle = rawUserHandle === null ? null : normalizeOptionalTrimmedString(rawUserHandle) || null;
15702
- const rawAuthenticatorAttachment = Reflect.get(input, "authenticatorAttachment");
15703
- const authenticatorAttachment = rawAuthenticatorAttachment === null ? null : normalizeOptionalTrimmedString(rawAuthenticatorAttachment) || null;
15704
- if (!id || !rawId || type !== "public-key") return null;
15705
- if (!clientDataJSON || !authenticatorData || !signature) return null;
15706
- return {
15707
- id,
15708
- rawId,
15709
- type,
15710
- authenticatorAttachment,
15711
- response: {
15712
- clientDataJSON,
15713
- authenticatorData,
15714
- signature,
15715
- userHandle
15716
- },
15717
- clientExtensionResults: Reflect.get(input, "clientExtensionResults") ?? null
15718
- };
15719
- }
15720
- //#endregion
15721
- //#region src/router/transport/fetch/routes/walletCustodyRequestBoundary.ts
15722
- function requireExactObjectFields(value, fields, label) {
15723
- const keys = Object.keys(value).sort();
15724
- const expected = [...fields].sort();
15725
- if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) throw new Error(`${label} contains unsupported fields`);
15726
- }
15727
- function parseCustodyOwnerProof(value) {
15728
- if (!isPlainObject(value)) throw new Error("factorProof is required");
15729
- const kind = normalizeOptionalTrimmedString(value.kind);
15730
- if (kind === "passkey") {
15731
- requireExactObjectFields(value, [
15732
- "kind",
15733
- "walletId",
15734
- "rpId",
15735
- "credentialIdB64u",
15736
- "challenge_digest",
15737
- "webauthn_authentication"
15738
- ], "factorProof");
15739
- const extensionField = unredactedWebAuthnExtensionField(value.webauthn_authentication);
15740
- if (extensionField) throw new Error(`factorProof.webauthn_authentication.${extensionField} must be redacted`);
15741
- const webauthnAuthentication = parseWebAuthnAuthenticationCredential$1(value.webauthn_authentication);
15742
- if (!webauthnAuthentication) throw new Error("factorProof.webauthn_authentication is invalid");
15743
- return {
15744
- kind,
15745
- walletId: requireTrimmedString(value.walletId, "factorProof.walletId"),
15746
- rpId: requireTrimmedString(value.rpId, "factorProof.rpId"),
15747
- credentialIdB64u: requireTrimmedString(value.credentialIdB64u, "factorProof.credentialIdB64u"),
15748
- challenge_digest: requireTrimmedString(value.challenge_digest, "factorProof.challenge_digest"),
15749
- webauthn_authentication: webauthnAuthentication
15750
- };
15751
- }
15752
- if (kind === "email_otp") {
15753
- requireExactObjectFields(value, [
15754
- "kind",
15755
- "provider_subject_id",
15756
- "challenge_id",
15757
- "otp_code",
15758
- "challenge_digest"
15759
- ], "factorProof");
15760
- return {
15761
- kind,
15762
- provider_subject_id: requireTrimmedString(value.provider_subject_id, "factorProof.provider_subject_id"),
15763
- challenge_id: requireTrimmedString(value.challenge_id, "factorProof.challenge_id"),
15764
- otp_code: requireTrimmedString(value.otp_code, "factorProof.otp_code"),
15765
- challenge_digest: requireTrimmedString(value.challenge_digest, "factorProof.challenge_digest")
15766
- };
15767
- }
15768
- throw new Error("factorProof.kind must be passkey or email_otp");
15769
- }
15770
- function parseRecoveryBackupAcknowledgementRequest(body) {
15771
- if (!isPlainObject(body)) throw new Error("acknowledgement body is required");
15772
- requireExactObjectFields(body, [
15773
- "expectedIssuedAtMs",
15774
- "factorProof",
15775
- "requestId",
15776
- "walletId"
15777
- ], "recovery backup acknowledgement");
15778
- return {
15779
- walletId: requireTrimmedString(body.walletId, "walletId"),
15780
- requestId: requireTrimmedString(body.requestId, "requestId"),
15781
- expectedIssuedAtMs: parseUnixMs$1(body.expectedIssuedAtMs, "expectedIssuedAtMs"),
15782
- factorProof: body.factorProof
15783
- };
15784
- }
15785
- function parseRecoveryStatusRequest(body) {
15786
- if (!isPlainObject(body)) throw new Error("status body is required");
15787
- requireExactObjectFields(body, ["factorProof", "requestId"], "recovery status");
15788
- return {
15789
- requestId: requireTrimmedString(body.requestId, "requestId"),
15790
- factorProof: body.factorProof
15791
- };
15792
- }
15793
- //#endregion
15794
- //#region src/core/authService/webauthnOidcHelpers.ts
15795
- function originHostnameOrEmpty(origin) {
15796
- try {
15797
- return new URL(origin).hostname.toLowerCase();
15798
- } catch {
15799
- return "";
16469
+ throw new Error(`${label} ${error instanceof Error ? error.message : "must be a canonical digest"}`);
15800
16470
  }
15801
16471
  }
15802
- function isHostWithinRpId(host, rpId) {
15803
- const h = (host || "").toLowerCase();
15804
- const r = String(rpId).toLowerCase();
15805
- if (!h || !r) return false;
15806
- if ((process.env.NO_CADDY === "1" || process.env.VITE_NO_CADDY === "1") && (h === "localhost" || h === "127.0.0.1") && r.endsWith(".localhost")) return true;
15807
- return h === r || h.endsWith(`.${r}`);
15808
- }
15809
16472
  //#endregion
15810
16473
  //#region ../shared-ts/src/wallet-recovery/recoveryCodeReservation.ts
15811
16474
  function parseRecoveryCodeReservationId(value) {
@@ -15813,182 +16476,6 @@ function parseRecoveryCodeReservationId(value) {
15813
16476
  return value;
15814
16477
  }
15815
16478
  //#endregion
15816
- //#region ../shared-ts/src/authorization/walletCustodyOperation.ts
15817
- /** Public challenge digest for a single wallet-administration factor proof. */
15818
- async function computeWalletCustodyAdminChallengeDigest(input) {
15819
- return sha256Utf8DigestB64u(`seams:wallet-custody:challenge:v1|${alphabetizeStringify({
15820
- walletId: input.walletId,
15821
- operation: input.operation,
15822
- payload: input.payload,
15823
- requestOrigin: input.requestOrigin
15824
- })}`);
15825
- }
15826
- //#endregion
15827
- //#region ../shared-ts/src/wallet-recovery/walletRecoveryTarget.ts
15828
- function parseWalletRecoveryTargetV1(raw) {
15829
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("wallet recovery target must be an object");
15830
- const record = raw;
15831
- const fields = Object.keys(record);
15832
- switch (record.kind) {
15833
- case "passkey": {
15834
- if (fields.length !== 2 || !fields.includes("rpId")) throw new Error("Passkey recovery target fields are invalid");
15835
- const rpId = parseWebAuthnRpId(record.rpId);
15836
- if (!rpId.ok) throw new Error("Passkey recovery target RP ID is invalid");
15837
- return {
15838
- kind: "passkey",
15839
- rpId: rpId.value
15840
- };
15841
- }
15842
- case "google_email_otp":
15843
- if (fields.length !== 2 || !fields.includes("googleProvider") || record.googleProvider !== "google") throw new Error("Google Email OTP recovery target fields are invalid");
15844
- return {
15845
- kind: "google_email_otp",
15846
- googleProvider: "google"
15847
- };
15848
- default: throw new Error("wallet recovery target kind is unsupported");
15849
- }
15850
- }
15851
- //#endregion
15852
- //#region src/router/domains/emailOtp/emailOtpSessionRouteHelpers.ts
15853
- function emailOtpStatusCode(code) {
15854
- if (code === "internal") return 500;
15855
- if (code === "not_configured") return 503;
15856
- if (code === "not_found") return 404;
15857
- if (code === "rate_limited") return 429;
15858
- if (code === "stronger_auth_required") return 403;
15859
- if (code === "reenrollment_required" || code === "registration_attempt_missing" || code === "registration_attempt_expired") return 409;
15860
- if (code === "challenge_id_mismatch" || code === "challenge_purpose_mismatch" || code === "challenge_subject_mismatch" || code === "challenge_email_mismatch" || code === "challenge_wallet_mismatch" || code === "challenge_session_mismatch" || code === "challenge_org_mismatch" || code === "challenge_channel_mismatch" || code === "registration_reroll_disallowed" || code === "challenge_expired_or_invalid" || code === "invalid_otp") return 401;
15861
- return 400;
15862
- }
15863
- function emailOtpFailureAuditPayload(input) {
15864
- return {
15865
- source: input.source,
15866
- code: input.code,
15867
- message: input.message,
15868
- ...input.challengeId ? { challengeId: input.challengeId } : {},
15869
- ...input.otpChannel ? { otpChannel: input.otpChannel } : {},
15870
- ...input.operation ? { operation: input.operation } : {},
15871
- ...typeof input.lockedUntilMs === "number" ? { lockedUntilMs: input.lockedUntilMs } : {}
15872
- };
15873
- }
15874
- function shouldEmitEmailOtpLockedWebhook(code) {
15875
- return code === "otp_locked_out" || code === "otp_attempts_exhausted";
15876
- }
15877
- function emailOtpFailureWebhookEventDescriptors(input) {
15878
- const payload = emailOtpFailureAuditPayload(input);
15879
- const event = {
15880
- eventType: "wallet.email_otp.failed",
15881
- ...input.challengeId ? { eventId: input.challengeId } : {},
15882
- payload
15883
- };
15884
- if (!shouldEmitEmailOtpLockedWebhook(input.code)) return [event];
15885
- return [event, {
15886
- eventType: "wallet.email_otp.locked",
15887
- ...input.challengeId ? { eventId: input.challengeId } : {},
15888
- payload
15889
- }];
15890
- }
15891
- function emailOtpLoggedInWebhookEventDescriptor(input) {
15892
- return {
15893
- eventType: "wallet.email_otp.logged_in",
15894
- eventId: input.challengeId,
15895
- payload: {
15896
- otpChannel: input.otpChannel,
15897
- unlockBackend: input.unlockBackend,
15898
- challengeId: input.challengeId
15899
- }
15900
- };
15901
- }
15902
- async function hashEmailOtpOperationBinding(input) {
15903
- return base64UrlEncode(await sha256BytesUtf8(alphabetizeStringify({
15904
- walletId: input.walletId,
15905
- providerUserId: input.providerUserId,
15906
- orgId: input.orgId,
15907
- operation: input.operation,
15908
- requestOrigin: input.requestOrigin || "",
15909
- audience: input.audience || "",
15910
- ...input.authorityRef !== void 0 ? { authorityRef: input.authorityRef } : {},
15911
- ...input.operationFingerprintDigest ? { operationFingerprintDigest: input.operationFingerprintDigest } : {}
15912
- })));
15913
- }
15914
- //#endregion
15915
- //#region src/authorization/factorEvidence.ts
15916
- function buildVerifiedWalletOperationPasskeyFactorResult(fields) {
15917
- requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
15918
- return {
15919
- kind: "verified_wallet_operation_passkey_factor",
15920
- ...fields
15921
- };
15922
- }
15923
- function buildVerifiedWalletOperationEmailOtpFactorResult(fields) {
15924
- requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
15925
- return {
15926
- kind: "verified_wallet_operation_email_otp_factor",
15927
- ...fields
15928
- };
15929
- }
15930
- function buildVerifiedWalletSessionPasskeyFactorResult(fields) {
15931
- requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
15932
- return {
15933
- kind: "verified_wallet_session_passkey_factor",
15934
- tenantId: fields.tenantId,
15935
- principalId: fields.principalId,
15936
- walletId: fields.walletId,
15937
- authorityRef: fields.authorityRef,
15938
- requestOrigin: fields.requestOrigin,
15939
- audience: fields.audience,
15940
- factorId: fields.factorId,
15941
- credentialIdB64u: fields.credentialIdB64u,
15942
- assertionDigest: fields.assertionDigest,
15943
- verifiedAtMs: fields.verifiedAtMs,
15944
- expiresAtMs: fields.expiresAtMs
15945
- };
15946
- }
15947
- function buildVerifiedWalletSessionEmailOtpFactorResult(fields) {
15948
- requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
15949
- return {
15950
- kind: "verified_wallet_session_email_otp_factor",
15951
- tenantId: fields.tenantId,
15952
- principalId: fields.principalId,
15953
- walletId: fields.walletId,
15954
- authorityRef: fields.authorityRef,
15955
- requestOrigin: fields.requestOrigin,
15956
- audience: fields.audience,
15957
- factorId: fields.factorId,
15958
- challengeId: fields.challengeId,
15959
- verificationReceiptDigest: fields.verificationReceiptDigest,
15960
- verifiedAtMs: fields.verifiedAtMs,
15961
- expiresAtMs: fields.expiresAtMs
15962
- };
15963
- }
15964
- function requireVerificationWindow(verifiedAtMs, expiresAtMs) {
15965
- if (!Number.isSafeInteger(verifiedAtMs) || !Number.isSafeInteger(expiresAtMs) || verifiedAtMs <= 0 || expiresAtMs <= verifiedAtMs) throw new Error("verified factor expiry must follow verification");
15966
- }
15967
- //#endregion
15968
- //#region ../shared-ts/src/utils/emailOtpDomain.ts
15969
- const EMAIL_OTP_CHANNEL = "email_otp";
15970
- /** Domain label that opens the AAD binding a released Email OTP factor to its challenge. */
15971
- const EMAIL_OTP_FACTOR_RELEASE_AAD_DOMAIN_V1 = "seams/email-otp/factor-release/v1";
15972
- const WALLET_UNLOCK_BACKENDS = ["passkey", EMAIL_OTP_CHANNEL];
15973
- const WALLET_EMAIL_OTP_LOGIN_OPERATIONS = [
15974
- "wallet_unlock",
15975
- "transaction_sign",
15976
- "export_key"
15977
- ];
15978
- const WALLET_EMAIL_OTP_EXPORT_OPERATION = "export_key";
15979
- const WALLET_EMAIL_OTP_UNLOCK_OPERATION = "wallet_unlock";
15980
- const WALLET_EMAIL_OTP_TRANSACTION_SIGN_OPERATION = "transaction_sign";
15981
- function isWalletUnlockBackend(value) {
15982
- return WALLET_UNLOCK_BACKENDS.includes(value);
15983
- }
15984
- function isWalletEmailOtpLoginOperation(value) {
15985
- return WALLET_EMAIL_OTP_LOGIN_OPERATIONS.includes(value);
15986
- }
15987
- Object.freeze(Array.from("0123456789ABCDEFGHJKMNPQRSTVWXYZ").reduce((acc, char, index) => {
15988
- acc[char] = index;
15989
- return acc;
15990
- }, {}));
15991
- //#endregion
15992
16479
  //#region ../shared-ts/src/wallet-recovery/recoveryKeyId.ts
15993
16480
  /** The prefix plus unpadded base64url over one SHA-256 digest. */
15994
16481
  const DERIVED_WALLET_RECOVERY_KEY_ID_PATTERN = /^wallet-rkid-v1-[A-Za-z0-9_-]{43}$/;
@@ -15997,6 +16484,40 @@ function parseDerivedWalletRecoveryKeyId(value, label = "recoveryKeyId") {
15997
16484
  return value;
15998
16485
  }
15999
16486
  //#endregion
16487
+ //#region ../shared-ts/src/wallet-recovery/walletRecoveryEcdsaPossession.ts
16488
+ const POSSESSION_PROOF_SCHEME_V1 = "secp256k1_bip340_sha256_v1";
16489
+ function requireExactKeys$3(record, label, keys) {
16490
+ const allowed = new Set(keys);
16491
+ for (const key of Object.keys(record)) if (!allowed.has(key)) throw new Error(`${label}.${key} is not a supported field`);
16492
+ for (const key of keys) if (!Object.hasOwn(record, key)) throw new Error(`${label}.${key} is required`);
16493
+ }
16494
+ function requireSignature64(value, label) {
16495
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new Error(`${label} must be canonical base64url`);
16496
+ let bytes;
16497
+ try {
16498
+ bytes = base64UrlDecode(value);
16499
+ } catch {
16500
+ throw new Error(`${label} must be valid base64url`);
16501
+ }
16502
+ if (bytes.length !== 64 || base64UrlEncode(bytes) !== value) throw new Error(`${label} must be canonical base64url for 64 bytes`);
16503
+ return value;
16504
+ }
16505
+ function parseWalletRecoveryEcdsaPossessionProofV1(value) {
16506
+ const record = requireRecord$1(value, "walletRecoveryEcdsaPossessionProof");
16507
+ requireExactKeys$3(record, "walletRecoveryEcdsaPossessionProof", [
16508
+ "kind",
16509
+ "scheme",
16510
+ "signature64B64u"
16511
+ ]);
16512
+ if (record.kind !== "wallet_recovery_ecdsa_possession_proof_v1") throw new Error("walletRecoveryEcdsaPossessionProof.kind is invalid");
16513
+ if (record.scheme !== POSSESSION_PROOF_SCHEME_V1) throw new Error("walletRecoveryEcdsaPossessionProof.scheme is invalid");
16514
+ return {
16515
+ kind: "wallet_recovery_ecdsa_possession_proof_v1",
16516
+ scheme: POSSESSION_PROOF_SCHEME_V1,
16517
+ signature64B64u: requireSignature64(record.signature64B64u, "walletRecoveryEcdsaPossessionProof.signature64B64u")
16518
+ };
16519
+ }
16520
+ //#endregion
16000
16521
  //#region ../shared-ts/src/wallet-recovery/walletRecoveryEnvelopeSet.ts
16001
16522
  function buildWalletCustodySeedRecoveryEntry(args) {
16002
16523
  return {
@@ -16071,47 +16592,45 @@ function parseWalletRecoveryEnvelopeEntry(raw, label) {
16071
16592
  });
16072
16593
  }
16073
16594
  //#endregion
16074
- //#region ../shared-ts/src/wallet-recovery/recoveryCodeLocator.ts
16075
- function parseRecoveryCodeLocatorV1(value, label = "recoveryCodeLocator") {
16076
- try {
16077
- return parseDigestB64u(value);
16078
- } catch (error) {
16079
- throw new Error(`${label} ${error instanceof Error ? error.message : "must be a canonical digest"}`);
16595
+ //#region ../shared-ts/src/wallet-recovery/walletRecoveryTarget.ts
16596
+ function parseWalletRecoveryTargetV1(raw) {
16597
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("wallet recovery target must be an object");
16598
+ const record = raw;
16599
+ const fields = Object.keys(record);
16600
+ switch (record.kind) {
16601
+ case "passkey": {
16602
+ if (fields.length !== 2 || !fields.includes("rpId")) throw new Error("Passkey recovery target fields are invalid");
16603
+ const rpId = parseWebAuthnRpId(record.rpId);
16604
+ if (!rpId.ok) throw new Error("Passkey recovery target RP ID is invalid");
16605
+ return {
16606
+ kind: "passkey",
16607
+ rpId: rpId.value
16608
+ };
16609
+ }
16610
+ case "google_email_otp":
16611
+ if (fields.length !== 2 || !fields.includes("googleProvider") || record.googleProvider !== "google") throw new Error("Google Email OTP recovery target fields are invalid");
16612
+ return {
16613
+ kind: "google_email_otp",
16614
+ googleProvider: "google"
16615
+ };
16616
+ default: throw new Error("wallet recovery target kind is unsupported");
16080
16617
  }
16081
16618
  }
16082
16619
  //#endregion
16083
- //#region ../shared-ts/src/wallet-recovery/walletRecoveryEcdsaPossession.ts
16084
- const POSSESSION_PROOF_SCHEME_V1 = "secp256k1_bip340_sha256_v1";
16085
- function requireExactKeys$3(record, label, keys) {
16086
- const allowed = new Set(keys);
16087
- for (const key of Object.keys(record)) if (!allowed.has(key)) throw new Error(`${label}.${key} is not a supported field`);
16088
- for (const key of keys) if (!Object.hasOwn(record, key)) throw new Error(`${label}.${key} is required`);
16089
- }
16090
- function requireSignature64(value, label) {
16091
- if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new Error(`${label} must be canonical base64url`);
16092
- let bytes;
16620
+ //#region src/core/authService/webauthnOidcHelpers.ts
16621
+ function originHostnameOrEmpty(origin) {
16093
16622
  try {
16094
- bytes = base64UrlDecode(value);
16623
+ return new URL(origin).hostname.toLowerCase();
16095
16624
  } catch {
16096
- throw new Error(`${label} must be valid base64url`);
16625
+ return "";
16097
16626
  }
16098
- if (bytes.length !== 64 || base64UrlEncode(bytes) !== value) throw new Error(`${label} must be canonical base64url for 64 bytes`);
16099
- return value;
16100
16627
  }
16101
- function parseWalletRecoveryEcdsaPossessionProofV1(value) {
16102
- const record = requireRecord$1(value, "walletRecoveryEcdsaPossessionProof");
16103
- requireExactKeys$3(record, "walletRecoveryEcdsaPossessionProof", [
16104
- "kind",
16105
- "scheme",
16106
- "signature64B64u"
16107
- ]);
16108
- if (record.kind !== "wallet_recovery_ecdsa_possession_proof_v1") throw new Error("walletRecoveryEcdsaPossessionProof.kind is invalid");
16109
- if (record.scheme !== POSSESSION_PROOF_SCHEME_V1) throw new Error("walletRecoveryEcdsaPossessionProof.scheme is invalid");
16110
- return {
16111
- kind: "wallet_recovery_ecdsa_possession_proof_v1",
16112
- scheme: POSSESSION_PROOF_SCHEME_V1,
16113
- signature64B64u: requireSignature64(record.signature64B64u, "walletRecoveryEcdsaPossessionProof.signature64B64u")
16114
- };
16628
+ function isHostWithinRpId(host, rpId) {
16629
+ const h = (host || "").toLowerCase();
16630
+ const r = String(rpId).toLowerCase();
16631
+ if (!h || !r) return false;
16632
+ if ((process.env.NO_CADDY === "1" || process.env.VITE_NO_CADDY === "1") && (h === "localhost" || h === "127.0.0.1") && r.endsWith(".localhost")) return true;
16633
+ return h === r || h.endsWith(`.${r}`);
16115
16634
  }
16116
16635
  //#endregion
16117
16636
  //#region src/router/transport/fetch/routes/passkeyCustody.ts
@@ -16139,239 +16658,6 @@ const RECOVERY_READ_ROUTE_ID = "wallet_recovery_codes_read";
16139
16658
  const RECOVERY_STATUS_ROUTE_ID = "wallet_recovery_status";
16140
16659
  const EMAIL_OTP_CHALLENGE_ROUTE_ID = "wallet_custody_email_otp_challenge";
16141
16660
  const ENVELOPE_OWNERSHIP_UPGRADE_ROUTE_ID = "wallet_custody_envelope_ownership_upgrade";
16142
- const CUSTODY_OPERATION_CAPABILITY_ID = "wallet-custody-admin";
16143
- const CUSTODY_PROOF_TTL_MS = 6e4;
16144
- function hasFullOwnerAuthority$1(authority) {
16145
- return sameDelegatedWalletAuthorityV1(buildDelegatedWalletAuthorityV1({ permissions: authority.permissions }), buildDelegatedWalletAuthorityV1({ permissions: buildFullOwnerPermissionsV1() }));
16146
- }
16147
- function parseRequiredAuthorizationValue(parsed) {
16148
- if (!parsed.ok) throw new Error(parsed.error.message);
16149
- return parsed.value;
16150
- }
16151
- async function custodyDigest(domain, value) {
16152
- return sha256Utf8DigestB64u(`${domain}|${alphabetizeStringify(value)}`);
16153
- }
16154
- async function buildWalletCustodyOperation(input) {
16155
- const tenantId = parseRequiredAuthorizationValue(parseTenantId(input.tenantId));
16156
- const principalId = parseRequiredAuthorizationValue(parsePrincipalId(input.principalId));
16157
- const capabilityId = parseRequiredAuthorizationValue(parseCapabilityId(CUSTODY_OPERATION_CAPABILITY_ID));
16158
- const operationRef = buildVaultOperationRef("vault.reveal");
16159
- const laneDigest = await custodyDigest("seams:wallet-custody:lane:v1", {
16160
- capabilityId,
16161
- operation: input.operation,
16162
- operationRef
16163
- });
16164
- const challengeDigest = await computeWalletCustodyAdminChallengeDigest({
16165
- walletId: input.walletId,
16166
- operation: input.operation,
16167
- payload: input.payload,
16168
- requestOrigin: input.requestOrigin
16169
- });
16170
- const intentDigest = challengeDigest;
16171
- const displayDigest = await custodyDigest("seams:wallet-custody:display:v1", {
16172
- walletId: input.walletId,
16173
- operation: input.operation
16174
- });
16175
- return {
16176
- operation: buildCapabilityOperationEnvelope({
16177
- tenantId,
16178
- principalId,
16179
- capabilityId,
16180
- operationId: parseRequiredAuthorizationValue(parseCapabilityOperationId(`wallet-custody:${input.operation}:${intentDigest}`)),
16181
- operation: operationRef,
16182
- digests: {
16183
- laneDigest,
16184
- intentDigest,
16185
- displayDigest
16186
- }
16187
- }),
16188
- challengeDigest
16189
- };
16190
- }
16191
- async function authorizeWalletCustodyOperation(input) {
16192
- const requestOrigin = normalizeOptionalTrimmedString(input.ctx.request.headers.get("origin"));
16193
- if (!requestOrigin) return toCustodyAuthorizationFailure(400, "invalid_origin", "request Origin is required");
16194
- let origin;
16195
- let proof;
16196
- let walletId;
16197
- try {
16198
- origin = parseSessionOrigin(requestOrigin);
16199
- walletId = parseRequiredAuthorizationValue(parseWalletId(input.walletId));
16200
- proof = parseCustodyOwnerProof(input.factorProof);
16201
- } catch (error) {
16202
- return toCustodyAuthorizationFailure(400, "invalid_owner_proof", error instanceof Error ? error.message : "owner proof is invalid");
16203
- }
16204
- if (proof.kind === "passkey" && proof.walletId !== input.walletId) return toCustodyAuthorizationFailure(403, "scope_mismatch", "owner proof wallet differs");
16205
- let principalId;
16206
- let factor;
16207
- let operation;
16208
- let challengeDigest;
16209
- try {
16210
- principalId = proof.kind === "passkey" ? input.walletId : proof.provider_subject_id;
16211
- const built = await buildWalletCustodyOperation({
16212
- tenantId: input.ctx.service.authorizedOperations.tenantId,
16213
- principalId,
16214
- walletId: input.walletId,
16215
- operation: input.operation,
16216
- payload: input.payload,
16217
- requestOrigin
16218
- });
16219
- operation = built.operation;
16220
- challengeDigest = built.challengeDigest;
16221
- } catch (error) {
16222
- return toCustodyAuthorizationFailure(400, "invalid_owner_proof", error instanceof Error ? error.message : "owner proof binding is invalid");
16223
- }
16224
- const operationFingerprintDigest = await computeCapabilityOperationFingerprintDigest(operation);
16225
- const nowMs = Date.now();
16226
- if (proof.challenge_digest !== String(challengeDigest)) return toCustodyAuthorizationFailure(403, "scope_mismatch", "factor proof challenge does not match the requested operation");
16227
- if (proof.kind === "passkey") {
16228
- let authority;
16229
- try {
16230
- authority = buildPasskeyWalletAuthAuthority({
16231
- walletId,
16232
- rpId: proof.rpId,
16233
- credentialIdB64u: proof.credentialIdB64u
16234
- });
16235
- } catch (error) {
16236
- return toCustodyAuthorizationFailure(400, "invalid_owner_proof", error instanceof Error ? error.message : "passkey authority is invalid");
16237
- }
16238
- const active = await input.ctx.service.walletAuthMethods.resolveActivePasskeyAuthorityForVerifiedCredential({
16239
- walletId,
16240
- rpId: authority.verifier.rpId,
16241
- credentialIdB64u: authority.factor.credentialIdB64u
16242
- });
16243
- if (!active.ok) return toCustodyAuthorizationFailure(403, active.code, active.message);
16244
- if (!hasFullOwnerAuthority$1(active.walletAuthority)) return toCustodyAuthorizationFailure(403, "forbidden", "Full owner authority is required");
16245
- const credential = parseWebAuthnAuthenticationCredential$1(proof.webauthn_authentication);
16246
- if (!credential) return toCustodyAuthorizationFailure(400, "invalid_owner_proof", "passkey assertion is invalid");
16247
- if (String(credential.rawId || credential.id).trim() !== authority.factor.credentialIdB64u) return toCustodyAuthorizationFailure(401, "unauthorized", "passkey credential changed");
16248
- const verified = await input.ctx.service.webAuthn.verifyWebAuthnAuthenticationLite({
16249
- userId: input.walletId,
16250
- rpId: authority.verifier.rpId,
16251
- expectedChallenge: challengeDigest,
16252
- expected_origin: requestOrigin,
16253
- webauthn_authentication: credential
16254
- });
16255
- if (!verified.success || !verified.verified) return toCustodyAuthorizationFailure(401, verified.code || "not_verified", verified.message || "WebAuthn authentication verification failed");
16256
- factor = buildVerifiedWalletOperationPasskeyFactorResult({
16257
- tenantId: operation.tenantId,
16258
- principalId: operation.principalId,
16259
- walletId,
16260
- authorityRef: await walletAuthAuthorityRef({ authority: active.authority }),
16261
- requestOrigin: origin,
16262
- audience: origin,
16263
- factorId: parseRequiredAuthorizationValue(parseAuthFactorId(`passkey:${authority.factor.credentialIdB64u}`)),
16264
- credentialIdB64u: parseRequiredAuthorizationValue(parseWebAuthnCredentialIdB64u(authority.factor.credentialIdB64u)),
16265
- assertionDigest: await custodyDigest("seams:wallet-custody:assertion:v1", { credential }),
16266
- operation,
16267
- verifiedAtMs: nowMs,
16268
- expiresAtMs: nowMs + CUSTODY_PROOF_TTL_MS
16269
- });
16270
- } else {
16271
- const providerSubject = parseRequiredAuthorizationValue(parseProviderSubject(proof.provider_subject_id));
16272
- const orgId = parseRequiredAuthorizationValue(parseOrgId(input.ctx.service.authorizedOperations.tenantId));
16273
- const active = await input.ctx.service.walletAuthMethods.resolveActiveEmailOtpAuthorityForVerifiedSubject({
16274
- walletId: input.walletId,
16275
- providerUserId: proof.provider_subject_id
16276
- });
16277
- if (!active.ok) return toCustodyAuthorizationFailure(403, active.code, active.message);
16278
- const activeAuthorityRef = await walletAuthAuthorityRef({ authority: active.authority });
16279
- const ownerProofBindingDigest = await hashEmailOtpOperationBinding({
16280
- walletId: input.walletId,
16281
- providerUserId: proof.provider_subject_id,
16282
- orgId,
16283
- operation: `wallet_custody:${input.operation}`,
16284
- requestOrigin,
16285
- audience: requestOrigin,
16286
- authorityRef: activeAuthorityRef,
16287
- operationFingerprintDigest: challengeDigest
16288
- });
16289
- const verified = await input.ctx.service.emailOtp.verifyEmailOtpChallenge({
16290
- userId: proof.provider_subject_id,
16291
- walletId: input.walletId,
16292
- orgId,
16293
- challengeId: proof.challenge_id,
16294
- otpCode: proof.otp_code,
16295
- otpChannel: EMAIL_OTP_CHANNEL,
16296
- ownerProofBindingDigest,
16297
- operation: WALLET_EMAIL_OTP_UNLOCK_OPERATION
16298
- });
16299
- if (!verified.ok) return toCustodyAuthorizationFailure(verified.code === "invalid_body" ? 400 : 401, verified.code, verified.message);
16300
- const consumed = await input.ctx.service.emailOtp.consumeEmailOtpGrant({
16301
- subject: {
16302
- kind: "provider_identity",
16303
- orgId,
16304
- providerSubject,
16305
- walletId
16306
- },
16307
- loginGrant: verified.loginGrant,
16308
- otpChannel: EMAIL_OTP_CHANNEL
16309
- });
16310
- if (!consumed.ok) return toCustodyAuthorizationFailure(emailOtpStatusCode(consumed.code), consumed.code, consumed.message);
16311
- factor = buildVerifiedWalletOperationEmailOtpFactorResult({
16312
- tenantId: operation.tenantId,
16313
- principalId: operation.principalId,
16314
- walletId,
16315
- authorityRef: activeAuthorityRef,
16316
- requestOrigin: origin,
16317
- audience: origin,
16318
- factorId: parseRequiredAuthorizationValue(parseAuthFactorId(`email_otp:${active.authority.factor.provider}:${proof.provider_subject_id}`)),
16319
- operation,
16320
- challengeId: parseRequiredAuthorizationValue(parseEmailOtpChallengeId(consumed.challengeId)),
16321
- verificationReceiptDigest: await custodyDigest("seams:wallet-custody:otp-receipt:v1", {
16322
- challengeId: consumed.challengeId,
16323
- operationFingerprintDigest
16324
- }),
16325
- verifiedAtMs: nowMs,
16326
- expiresAtMs: Math.min(nowMs + CUSTODY_PROOF_TTL_MS, verified.grantExpiresAtMs)
16327
- });
16328
- }
16329
- const ownerProof = await input.ctx.service.authorizedOperations.buildVerifiedOwnerProof({
16330
- purpose: "operation",
16331
- proofId: parseVerifiedOwnerProofId(`wallet-custody:${operation.operationId}`),
16332
- factor
16333
- });
16334
- if (ownerProof.purpose !== "operation" || ownerProof.operation.operationFingerprintDigest !== operationFingerprintDigest) return toCustodyAuthorizationFailure(403, "owner_proof_rejected", "owner proof binding is invalid");
16335
- const evidenceSet = await input.ctx.service.authorizedOperations.recordVerifiedWalletOperationFactorEvidenceSet({
16336
- operation,
16337
- evidenceId: parseRequiredAuthorizationValue(parseAuthorizationEvidenceId(`wallet-custody:evidence:${operation.operationId}`)),
16338
- evidenceSetId: parseRequiredAuthorizationValue(parseAuthorizationEvidenceSetId(`wallet-custody:evidence-set:${operation.operationId}`)),
16339
- factor
16340
- });
16341
- const admission = await input.ctx.service.authorizedOperations.admitAuthorizedOperation({ operation: {
16342
- tenantId: operation.tenantId,
16343
- authorizedOperationId: parseRequiredAuthorizationValue(parseAuthorizedOperationId(`wallet-custody:authorized:${operation.operationId}`)),
16344
- auditEventId: parseRequiredAuthorizationValue(parseAuthorizationAuditEventId(`wallet-custody:audit:${operation.operationId}`)),
16345
- operation,
16346
- authorization: {
16347
- kind: "verified_step_up",
16348
- evidenceSetDigest: evidenceSet.evidenceSetDigest
16349
- },
16350
- quota: { kind: "quota_neutral" },
16351
- claimedAtMs: nowMs
16352
- } });
16353
- switch (admission.kind) {
16354
- case "claimed": return {
16355
- ok: true,
16356
- operation: admission.operation
16357
- };
16358
- case "replayed":
16359
- case "operation_in_progress": return {
16360
- ok: false,
16361
- response: authorizedOperationReplay(admission.operation)
16362
- };
16363
- case "authorization_grant_rejected":
16364
- case "verified_step_up_rejected":
16365
- case "wallet_session_quota_exhausted":
16366
- case "material_mismatch": return toCustodyAuthorizationFailure(403, "owner_proof_rejected", "owner proof was rejected");
16367
- }
16368
- }
16369
- function toCustodyAuthorizationFailure(status, code, message) {
16370
- return {
16371
- ok: false,
16372
- response: routeErrorResponse(status, code, message)
16373
- };
16374
- }
16375
16661
  function routeErrorResponse(status, code, message) {
16376
16662
  return toFetchRouteResponse({
16377
16663
  status,
@@ -16387,23 +16673,6 @@ function matchRoute(ctx, routeId) {
16387
16673
  if (!route) throw new Error(`Missing route definition for ${routeId}`);
16388
16674
  return matchesRouteDefinitionRequest(route, ctx.method, ctx.pathname) ? route : null;
16389
16675
  }
16390
- async function completeWalletCustodyOperation(ctx, operation, status, body, result = "succeeded") {
16391
- const bodyText = JSON.stringify(body);
16392
- await ctx.service.authorizedOperations.completeAuthorizedOperation({
16393
- operation,
16394
- result,
16395
- response: {
16396
- status,
16397
- contentType: "application/json",
16398
- bodyText
16399
- },
16400
- completedAtMs: Date.now()
16401
- });
16402
- return toFetchRouteResponse({
16403
- status,
16404
- body
16405
- });
16406
- }
16407
16676
  async function handlePasskeyCustody(ctx) {
16408
16677
  if (!matchRoute(ctx, ROUTE_ID)) return null;
16409
16678
  const body = await readJsonObject(ctx.request);
@@ -16431,8 +16700,8 @@ function parseWalletCustodyEmailOtpChallengeRequest(value, actualOrigin) {
16431
16700
  ];
16432
16701
  const withoutOptional = keys.filter((key) => key !== "requestOrigin");
16433
16702
  if (withoutOptional.length !== required.length || withoutOptional.some((key, index) => key !== required.slice().sort()[index]) || keys.includes("requestOrigin") && keys.length !== expected.length) throw new Error("Email OTP custody challenge body contains unsupported fields");
16434
- const walletId = parseRequiredAuthorizationValue(parseWalletId(value.walletId));
16435
- const providerSubjectId = parseRequiredAuthorizationValue(parseProviderSubject(value.providerSubjectId));
16703
+ const walletId = requireParsed$4(parseWalletId(value.walletId), "authorization");
16704
+ const providerSubjectId = requireParsed$4(parseProviderSubject(value.providerSubjectId), "authorization");
16436
16705
  const operation = value.operation;
16437
16706
  if (operation !== "credentials_list" && operation !== "credential_label" && operation !== "recovery_rotate" && operation !== "recovery_read" && operation !== "recovery_status" && operation !== "recovery_backup_acknowledge") throw new Error("Email OTP custody challenge operation is invalid");
16438
16707
  if (!isPlainObject(value.payload)) throw new Error("Email OTP custody challenge payload is invalid");
@@ -16461,8 +16730,8 @@ async function handleWalletCustodyEmailOtpChallenge(ctx) {
16461
16730
  providerUserId: request.providerSubjectId
16462
16731
  });
16463
16732
  if (!active.ok) return routeErrorResponse(403, active.code, active.message);
16464
- const authoritativeOrgId = parseRequiredAuthorizationValue(parseOrgId(ctx.service.authorizedOperations.tenantId));
16465
- const challengeDigest = await computeWalletCustodyAdminChallengeDigest(request);
16733
+ const authoritativeOrgId = requireParsed$4(parseOrgId(ctx.service.authorizedOperations.tenantId), "authorization");
16734
+ const challengeDigest = await computeWalletOwnerAdminChallengeDigest(request);
16466
16735
  const ownerProofBindingDigest = await hashEmailOtpOperationBinding({
16467
16736
  walletId: request.walletId,
16468
16737
  providerUserId: request.providerSubjectId,
@@ -16510,7 +16779,7 @@ async function handleWalletCustodyCredentialsList(ctx) {
16510
16779
  const walletId = walletIdFromPath(route.path, ctx.pathname);
16511
16780
  if (!walletId) return routeErrorResponse(400, "invalid_request", "credential list needs a wallet");
16512
16781
  const body = await readJsonObject(ctx.request);
16513
- const authorized = await authorizeWalletCustodyOperation({
16782
+ const authorized = await authorizeWalletOwnerOperation({
16514
16783
  ctx,
16515
16784
  walletId,
16516
16785
  operation: "credentials_list",
@@ -16521,7 +16790,7 @@ async function handleWalletCustodyCredentialsList(ctx) {
16521
16790
  const parsedWalletId = parseWalletId(walletId);
16522
16791
  if (!parsedWalletId.ok) return routeErrorResponse(400, "invalid_request", "wallet id is invalid");
16523
16792
  const result = await ctx.service.passkeyCustody.listWalletCredentials({ walletId: parsedWalletId.value });
16524
- return await completeWalletCustodyOperation(ctx, authorized.operation, 200, {
16793
+ return await completeWalletOwnerOperation(ctx, authorized.operation, 200, {
16525
16794
  ok: true,
16526
16795
  credentials: result
16527
16796
  });
@@ -16589,7 +16858,7 @@ async function handleWalletCustodyCredentialLabel(ctx) {
16589
16858
  const envelopeId = normalizeOptionalTrimmedString(body?.envelopeId);
16590
16859
  const label = body?.label;
16591
16860
  if (!walletId || !envelopeId || label !== void 0 && typeof label !== "string") return routeErrorResponse(400, "invalid_request", "credential rename needs a wallet, envelope, and optional label");
16592
- const authorized = await authorizeWalletCustodyOperation({
16861
+ const authorized = await authorizeWalletOwnerOperation({
16593
16862
  ctx,
16594
16863
  walletId,
16595
16864
  operation: "credential_label",
@@ -16609,26 +16878,26 @@ async function handleWalletCustodyCredentialLabel(ctx) {
16609
16878
  ...label === void 0 ? {} : { label }
16610
16879
  });
16611
16880
  switch (result.kind) {
16612
- case "updated": return await completeWalletCustodyOperation(ctx, authorized.operation, 200, {
16881
+ case "updated": return await completeWalletOwnerOperation(ctx, authorized.operation, 200, {
16613
16882
  ok: true,
16614
16883
  credential: result.projection
16615
16884
  });
16616
- case "missing": return await completeWalletCustodyOperation(ctx, authorized.operation, 404, {
16885
+ case "missing": return await completeWalletOwnerOperation(ctx, authorized.operation, 404, {
16617
16886
  ok: false,
16618
16887
  code: "credential_not_found",
16619
16888
  message: "credential not found"
16620
16889
  }, "failed_before_side_effect");
16621
- case "conflict": return await completeWalletCustodyOperation(ctx, authorized.operation, 409, {
16890
+ case "conflict": return await completeWalletOwnerOperation(ctx, authorized.operation, 409, {
16622
16891
  ok: false,
16623
16892
  code: "credential_activity_conflict",
16624
16893
  message: "credential changed; retry"
16625
16894
  }, "failed_before_side_effect");
16626
- case "invalid_envelope_id": return await completeWalletCustodyOperation(ctx, authorized.operation, 400, {
16895
+ case "invalid_envelope_id": return await completeWalletOwnerOperation(ctx, authorized.operation, 400, {
16627
16896
  ok: false,
16628
16897
  code: "invalid_envelope_id",
16629
16898
  message: "credential envelope id is invalid"
16630
16899
  }, "failed_before_side_effect");
16631
- case "invalid_label": return await completeWalletCustodyOperation(ctx, authorized.operation, 400, {
16900
+ case "invalid_label": return await completeWalletOwnerOperation(ctx, authorized.operation, 400, {
16632
16901
  ok: false,
16633
16902
  code: "invalid_label",
16634
16903
  message: result.reason
@@ -16854,7 +17123,7 @@ function parseWalletRecoveryOperationRequest(body, fields, label) {
16854
17123
  if (!body) throw new Error(`${label} body is required`);
16855
17124
  requireExactObjectFields(body, fields, label);
16856
17125
  return {
16857
- recoveryOperationId: parseRequiredAuthorizationValue(parseWalletRecoveryOperationId(body.recoveryOperationId)),
17126
+ recoveryOperationId: requireParsed$4(parseWalletRecoveryOperationId(body.recoveryOperationId), "authorization"),
16858
17127
  reservationId: parseRecoveryCodeReservationId(body.reservationId)
16859
17128
  };
16860
17129
  }
@@ -16876,7 +17145,7 @@ function parseWalletRecoveryEmailOtpVerifyRequest(body) {
16876
17145
  "challengeId",
16877
17146
  "otpCode"
16878
17147
  ], "wallet recovery Email OTP verification"),
16879
- challengeId: parseRequiredAuthorizationValue(parseEmailOtpChallengeId(body?.challengeId)),
17148
+ challengeId: requireParsed$4(parseEmailOtpChallengeId(body?.challengeId), "authorization"),
16880
17149
  otpCode: requireTrimmedString(body?.otpCode, "otpCode")
16881
17150
  };
16882
17151
  }
@@ -16997,7 +17266,7 @@ function parseWireRequest(body, originHeader) {
16997
17266
  if (!body || typeof body !== "object") return null;
16998
17267
  const challengeId = normalizeOptionalTrimmedString(body.challengeId);
16999
17268
  const locator = parseEnvelopeRetrievalLocator(body.locator);
17000
- const webauthnAuthentication = parseWebAuthnAuthenticationCredential$1(body.webauthnAuthentication);
17269
+ const webauthnAuthentication = parseWebAuthnAuthenticationCredential$2(body.webauthnAuthentication);
17001
17270
  if (!challengeId || !locator || !webauthnAuthentication) return null;
17002
17271
  const expectedOrigin = normalizeOptionalTrimmedString(originHeader);
17003
17272
  if (!expectedOrigin) return null;
@@ -17179,13 +17448,6 @@ function parseEcdsaMaterialPossessionProofs(value) {
17179
17448
  };
17180
17449
  });
17181
17450
  }
17182
- function authorizedOperationReplay(operation) {
17183
- if (operation.lifecycle !== "completed") return routeErrorResponse(409, "recovery_in_progress", "wallet recovery is still in progress");
17184
- return new Response(operation.response.bodyText, {
17185
- status: operation.response.status,
17186
- headers: { "content-type": operation.response.contentType }
17187
- });
17188
- }
17189
17451
  async function handleWalletRecoveryBackupAcknowledge(ctx) {
17190
17452
  if (!matchRoute(ctx, RECOVERY_ACK_ROUTE_ID)) return null;
17191
17453
  let request;
@@ -17194,7 +17456,7 @@ async function handleWalletRecoveryBackupAcknowledge(ctx) {
17194
17456
  } catch {
17195
17457
  return routeErrorResponse(400, "invalid_request", "acknowledgement body is invalid");
17196
17458
  }
17197
- const authorized = await authorizeWalletCustodyOperation({
17459
+ const authorized = await authorizeWalletOwnerOperation({
17198
17460
  ctx,
17199
17461
  walletId: request.walletId,
17200
17462
  operation: "recovery_backup_acknowledge",
@@ -17210,17 +17472,17 @@ async function handleWalletRecoveryBackupAcknowledge(ctx) {
17210
17472
  walletId: request.walletId,
17211
17473
  expectedIssuedAtMs: request.expectedIssuedAtMs
17212
17474
  });
17213
- if (result.kind === "conflict") return await completeWalletCustodyOperation(ctx, authorized.operation, 409, {
17475
+ if (result.kind === "conflict") return await completeWalletOwnerOperation(ctx, authorized.operation, 409, {
17214
17476
  ok: false,
17215
17477
  code: "recovery_set_conflict",
17216
17478
  message: "the recovery set changed"
17217
17479
  }, "failed_before_side_effect");
17218
- if (result.kind === "no_recovery_set") return await completeWalletCustodyOperation(ctx, authorized.operation, 404, {
17480
+ if (result.kind === "no_recovery_set") return await completeWalletOwnerOperation(ctx, authorized.operation, 404, {
17219
17481
  ok: false,
17220
17482
  code: "no_recovery_set",
17221
17483
  message: "this wallet has no issued recovery codes to acknowledge"
17222
17484
  }, "failed_before_side_effect");
17223
- return await completeWalletCustodyOperation(ctx, authorized.operation, 200, {
17485
+ return await completeWalletOwnerOperation(ctx, authorized.operation, 200, {
17224
17486
  ok: true,
17225
17487
  issuedAtMs: result.issuedAtMs
17226
17488
  });
@@ -17258,7 +17520,7 @@ async function handleWalletRecoveryRotate(ctx) {
17258
17520
  } catch (error) {
17259
17521
  return routeErrorResponse(400, "invalid_request", error instanceof Error ? error.message : "replacement recovery set is invalid");
17260
17522
  }
17261
- const authorized = await authorizeWalletCustodyOperation({
17523
+ const authorized = await authorizeWalletOwnerOperation({
17262
17524
  ctx,
17263
17525
  walletId,
17264
17526
  operation: "recovery_rotate",
@@ -17279,22 +17541,22 @@ async function handleWalletRecoveryRotate(ctx) {
17279
17541
  expectedStoreVersion
17280
17542
  });
17281
17543
  switch (result.kind) {
17282
- case "rotated": return await completeWalletCustodyOperation(ctx, authorized.operation, 200, {
17544
+ case "rotated": return await completeWalletOwnerOperation(ctx, authorized.operation, 200, {
17283
17545
  ok: true,
17284
17546
  issuedAtMs: result.issuedAtMs,
17285
17547
  storeVersion: result.storeVersion
17286
17548
  });
17287
- case "no_recovery_set": return await completeWalletCustodyOperation(ctx, authorized.operation, 404, {
17549
+ case "no_recovery_set": return await completeWalletOwnerOperation(ctx, authorized.operation, 404, {
17288
17550
  ok: false,
17289
17551
  code: "no_recovery_set",
17290
17552
  message: "this wallet has no codes to rotate"
17291
17553
  }, "failed_before_side_effect");
17292
- case "conflict": return await completeWalletCustodyOperation(ctx, authorized.operation, 409, {
17554
+ case "conflict": return await completeWalletOwnerOperation(ctx, authorized.operation, 409, {
17293
17555
  ok: false,
17294
17556
  code: "recovery_set_conflict",
17295
17557
  message: "the recovery set changed during this rotation; try again"
17296
17558
  }, "failed_before_side_effect");
17297
- case "rejected": return await completeWalletCustodyOperation(ctx, authorized.operation, 400, {
17559
+ case "rejected": return await completeWalletOwnerOperation(ctx, authorized.operation, 400, {
17298
17560
  ok: false,
17299
17561
  code: "rotation_rejected",
17300
17562
  message: result.reason
@@ -17306,7 +17568,7 @@ async function handleWalletRecoveryRead(ctx) {
17306
17568
  const body = await readJsonObject(ctx.request);
17307
17569
  const walletId = normalizeOptionalTrimmedString(body?.walletId);
17308
17570
  if (!walletId) return routeErrorResponse(400, "invalid_request", "a read needs a wallet");
17309
- const authorized = await authorizeWalletCustodyOperation({
17571
+ const authorized = await authorizeWalletOwnerOperation({
17310
17572
  ctx,
17311
17573
  walletId,
17312
17574
  operation: "recovery_read",
@@ -17315,12 +17577,12 @@ async function handleWalletRecoveryRead(ctx) {
17315
17577
  });
17316
17578
  if (!authorized.ok) return authorized.response;
17317
17579
  const result = await ctx.service.passkeyCustody.readRecoverySet({ walletId });
17318
- if (result.kind === "no_recovery_set") return await completeWalletCustodyOperation(ctx, authorized.operation, 404, {
17580
+ if (result.kind === "no_recovery_set") return await completeWalletOwnerOperation(ctx, authorized.operation, 404, {
17319
17581
  ok: false,
17320
17582
  code: "no_recovery_set",
17321
17583
  message: "this wallet has no issued recovery codes"
17322
17584
  }, "failed_before_side_effect");
17323
- return await completeWalletCustodyOperation(ctx, authorized.operation, 200, {
17585
+ return await completeWalletOwnerOperation(ctx, authorized.operation, 200, {
17324
17586
  ok: true,
17325
17587
  recoverySet: result.record,
17326
17588
  storeVersion: result.storeVersion
@@ -17338,7 +17600,7 @@ async function handleWalletRecoveryStatus(ctx) {
17338
17600
  } catch {
17339
17601
  return routeErrorResponse(400, "invalid_request", "status body is invalid");
17340
17602
  }
17341
- const authorized = await authorizeWalletCustodyOperation({
17603
+ const authorized = await authorizeWalletOwnerOperation({
17342
17604
  ctx,
17343
17605
  walletId,
17344
17606
  operation: "recovery_status",
@@ -17350,12 +17612,12 @@ async function handleWalletRecoveryStatus(ctx) {
17350
17612
  });
17351
17613
  if (!authorized.ok) return authorized.response;
17352
17614
  const result = await ctx.service.passkeyCustody.readRecoveryStatus({ walletId });
17353
- if (result.kind === "no_recovery_set") return await completeWalletCustodyOperation(ctx, authorized.operation, 404, {
17615
+ if (result.kind === "no_recovery_set") return await completeWalletOwnerOperation(ctx, authorized.operation, 404, {
17354
17616
  ok: false,
17355
17617
  code: "no_recovery_set",
17356
17618
  message: "this wallet has no recovery codes"
17357
17619
  }, "failed_before_side_effect");
17358
- return await completeWalletCustodyOperation(ctx, authorized.operation, 200, {
17620
+ return await completeWalletOwnerOperation(ctx, authorized.operation, 200, {
17359
17621
  ok: true,
17360
17622
  activeCodeCount: result.activeCodeCount,
17361
17623
  totalCodeCount: result.totalCodeCount,
@@ -23789,7 +24051,7 @@ function parseSyncAccountVerifyRequest(input) {
23789
24051
  if (unexpectedKey) return invalidSyncAccountBody(`Unsupported sync-account verify field: ${unexpectedKey}`);
23790
24052
  const challengeId = normalizeOptionalTrimmedString(input.body.challengeId);
23791
24053
  if (!challengeId) return invalidSyncAccountBody("challengeId is required");
23792
- const webauthnAuthentication = parseWebAuthnAuthenticationCredential$1(input.body.webauthn_authentication);
24054
+ const webauthnAuthentication = parseWebAuthnAuthenticationCredential$2(input.body.webauthn_authentication);
23793
24055
  if (!webauthnAuthentication) return invalidSyncAccountBody("webauthn_authentication is required");
23794
24056
  const expectedOrigin = normalizeOptionalTrimmedString(input.origin);
23795
24057
  if (!expectedOrigin) return invalidSyncAccountBody("Origin header is required");
@@ -24549,6 +24811,40 @@ function isWalletSessionUnavailableStepUpError(result) {
24549
24811
  return isPlainObject(result.body) && result.body.code === "wallet_session_unavailable";
24550
24812
  }
24551
24813
  async function authenticateRouterAbWalletOperationStepUp(input) {
24814
+ if (!extractBearerCredential(input.headers) && input.authorizedOperations) {
24815
+ const origin = parseStepUpRequestOrigin(input.headers);
24816
+ if (!origin.ok) return origin;
24817
+ const operationId = requireAuthorizationValue$2(parseAuthorizedOperationId(`normal-signing-operation:${input.scope.request_id}`));
24818
+ const parsedWalletId = parseWalletId(input.scope.account_id);
24819
+ if (!parsedWalletId.ok) return walletSessionScopeInvalidFailure();
24820
+ const walletId = parsedWalletId.value;
24821
+ const recorded = await input.authorizedOperations.resolveRecordedPasskeyOwnerOperation({
24822
+ authorizedOperationId: operationId,
24823
+ walletId,
24824
+ requestOrigin: origin.requestOrigin,
24825
+ nowMs: Date.now()
24826
+ });
24827
+ if (!recorded || input.requestExpiresAtMs > recorded.expiresAtMs) return {
24828
+ ok: false,
24829
+ error: routerAbStepUpError(403, "unauthorized", "Recorded owner approval is unavailable")
24830
+ };
24831
+ const activeMaterial = await input.resolveEd25519MaterialActivation({
24832
+ walletId,
24833
+ materialActivation: input.scope.material_activation
24834
+ });
24835
+ if (!activeMaterial.ok) return walletSessionScopeInvalidFailure();
24836
+ return authenticateRouterAbWalletOperationStepUpIdentity({
24837
+ kind: "verified_owner_proof",
24838
+ headers: input.headers,
24839
+ walletId,
24840
+ materialOwner: input.scope.material_activation.material_owner,
24841
+ operationId: recorded.operation.operation.operationId,
24842
+ authority: recorded.authority,
24843
+ runtimePolicyScope: activeMaterial.runtimePolicyScope,
24844
+ expiresAtMs: recorded.expiresAtMs,
24845
+ authorizedOperations: input.authorizedOperations
24846
+ });
24847
+ }
24552
24848
  return authenticateRouterAbWalletOperationStepUpIdentity({
24553
24849
  kind: "wallet_session_operation_credential_v1",
24554
24850
  headers: input.headers,
@@ -26694,7 +26990,7 @@ function parsePasskeyLoginVerifyRequest(input) {
26694
26990
  if (!keys.ok) return keys;
26695
26991
  const challengeId = requireTrimmedField(body.request, "challengeId");
26696
26992
  if (!challengeId.ok) return challengeId;
26697
- const webauthnAuthentication = parseWebAuthnAuthenticationCredential$1(body.request.webauthn_authentication);
26993
+ const webauthnAuthentication = parseWebAuthnAuthenticationCredential$2(body.request.webauthn_authentication);
26698
26994
  if (!webauthnAuthentication) return invalidAuthBody("webauthn_authentication is required");
26699
26995
  const expectedOrigin = normalizeOptionalTrimmedString(input.origin);
26700
26996
  if (!expectedOrigin) return invalidAuthBody("Origin header is required");
@@ -26739,7 +27035,7 @@ function parseGoogleLoginVerifyRequest(raw) {
26739
27035
  function parsePasskeyStepUpRequest(input) {
26740
27036
  const challengeId = requireTrimmedField(input.body, "step_up_challenge_id");
26741
27037
  if (!challengeId.ok) return challengeId;
26742
- const webauthnAuthentication = parseWebAuthnAuthenticationCredential$1(input.body.webauthn_authentication);
27038
+ const webauthnAuthentication = parseWebAuthnAuthenticationCredential$2(input.body.webauthn_authentication);
26743
27039
  if (!webauthnAuthentication) return invalidAuthBody("webauthn_authentication is required for step-up");
26744
27040
  const expectedOrigin = normalizeOptionalTrimmedString(input.origin);
26745
27041
  if (!expectedOrigin) return invalidAuthBody("Origin header is required");
@@ -32656,7 +32952,7 @@ var CloudflareD1WebAuthnAuthService = class {
32656
32952
  const rpId = parseWebAuthnRpId(input.rpId);
32657
32953
  const expectedChallenge = normalizeOptionalTrimmedString(input.expectedChallenge);
32658
32954
  const expectedOrigin = normalizeOptionalTrimmedString(input.expected_origin);
32659
- const credential = parseWebAuthnAuthenticationCredential$1(input.webauthn_authentication);
32955
+ const credential = parseWebAuthnAuthenticationCredential$2(input.webauthn_authentication);
32660
32956
  if (!userId.ok) return {
32661
32957
  success: false,
32662
32958
  verified: false,
@@ -32796,7 +33092,7 @@ var CloudflareD1WebAuthnAuthService = class {
32796
33092
  if (!expectedOrigin) return failedVerification("invalid_body", "expected_origin is required for WebAuthn authentication verification");
32797
33093
  const rpId = parseWebAuthnRpId(challenge.rpId);
32798
33094
  if (!rpId.ok) return failedVerification("internal", `Stored login challenge rpId is invalid: ${rpId.error.message}`);
32799
- const credential = parseWebAuthnAuthenticationCredential$1(input.webauthn_authentication);
33095
+ const credential = parseWebAuthnAuthenticationCredential$2(input.webauthn_authentication);
32800
33096
  if (!credential) return failedVerification("invalid_body", "Missing webauthn_authentication");
32801
33097
  const credentialId = webAuthnCredentialIdB64uFromCredential(credential);
32802
33098
  if (!credentialId.ok) return failedVerification(credentialId.code, credentialId.message);
@@ -32837,7 +33133,7 @@ var CloudflareD1WebAuthnAuthService = class {
32837
33133
  try {
32838
33134
  const challengeId = normalizeOptionalTrimmedString(input.challengeId ?? input.challenge_id);
32839
33135
  if (!challengeId) return failure$6("invalid_body", "Missing challengeId");
32840
- const credential = parseWebAuthnAuthenticationCredential$1(input.webauthn_authentication);
33136
+ const credential = parseWebAuthnAuthenticationCredential$2(input.webauthn_authentication);
32841
33137
  if (!credential) return failedVerification("invalid_body", "Missing webauthn_authentication");
32842
33138
  const credentialId = webAuthnCredentialIdB64uFromCredential(credential);
32843
33139
  if (!credentialId.ok) return failedVerification(credentialId.code, credentialId.message);
@@ -39007,7 +39303,7 @@ function parsePasskeyExportAdmissionAuthorization(authorization) {
39007
39303
  ok: false,
39008
39304
  message: `passkey export authorization has unknown field: ${unexpectedField}`
39009
39305
  };
39010
- const webauthnAuthentication = parseWebAuthnAuthenticationCredential$1(authorization.webauthnAuthentication);
39306
+ const webauthnAuthentication = parseWebAuthnAuthenticationCredential$2(authorization.webauthnAuthentication);
39011
39307
  if (!webauthnAuthentication) return {
39012
39308
  ok: false,
39013
39309
  message: "webauthnAuthentication is invalid"