@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.
- package/README.md +19 -4
- 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/application-auth.js +2 -0
- package/dist/esm/auth/applicationAuth.js +85 -0
- package/dist/esm/auth/externalAuthService.js +58 -7
- package/dist/esm/auth/externalAuthStore.js +157 -21
- package/dist/esm/auth/externalOwnerOperations.js +82 -0
- package/dist/esm/cloud-host.js +4 -4
- package/dist/esm/core/nearRelayerAccountProvisioning.js +1 -1
- package/dist/esm/router/cloudflare/d1/auth/d1AuthorizedOperationRouteService.js +2 -0
- package/dist/esm/router/cloudflare/d1/auth/d1RecordedOwnerOperation.js +43 -0
- package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +22 -1
- package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpRecoveryService.js +1 -1
- package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpRegistrationEnrollmentFinalizer.js +1 -1
- package/dist/esm/router/cloudflare/d1/emailOtp/d1GoogleEmailOtpSessionResolver.js +1 -1
- package/dist/esm/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyRouteService.js +2 -2
- package/dist/esm/router/cloudflare/d1/passkeyCustody/d1WalletCustodyCommitStore.js +2 -2
- package/dist/esm/router/cloudflare/d1/passkeyCustody/d1WalletRecoveryGoogleEmailOtpService.js +2 -2
- package/dist/esm/router/cloudflare/d1/registration/d1RegistrationCeremonyRecords.js +9 -0
- package/dist/esm/router/cloudflare/d1/registration/d1WalletExecutionAuthority.js +9 -16
- package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationService.js +43 -1
- package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationSetup.js +12 -0
- package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationSetupExecution.js +1 -0
- package/dist/esm/router/cloudflare/d1/registration/registrationCompletionWrite.js +1 -0
- package/dist/esm/router/cloudflare/d1/registration/walletRegistrationSessionCommitReceipt.js +35 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthMethodBoundary.js +1 -1
- package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthMethodService.js +4 -3
- package/dist/esm/router/cloudflare.js +1785 -1489
- package/dist/esm/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.js +2 -2
- package/dist/esm/router/domains/emailOtp/emailOtpRouteHandlers.js +1 -1
- package/dist/esm/router/domains/passkeyCustody/walletCustodyRegistrationCommit.js +1 -1
- package/dist/esm/router/domains/signingOperations/routerAbEd25519NormalSigningRoute.js +35 -1
- package/dist/esm/router/domains/walletRegistration/walletRegistrationReservation.js +17 -2
- package/dist/esm/router/domains/walletRegistration/walletRegistrationRoutes.js +9 -1
- package/dist/esm/router/domains/walletUnlock/walletUnlockRouteHandlers.js +1 -1
- package/dist/esm/router/transport/fetch/routes/ecdsaOperationStepUpFactor.js +2 -2
- package/dist/esm/router/transport/fetch/routes/externalAuth.js +139 -32
- package/dist/esm/router/transport/fetch/routes/passkeyCustody.js +44 -304
- package/dist/esm/router/transport/fetch/routes/sessions.js +2 -2
- package/dist/esm/router/transport/fetch/routes/thresholdEd25519.js +2 -2
- package/dist/esm/router/transport/fetch/routes/walletRegistration.js +2 -2
- package/dist/esm/router/transport/fetch/walletOwnerOperationAuthorization.js +306 -0
- package/dist/esm/shared-ts/src/auth/externalAuth.js +87 -14
- package/dist/esm/shared-ts/src/authorization/index.js +1 -1
- package/dist/esm/shared-ts/src/authorization/{walletCustodyOperation.js → walletOwnerOperation.js} +4 -4
- package/dist/esm/shared-ts/src/wallet-recovery/index.js +3 -3
- package/dist/types/shared-ts/src/auth/externalAuth.d.ts +51 -8
- package/dist/types/shared-ts/src/authorization/index.d.ts +1 -1
- package/dist/types/shared-ts/src/authorization/walletOwnerOperation.d.ts +10 -0
- package/dist/types/shared-ts/src/utils/registrationEstablishedSession.d.ts +4 -0
- package/dist/types/shared-ts/src/utils/registrationIntent.d.ts +2 -0
- package/dist/types/shared-ts/src/utils/walletManagementProtocol.d.ts +1 -1
- package/dist/types/wallet-server/src/application-auth.d.ts +3 -0
- package/dist/types/wallet-server/src/auth/applicationAuth.d.ts +29 -0
- package/dist/types/wallet-server/src/auth/externalAuthService.d.ts +29 -10
- package/dist/types/wallet-server/src/auth/externalAuthStore.d.ts +60 -6
- package/dist/types/wallet-server/src/auth/externalOwnerOperations.d.ts +18 -0
- package/dist/types/wallet-server/src/cloud-host.d.ts +1 -1
- package/dist/types/wallet-server/src/core/threeRouteRegistrationContracts.d.ts +12 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/auth/d1AuthorizedOperationRouteService.d.ts +5 -1
- package/dist/types/wallet-server/src/router/cloudflare/d1/auth/d1RecordedOwnerOperation.d.ts +16 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/registration/d1RegistrationCeremonyRecords.d.ts +1 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/registration/d1WalletRegistrationSetup.d.ts +2 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/registration/walletRegistrationSessionCommitReceipt.d.ts +7 -1
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1WalletAuthMethodService.d.ts +1 -0
- package/dist/types/wallet-server/src/router/domains/signingOperations/routerAbEd25519NormalSigningRoute.d.ts +2 -2
- package/dist/types/wallet-server/src/router/domains/signingOperations/routerAbOperationStepUp.d.ts +1 -1
- package/dist/types/wallet-server/src/router/domains/walletRegistration/walletRegistrationInputs.d.ts +1 -0
- package/dist/types/wallet-server/src/router/domains/walletRegistration/walletRegistrationReservation.d.ts +11 -6
- package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +11 -0
- package/dist/types/wallet-server/src/router/transport/fetch/walletOwnerOperationAuthorization.d.ts +23 -0
- package/migrations/d1-signer/0086_application_auth.sql +67 -0
- package/migrations/d1-signer/0087_registration_without_session.sql +43 -0
- package/package.json +7 -2
- package/dist/types/shared-ts/src/authorization/walletCustodyOperation.d.ts +0 -10
|
@@ -12,12 +12,12 @@ import { ROUTER_AB_ED25519_YAO_EXPORT_ADMISSION_PATH_V1, ROUTER_AB_ED25519_YAO_E
|
|
|
12
12
|
import { walletAuthAuthorityRef } from "../../../../shared-ts/src/utils/walletAuthAuthority.js";
|
|
13
13
|
import { parseSessionOrigin, parseVerifiedOwnerProofId } from "../../../../authorization/domain.js";
|
|
14
14
|
import { buildVerifiedWalletOperationEmailOtpFactorResult, buildVerifiedWalletOperationPasskeyFactorResult } from "../../../../authorization/factorEvidence.js";
|
|
15
|
+
import { WALLET_EMAIL_OTP_EXPORT_OPERATION } from "../../../../shared-ts/src/utils/emailOtpDomain.js";
|
|
16
|
+
import { parseWebAuthnAuthenticationCredential, webAuthnCredentialIdB64uFromCredential } from "../../../auth/webAuthnCredentialCodecs.js";
|
|
15
17
|
import { normalizeCorsOrigin } from "../../../../core/SessionService.js";
|
|
16
18
|
import { ROUTER_AB_TRACE_ID_HEADER_V1, createRouterAbTraceContextV1, parseRouterAbTraceContextV1 } from "../../../../shared-ts/src/utils/routerAbTraceContext.js";
|
|
17
19
|
import { json, readJson } from "../../../framework/http.js";
|
|
18
20
|
import { defineRoute } from "../../../framework/routeDefinitions.js";
|
|
19
|
-
import { parseWebAuthnAuthenticationCredential, webAuthnCredentialIdB64uFromCredential } from "../../../auth/webAuthnCredentialCodecs.js";
|
|
20
|
-
import { WALLET_EMAIL_OTP_EXPORT_OPERATION } from "../../../../shared-ts/src/utils/emailOtpDomain.js";
|
|
21
21
|
import { createRouterApiModule } from "../../../framework/modules.js";
|
|
22
22
|
//#region src/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.ts
|
|
23
23
|
const EXPORT_AUTH_MAX_TTL_MS = 6e4;
|
|
@@ -3,8 +3,8 @@ import { isPlainObject } from "../../../shared-ts/src/utils/validation.js";
|
|
|
3
3
|
import { base64UrlDecode, base64UrlEncode } from "../../../shared-ts/src/utils/base64.js";
|
|
4
4
|
import { failure } from "../../../shared-ts/src/utils/failure.js";
|
|
5
5
|
import "../../../shared-ts/src/utils/encoders.js";
|
|
6
|
-
import { emailOtpStatusCode } from "./emailOtpSessionRouteHelpers.js";
|
|
7
6
|
import { EMAIL_OTP_FACTOR_RELEASE_AAD_DOMAIN_V1 } from "../../../shared-ts/src/utils/emailOtpDomain.js";
|
|
7
|
+
import { emailOtpStatusCode } from "./emailOtpSessionRouteHelpers.js";
|
|
8
8
|
//#region src/router/domains/emailOtp/emailOtpRouteHandlers.ts
|
|
9
9
|
function decodeEmailOtpFactorSecret32(value) {
|
|
10
10
|
const magnitude = base64UrlDecode(value);
|
|
@@ -4,11 +4,11 @@ import { parseDigestField, parseEnvelopeCiphertextB64u, parseEnvelopeNonceB64u,
|
|
|
4
4
|
import { parsePasskeyCustodySecretBinding } from "../../../shared-ts/src/passkey-custody/custodySecretBinding.js";
|
|
5
5
|
import { buildActiveEnvelopeLifecycle, buildPasskeyCustodyEnvelopeRecord, parseWalletCustodyEnvelopeFactor, parseWalletCustodyEnvelopeOwnershipWireV1 } from "../../../shared-ts/src/passkey-custody/custodyEnvelope.js";
|
|
6
6
|
import "../../../shared-ts/src/passkey-custody/index.js";
|
|
7
|
+
import { parseRecoveryCodeLocatorV1 } from "../../../shared-ts/src/wallet-recovery/recoveryCodeLocator.js";
|
|
7
8
|
import { parseDerivedWalletRecoveryKeyId } from "../../../shared-ts/src/wallet-recovery/recoveryKeyId.js";
|
|
8
9
|
import { buildWalletRecoveryBackupAcknowledgementV1 } from "../../../shared-ts/src/wallet-recovery/backupAcknowledgement.js";
|
|
9
10
|
import "../../../shared-ts/src/wallet-recovery/recoveryCodes.js";
|
|
10
11
|
import { buildWalletCustodySeedRecoveryEntry, buildWalletRecoveryEnvelopeSetRecord, buildWalletRecoveryManifestKekWrap } from "../../../shared-ts/src/wallet-recovery/walletRecoveryEnvelopeSet.js";
|
|
11
|
-
import { parseRecoveryCodeLocatorV1 } from "../../../shared-ts/src/wallet-recovery/recoveryCodeLocator.js";
|
|
12
12
|
import "../../../shared-ts/src/wallet-recovery/index.js";
|
|
13
13
|
//#region src/router/domains/passkeyCustody/walletCustodyRegistrationCommit.ts
|
|
14
14
|
function requireNonEmpty(value, label) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mpcMaterialActivationRefsEqual } from "../../../shared-ts/src/utils/domainIds.js";
|
|
1
|
+
import { mpcMaterialActivationRefsEqual, parseWalletId } from "../../../shared-ts/src/utils/domainIds.js";
|
|
2
2
|
import { isPlainObject } from "../../../shared-ts/src/utils/validation.js";
|
|
3
3
|
import { base64UrlEncode } from "../../../shared-ts/src/utils/base64.js";
|
|
4
4
|
import { parseDigestB64u } from "../../../shared-ts/src/utils/canonicalPrimitives.js";
|
|
@@ -263,6 +263,40 @@ function isWalletSessionUnavailableStepUpError(result) {
|
|
|
263
263
|
return isPlainObject(result.body) && result.body.code === "wallet_session_unavailable";
|
|
264
264
|
}
|
|
265
265
|
async function authenticateRouterAbWalletOperationStepUp(input) {
|
|
266
|
+
if (!extractBearerCredential(input.headers) && input.authorizedOperations) {
|
|
267
|
+
const origin = parseStepUpRequestOrigin(input.headers);
|
|
268
|
+
if (!origin.ok) return origin;
|
|
269
|
+
const operationId = requireAuthorizationValue(parseAuthorizedOperationId(`normal-signing-operation:${input.scope.request_id}`));
|
|
270
|
+
const parsedWalletId = parseWalletId(input.scope.account_id);
|
|
271
|
+
if (!parsedWalletId.ok) return walletSessionScopeInvalidFailure();
|
|
272
|
+
const walletId = parsedWalletId.value;
|
|
273
|
+
const recorded = await input.authorizedOperations.resolveRecordedPasskeyOwnerOperation({
|
|
274
|
+
authorizedOperationId: operationId,
|
|
275
|
+
walletId,
|
|
276
|
+
requestOrigin: origin.requestOrigin,
|
|
277
|
+
nowMs: Date.now()
|
|
278
|
+
});
|
|
279
|
+
if (!recorded || input.requestExpiresAtMs > recorded.expiresAtMs) return {
|
|
280
|
+
ok: false,
|
|
281
|
+
error: routerAbStepUpError(403, "unauthorized", "Recorded owner approval is unavailable")
|
|
282
|
+
};
|
|
283
|
+
const activeMaterial = await input.resolveEd25519MaterialActivation({
|
|
284
|
+
walletId,
|
|
285
|
+
materialActivation: input.scope.material_activation
|
|
286
|
+
});
|
|
287
|
+
if (!activeMaterial.ok) return walletSessionScopeInvalidFailure();
|
|
288
|
+
return authenticateRouterAbWalletOperationStepUpIdentity({
|
|
289
|
+
kind: "verified_owner_proof",
|
|
290
|
+
headers: input.headers,
|
|
291
|
+
walletId,
|
|
292
|
+
materialOwner: input.scope.material_activation.material_owner,
|
|
293
|
+
operationId: recorded.operation.operation.operationId,
|
|
294
|
+
authority: recorded.authority,
|
|
295
|
+
runtimePolicyScope: activeMaterial.runtimePolicyScope,
|
|
296
|
+
expiresAtMs: recorded.expiresAtMs,
|
|
297
|
+
authorizedOperations: input.authorizedOperations
|
|
298
|
+
});
|
|
299
|
+
}
|
|
266
300
|
return authenticateRouterAbWalletOperationStepUpIdentity({
|
|
267
301
|
kind: "wallet_session_operation_credential_v1",
|
|
268
302
|
headers: input.headers,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseWalletAuthMethodId, parseWalletAuthorityId, parseWalletId } from "../../../shared-ts/src/utils/domainIds.js";
|
|
2
|
-
import { isPlainObject, requireCanonicalString } from "../../../shared-ts/src/utils/validation.js";
|
|
2
|
+
import { isPlainObject, requireCanonicalString, requireRecord } from "../../../shared-ts/src/utils/validation.js";
|
|
3
3
|
import { alphabetizeStringify, sha256HexUtf8 } from "../../../shared-ts/src/utils/digests.js";
|
|
4
|
+
import { parseDigestB64u } from "../../../shared-ts/src/utils/canonicalPrimitives.js";
|
|
4
5
|
import { parseDeviceId } from "../../../shared-ts/src/authorization/capabilityKinds.js";
|
|
5
6
|
import { secureRandomBase64Url } from "../../../shared-ts/src/utils/secureRandomId.js";
|
|
6
7
|
import { createServerAllocatedWalletId } from "../../../shared-ts/src/utils/registrationIds.js";
|
|
@@ -44,6 +45,7 @@ async function walletRegistrationSetupRequestDigest(namespace, input) {
|
|
|
44
45
|
runtimePolicyScope: input.runtimePolicyScope,
|
|
45
46
|
expectedOrigin: input.expectedOrigin,
|
|
46
47
|
wallet: input.request.wallet ?? { kind: "server_allocated" },
|
|
48
|
+
completion: input.request.completion,
|
|
47
49
|
authMethod: input.request.authMethod,
|
|
48
50
|
signerSelection: input.request.signerSelection
|
|
49
51
|
}));
|
|
@@ -64,7 +66,20 @@ async function admitRegistrationReservationHome(authority, input) {
|
|
|
64
66
|
};
|
|
65
67
|
return authority.admitHome(input);
|
|
66
68
|
}
|
|
69
|
+
function parseRegistrationSessionPublication(raw, walletId) {
|
|
70
|
+
const value = requireRecord(raw, "registration session publication");
|
|
71
|
+
if (value.kind === "none") return { kind: "none" };
|
|
72
|
+
if (value.kind !== "credential" || value.walletId !== String(walletId)) throw new Error("Invalid registration session publication");
|
|
73
|
+
const expiresAtMs = value.expiresAtMs;
|
|
74
|
+
if (typeof expiresAtMs !== "number" || !Number.isSafeInteger(expiresAtMs) || expiresAtMs <= 0) throw new Error("Invalid registration session publication expiry");
|
|
75
|
+
return {
|
|
76
|
+
kind: "credential",
|
|
77
|
+
walletId,
|
|
78
|
+
digest: parseDigestB64u(value.digest),
|
|
79
|
+
expiresAtMs
|
|
80
|
+
};
|
|
81
|
+
}
|
|
67
82
|
//#endregion
|
|
68
|
-
export { admitRegistrationReservationHome, cancelRegistrationHome, parseWalletRegistrationSetupReservation, proposeWalletRegistrationSetup, walletRegistrationSetupRequestDigest };
|
|
83
|
+
export { admitRegistrationReservationHome, cancelRegistrationHome, parseRegistrationSessionPublication, parseWalletRegistrationSetupReservation, proposeWalletRegistrationSetup, walletRegistrationSetupRequestDigest };
|
|
69
84
|
|
|
70
85
|
//# sourceMappingURL=walletRegistrationReservation.js.map
|
|
@@ -11,6 +11,7 @@ import { normalizeRuntimePolicyScope } from "../../../shared-ts/src/threshold/si
|
|
|
11
11
|
import { parseRouterAbEcdsaRegistrationActivationRequestV1, parseRouterAbEcdsaRegistrationRequestV1 } from "../../../shared-ts/src/utils/routerAbEcdsaDerivation.js";
|
|
12
12
|
import { normalizeAddAuthMethodInput, normalizeEmailOtpRegistrationProof, normalizeRegistrationAuthMethodInput } from "../../../shared-ts/src/utils/registrationAuthMethodInput.js";
|
|
13
13
|
import { parseHostedWalletSessionOperationCredentialToken, parsePrimaryWalletSessionOperationCredentialToken, parseSessionOrigin } from "../../../authorization/domain.js";
|
|
14
|
+
import { routeError, routeJson } from "../../framework/routeResponses.js";
|
|
14
15
|
import { extractBearerCredential, extractRouterApiEnvironmentId } from "../../auth/routerApiKeyAuth.js";
|
|
15
16
|
import { normalizeCorsOrigin } from "../../../core/SessionService.js";
|
|
16
17
|
import { parseRouterAbTraceContextV1 } from "../../../shared-ts/src/utils/routerAbTraceContext.js";
|
|
@@ -22,7 +23,6 @@ import { publishableKeyRoutePrincipal, resolvePublishableKeyApiCredentialAuth, r
|
|
|
22
23
|
import { resolveActiveRuntimePolicyScopeForEnvironment, resolveWalletSessionAdministrationAdmission, resolveWalletSessionOperationCredentialAdmission, resolveWalletSessionOperationCredentialAdmissionFromContext } from "../../auth/commonRouterUtils.js";
|
|
23
24
|
import { enforceRoutePolicy } from "../../framework/enforceRoutePolicy.js";
|
|
24
25
|
import { parseRegistrationPreparedSessionSeal } from "./registrationSessionSeal.js";
|
|
25
|
-
import { routeError, routeJson } from "../../framework/routeResponses.js";
|
|
26
26
|
import { parsePasskeyCustodyEnvelopeRecord } from "../../../shared-ts/src/passkey-custody/custodyEnvelope.js";
|
|
27
27
|
import "../../../shared-ts/src/passkey-custody/index.js";
|
|
28
28
|
import { computeWalletEcdsaKeyFactsInventoryChallengeDigestB64u } from "../../../shared-ts/src/utils/ecdsaKeyFactsInventory.js";
|
|
@@ -343,12 +343,19 @@ function parseWalletRegistrationSetupRequest(body) {
|
|
|
343
343
|
code: "invalid_body",
|
|
344
344
|
message: "authMethod is required"
|
|
345
345
|
};
|
|
346
|
+
const completion = body.completion ?? "wallet_session";
|
|
347
|
+
if (completion !== "wallet_session" && completion !== "owner_operations") return {
|
|
348
|
+
ok: false,
|
|
349
|
+
code: "invalid_body",
|
|
350
|
+
message: "Invalid registration completion"
|
|
351
|
+
};
|
|
346
352
|
const wallet = parseWalletRegistrationSetupWallet(body.wallet);
|
|
347
353
|
if (!wallet.ok) return wallet;
|
|
348
354
|
return {
|
|
349
355
|
ok: true,
|
|
350
356
|
value: {
|
|
351
357
|
registrationOperationId,
|
|
358
|
+
completion,
|
|
352
359
|
...wallet.value ? { wallet: wallet.value } : {},
|
|
353
360
|
signerSelection: signerSelection.value.selection,
|
|
354
361
|
authMethod
|
|
@@ -1928,6 +1935,7 @@ async function handleRouterApiWalletRegistrationNearProvisioning(input) {
|
|
|
1928
1935
|
if (sessionSeal.value) {
|
|
1929
1936
|
if (!isPasskeyWalletRegistrationNearProvisioningSuccessV2(result)) return routeError(400, "invalid_body", "Registration session sealing requires a passkey authority");
|
|
1930
1937
|
const sealStartedAt = Date.now();
|
|
1938
|
+
if (result.registrationEstablishedSession.kind === "owner_operations") return routeError(400, "invalid_body", "Owner registration has no session seal");
|
|
1931
1939
|
const sealed = await sessionSeal.value.finish(result.registrationEstablishedSession.session);
|
|
1932
1940
|
serverTiming.push(["near_finalize_session_seal", Math.max(0, Date.now() - sealStartedAt)]);
|
|
1933
1941
|
if (!sealed.ok) return routeJson(503, {
|
|
@@ -7,8 +7,8 @@ import { ROUTER_AB_ED25519_YAO_EMAIL_OTP_RECOVERY_BOOTSTRAP_KIND_V1 } from "../.
|
|
|
7
7
|
import { walletAuthAuthorityRef } from "../../../shared-ts/src/utils/walletAuthAuthority.js";
|
|
8
8
|
import { parseSessionOrigin, parseVerifiedOwnerProofId, projectActiveWalletSession } from "../../../authorization/domain.js";
|
|
9
9
|
import { buildVerifiedWalletSessionEmailOtpFactorResult, buildVerifiedWalletSessionPasskeyFactorResult } from "../../../authorization/factorEvidence.js";
|
|
10
|
-
import { emailOtpFailureWebhookEventDescriptors, emailOtpLoggedInWebhookEventDescriptor } from "../emailOtp/emailOtpSessionRouteHelpers.js";
|
|
11
10
|
import { EMAIL_OTP_CHANNEL } from "../../../shared-ts/src/utils/emailOtpDomain.js";
|
|
11
|
+
import { emailOtpFailureWebhookEventDescriptors, emailOtpLoggedInWebhookEventDescriptor } from "../emailOtp/emailOtpSessionRouteHelpers.js";
|
|
12
12
|
import { thresholdEd25519StatusCode } from "../../../threshold/statusCodes.js";
|
|
13
13
|
import { parseWalletUnlockBackend } from "../emailOtp/emailOtpRequestValidation.js";
|
|
14
14
|
//#region src/router/domains/walletUnlock/walletUnlockRouteHandlers.ts
|
|
@@ -5,9 +5,9 @@ import { parseDigestB64u } from "../../../../shared-ts/src/utils/canonicalPrimit
|
|
|
5
5
|
import "../../../../shared-ts/src/utils/encoders.js";
|
|
6
6
|
import { parseAuthFactorId } from "../../../../shared-ts/src/authorization/capabilityKinds.js";
|
|
7
7
|
import { buildVerifiedWalletOperationEmailOtpFactorResult, buildVerifiedWalletOperationPasskeyFactorResult } from "../../../../authorization/factorEvidence.js";
|
|
8
|
-
import { json } from "../../../framework/http.js";
|
|
9
|
-
import { hashEmailOtpOperationBinding } from "../../../domains/emailOtp/emailOtpSessionRouteHelpers.js";
|
|
10
8
|
import { EMAIL_OTP_CHANNEL, WALLET_EMAIL_OTP_EXPORT_OPERATION, WALLET_EMAIL_OTP_TRANSACTION_SIGN_OPERATION } from "../../../../shared-ts/src/utils/emailOtpDomain.js";
|
|
9
|
+
import { hashEmailOtpOperationBinding } from "../../../domains/emailOtp/emailOtpSessionRouteHelpers.js";
|
|
10
|
+
import { json } from "../../../framework/http.js";
|
|
11
11
|
//#region src/router/transport/fetch/routes/ecdsaOperationStepUpFactor.ts
|
|
12
12
|
async function verifyEcdsaOperationStepUpFactor(input) {
|
|
13
13
|
const proof = input.proof;
|
|
@@ -2,7 +2,8 @@ import { parseWalletId } from "../../../../shared-ts/src/utils/domainIds.js";
|
|
|
2
2
|
import { requireCanonicalString, requireRecord } from "../../../../shared-ts/src/utils/validation.js";
|
|
3
3
|
import { sha256HexUtf8 } from "../../../../shared-ts/src/utils/digests.js";
|
|
4
4
|
import { EXTERNAL_AUTH_PATH, externalAuthFailure, parseExternalAuthConfiguration } from "../../../../shared-ts/src/auth/externalAuth.js";
|
|
5
|
-
import {
|
|
5
|
+
import { externalOwnerChallenge, readExternalOwnerOperation } from "../../../../auth/externalOwnerOperations.js";
|
|
6
|
+
import { authorizeWalletOwnerOperation, completeWalletOwnerOperation, walletOwnerOperationAuthorizationId } from "../walletOwnerOperationAuthorization.js";
|
|
6
7
|
import { extractBearerCredential, resolveSourceIpFromFetchHeaders } from "../../../auth/routerApiKeyAuth.js";
|
|
7
8
|
import { json, readJson } from "../../../framework/http.js";
|
|
8
9
|
//#region src/router/transport/fetch/routes/externalAuth.ts
|
|
@@ -10,12 +11,29 @@ const ACTIONS = [
|
|
|
10
11
|
"associate",
|
|
11
12
|
"unlink",
|
|
12
13
|
"prepare",
|
|
14
|
+
"prepare-association",
|
|
15
|
+
"prepare-unlink",
|
|
16
|
+
"owner-challenge",
|
|
13
17
|
"exchange",
|
|
14
18
|
"backend-exchange",
|
|
15
19
|
"redeem",
|
|
16
20
|
"settings",
|
|
17
21
|
"logout"
|
|
18
22
|
];
|
|
23
|
+
function parseIdentityProof(value) {
|
|
24
|
+
const proof = requireRecord(value, "external identity proof");
|
|
25
|
+
switch (proof.kind) {
|
|
26
|
+
case "google": return {
|
|
27
|
+
kind: "google",
|
|
28
|
+
idToken: requireCanonicalString(proof.idToken, "idToken")
|
|
29
|
+
};
|
|
30
|
+
case "application": return {
|
|
31
|
+
kind: "application",
|
|
32
|
+
assertion: requireCanonicalString(proof.assertion, "assertion")
|
|
33
|
+
};
|
|
34
|
+
default: throw new Error("Unsupported external identity proof");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
19
37
|
function parseCommand(action, body) {
|
|
20
38
|
const wallet = parseWalletId(body.walletId);
|
|
21
39
|
if (!wallet.ok) throw new Error("Wallet ID is invalid");
|
|
@@ -24,26 +42,41 @@ function parseCommand(action, body) {
|
|
|
24
42
|
action,
|
|
25
43
|
walletId,
|
|
26
44
|
attemptId: requireCanonicalString(body.attemptId, "attemptId"),
|
|
27
|
-
|
|
45
|
+
proof: parseIdentityProof(body.proof)
|
|
28
46
|
};
|
|
29
47
|
const appOrigin = requireCanonicalString(body.appOrigin, "appOrigin");
|
|
30
48
|
if (new URL(appOrigin).origin !== appOrigin) throw new Error("Application origin is invalid");
|
|
31
49
|
switch (action) {
|
|
32
|
-
case "associate":
|
|
50
|
+
case "associate":
|
|
51
|
+
case "unlink": return {
|
|
33
52
|
action,
|
|
34
53
|
walletId,
|
|
35
54
|
appOrigin,
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
attemptId: requireCanonicalString(body.attemptId, "attemptId"),
|
|
56
|
+
binding: secret(body.binding, "binding"),
|
|
57
|
+
sessionToken: secret(body.sessionToken, "sessionToken"),
|
|
58
|
+
code: secret(body.code, "code"),
|
|
59
|
+
factorProof: body.factorProof
|
|
38
60
|
};
|
|
39
|
-
case "
|
|
61
|
+
case "owner-challenge": return {
|
|
40
62
|
action,
|
|
41
63
|
walletId,
|
|
42
64
|
appOrigin,
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
attemptId: requireCanonicalString(body.attemptId, "attemptId"),
|
|
66
|
+
binding: secret(body.binding, "binding"),
|
|
67
|
+
sessionToken: secret(body.sessionToken, "sessionToken"),
|
|
68
|
+
code: secret(body.code, "code")
|
|
69
|
+
};
|
|
70
|
+
case "prepare-unlink": return {
|
|
71
|
+
action,
|
|
72
|
+
walletId,
|
|
73
|
+
appOrigin,
|
|
74
|
+
associationId: requireCanonicalString(body.associationId, "associationId"),
|
|
75
|
+
binding: secret(body.binding, "binding"),
|
|
76
|
+
sessionToken: secret(body.sessionToken, "sessionToken")
|
|
45
77
|
};
|
|
46
|
-
case "prepare":
|
|
78
|
+
case "prepare":
|
|
79
|
+
case "prepare-association": return {
|
|
47
80
|
action,
|
|
48
81
|
walletId,
|
|
49
82
|
appOrigin,
|
|
@@ -143,11 +176,10 @@ async function resolveApplicationTrust(ctx, service, command) {
|
|
|
143
176
|
actorUserId: result.principal.apiKeyId,
|
|
144
177
|
roles: []
|
|
145
178
|
}, { status: "ACTIVE" }))?.find(matchesEnvironment.bind(null, scope.envId));
|
|
146
|
-
|
|
179
|
+
return parseExternalAuthConfiguration(environment?.externalAuth ?? {
|
|
147
180
|
kind: "disabled",
|
|
148
181
|
revision: 0
|
|
149
182
|
});
|
|
150
|
-
return configuration.kind === "google" ? configuration : null;
|
|
151
183
|
}
|
|
152
184
|
function matchesEnvironment(environmentId, environment) {
|
|
153
185
|
return environment.id === environmentId;
|
|
@@ -174,43 +206,118 @@ async function handleExternalAuth(ctx) {
|
|
|
174
206
|
}
|
|
175
207
|
async function dispatch(ctx, service, trust, command) {
|
|
176
208
|
const walletId = command.walletId;
|
|
177
|
-
|
|
178
|
-
if (command.action === "prepare" || command.action === "exchange" || command.action === "backend-exchange" || command.action === "associate") {
|
|
209
|
+
if (command.action === "prepare" || command.action === "prepare-association" || command.action === "prepare-unlink" || command.action === "exchange" || command.action === "backend-exchange" || command.action === "associate") {
|
|
179
210
|
const rateKey = await sha256HexUtf8(resolveSourceIpFromFetchHeaders(ctx.request.headers) || "unknown");
|
|
180
211
|
if (!await service.store.consumeAuthenticationAllowance(rateKey, Date.now())) return failure(429, "rate_limited");
|
|
181
212
|
}
|
|
182
|
-
if (command.action === "
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
213
|
+
if (command.action === "backend-exchange") {
|
|
214
|
+
if (trust.kind === "disabled") return failure(403, "access_denied");
|
|
215
|
+
return json(await service.exchange({
|
|
216
|
+
walletId,
|
|
217
|
+
attemptId: command.attemptId,
|
|
218
|
+
proof: command.proof,
|
|
219
|
+
trust
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
const originBinding = JSON.stringify([ctx.request.headers.get("origin") || "", command.appOrigin]);
|
|
223
|
+
if (command.action === "prepare-unlink") return json(await service.prepareUnlink({
|
|
224
|
+
walletId,
|
|
225
|
+
associationId: command.associationId,
|
|
226
|
+
originBinding,
|
|
227
|
+
binding: command.binding,
|
|
228
|
+
sessionToken: command.sessionToken,
|
|
229
|
+
trustRevision: trust.revision
|
|
230
|
+
}));
|
|
231
|
+
if (command.action === "owner-challenge" || command.action === "associate" || command.action === "unlink") {
|
|
232
|
+
const binding = {
|
|
233
|
+
walletId,
|
|
234
|
+
attemptId: command.attemptId,
|
|
235
|
+
originBinding,
|
|
236
|
+
bindingHash: await sha256HexUtf8(command.binding),
|
|
237
|
+
sessionTokenHash: await sha256HexUtf8(command.sessionToken),
|
|
238
|
+
codeHash: await sha256HexUtf8(command.code),
|
|
239
|
+
trustRevision: trust.revision,
|
|
187
240
|
nowMs: Date.now()
|
|
241
|
+
};
|
|
242
|
+
const result = await readExternalOwnerOperation({
|
|
243
|
+
store: service.store,
|
|
244
|
+
binding,
|
|
245
|
+
requestOrigin: ctx.request.headers.get("origin") || "",
|
|
246
|
+
phase: command.action === "owner-challenge" ? "prepare" : "complete"
|
|
247
|
+
});
|
|
248
|
+
if (!result.ok) return json(result, { status: 403 });
|
|
249
|
+
const ownerOperation = result.value;
|
|
250
|
+
if (command.action === "owner-challenge") return json({
|
|
251
|
+
ok: true,
|
|
252
|
+
value: externalOwnerChallenge(ownerOperation)
|
|
188
253
|
});
|
|
189
|
-
if (
|
|
190
|
-
if (
|
|
191
|
-
await service.
|
|
192
|
-
|
|
254
|
+
if (command.action !== ownerOperation.attempt.purpose) return failure(403, "access_denied");
|
|
255
|
+
if (ownerOperation.attempt.status === "consumed") {
|
|
256
|
+
const stored = await ctx.service.authorizedOperations.readAuthorizedOperationById({
|
|
257
|
+
tenantId: ctx.service.authorizedOperations.tenantId,
|
|
258
|
+
authorizedOperationId: walletOwnerOperationAuthorizationId(ownerOperation.operation, ownerOperation.challengeB64u)
|
|
259
|
+
});
|
|
260
|
+
if (!stored || stored.authorization.kind !== "verified_step_up" || stored.quota.kind !== "quota_neutral" || stored.lifecycle !== "completed") return failure(503, "unavailable");
|
|
261
|
+
return new Response(stored.response.bodyText, {
|
|
262
|
+
status: stored.response.status,
|
|
263
|
+
headers: {
|
|
264
|
+
"Content-Type": stored.response.contentType,
|
|
265
|
+
"Cache-Control": "no-store"
|
|
266
|
+
}
|
|
267
|
+
});
|
|
193
268
|
}
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
return await service.store.associate({
|
|
269
|
+
const authorized = await authorizeWalletOwnerOperation({
|
|
270
|
+
ctx,
|
|
197
271
|
walletId,
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
})
|
|
272
|
+
operation: ownerOperation.operation,
|
|
273
|
+
payload: ownerOperation.payload,
|
|
274
|
+
factorProof: command.factorProof
|
|
275
|
+
});
|
|
276
|
+
if (!authorized.ok) return authorized.response;
|
|
277
|
+
if (!await service.store.consumeOwnerAttempt({
|
|
278
|
+
walletId,
|
|
279
|
+
attemptId: binding.attemptId,
|
|
280
|
+
originBinding,
|
|
281
|
+
bindingHash: binding.bindingHash,
|
|
282
|
+
sessionTokenHash: binding.sessionTokenHash,
|
|
283
|
+
codeHash: binding.codeHash,
|
|
284
|
+
trustRevision: binding.trustRevision,
|
|
285
|
+
nowMs: Date.now()
|
|
286
|
+
}, authorized.ownerAuthority)) return completeWalletOwnerOperation(ctx, authorized.operation, 403, externalAuthFailure("access_denied"), "failed_before_side_effect");
|
|
287
|
+
const attempt = ownerOperation.attempt;
|
|
288
|
+
if (attempt.purpose === "associate") {
|
|
289
|
+
if (!await service.store.associate({
|
|
290
|
+
walletId,
|
|
291
|
+
identity: attempt.identity,
|
|
292
|
+
authorityId: authorized.ownerAuthority.authorityId,
|
|
293
|
+
authorityEpoch: authorized.ownerAuthority.revocationEpoch
|
|
294
|
+
})) return completeWalletOwnerOperation(ctx, authorized.operation, 403, externalAuthFailure("access_denied"), "failed_before_side_effect");
|
|
295
|
+
} else await service.store.revokeAssociation({
|
|
296
|
+
walletId,
|
|
297
|
+
associationId: attempt.associationId,
|
|
298
|
+
owner: authorized.ownerAuthority
|
|
299
|
+
});
|
|
300
|
+
return completeWalletOwnerOperation(ctx, authorized.operation, 200, {
|
|
301
|
+
ok: true,
|
|
302
|
+
value: null
|
|
303
|
+
});
|
|
202
304
|
}
|
|
203
|
-
if (
|
|
305
|
+
if (trust.kind === "disabled") return failure(403, "access_denied");
|
|
306
|
+
if (command.action === "prepare" || command.action === "prepare-association") return json(await service.prepare({
|
|
307
|
+
purpose: command.action === "prepare" ? "authenticate" : "associate",
|
|
204
308
|
walletId,
|
|
205
309
|
originBinding,
|
|
206
310
|
binding: command.binding,
|
|
207
311
|
sessionToken: command.sessionToken
|
|
208
312
|
}));
|
|
209
|
-
if (command.action === "exchange"
|
|
313
|
+
if (command.action === "exchange") return json(await service.exchange({
|
|
210
314
|
trust,
|
|
211
315
|
walletId,
|
|
212
316
|
attemptId: command.attemptId,
|
|
213
|
-
|
|
317
|
+
proof: {
|
|
318
|
+
kind: "google",
|
|
319
|
+
idToken: command.idToken
|
|
320
|
+
}
|
|
214
321
|
}));
|
|
215
322
|
const tokenHash = await sha256HexUtf8(command.sessionToken);
|
|
216
323
|
if (command.action === "logout") {
|