@seams/wallet-server 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +182 -5
- package/artifact-manifest.json +23 -13
- package/cloudflare-router-ab/build/deriver-a/build-stamp.json +1 -1
- package/cloudflare-router-ab/build/deriver-b/build-stamp.json +1 -1
- package/cloudflare-router-ab/build/router/build-stamp.json +1 -1
- package/cloudflare-router-ab/build/signing-worker/build-stamp.json +1 -1
- package/cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json +1 -1
- package/dist/esm/authorization/voiceIdDeviceProof.js +32 -0
- package/dist/esm/core/ThresholdService/validation.js +17 -1
- package/dist/esm/core/d1WalletAuthMethodStore.js +42 -1
- package/dist/esm/core/deviceLinking/linkedDeviceManagement.js +5 -0
- package/dist/esm/core/signingLanes/WalletExecutionLaneProjection.js +11 -2
- package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +23 -6
- package/dist/esm/router/cloudflare/d1/deviceLinking/d1LinkedDeviceAuthorityInstallService.js +5 -5
- package/dist/esm/router/cloudflare/d1/emailOtp/d1GoogleEmailOtpRegistrationAttemptStore.js +1 -1
- package/dist/esm/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyEnvelopeStore.js +6 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdActivation.js +139 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdSessions.js +138 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdWalletSessionChallenges.js +88 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthMethodService.js +12 -4
- package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthorityStore.js +14 -0
- package/dist/esm/router/cloudflare/d1/webauthn/d1WebAuthnRecords.js +1 -0
- package/dist/esm/router/cloudflare.js +576 -10
- package/dist/esm/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.js +5 -0
- package/dist/esm/router/domains/passkeyCustody/walletRecoveryFinalization.js +1 -0
- package/dist/esm/router/domains/signingOperations/routerAbNormalSigningAdmissionCore.js +1 -0
- package/dist/esm/router/domains/signingOperations/walletExecutionAdmission.js +1 -0
- package/dist/esm/router/domains/voiceId/voiceIdActivation.js +12 -0
- package/dist/esm/router/domains/voiceId/voiceIdSessions.js +0 -0
- package/dist/esm/router/framework/routeDefinitions.js +13 -0
- package/dist/esm/router/transport/fetch/createFetchRouter.js +5 -0
- package/dist/esm/router/transport/fetch/routes/deviceLinkingOwnerAuthorization.js +12 -1
- package/dist/esm/router/transport/fetch/routes/walletExecutionLanePreflight.js +1 -0
- package/dist/esm/router/transport/fetch/routes/walletVoiceIdActivation.js +139 -0
- package/dist/esm/router/transport/fetch/routes/walletVoiceIdNear.js +36 -0
- package/dist/esm/router/transport/fetch/routes/walletVoiceIdSessions.js +127 -0
- package/dist/esm/shared-ts/src/authorization/voiceIdDeviceAssertion.js +110 -0
- package/dist/esm/shared-ts/src/device-linking/authorityActivation.js +1 -0
- package/dist/esm/shared-ts/src/device-linking/committedSignerPackages.js +1 -1
- package/dist/esm/shared-ts/src/passkey-custody/custodyEnvelope.js +30 -3
- package/dist/esm/shared-ts/src/passkey-custody/custodySecretBinding.js +1 -1
- package/dist/esm/shared-ts/src/signing-lanes/recordParsers.js +15 -1
- package/dist/esm/shared-ts/src/threshold/sessionPolicy.js +2 -1
- package/dist/esm/shared-ts/src/utils/registrationIntent.js +1 -3
- package/dist/esm/shared-ts/src/utils/walletAuthAuthority.js +46 -2
- package/dist/esm/shared-ts/src/utils/walletAuthMethodRecord.js +116 -7
- package/dist/esm/shared-ts/src/wallet-recovery/walletRecoveryCommittedProjection.js +1 -0
- package/dist/esm/wasm/near_signer/pkg/wasm_signer_worker_bg.wasm +0 -0
- package/dist/types/shared-ts/src/authorization/voiceIdDeviceAssertion.d.ts +32 -0
- package/dist/types/shared-ts/src/device-linking/committedSignerPackages.d.ts +3 -1
- package/dist/types/shared-ts/src/device-linking/contracts.d.ts +8 -0
- package/dist/types/shared-ts/src/passkey-custody/custodyEnvelope.d.ts +29 -0
- package/dist/types/shared-ts/src/passkey-custody/custodySecretBinding.d.ts +1 -0
- package/dist/types/shared-ts/src/signing-lanes/execution.d.ts +2 -2
- package/dist/types/shared-ts/src/signing-lanes/recordParsers.d.ts +2 -1
- package/dist/types/shared-ts/src/signing-lanes/records.d.ts +10 -2
- package/dist/types/shared-ts/src/signing-lanes/rotation.d.ts +2 -2
- package/dist/types/shared-ts/src/threshold/sessionPolicy.d.ts +9 -0
- package/dist/types/shared-ts/src/utils/addWalletAuthMethod.d.ts +3 -3
- package/dist/types/shared-ts/src/utils/domainIds.d.ts +4 -5
- package/dist/types/shared-ts/src/utils/registrationIntent.d.ts +0 -1
- package/dist/types/shared-ts/src/utils/signerDomain.d.ts +3 -0
- package/dist/types/shared-ts/src/utils/walletAuthAuthority.d.ts +32 -2
- package/dist/types/shared-ts/src/utils/walletAuthMethodRecord.d.ts +15 -1
- package/dist/types/shared-ts/src/utils/walletCapabilityBindings.d.ts +20 -0
- package/dist/types/wallet-server/src/authorization/voiceIdDeviceProof.d.ts +29 -0
- package/dist/types/wallet-server/src/core/d1WalletAuthMethodStore.d.ts +13 -2
- package/dist/types/wallet-server/src/core/registrationContracts.d.ts +8 -0
- package/dist/types/wallet-server/src/core/signingLanes/WalletExecutionLaneProjection.d.ts +4 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyEnvelopeStore.d.ts +4 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdActivation.d.ts +14 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdSessions.d.ts +19 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdWalletSessionChallenges.d.ts +26 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1WalletAuthMethodService.d.ts +1 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1WalletRelocationOwner.d.ts +25 -0
- package/dist/types/wallet-server/src/router/domains/voiceId/voiceIdActivation.d.ts +29 -0
- package/dist/types/wallet-server/src/router/domains/voiceId/voiceIdSessions.d.ts +35 -0
- package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +3 -0
- package/dist/types/wallet-server/src/router/transport/fetch/routes/walletVoiceIdActivation.d.ts +12 -0
- package/dist/types/wallet-server/src/router/transport/fetch/routes/walletVoiceIdNear.d.ts +14 -0
- package/dist/types/wallet-server/src/router/transport/fetch/routes/walletVoiceIdSessions.d.ts +19 -0
- package/migrations/d1-signer/0083_voiceid_wallet_session_challenges.sql +29 -0
- package/migrations/d1-signer/0084_voiceid_wallet_auth_methods.sql +373 -0
- package/package.json +1 -1
|
@@ -257,6 +257,7 @@ function runtimePolicyScopeKey(scope) {
|
|
|
257
257
|
}
|
|
258
258
|
function ed25519AdmissionAuthorityScopeKey(scope) {
|
|
259
259
|
switch (scope.kind) {
|
|
260
|
+
case "voiceid": return `voiceid:${scope.deviceKeyId}`;
|
|
260
261
|
case "passkey_rp": return `passkey_rp:${scope.rpId}`;
|
|
261
262
|
case "email_otp": return `email_otp:${scope.provider}:${scope.providerUserId}`;
|
|
262
263
|
}
|
|
@@ -1536,6 +1537,20 @@ function parseWalletCapabilitySubjectV1(raw, label) {
|
|
|
1536
1537
|
//#endregion
|
|
1537
1538
|
//#region ../shared-ts/src/utils/walletAuthAuthority.ts
|
|
1538
1539
|
const WALLET_AUTHORITY_BINDING_DIGEST_DOMAIN = "seams:wallet-authority-binding:v1|";
|
|
1540
|
+
function buildVoiceIdWalletAuthAuthority(input) {
|
|
1541
|
+
return {
|
|
1542
|
+
walletId: input.walletId,
|
|
1543
|
+
bindingId: input.walletAuthMethodId,
|
|
1544
|
+
factor: {
|
|
1545
|
+
kind: "voiceid",
|
|
1546
|
+
deviceKeyId: input.deviceKeyId
|
|
1547
|
+
},
|
|
1548
|
+
verifier: { kind: "device_signature" }
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function isVoiceIdWalletAuthAuthority(authority) {
|
|
1552
|
+
return authority.factor.kind === "voiceid";
|
|
1553
|
+
}
|
|
1539
1554
|
function isPasskeyWalletAuthAuthority(authority) {
|
|
1540
1555
|
return authority.factor.kind === "passkey";
|
|
1541
1556
|
}
|
|
@@ -1705,7 +1720,33 @@ function parseEmailOtpWalletAuthAuthorityObject(obj) {
|
|
|
1705
1720
|
function parseWalletAuthAuthority(raw) {
|
|
1706
1721
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
1707
1722
|
const obj = raw;
|
|
1708
|
-
return parsePasskeyWalletAuthAuthorityObject(obj) || parseEmailOtpWalletAuthAuthorityObject(obj);
|
|
1723
|
+
return parsePasskeyWalletAuthAuthorityObject(obj) || parseEmailOtpWalletAuthAuthorityObject(obj) || parseVoiceIdWalletAuthAuthority(obj);
|
|
1724
|
+
}
|
|
1725
|
+
function parseVoiceIdWalletAuthAuthority(raw) {
|
|
1726
|
+
try {
|
|
1727
|
+
const record = requireRecord$1(raw, "device authority");
|
|
1728
|
+
rejectUnknownFields(record, [
|
|
1729
|
+
"walletId",
|
|
1730
|
+
"bindingId",
|
|
1731
|
+
"factor",
|
|
1732
|
+
"verifier"
|
|
1733
|
+
], "device authority");
|
|
1734
|
+
const factor = requireRecord$1(record.factor, "device factor");
|
|
1735
|
+
const verifier = requireRecord$1(record.verifier, "device verifier");
|
|
1736
|
+
rejectUnknownFields(factor, ["kind", "deviceKeyId"], "device factor");
|
|
1737
|
+
rejectUnknownFields(verifier, ["kind"], "device verifier");
|
|
1738
|
+
if (factor.kind !== "voiceid" || verifier.kind !== "device_signature") return null;
|
|
1739
|
+
const walletId = parseWalletId(record.walletId);
|
|
1740
|
+
const method = parseWalletAuthMethodId(record.bindingId);
|
|
1741
|
+
if (!walletId.ok || !method.ok) return null;
|
|
1742
|
+
return buildVoiceIdWalletAuthAuthority({
|
|
1743
|
+
walletId: walletId.value,
|
|
1744
|
+
walletAuthMethodId: method.value,
|
|
1745
|
+
deviceKeyId: parseDigestB64u(factor.deviceKeyId)
|
|
1746
|
+
});
|
|
1747
|
+
} catch {
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1709
1750
|
}
|
|
1710
1751
|
function canonicalWalletAuthorityBindingDigestInput(args) {
|
|
1711
1752
|
return `${WALLET_AUTHORITY_BINDING_DIGEST_DOMAIN}${alphabetizeStringify(args.authority)}`;
|
|
@@ -5433,6 +5474,7 @@ const ROUTER_API_PASSKEY_CUSTODY_SERVICES = [
|
|
|
5433
5474
|
];
|
|
5434
5475
|
const ROUTER_API_LINKED_DEVICE_PASSKEY_SERVICES = ["publishableKeyAuth"];
|
|
5435
5476
|
const ROUTER_API_WALLET_UNLOCK_SERVICES = ["walletUnlock"];
|
|
5477
|
+
const ROUTER_API_VOICEID_ACTIVATION_SERVICES = ["session", "walletAuthMethods"];
|
|
5436
5478
|
const ROUTER_API_HOSTED_SESSION_EXCHANGE_SERVICES = ["session"];
|
|
5437
5479
|
const ROUTER_API_EMAIL_OTP_PUBLIC_SERVICES = ["emailOtp"];
|
|
5438
5480
|
const ROUTER_API_EMAIL_OTP_CHALLENGE_SERVICES = ["emailOtp", "walletAuthMethods"];
|
|
@@ -5808,6 +5850,18 @@ function createRouterApiRouteDefinitions(options = {}) {
|
|
|
5808
5850
|
plane: "public",
|
|
5809
5851
|
proof: "challenge_exchange",
|
|
5810
5852
|
rationale: "Wallet unlock verification is intentionally public because the challenge proof is the gate."
|
|
5853
|
+
}, ROUTER_API_WALLET_UNLOCK_SERVICES), publicRoute("wallet_voiceid_activate", "POST", "/wallet/voiceid/activate", "Activate a device-bound VoiceID method under an owner Wallet Session", {
|
|
5854
|
+
plane: "public",
|
|
5855
|
+
proof: "challenge_exchange",
|
|
5856
|
+
rationale: "An active full-owner Wallet Session and the hosted wallet origin authorize activation."
|
|
5857
|
+
}, ROUTER_API_VOICEID_ACTIVATION_SERVICES), publicRoute("wallet_voiceid_challenge", "POST", "/wallet/voiceid/challenge", "Issue a device-bound VoiceID Wallet Session challenge", {
|
|
5858
|
+
plane: "public",
|
|
5859
|
+
proof: "challenge_exchange",
|
|
5860
|
+
rationale: "An active method receives a short-lived challenge bound to the hosted wallet origin."
|
|
5861
|
+
}, ROUTER_API_WALLET_UNLOCK_SERVICES), publicRoute("wallet_voiceid_verify", "POST", "/wallet/voiceid/verify", "Verify a VoiceID device assertion and issue a Wallet Session", {
|
|
5862
|
+
plane: "public",
|
|
5863
|
+
proof: "signed_payload",
|
|
5864
|
+
rationale: "The registered device signature and single-use server challenge authorize issuance."
|
|
5811
5865
|
}, ROUTER_API_WALLET_UNLOCK_SERVICES), publicRoute("wallet_email_otp_challenge", "POST", "/wallet/email-otp/challenge", "Create an operation-bound Email OTP challenge", {
|
|
5812
5866
|
plane: "public",
|
|
5813
5867
|
proof: "challenge_exchange",
|
|
@@ -7530,6 +7584,102 @@ function exactRecord$2(raw, keys, label) {
|
|
|
7530
7584
|
return record;
|
|
7531
7585
|
}
|
|
7532
7586
|
//#endregion
|
|
7587
|
+
//#region ../shared-ts/src/authorization/voiceIdDeviceAssertion.ts
|
|
7588
|
+
const MAXIMUM_CHALLENGE_MS = 6e4;
|
|
7589
|
+
function parseVoiceIdDevicePublicKeyB64u(value) {
|
|
7590
|
+
if (typeof value !== "string") throw new Error("VoiceID public key is invalid");
|
|
7591
|
+
const bytes = base64UrlDecode(value);
|
|
7592
|
+
if (bytes.length !== 32 || base64UrlEncode(bytes) !== value) throw new Error("VoiceID public key must be canonical Ed25519 bytes");
|
|
7593
|
+
return value;
|
|
7594
|
+
}
|
|
7595
|
+
function exactKeys(value, keys) {
|
|
7596
|
+
if (Object.keys(value).length !== keys.length) return false;
|
|
7597
|
+
for (const key of keys) if (!Object.hasOwn(value, key)) return false;
|
|
7598
|
+
return true;
|
|
7599
|
+
}
|
|
7600
|
+
function requireOrigin(value, label) {
|
|
7601
|
+
if (typeof value !== "string") throw new Error(`${label} must be an origin`);
|
|
7602
|
+
let origin;
|
|
7603
|
+
let protocol;
|
|
7604
|
+
try {
|
|
7605
|
+
const parsed = new URL(value);
|
|
7606
|
+
origin = parsed.origin;
|
|
7607
|
+
protocol = parsed.protocol;
|
|
7608
|
+
} catch {
|
|
7609
|
+
throw new Error(`${label} must be an origin`);
|
|
7610
|
+
}
|
|
7611
|
+
if (origin !== value || protocol !== "http:" && protocol !== "https:") throw new Error(`${label} must be a canonical HTTP origin`);
|
|
7612
|
+
return origin;
|
|
7613
|
+
}
|
|
7614
|
+
function requireWindow(issuedAtMs, expiresAtMs) {
|
|
7615
|
+
if (!Number.isSafeInteger(issuedAtMs) || !Number.isSafeInteger(expiresAtMs) || Number(issuedAtMs) <= 0 || Number(expiresAtMs) <= Number(issuedAtMs) || Number(expiresAtMs) - Number(issuedAtMs) > MAXIMUM_CHALLENGE_MS) throw new Error("VoiceID challenge has an invalid validity window");
|
|
7616
|
+
}
|
|
7617
|
+
function buildVoiceIdWalletSessionChallenge(fields) {
|
|
7618
|
+
requireWindow(fields.issuedAtMs, fields.expiresAtMs);
|
|
7619
|
+
requireOrigin(fields.requestOrigin, "VoiceID requestOrigin");
|
|
7620
|
+
requireOrigin(fields.audience, "VoiceID audience");
|
|
7621
|
+
return {
|
|
7622
|
+
kind: "voiceid_wallet_session_challenge_v1",
|
|
7623
|
+
walletId: fields.walletId,
|
|
7624
|
+
walletAuthMethodId: fields.walletAuthMethodId,
|
|
7625
|
+
walletAuthorityId: fields.walletAuthorityId,
|
|
7626
|
+
deviceKeyId: fields.deviceKeyId,
|
|
7627
|
+
challengeId: fields.challengeId,
|
|
7628
|
+
nonce: fields.nonce,
|
|
7629
|
+
requestOrigin: fields.requestOrigin,
|
|
7630
|
+
audience: fields.audience,
|
|
7631
|
+
issuedAtMs: fields.issuedAtMs,
|
|
7632
|
+
expiresAtMs: fields.expiresAtMs
|
|
7633
|
+
};
|
|
7634
|
+
}
|
|
7635
|
+
function parseVoiceIdWalletSessionChallenge(raw) {
|
|
7636
|
+
if (!isPlainObject(raw) || !exactKeys(raw, [
|
|
7637
|
+
"kind",
|
|
7638
|
+
"walletId",
|
|
7639
|
+
"walletAuthMethodId",
|
|
7640
|
+
"walletAuthorityId",
|
|
7641
|
+
"deviceKeyId",
|
|
7642
|
+
"challengeId",
|
|
7643
|
+
"nonce",
|
|
7644
|
+
"requestOrigin",
|
|
7645
|
+
"audience",
|
|
7646
|
+
"issuedAtMs",
|
|
7647
|
+
"expiresAtMs"
|
|
7648
|
+
]) || raw.kind !== "voiceid_wallet_session_challenge_v1") throw new Error("Invalid VoiceID Wallet Session challenge");
|
|
7649
|
+
const walletId = parseWalletId(raw.walletId);
|
|
7650
|
+
const walletAuthMethodId = parseWalletAuthMethodId(raw.walletAuthMethodId);
|
|
7651
|
+
const walletAuthorityId = parseWalletAuthorityId(raw.walletAuthorityId);
|
|
7652
|
+
const challengeId = parseWalletSessionMintId(raw.challengeId);
|
|
7653
|
+
if (!walletId.ok || !walletAuthMethodId.ok || !walletAuthorityId.ok || !challengeId.ok) throw new Error("VoiceID challenge identity is invalid");
|
|
7654
|
+
if (typeof raw.issuedAtMs !== "number" || typeof raw.expiresAtMs !== "number") throw new Error("VoiceID challenge times must be numbers");
|
|
7655
|
+
return buildVoiceIdWalletSessionChallenge({
|
|
7656
|
+
walletId: walletId.value,
|
|
7657
|
+
walletAuthMethodId: walletAuthMethodId.value,
|
|
7658
|
+
walletAuthorityId: walletAuthorityId.value,
|
|
7659
|
+
deviceKeyId: parseDigestB64u(raw.deviceKeyId),
|
|
7660
|
+
challengeId: challengeId.value,
|
|
7661
|
+
nonce: parseDigestB64u(raw.nonce),
|
|
7662
|
+
requestOrigin: requireOrigin(raw.requestOrigin, "VoiceID requestOrigin"),
|
|
7663
|
+
audience: requireOrigin(raw.audience, "VoiceID audience"),
|
|
7664
|
+
issuedAtMs: raw.issuedAtMs,
|
|
7665
|
+
expiresAtMs: raw.expiresAtMs
|
|
7666
|
+
});
|
|
7667
|
+
}
|
|
7668
|
+
function parseVoiceIdDeviceAssertion(raw) {
|
|
7669
|
+
if (!isPlainObject(raw) || !exactKeys(raw, [
|
|
7670
|
+
"kind",
|
|
7671
|
+
"challenge",
|
|
7672
|
+
"signatureB64u"
|
|
7673
|
+
]) || raw.kind !== "voiceid_device_assertion_v1" || typeof raw.signatureB64u !== "string") throw new Error("Invalid VoiceID device assertion");
|
|
7674
|
+
const signature = base64UrlDecode(raw.signatureB64u);
|
|
7675
|
+
if (signature.length !== 64 || base64UrlEncode(signature) !== raw.signatureB64u) throw new Error("VoiceID signature must be canonical Ed25519 bytes");
|
|
7676
|
+
return {
|
|
7677
|
+
kind: "voiceid_device_assertion_v1",
|
|
7678
|
+
challenge: parseVoiceIdWalletSessionChallenge(raw.challenge),
|
|
7679
|
+
signatureB64u: raw.signatureB64u
|
|
7680
|
+
};
|
|
7681
|
+
}
|
|
7682
|
+
//#endregion
|
|
7533
7683
|
//#region ../shared-ts/src/utils/registrationAuthMethodInput.ts
|
|
7534
7684
|
function inspectRawObject$2(value) {
|
|
7535
7685
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -7660,7 +7810,7 @@ function parseWalletAuthMethodRecordV2(raw) {
|
|
|
7660
7810
|
const version = trimString(record.version);
|
|
7661
7811
|
const kind = trimString(record.kind);
|
|
7662
7812
|
const status = trimString(record.status);
|
|
7663
|
-
if (version !== "wallet_auth_method_v2" || kind !== "passkey" && kind !== "email_otp") return null;
|
|
7813
|
+
if (version !== "wallet_auth_method_v2" || kind !== "passkey" && kind !== "email_otp" && kind !== "voiceid") return null;
|
|
7664
7814
|
try {
|
|
7665
7815
|
const common = parseWalletAuthMethodRecordV2Common(record);
|
|
7666
7816
|
if (kind === "passkey") {
|
|
@@ -7678,6 +7828,16 @@ function parseWalletAuthMethodRecordV2(raw) {
|
|
|
7678
7828
|
counter: parseNonNegativeInteger(record.counter)
|
|
7679
7829
|
});
|
|
7680
7830
|
}
|
|
7831
|
+
if (kind === "voiceid") {
|
|
7832
|
+
if (!("deviceKeyId" in record) || !("devicePublicKeyB64u" in record)) return null;
|
|
7833
|
+
exactWalletAuthMethodV2Fields(record, "voiceid", status);
|
|
7834
|
+
return buildParsedVoiceIdWalletAuthMethodRecordV2({
|
|
7835
|
+
common,
|
|
7836
|
+
lifecycle: parseWalletAuthMethodLifecycle(record),
|
|
7837
|
+
deviceKeyId: parseDigestB64u(record.deviceKeyId),
|
|
7838
|
+
devicePublicKeyB64u: parseVoiceIdDevicePublicKeyB64u(record.devicePublicKeyB64u)
|
|
7839
|
+
});
|
|
7840
|
+
}
|
|
7681
7841
|
if (!("emailHashHex" in record) || !("registrationAuthorityId" in record)) return null;
|
|
7682
7842
|
exactWalletAuthMethodV2Fields(record, "email_otp", status);
|
|
7683
7843
|
const emailHashHex = trimString(record.emailHashHex);
|
|
@@ -7736,7 +7896,8 @@ function exactWalletAuthMethodV2Fields(raw, kind, status) {
|
|
|
7736
7896
|
"updatedAtMs"
|
|
7737
7897
|
];
|
|
7738
7898
|
if (kind === "passkey") fields.push("rpId", "credentialIdB64u", "credentialPublicKeyB64u", "counter");
|
|
7739
|
-
else fields.push("emailHashHex", "registrationAuthorityId");
|
|
7899
|
+
else if (kind === "email_otp") fields.push("emailHashHex", "registrationAuthorityId");
|
|
7900
|
+
else fields.push("deviceKeyId", "devicePublicKeyB64u");
|
|
7740
7901
|
if (status === "active") fields.push("activatedAtMs");
|
|
7741
7902
|
if (status === "revoked") fields.push("activatedAtMs", "revokedAtMs");
|
|
7742
7903
|
if (status !== "pending_local_install" && status !== "active" && status !== "revoked") throw new Error("wallet auth method status is unsupported");
|
|
@@ -7837,6 +7998,49 @@ function buildParsedEmailOtpWalletAuthMethodRecordV2(input) {
|
|
|
7837
7998
|
};
|
|
7838
7999
|
}
|
|
7839
8000
|
}
|
|
8001
|
+
function buildParsedVoiceIdWalletAuthMethodRecordV2(input) {
|
|
8002
|
+
switch (input.lifecycle.status) {
|
|
8003
|
+
case "pending_local_install": return {
|
|
8004
|
+
version: "wallet_auth_method_v2",
|
|
8005
|
+
walletAuthMethodId: input.common.walletAuthMethodId,
|
|
8006
|
+
walletId: input.common.walletId,
|
|
8007
|
+
walletAuthorityId: input.common.walletAuthorityId,
|
|
8008
|
+
kind: "voiceid",
|
|
8009
|
+
status: "pending_local_install",
|
|
8010
|
+
createdAtMs: input.common.createdAtMs,
|
|
8011
|
+
updatedAtMs: input.common.updatedAtMs,
|
|
8012
|
+
deviceKeyId: input.deviceKeyId,
|
|
8013
|
+
devicePublicKeyB64u: input.devicePublicKeyB64u
|
|
8014
|
+
};
|
|
8015
|
+
case "active": return {
|
|
8016
|
+
version: "wallet_auth_method_v2",
|
|
8017
|
+
walletAuthMethodId: input.common.walletAuthMethodId,
|
|
8018
|
+
walletId: input.common.walletId,
|
|
8019
|
+
walletAuthorityId: input.common.walletAuthorityId,
|
|
8020
|
+
kind: "voiceid",
|
|
8021
|
+
status: "active",
|
|
8022
|
+
createdAtMs: input.common.createdAtMs,
|
|
8023
|
+
updatedAtMs: input.common.updatedAtMs,
|
|
8024
|
+
deviceKeyId: input.deviceKeyId,
|
|
8025
|
+
devicePublicKeyB64u: input.devicePublicKeyB64u,
|
|
8026
|
+
activatedAtMs: input.lifecycle.activatedAtMs
|
|
8027
|
+
};
|
|
8028
|
+
case "revoked": return {
|
|
8029
|
+
version: "wallet_auth_method_v2",
|
|
8030
|
+
walletAuthMethodId: input.common.walletAuthMethodId,
|
|
8031
|
+
walletId: input.common.walletId,
|
|
8032
|
+
walletAuthorityId: input.common.walletAuthorityId,
|
|
8033
|
+
kind: "voiceid",
|
|
8034
|
+
status: "revoked",
|
|
8035
|
+
createdAtMs: input.common.createdAtMs,
|
|
8036
|
+
updatedAtMs: input.common.updatedAtMs,
|
|
8037
|
+
deviceKeyId: input.deviceKeyId,
|
|
8038
|
+
devicePublicKeyB64u: input.devicePublicKeyB64u,
|
|
8039
|
+
activatedAtMs: input.lifecycle.activatedAtMs,
|
|
8040
|
+
revokedAtMs: input.lifecycle.revokedAtMs
|
|
8041
|
+
};
|
|
8042
|
+
}
|
|
8043
|
+
}
|
|
7840
8044
|
function parseWalletAuthMethodIdRequired$1(raw) {
|
|
7841
8045
|
const parsed = parseWalletAuthMethodId(raw);
|
|
7842
8046
|
if (!parsed.ok) throw new Error(parsed.error.message);
|
|
@@ -8433,7 +8637,7 @@ function parseCommittedAuthorityPackagesV1(raw) {
|
|
|
8433
8637
|
const authority = parseWalletAuthorityV1(record.authority);
|
|
8434
8638
|
if (!authority.ok || authority.value.state !== "pending_local_install") throw new Error("CommittedAuthorityPackagesV1.authority is not pending");
|
|
8435
8639
|
const authMethod = parseWalletAuthMethodRecordV2(record.authMethod);
|
|
8436
|
-
if (!authMethod || authMethod.status !== "pending_local_install") throw new Error("CommittedAuthorityPackagesV1.authMethod is not pending");
|
|
8640
|
+
if (!authMethod || authMethod.status !== "pending_local_install" || authMethod.kind === "voiceid") throw new Error("CommittedAuthorityPackagesV1.authMethod is not pending");
|
|
8437
8641
|
if (authMethod.walletId !== authority.value.walletId || authMethod.walletAuthorityId !== authority.value.authorityId) throw new Error("CommittedAuthorityPackagesV1 authority and auth method identities differ");
|
|
8438
8642
|
const signerPackages = parseCommittedSignerPackageSetV1(record.signerPackages);
|
|
8439
8643
|
assertSignerPackagesMatchAuthority(signerPackages, authority.value.signerActivations);
|
|
@@ -10717,6 +10921,7 @@ function isActiveOwnerLane(lane) {
|
|
|
10717
10921
|
if (lane.lifecycle.state !== "active") return false;
|
|
10718
10922
|
switch (lane.laneKind) {
|
|
10719
10923
|
case "owner_passkey":
|
|
10924
|
+
case "owner_voiceid":
|
|
10720
10925
|
case "owner_email_otp":
|
|
10721
10926
|
case "recovery":
|
|
10722
10927
|
case "break_glass": return true;
|
|
@@ -11738,6 +11943,13 @@ function parsePasskeyCustodySecretBinding(raw, label = "passkeyCustodySecretBind
|
|
|
11738
11943
|
const WALLET_CUSTODY_ENVELOPE_VERSION_V2 = "wallet_custody_envelope_v2";
|
|
11739
11944
|
const PASSKEY_PRF_KEK_VERSION_V1 = "passkey_prf_kek_hkdf_sha256_v1";
|
|
11740
11945
|
const EMAIL_OTP_FACTOR_KEK_VERSION_V1 = "email_otp_factor_kek_hkdf_sha256_v1";
|
|
11946
|
+
const VOICEID_DEVICE_FACTOR_KEK_VERSION_V1 = "voiceid_device_factor_kek_hkdf_sha256_v1";
|
|
11947
|
+
function isActiveMethodBoundWalletSeedEnvelope(envelope) {
|
|
11948
|
+
return envelope.ownership.kind === "method_bound" && envelope.binding.kind === "wallet_custody_seed_v1" && envelope.lifecycle.state === "active";
|
|
11949
|
+
}
|
|
11950
|
+
function isVoiceIdSeedEnvelope(envelope) {
|
|
11951
|
+
return isActiveMethodBoundWalletSeedEnvelope(envelope) && envelope.factor.kind === "voiceid";
|
|
11952
|
+
}
|
|
11741
11953
|
const ENVELOPE_OWNERSHIP_FIELDS = ["kind", "walletAuthMethodId"];
|
|
11742
11954
|
function parseWalletCustodyEnvelopeOwnership(raw, label) {
|
|
11743
11955
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new Error(`${label} must be an object`);
|
|
@@ -11774,6 +11986,13 @@ function buildEmailOtpEnvelopeFactor(args) {
|
|
|
11774
11986
|
kekVersion: EMAIL_OTP_FACTOR_KEK_VERSION_V1
|
|
11775
11987
|
};
|
|
11776
11988
|
}
|
|
11989
|
+
function buildVoiceIdEnvelopeFactor(args) {
|
|
11990
|
+
return {
|
|
11991
|
+
kind: "voiceid",
|
|
11992
|
+
deviceKeyId: args.deviceKeyId,
|
|
11993
|
+
kekVersion: VOICEID_DEVICE_FACTOR_KEK_VERSION_V1
|
|
11994
|
+
};
|
|
11995
|
+
}
|
|
11777
11996
|
function buildActiveEnvelopeLifecycle(args) {
|
|
11778
11997
|
return {
|
|
11779
11998
|
state: "active",
|
|
@@ -11860,7 +12079,16 @@ const EMAIL_OTP_FACTOR_FIELDS = [
|
|
|
11860
12079
|
"enrollmentSealKeyVersion",
|
|
11861
12080
|
"kekVersion"
|
|
11862
12081
|
];
|
|
11863
|
-
const
|
|
12082
|
+
const VOICEID_FACTOR_FIELDS = [
|
|
12083
|
+
"kind",
|
|
12084
|
+
"deviceKeyId",
|
|
12085
|
+
"kekVersion"
|
|
12086
|
+
];
|
|
12087
|
+
const ALL_FACTOR_FIELDS = Array.from(new Set([
|
|
12088
|
+
...PASSKEY_FACTOR_FIELDS,
|
|
12089
|
+
...EMAIL_OTP_FACTOR_FIELDS,
|
|
12090
|
+
...VOICEID_FACTOR_FIELDS
|
|
12091
|
+
]));
|
|
11864
12092
|
function requireNonEmptyString(value, label) {
|
|
11865
12093
|
if (typeof value !== "string" || !value.trim()) throw new Error(`${label} is required`);
|
|
11866
12094
|
return value;
|
|
@@ -11887,7 +12115,11 @@ function parseWalletCustodyEnvelopeFactor(raw, label = "factor") {
|
|
|
11887
12115
|
enrollmentId: requireNonEmptyString(record.enrollmentId, `${label}.enrollmentId`),
|
|
11888
12116
|
enrollmentSealKeyVersion: requireNonEmptyString(record.enrollmentSealKeyVersion, `${label}.enrollmentSealKeyVersion`)
|
|
11889
12117
|
});
|
|
11890
|
-
|
|
12118
|
+
case "voiceid":
|
|
12119
|
+
rejectUnknownFields(record, VOICEID_FACTOR_FIELDS, label, ALL_FACTOR_FIELDS);
|
|
12120
|
+
if (record.kekVersion !== VOICEID_DEVICE_FACTOR_KEK_VERSION_V1) throw new Error(`${label}.kekVersion must be ${VOICEID_DEVICE_FACTOR_KEK_VERSION_V1}`);
|
|
12121
|
+
return buildVoiceIdEnvelopeFactor({ deviceKeyId: parseDigestField$3(record.deviceKeyId, `${label}.deviceKeyId`) });
|
|
12122
|
+
default: throw new Error(`${label}.kind must be passkey, email_otp, or voiceid`);
|
|
11891
12123
|
}
|
|
11892
12124
|
}
|
|
11893
12125
|
const ENVELOPE_RECORD_FIELDS = [
|
|
@@ -12419,16 +12651,14 @@ function normalizeAddAuthMethodIntentCaller(raw) {
|
|
|
12419
12651
|
if (!source) return null;
|
|
12420
12652
|
const walletAuthorityId = parseWalletAuthorityId("walletAuthorityId" in source ? source.walletAuthorityId : void 0);
|
|
12421
12653
|
const walletAuthMethodId = parseWalletAuthMethodId("walletAuthMethodId" in source ? source.walletAuthMethodId : void 0);
|
|
12422
|
-
const walletSessionId = trimString("walletSessionId" in source ? source.walletSessionId : void 0);
|
|
12423
12654
|
const authorityDigestB64u = trimString("authorityDigestB64u" in source ? source.authorityDigestB64u : void 0);
|
|
12424
12655
|
const revocationEpoch = "revocationEpoch" in source ? source.revocationEpoch : void 0;
|
|
12425
|
-
if (!walletAuthorityId.ok || !walletAuthMethodId.ok || !
|
|
12656
|
+
if (!walletAuthorityId.ok || !walletAuthMethodId.ok || !authorityDigestB64u || typeof revocationEpoch !== "number" || !Number.isSafeInteger(revocationEpoch) || revocationEpoch < 0) return null;
|
|
12426
12657
|
return {
|
|
12427
12658
|
caller: "same_device_addition",
|
|
12428
12659
|
source: {
|
|
12429
12660
|
walletAuthorityId: walletAuthorityId.value,
|
|
12430
12661
|
walletAuthMethodId: walletAuthMethodId.value,
|
|
12431
|
-
walletSessionId,
|
|
12432
12662
|
authorityDigestB64u,
|
|
12433
12663
|
revocationEpoch
|
|
12434
12664
|
}
|
|
@@ -14817,6 +15047,290 @@ async function handleWalletRegistration(ctx) {
|
|
|
14817
15047
|
return toFetchRouteResponse(response);
|
|
14818
15048
|
}
|
|
14819
15049
|
//#endregion
|
|
15050
|
+
//#region src/router/domains/voiceId/voiceIdActivation.ts
|
|
15051
|
+
/** Accepts only a method-bound owner seed envelope for the new VoiceID method. */
|
|
15052
|
+
function parseVoiceIdActivationEnvelope(raw) {
|
|
15053
|
+
const envelope = parsePasskeyCustodyEnvelopeRecord(raw);
|
|
15054
|
+
if (!isVoiceIdSeedEnvelope(envelope) || Number(envelope.envelopeRevision) !== 1) throw new Error("VoiceID activation requires an active, method-bound wallet seed envelope");
|
|
15055
|
+
return envelope;
|
|
15056
|
+
}
|
|
15057
|
+
//#endregion
|
|
15058
|
+
//#region src/router/transport/fetch/routes/walletVoiceIdActivation.ts
|
|
15059
|
+
const ACTIVATION_FIELDS = [
|
|
15060
|
+
"targetWalletAuthMethodId",
|
|
15061
|
+
"devicePublicKeyB64u",
|
|
15062
|
+
"custodyEnvelope"
|
|
15063
|
+
];
|
|
15064
|
+
function hostedWalletOriginAllowed(request, allowedOrigins) {
|
|
15065
|
+
const rawOrigin = request.headers.get("origin");
|
|
15066
|
+
let requestOrigin;
|
|
15067
|
+
try {
|
|
15068
|
+
requestOrigin = parseSessionOrigin(rawOrigin);
|
|
15069
|
+
} catch {
|
|
15070
|
+
return false;
|
|
15071
|
+
}
|
|
15072
|
+
for (const allowed of allowedOrigins) try {
|
|
15073
|
+
if (parseSessionOrigin(allowed) === requestOrigin) return true;
|
|
15074
|
+
} catch {
|
|
15075
|
+
continue;
|
|
15076
|
+
}
|
|
15077
|
+
return false;
|
|
15078
|
+
}
|
|
15079
|
+
function parseActivationBody(raw) {
|
|
15080
|
+
const body = requireRecord$1(raw, "VoiceID activation");
|
|
15081
|
+
rejectUnknownFields(body, ACTIVATION_FIELDS, "VoiceID activation");
|
|
15082
|
+
const target = parseWalletAuthMethodId(body.targetWalletAuthMethodId);
|
|
15083
|
+
if (!target.ok) throw new Error(target.error.message);
|
|
15084
|
+
return {
|
|
15085
|
+
targetWalletAuthMethodId: target.value,
|
|
15086
|
+
devicePublicKeyB64u: parseVoiceIdDevicePublicKeyB64u(body.devicePublicKeyB64u),
|
|
15087
|
+
envelope: parseVoiceIdActivationEnvelope(body.custodyEnvelope)
|
|
15088
|
+
};
|
|
15089
|
+
}
|
|
15090
|
+
function rejectionStatus(code) {
|
|
15091
|
+
switch (code) {
|
|
15092
|
+
case "invalid_source": return 403;
|
|
15093
|
+
case "invalid_target": return 400;
|
|
15094
|
+
case "conflict": return 409;
|
|
15095
|
+
default: {
|
|
15096
|
+
const unexpected = code;
|
|
15097
|
+
throw new Error(`Unsupported VoiceID activation rejection: ${unexpected}`);
|
|
15098
|
+
}
|
|
15099
|
+
}
|
|
15100
|
+
}
|
|
15101
|
+
async function handleWalletVoiceIdActivation(ctx) {
|
|
15102
|
+
if (ctx.method !== "POST" || ctx.pathname !== "/wallet/voiceid/activate") return null;
|
|
15103
|
+
return await handleVoiceIdActivationRequest(ctx.request, {
|
|
15104
|
+
tenantId: ctx.service.authorizationSessions.tenantId,
|
|
15105
|
+
readWalletSessionAuthorizationV2ByOperationCredential: ctx.service.authorizationSessions.readWalletSessionAuthorizationV2ByOperationCredential,
|
|
15106
|
+
activateVoiceId: ctx.service.walletAuthMethods.activateVoiceId,
|
|
15107
|
+
hostedWalletOrigins: ctx.opts.hostedWalletOrigins ?? []
|
|
15108
|
+
});
|
|
15109
|
+
}
|
|
15110
|
+
async function handleVoiceIdActivationRequest(request, deps) {
|
|
15111
|
+
if (!hostedWalletOriginAllowed(request, deps.hostedWalletOrigins)) return json$3({
|
|
15112
|
+
ok: false,
|
|
15113
|
+
code: "origin_mismatch"
|
|
15114
|
+
}, { status: 403 });
|
|
15115
|
+
const credential = extractBearerCredential(request.headers);
|
|
15116
|
+
if (!credential) return json$3({
|
|
15117
|
+
ok: false,
|
|
15118
|
+
code: "unauthorized"
|
|
15119
|
+
}, { status: 401 });
|
|
15120
|
+
let token;
|
|
15121
|
+
try {
|
|
15122
|
+
token = parsePrimaryWalletSessionOperationCredentialToken(credential);
|
|
15123
|
+
} catch {
|
|
15124
|
+
return json$3({
|
|
15125
|
+
ok: false,
|
|
15126
|
+
code: "unauthorized"
|
|
15127
|
+
}, { status: 401 });
|
|
15128
|
+
}
|
|
15129
|
+
const nowMs = Date.now();
|
|
15130
|
+
let source;
|
|
15131
|
+
try {
|
|
15132
|
+
source = await deps.readWalletSessionAuthorizationV2ByOperationCredential({
|
|
15133
|
+
tenantId: deps.tenantId,
|
|
15134
|
+
token,
|
|
15135
|
+
nowMs
|
|
15136
|
+
});
|
|
15137
|
+
} catch {
|
|
15138
|
+
return json$3({
|
|
15139
|
+
ok: false,
|
|
15140
|
+
code: "wallet_session_unavailable"
|
|
15141
|
+
}, { status: 503 });
|
|
15142
|
+
}
|
|
15143
|
+
if (!source || source.retiredAtMs !== null || source.authMethod.status !== "active" || source.authMethod.kind !== "passkey" && source.authMethod.kind !== "email_otp") return json$3({
|
|
15144
|
+
ok: false,
|
|
15145
|
+
code: "unauthorized"
|
|
15146
|
+
}, { status: 401 });
|
|
15147
|
+
let parsedBody;
|
|
15148
|
+
try {
|
|
15149
|
+
parsedBody = parseActivationBody(await readJson(request));
|
|
15150
|
+
} catch (error) {
|
|
15151
|
+
return json$3({
|
|
15152
|
+
ok: false,
|
|
15153
|
+
code: "invalid_body",
|
|
15154
|
+
message: error instanceof Error ? error.message : "VoiceID activation body is invalid"
|
|
15155
|
+
}, { status: 400 });
|
|
15156
|
+
}
|
|
15157
|
+
let result;
|
|
15158
|
+
try {
|
|
15159
|
+
result = await deps.activateVoiceId({
|
|
15160
|
+
source: {
|
|
15161
|
+
authorization: source.authorization,
|
|
15162
|
+
authority: source.authority,
|
|
15163
|
+
authMethod: source.authMethod
|
|
15164
|
+
},
|
|
15165
|
+
targetWalletAuthMethodId: parsedBody.targetWalletAuthMethodId,
|
|
15166
|
+
devicePublicKeyB64u: parsedBody.devicePublicKeyB64u,
|
|
15167
|
+
envelope: parsedBody.envelope,
|
|
15168
|
+
nowMs
|
|
15169
|
+
});
|
|
15170
|
+
} catch {
|
|
15171
|
+
return json$3({
|
|
15172
|
+
ok: false,
|
|
15173
|
+
code: "activation_unavailable"
|
|
15174
|
+
}, { status: 503 });
|
|
15175
|
+
}
|
|
15176
|
+
if (result.kind === "rejected") return json$3({
|
|
15177
|
+
ok: false,
|
|
15178
|
+
code: result.code
|
|
15179
|
+
}, { status: rejectionStatus(result.code) });
|
|
15180
|
+
return json$3({
|
|
15181
|
+
ok: true,
|
|
15182
|
+
authMethod: result.authMethod
|
|
15183
|
+
}, { status: 200 });
|
|
15184
|
+
}
|
|
15185
|
+
//#endregion
|
|
15186
|
+
//#region src/router/transport/fetch/routes/walletVoiceIdNear.ts
|
|
15187
|
+
async function prepareVoiceIdNearInstallation(ctx, verified) {
|
|
15188
|
+
const session = verified.authorization.session;
|
|
15189
|
+
const activation = session.capabilitySubjects.find(isNearSignSubject);
|
|
15190
|
+
if (!activation || activation.kind !== "sign") return { kind: "unavailable" };
|
|
15191
|
+
const resolved = await ctx.service.walletRegistration.resolveEd25519MaterialActivation({
|
|
15192
|
+
walletId: session.walletId,
|
|
15193
|
+
materialActivation: routerAbMpcMaterialActivationRefToWire(activation.materialActivation)
|
|
15194
|
+
});
|
|
15195
|
+
if (!resolved.ok) throw new Error(resolved.message);
|
|
15196
|
+
const product = ctx.opts.routerAbEd25519YaoProduct;
|
|
15197
|
+
if (!product) throw new Error("NEAR signing is not configured");
|
|
15198
|
+
const capability = await product.resolveActiveCapability({
|
|
15199
|
+
kind: "router_ab_ed25519_yao_active_capability_lookup_v1",
|
|
15200
|
+
walletId: session.walletId,
|
|
15201
|
+
nearEd25519SigningKeyId: resolved.exportIdentity.application_binding.near_ed25519_signing_key_id,
|
|
15202
|
+
signerSlot: resolved.signerSlot,
|
|
15203
|
+
signingWorkerId: resolved.signingWorkerId,
|
|
15204
|
+
participantIds: resolved.participantIds
|
|
15205
|
+
});
|
|
15206
|
+
if (!capability.ok) throw new Error(capability.message);
|
|
15207
|
+
if (!sameRouterAbMpcMaterialActivationRef(capability.capability.materialActivation, resolved.materialActivation)) throw new Error("NEAR capability does not belong to this Wallet Session");
|
|
15208
|
+
return {
|
|
15209
|
+
kind: "ready",
|
|
15210
|
+
capability: capability.capability,
|
|
15211
|
+
remainingUses: verified.authorization.quota.remainingUses
|
|
15212
|
+
};
|
|
15213
|
+
}
|
|
15214
|
+
function isNearSignSubject(subject) {
|
|
15215
|
+
return subject.kind === "sign" && subject.keyFamily === "ed25519";
|
|
15216
|
+
}
|
|
15217
|
+
//#endregion
|
|
15218
|
+
//#region src/router/transport/fetch/routes/walletVoiceIdSessions.ts
|
|
15219
|
+
function hostedOrigin(request, allowedOrigins) {
|
|
15220
|
+
if (!hostedWalletOriginAllowed(request, allowedOrigins)) return null;
|
|
15221
|
+
return parseSessionOrigin(request.headers.get("origin"));
|
|
15222
|
+
}
|
|
15223
|
+
function parseChallengeBody(raw) {
|
|
15224
|
+
const body = requireRecord$1(raw, "VoiceID challenge");
|
|
15225
|
+
rejectUnknownFields(body, ["walletId", "walletAuthMethodId"], "VoiceID challenge");
|
|
15226
|
+
const walletId = parseWalletId(body.walletId);
|
|
15227
|
+
if (!walletId.ok) throw new Error(walletId.error.message);
|
|
15228
|
+
const walletAuthMethodId = parseWalletAuthMethodId(body.walletAuthMethodId);
|
|
15229
|
+
if (!walletAuthMethodId.ok) throw new Error(walletAuthMethodId.error.message);
|
|
15230
|
+
return {
|
|
15231
|
+
walletId: walletId.value,
|
|
15232
|
+
walletAuthMethodId: walletAuthMethodId.value
|
|
15233
|
+
};
|
|
15234
|
+
}
|
|
15235
|
+
async function handleWalletVoiceIdChallenge(ctx) {
|
|
15236
|
+
if (ctx.method !== "POST" || ctx.pathname !== "/wallet/voiceid/challenge") return null;
|
|
15237
|
+
return await handleVoiceIdChallengeRequest(ctx.request, {
|
|
15238
|
+
issueVoiceIdChallenge: ctx.service.walletUnlock.issueVoiceIdChallenge,
|
|
15239
|
+
hostedWalletOrigins: ctx.opts.hostedWalletOrigins ?? []
|
|
15240
|
+
});
|
|
15241
|
+
}
|
|
15242
|
+
async function handleWalletVoiceIdVerify(ctx) {
|
|
15243
|
+
if (ctx.method !== "POST" || ctx.pathname !== "/wallet/voiceid/verify") return null;
|
|
15244
|
+
return await handleVoiceIdVerifyRequest(ctx.request, {
|
|
15245
|
+
verifyAndIssueVoiceId: ctx.service.walletUnlock.verifyAndIssueVoiceId,
|
|
15246
|
+
prepareNear: prepareVoiceIdNearInstallation.bind(null, ctx),
|
|
15247
|
+
hostedWalletOrigins: ctx.opts.hostedWalletOrigins ?? []
|
|
15248
|
+
});
|
|
15249
|
+
}
|
|
15250
|
+
async function handleVoiceIdChallengeRequest(request, deps) {
|
|
15251
|
+
const requestOrigin = hostedOrigin(request, deps.hostedWalletOrigins);
|
|
15252
|
+
if (!requestOrigin) return json$3({
|
|
15253
|
+
ok: false,
|
|
15254
|
+
code: "origin_mismatch"
|
|
15255
|
+
}, { status: 403 });
|
|
15256
|
+
let body;
|
|
15257
|
+
try {
|
|
15258
|
+
body = parseChallengeBody(await readJson(request));
|
|
15259
|
+
} catch (error) {
|
|
15260
|
+
return json$3({
|
|
15261
|
+
ok: false,
|
|
15262
|
+
code: "invalid_body",
|
|
15263
|
+
message: error instanceof Error ? error.message : "VoiceID challenge body is invalid"
|
|
15264
|
+
}, { status: 400 });
|
|
15265
|
+
}
|
|
15266
|
+
try {
|
|
15267
|
+
const result = await deps.issueVoiceIdChallenge({
|
|
15268
|
+
walletId: body.walletId,
|
|
15269
|
+
walletAuthMethodId: body.walletAuthMethodId,
|
|
15270
|
+
requestOrigin,
|
|
15271
|
+
nowMs: Date.now()
|
|
15272
|
+
});
|
|
15273
|
+
if (result.kind === "rejected") return json$3({
|
|
15274
|
+
ok: false,
|
|
15275
|
+
code: result.code
|
|
15276
|
+
}, { status: 403 });
|
|
15277
|
+
return json$3({
|
|
15278
|
+
ok: true,
|
|
15279
|
+
challenge: result.challenge
|
|
15280
|
+
}, { status: 200 });
|
|
15281
|
+
} catch {
|
|
15282
|
+
return json$3({
|
|
15283
|
+
ok: false,
|
|
15284
|
+
code: "challenge_unavailable"
|
|
15285
|
+
}, { status: 503 });
|
|
15286
|
+
}
|
|
15287
|
+
}
|
|
15288
|
+
async function handleVoiceIdVerifyRequest(request, deps) {
|
|
15289
|
+
const requestOrigin = hostedOrigin(request, deps.hostedWalletOrigins);
|
|
15290
|
+
if (!requestOrigin) return json$3({
|
|
15291
|
+
ok: false,
|
|
15292
|
+
code: "origin_mismatch"
|
|
15293
|
+
}, { status: 403 });
|
|
15294
|
+
let assertion;
|
|
15295
|
+
try {
|
|
15296
|
+
const body = requireRecord$1(await readJson(request), "VoiceID verification");
|
|
15297
|
+
rejectUnknownFields(body, ["assertion"], "VoiceID verification");
|
|
15298
|
+
assertion = parseVoiceIdDeviceAssertion(body.assertion);
|
|
15299
|
+
} catch (error) {
|
|
15300
|
+
return json$3({
|
|
15301
|
+
ok: false,
|
|
15302
|
+
code: "invalid_body",
|
|
15303
|
+
message: error instanceof Error ? error.message : "VoiceID assertion is invalid"
|
|
15304
|
+
}, { status: 400 });
|
|
15305
|
+
}
|
|
15306
|
+
try {
|
|
15307
|
+
const result = await deps.verifyAndIssueVoiceId({
|
|
15308
|
+
assertion,
|
|
15309
|
+
requestOrigin,
|
|
15310
|
+
nowMs: Date.now()
|
|
15311
|
+
});
|
|
15312
|
+
if (result.kind === "rejected") {
|
|
15313
|
+
const status = result.code === "issuance_unavailable" ? 503 : 403;
|
|
15314
|
+
return json$3({
|
|
15315
|
+
ok: false,
|
|
15316
|
+
code: result.code
|
|
15317
|
+
}, { status });
|
|
15318
|
+
}
|
|
15319
|
+
return json$3({
|
|
15320
|
+
ok: true,
|
|
15321
|
+
walletSession: projectActiveWalletSession(result.authorization),
|
|
15322
|
+
operationCredential: result.operationCredential,
|
|
15323
|
+
custodyEnvelope: result.custodyEnvelope,
|
|
15324
|
+
near: await deps.prepareNear(result)
|
|
15325
|
+
}, { status: 200 });
|
|
15326
|
+
} catch {
|
|
15327
|
+
return json$3({
|
|
15328
|
+
ok: false,
|
|
15329
|
+
code: "verification_unavailable"
|
|
15330
|
+
}, { status: 503 });
|
|
15331
|
+
}
|
|
15332
|
+
}
|
|
15333
|
+
//#endregion
|
|
14820
15334
|
//#region ../shared-ts/src/utils/errors.ts
|
|
14821
15335
|
/**
|
|
14822
15336
|
* Best-effort error message extractor without relying on `any`.
|
|
@@ -19888,6 +20402,14 @@ function walletAuthorityForAuthMethod(authMethod, authSource) {
|
|
|
19888
20402
|
bindingId: authMethod.walletAuthMethodId
|
|
19889
20403
|
};
|
|
19890
20404
|
}
|
|
20405
|
+
if (authMethod.kind === "voiceid") {
|
|
20406
|
+
if (authSource.kind !== "voiceid" || authSource.deviceKeyId !== authMethod.deviceKeyId) return null;
|
|
20407
|
+
return buildVoiceIdWalletAuthAuthority({
|
|
20408
|
+
walletId: authMethod.walletId,
|
|
20409
|
+
walletAuthMethodId: authMethod.walletAuthMethodId,
|
|
20410
|
+
deviceKeyId: authMethod.deviceKeyId
|
|
20411
|
+
});
|
|
20412
|
+
}
|
|
19891
20413
|
if (authSource.kind !== "oidc_provider") return null;
|
|
19892
20414
|
const providerUserId = parseEmailOtpProviderUserId(authSource.providerSubject);
|
|
19893
20415
|
if (!providerUserId.ok) return null;
|
|
@@ -25679,6 +26201,7 @@ function serializeProjection(value) {
|
|
|
25679
26201
|
const lane = value.lane;
|
|
25680
26202
|
switch (lane.laneKind) {
|
|
25681
26203
|
case "owner_passkey":
|
|
26204
|
+
case "owner_voiceid":
|
|
25682
26205
|
case "owner_email_otp": return {
|
|
25683
26206
|
kind: value.kind,
|
|
25684
26207
|
walletKey: serializeWalletKey(value.walletKey),
|
|
@@ -27890,6 +28413,10 @@ function thresholdEd25519AuthorityScopeFromWalletAuthAuthority(authority) {
|
|
|
27890
28413
|
provider: authority.factor.provider,
|
|
27891
28414
|
providerUserId: authority.factor.providerUserId
|
|
27892
28415
|
};
|
|
28416
|
+
if (isVoiceIdWalletAuthAuthority(authority)) return {
|
|
28417
|
+
kind: "voiceid",
|
|
28418
|
+
deviceKeyId: authority.factor.deviceKeyId
|
|
28419
|
+
};
|
|
27893
28420
|
throw new Error("[threshold-ed25519] unsupported wallet auth authority");
|
|
27894
28421
|
}
|
|
27895
28422
|
async function authenticateDeviceLinkingOwnerWalletSessionRequestV1(input) {
|
|
@@ -28064,6 +28591,17 @@ function walletAuthAuthorityFromExactV2MethodV1(input) {
|
|
|
28064
28591
|
} catch {
|
|
28065
28592
|
return null;
|
|
28066
28593
|
}
|
|
28594
|
+
if (input.authMethod.kind === "voiceid") return {
|
|
28595
|
+
authority: buildVoiceIdWalletAuthAuthority({
|
|
28596
|
+
walletId: input.walletId,
|
|
28597
|
+
walletAuthMethodId: input.authMethod.walletAuthMethodId,
|
|
28598
|
+
deviceKeyId: input.authMethod.deviceKeyId
|
|
28599
|
+
}),
|
|
28600
|
+
authSource: {
|
|
28601
|
+
kind: "voiceid",
|
|
28602
|
+
deviceKeyId: input.authMethod.deviceKeyId
|
|
28603
|
+
}
|
|
28604
|
+
};
|
|
28067
28605
|
const providerSubject = parseProviderSubject(input.principalId);
|
|
28068
28606
|
if (!providerSubject.ok) return null;
|
|
28069
28607
|
try {
|
|
@@ -28482,6 +29020,9 @@ function createFetchRouter(service, opts, runtime) {
|
|
|
28482
29020
|
handleHostedWalletSessionExchangeRedeem,
|
|
28483
29021
|
handleWalletUnlockChallenge,
|
|
28484
29022
|
handleWalletUnlockVerify,
|
|
29023
|
+
handleWalletVoiceIdActivation,
|
|
29024
|
+
handleWalletVoiceIdChallenge,
|
|
29025
|
+
handleWalletVoiceIdVerify,
|
|
28485
29026
|
handleWalletEmailOtpChallenge,
|
|
28486
29027
|
handleWalletEmailOtpFactorRelease,
|
|
28487
29028
|
handleWalletEmailOtpDevCleanupGoogleRegistration,
|
|
@@ -32819,6 +33360,8 @@ Object.freeze([
|
|
|
32819
33360
|
updated_at_ms INTEGER NOT NULL,
|
|
32820
33361
|
activated_at_ms INTEGER,
|
|
32821
33362
|
revoked_at_ms INTEGER,
|
|
33363
|
+
provider TEXT,
|
|
33364
|
+
provider_user_id TEXT,
|
|
32822
33365
|
PRIMARY KEY (namespace, org_id, project_id, env_id, wallet_auth_method_id),
|
|
32823
33366
|
FOREIGN KEY (
|
|
32824
33367
|
namespace,
|
|
@@ -32835,7 +33378,7 @@ Object.freeze([
|
|
|
32835
33378
|
),
|
|
32836
33379
|
CHECK (length(wallet_id) > 0),
|
|
32837
33380
|
CHECK (length(wallet_authority_id) > 0),
|
|
32838
|
-
CHECK (kind IN ('passkey', 'email_otp')),
|
|
33381
|
+
CHECK (kind IN ('passkey', 'email_otp', 'voiceid')),
|
|
32839
33382
|
CHECK (status IN ('pending_local_install', 'active', 'revoked')),
|
|
32840
33383
|
CHECK (length(wallet_auth_method_id) > 0),
|
|
32841
33384
|
CHECK (length(auth_identifier_key) > 0),
|
|
@@ -32869,6 +33412,18 @@ Object.freeze([
|
|
|
32869
33412
|
AND registration_authority_id IS NOT NULL
|
|
32870
33413
|
AND length(registration_authority_id) > 0
|
|
32871
33414
|
AND auth_identifier_key = email_hash_hex)
|
|
33415
|
+
OR
|
|
33416
|
+
(kind = 'voiceid'
|
|
33417
|
+
AND rp_id = ''
|
|
33418
|
+
AND credential_id_b64u IS NULL
|
|
33419
|
+
AND credential_public_key_b64u IS NOT NULL
|
|
33420
|
+
AND length(credential_public_key_b64u) = 43
|
|
33421
|
+
AND email_hash_hex IS NULL
|
|
33422
|
+
AND registration_authority_id IS NULL
|
|
33423
|
+
AND provider IS NULL
|
|
33424
|
+
AND provider_user_id IS NULL
|
|
33425
|
+
AND length(auth_identifier_key) = 43
|
|
33426
|
+
AND COALESCE(auth_identifier_key = json_extract(record_json, '$.deviceKeyId'), 0))
|
|
32872
33427
|
),
|
|
32873
33428
|
CHECK (json_extract(record_json, '$.version') = 'wallet_auth_method_v2'),
|
|
32874
33429
|
CHECK (json_extract(record_json, '$.walletAuthMethodId') = wallet_auth_method_id),
|
|
@@ -32888,6 +33443,10 @@ Object.freeze([
|
|
|
32888
33443
|
(kind = 'email_otp'
|
|
32889
33444
|
AND json_extract(record_json, '$.emailHashHex') = email_hash_hex
|
|
32890
33445
|
AND json_extract(record_json, '$.registrationAuthorityId') = registration_authority_id)
|
|
33446
|
+
OR
|
|
33447
|
+
(kind = 'voiceid'
|
|
33448
|
+
AND COALESCE(json_extract(record_json, '$.deviceKeyId') = auth_identifier_key, 0)
|
|
33449
|
+
AND COALESCE(json_extract(record_json, '$.devicePublicKeyB64u') = credential_public_key_b64u, 0))
|
|
32891
33450
|
)
|
|
32892
33451
|
)
|
|
32893
33452
|
`,
|
|
@@ -32902,6 +33461,13 @@ Object.freeze([
|
|
|
32902
33461
|
wallet_authority_id,
|
|
32903
33462
|
status
|
|
32904
33463
|
)
|
|
33464
|
+
`,
|
|
33465
|
+
`
|
|
33466
|
+
CREATE UNIQUE INDEX IF NOT EXISTS wallet_auth_methods_v2_voiceid_key_uidx
|
|
33467
|
+
ON wallet_auth_methods (
|
|
33468
|
+
namespace, org_id, project_id, env_id, auth_identifier_key
|
|
33469
|
+
)
|
|
33470
|
+
WHERE kind = 'voiceid'
|
|
32905
33471
|
`,
|
|
32906
33472
|
`
|
|
32907
33473
|
CREATE UNIQUE INDEX IF NOT EXISTS wallet_auth_methods_v2_passkey_uidx
|