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