@seams/wallet-server 0.6.0 → 0.6.2
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 +1 -1
- package/cloudflare-router-ab/scripts/initialize-local-wallet.mjs +9 -1
- package/cloudflare-router-ab/scripts/start-local-role-workers.mjs +15 -9
- package/cloudflare-router-ab/scripts/start-local-wallet-system.mjs +44 -15
- package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +1 -0
- package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationService.js +195 -50
- package/dist/esm/router/cloudflare.js +227 -30
- package/dist/esm/router/domains/walletRegistration/walletRegistrationRoutes.js +166 -29
- package/dist/esm/router/express.js +227 -30
- package/dist/esm/router/framework/routeDefinitions.js +4 -0
- package/dist/esm/router/transport/fetch/routes/walletRegistration.js +59 -3
- package/dist/types/wallet-server/src/core/threeRouteRegistrationContracts.d.ts +31 -2
- package/dist/types/wallet-server/src/router/cloudflare/d1/registration/d1WalletRegistrationService.d.ts +7 -5
- package/dist/types/wallet-server/src/router/domains/walletRegistration/walletRegistrationInputs.d.ts +7 -0
- package/dist/types/wallet-server/src/router/domains/walletRegistration/walletRegistrationRoutes.d.ts +4 -1
- package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +6 -5
- package/package.json +1 -1
|
@@ -8091,6 +8091,10 @@ function createRouterApiRouteDefinitions(options = {}) {
|
|
|
8091
8091
|
plane: "public",
|
|
8092
8092
|
proof: "webauthn",
|
|
8093
8093
|
rationale: "Registration respond is authorized by the signed setup payload and the registration authority proof."
|
|
8094
|
+
}, ROUTER_API_WALLET_REGISTRATION_SESSION_SERVICES), publicRoute("wallet_registration_near_admission", "POST", "/wallets/register/near-admission", "Authorize deferred NEAR registration while ECDSA respond runs", {
|
|
8095
|
+
plane: "public",
|
|
8096
|
+
proof: "webauthn",
|
|
8097
|
+
rationale: "NEAR admission is authorized by the signed setup payload and the registration authority proof."
|
|
8094
8098
|
}, ROUTER_API_WALLET_REGISTRATION_SESSION_SERVICES), publicRoute("wallet_registration_activate", "POST", "/wallets/register/activate", "Activate and finalize one wallet ECDSA registration", {
|
|
8095
8099
|
plane: "public",
|
|
8096
8100
|
proof: "threshold_protocol_state",
|
|
@@ -11110,6 +11114,9 @@ function isEmailOtpWalletRegistrationActivateSuccessV2(result) {
|
|
|
11110
11114
|
function isPasskeyWalletRegistrationActivateSuccessV2(result) {
|
|
11111
11115
|
return result.authMethod.kind === "passkey";
|
|
11112
11116
|
}
|
|
11117
|
+
function isPasskeyWalletRegistrationNearProvisioningSuccessV2(result) {
|
|
11118
|
+
return result.authMethod.kind === "passkey";
|
|
11119
|
+
}
|
|
11113
11120
|
function walletRegistrationRoutePolicyServices(input) {
|
|
11114
11121
|
const service = input.services.walletRegistration;
|
|
11115
11122
|
return {
|
|
@@ -12683,13 +12690,62 @@ async function handleRouterApiWalletRegistrationRespond(input) {
|
|
|
12683
12690
|
if (!session) return routeError(500, "internal", "wallet registration respond requires session signing");
|
|
12684
12691
|
const parsed = parseWalletRegistrationRespondRequest(input.body);
|
|
12685
12692
|
if (!parsed.ok) return routeError(400, parsed.code, parsed.message);
|
|
12693
|
+
const serverTiming = [];
|
|
12686
12694
|
const result = await input.services.walletRegistration.respondWalletRegistration({
|
|
12687
12695
|
...parsed.value,
|
|
12688
12696
|
verifier: session,
|
|
12689
12697
|
...registrationUserAgentFromHeaders(input.headers) ? { userAgent: registrationUserAgentFromHeaders(input.headers) } : {}
|
|
12690
|
-
}, traceContext.value ?? void 0);
|
|
12698
|
+
}, traceContext.value ?? void 0, serverTiming);
|
|
12699
|
+
return routeJson(result.ok ? 200 : 400, result, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
12700
|
+
}
|
|
12701
|
+
async function handleRouterApiWalletRegistrationNearAdmission(input) {
|
|
12702
|
+
const traceContext = parseRegistrationTraceContext(input.headers);
|
|
12703
|
+
if (!traceContext.ok) return routeError(400, "invalid_trace_id", traceContext.message);
|
|
12704
|
+
if (!isPlainObject$1(input.body)) return routeError(400, "invalid_body", "JSON body required");
|
|
12705
|
+
const session = input.services.session;
|
|
12706
|
+
if (!session) return routeError(500, "internal", "NEAR admission requires setup verification");
|
|
12707
|
+
const body = input.body;
|
|
12708
|
+
const registrationCeremonyId = String(body.registrationCeremonyId || "").trim();
|
|
12709
|
+
const signedSetup = String(body.signedSetup || "").trim();
|
|
12710
|
+
if (!registrationCeremonyId || !signedSetup) return routeError(400, "invalid_body", "registrationCeremonyId and signedSetup are required");
|
|
12711
|
+
const authority = parseWalletRegistrationAuthorityProof(body);
|
|
12712
|
+
if (!authority.ok) return routeError(400, authority.code, authority.message);
|
|
12713
|
+
const result = await input.services.walletRegistration.authorizeWalletRegistrationNearAdmission({
|
|
12714
|
+
registrationCeremonyId,
|
|
12715
|
+
signedSetup,
|
|
12716
|
+
authority: authority.value,
|
|
12717
|
+
verifier: session,
|
|
12718
|
+
...registrationUserAgentFromHeaders(input.headers) ? { userAgent: registrationUserAgentFromHeaders(input.headers) } : {}
|
|
12719
|
+
});
|
|
12691
12720
|
return routeJson(result.ok ? 200 : 400, result);
|
|
12692
12721
|
}
|
|
12722
|
+
function parseWalletRegistrationAuthorityProof(body) {
|
|
12723
|
+
const hasWebAuthn = Object.prototype.hasOwnProperty.call(body, "webauthn_registration");
|
|
12724
|
+
if (hasWebAuthn === Object.prototype.hasOwnProperty.call(body, "emailOtpRegistrationProof")) return {
|
|
12725
|
+
ok: false,
|
|
12726
|
+
code: "invalid_body",
|
|
12727
|
+
message: "exactly one registration authority proof is required"
|
|
12728
|
+
};
|
|
12729
|
+
if (hasWebAuthn) return {
|
|
12730
|
+
ok: true,
|
|
12731
|
+
value: {
|
|
12732
|
+
kind: "passkey",
|
|
12733
|
+
webauthnRegistration: body.webauthn_registration
|
|
12734
|
+
}
|
|
12735
|
+
};
|
|
12736
|
+
const proof = normalizeEmailOtpRegistrationProof(body.emailOtpRegistrationProof);
|
|
12737
|
+
return proof ? {
|
|
12738
|
+
ok: true,
|
|
12739
|
+
value: {
|
|
12740
|
+
kind: "email_otp",
|
|
12741
|
+
emailOtpRegistrationProof: proof
|
|
12742
|
+
}
|
|
12743
|
+
} : {
|
|
12744
|
+
ok: false,
|
|
12745
|
+
code: "invalid_body",
|
|
12746
|
+
message: "emailOtpRegistrationProof is invalid"
|
|
12747
|
+
};
|
|
12748
|
+
}
|
|
12693
12749
|
/**
|
|
12694
12750
|
* Parses respond's public body. `signedSetup` stays an opaque string here —
|
|
12695
12751
|
* the service verifies it; parsing its contents at the boundary would invite
|
|
@@ -12724,29 +12780,9 @@ function parseWalletRegistrationRespondRequest(body) {
|
|
|
12724
12780
|
code: "invalid_body",
|
|
12725
12781
|
message: "an Ed25519-only respond carries no ECDSA registration"
|
|
12726
12782
|
};
|
|
12727
|
-
const
|
|
12728
|
-
if (
|
|
12729
|
-
|
|
12730
|
-
code: "invalid_body",
|
|
12731
|
-
message: "exactly one registration authority proof is required"
|
|
12732
|
-
};
|
|
12733
|
-
let authority;
|
|
12734
|
-
if (hasWebAuthn) authority = {
|
|
12735
|
-
kind: "passkey",
|
|
12736
|
-
webauthnRegistration: body.webauthn_registration
|
|
12737
|
-
};
|
|
12738
|
-
else {
|
|
12739
|
-
const proof = normalizeEmailOtpRegistrationProof(body.emailOtpRegistrationProof);
|
|
12740
|
-
if (!proof) return {
|
|
12741
|
-
ok: false,
|
|
12742
|
-
code: "invalid_body",
|
|
12743
|
-
message: "emailOtpRegistrationProof is invalid"
|
|
12744
|
-
};
|
|
12745
|
-
authority = {
|
|
12746
|
-
kind: "email_otp",
|
|
12747
|
-
emailOtpRegistrationProof: proof
|
|
12748
|
-
};
|
|
12749
|
-
}
|
|
12783
|
+
const parsedAuthority = parseWalletRegistrationAuthorityProof(body);
|
|
12784
|
+
if (!parsedAuthority.ok) return parsedAuthority;
|
|
12785
|
+
const authority = parsedAuthority.value;
|
|
12750
12786
|
if (planKind === "near_ed25519") return {
|
|
12751
12787
|
ok: true,
|
|
12752
12788
|
value: {
|
|
@@ -12873,6 +12909,7 @@ async function handleRouterApiWalletRegistrationActivate(input) {
|
|
|
12873
12909
|
} catch {
|
|
12874
12910
|
return routeError(400, "invalid_body", "ECDSA activation facts are invalid");
|
|
12875
12911
|
}
|
|
12912
|
+
const serverTiming = [];
|
|
12876
12913
|
const result = await input.services.walletRegistration.activateWalletRegistration({
|
|
12877
12914
|
registrationCeremonyId,
|
|
12878
12915
|
signedSetup,
|
|
@@ -12882,7 +12919,7 @@ async function handleRouterApiWalletRegistrationActivate(input) {
|
|
|
12882
12919
|
...body.emailOtpEnrollment ? { emailOtpEnrollment: body.emailOtpEnrollment } : {},
|
|
12883
12920
|
...body.walletCustodyCommit !== void 0 ? { walletCustodyCommit: body.walletCustodyCommit } : {},
|
|
12884
12921
|
verifier: session
|
|
12885
|
-
}, traceContext.value ?? void 0);
|
|
12922
|
+
}, traceContext.value ?? void 0, serverTiming);
|
|
12886
12923
|
if (!result.ok) return routeJson(400, result);
|
|
12887
12924
|
if (!isEmailOtpWalletRegistrationActivateSuccessV2(result) && !isPasskeyWalletRegistrationActivateSuccessV2(result)) return routeError(500, "internal", "Wallet registration activation returned an invalid authority");
|
|
12888
12925
|
const projectionError = await projectActivatedWallet({
|
|
@@ -12891,7 +12928,7 @@ async function handleRouterApiWalletRegistrationActivate(input) {
|
|
|
12891
12928
|
signedSetup
|
|
12892
12929
|
});
|
|
12893
12930
|
if (projectionError) return routeError(500, "internal", `Wallet was created but its Console projection failed: ${projectionError}`);
|
|
12894
|
-
return routeJson(200, result);
|
|
12931
|
+
return routeJson(200, result, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
12895
12932
|
}
|
|
12896
12933
|
/**
|
|
12897
12934
|
* Refactor 94C. `POST /wallets/register/near-provisioning` — the non-blocking
|
|
@@ -12909,6 +12946,9 @@ async function handleRouterApiWalletRegistrationNearProvisioning(input) {
|
|
|
12909
12946
|
if (!registrationCeremonyId || !signedSetup || !idempotencyKey) return routeError(400, "invalid_body", "registrationCeremonyId, signedSetup and idempotencyKey are required");
|
|
12910
12947
|
const ed25519 = parseWalletRegistrationEd25519Finalize(body.ed25519);
|
|
12911
12948
|
if (!ed25519.ok) return routeError(400, ed25519.code, ed25519.message);
|
|
12949
|
+
const sessionSeal = parseRegistrationPreparedSessionSeal(body.sessionSeal);
|
|
12950
|
+
if (!sessionSeal.ok) return routeError(400, sessionSeal.code, sessionSeal.message);
|
|
12951
|
+
const serverTiming = [];
|
|
12912
12952
|
const result = await input.services.walletRegistration.completeWalletRegistrationNearProvisioning({
|
|
12913
12953
|
authorization: registrationContinuationAuthorization(input.headers),
|
|
12914
12954
|
registrationCeremonyId,
|
|
@@ -12918,9 +12958,110 @@ async function handleRouterApiWalletRegistrationNearProvisioning(input) {
|
|
|
12918
12958
|
emailOtpEnrollment: body.emailOtpEnrollment,
|
|
12919
12959
|
...body.walletCustodyCommit !== void 0 ? { walletCustodyCommit: body.walletCustodyCommit } : {},
|
|
12920
12960
|
verifier: session
|
|
12921
|
-
});
|
|
12961
|
+
}, serverTiming);
|
|
12922
12962
|
if (!result.ok) return routeJson(400, result);
|
|
12923
|
-
|
|
12963
|
+
if (sessionSeal.value) {
|
|
12964
|
+
if (!isPasskeyWalletRegistrationNearProvisioningSuccessV2(result)) return routeError(400, "invalid_body", "Registration session sealing requires a passkey authority");
|
|
12965
|
+
const sealStartedAt = Date.now();
|
|
12966
|
+
const sealed = await applyRegistrationPreparedSessionSeal({
|
|
12967
|
+
options: input.signingSessionSeal,
|
|
12968
|
+
request: sessionSeal.value,
|
|
12969
|
+
response: result
|
|
12970
|
+
});
|
|
12971
|
+
serverTiming.push(["near_finalize_session_seal", Math.max(0, Date.now() - sealStartedAt)]);
|
|
12972
|
+
if (!sealed.ok) return routeJson(503, {
|
|
12973
|
+
ok: false,
|
|
12974
|
+
code: sealed.code,
|
|
12975
|
+
message: sealed.message
|
|
12976
|
+
}, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
12977
|
+
return routeJson(200, {
|
|
12978
|
+
...result,
|
|
12979
|
+
sessionSeal: sealed.value
|
|
12980
|
+
}, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
12981
|
+
}
|
|
12982
|
+
return routeJson(200, result, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
12983
|
+
}
|
|
12984
|
+
function parseRegistrationPreparedSessionSeal(raw) {
|
|
12985
|
+
if (raw === void 0) return {
|
|
12986
|
+
ok: true,
|
|
12987
|
+
value: void 0
|
|
12988
|
+
};
|
|
12989
|
+
if (!isPlainObject$1(raw)) return {
|
|
12990
|
+
ok: false,
|
|
12991
|
+
code: "invalid_body",
|
|
12992
|
+
message: "sessionSeal must be an object"
|
|
12993
|
+
};
|
|
12994
|
+
if (Object.keys(raw).some((field) => ![
|
|
12995
|
+
"thresholdSessionId",
|
|
12996
|
+
"ciphertext",
|
|
12997
|
+
"keyVersion"
|
|
12998
|
+
].includes(field))) return {
|
|
12999
|
+
ok: false,
|
|
13000
|
+
code: "invalid_body",
|
|
13001
|
+
message: "sessionSeal has unknown fields"
|
|
13002
|
+
};
|
|
13003
|
+
const thresholdSessionId = String(raw.thresholdSessionId || "").trim();
|
|
13004
|
+
const ciphertext = String(raw.ciphertext || "").trim();
|
|
13005
|
+
const keyVersion = String(raw.keyVersion || "").trim();
|
|
13006
|
+
if (!thresholdSessionId || !ciphertext) return {
|
|
13007
|
+
ok: false,
|
|
13008
|
+
code: "invalid_body",
|
|
13009
|
+
message: "sessionSeal thresholdSessionId and ciphertext are required"
|
|
13010
|
+
};
|
|
13011
|
+
return {
|
|
13012
|
+
ok: true,
|
|
13013
|
+
value: {
|
|
13014
|
+
thresholdSessionId,
|
|
13015
|
+
ciphertext,
|
|
13016
|
+
...keyVersion ? { keyVersion } : {}
|
|
13017
|
+
}
|
|
13018
|
+
};
|
|
13019
|
+
}
|
|
13020
|
+
async function applyRegistrationPreparedSessionSeal(args) {
|
|
13021
|
+
if (!args.options) return {
|
|
13022
|
+
ok: false,
|
|
13023
|
+
code: "signing_session_seal_unavailable",
|
|
13024
|
+
message: "Signing-session sealing is unavailable"
|
|
13025
|
+
};
|
|
13026
|
+
const session = args.response.registrationEstablishedSession.session;
|
|
13027
|
+
const tokens = session.tokens;
|
|
13028
|
+
const ed25519 = tokens.kind === "near_ed25519" || tokens.kind === "near_ed25519_and_evm_family_ecdsa" ? tokens.ed25519 : null;
|
|
13029
|
+
if (!ed25519 || String(ed25519.thresholdSessionId) !== args.request.thresholdSessionId) return {
|
|
13030
|
+
ok: false,
|
|
13031
|
+
code: "invalid_session_seal",
|
|
13032
|
+
message: "Prepared seal does not match the established Ed25519 session"
|
|
13033
|
+
};
|
|
13034
|
+
const sealed = await args.options.service.applyServerSeal(args.request, {
|
|
13035
|
+
userId: String(session.walletId),
|
|
13036
|
+
session: {
|
|
13037
|
+
kind: "exact_wallet_session_operation_credential",
|
|
13038
|
+
curve: "ed25519",
|
|
13039
|
+
thresholdSessionId: args.request.thresholdSessionId,
|
|
13040
|
+
userId: String(session.walletId),
|
|
13041
|
+
expiresAtMs: session.expiresAtMs
|
|
13042
|
+
}
|
|
13043
|
+
});
|
|
13044
|
+
if (!sealed.ok) return sealed;
|
|
13045
|
+
const keyVersion = String(sealed.keyVersion || "").trim();
|
|
13046
|
+
if (!keyVersion) return {
|
|
13047
|
+
ok: false,
|
|
13048
|
+
code: "invalid_session_seal",
|
|
13049
|
+
message: "Signing-session seal response is missing its key version"
|
|
13050
|
+
};
|
|
13051
|
+
return {
|
|
13052
|
+
ok: true,
|
|
13053
|
+
value: {
|
|
13054
|
+
ciphertext: sealed.ciphertext,
|
|
13055
|
+
keyVersion,
|
|
13056
|
+
expiresAtMs: Math.min(sealed.expiresAtMs ?? session.expiresAtMs, session.expiresAtMs),
|
|
13057
|
+
remainingUses: session.remainingUses
|
|
13058
|
+
}
|
|
13059
|
+
};
|
|
13060
|
+
}
|
|
13061
|
+
function ecdsaGatewayServerTimingHeaders(serverTiming) {
|
|
13062
|
+
if (serverTiming.length === 0) return void 0;
|
|
13063
|
+
const header = serverTiming.map(([name, durationMs]) => `${name};dur=${Math.max(0, durationMs).toFixed(1)}`).join(", ");
|
|
13064
|
+
return { "Server-Timing": header };
|
|
12924
13065
|
}
|
|
12925
13066
|
async function handleRouterApiWalletAddSignerStart(input) {
|
|
12926
13067
|
if (!isPlainObject$1(input.body)) return routeError(400, "invalid_body", "JSON body required");
|
|
@@ -13279,6 +13420,7 @@ function routerApiEmailOtpRouteService(service) {
|
|
|
13279
13420
|
const ROUTE_IDS = [
|
|
13280
13421
|
"wallet_registration_setup",
|
|
13281
13422
|
"wallet_registration_respond",
|
|
13423
|
+
"wallet_registration_near_admission",
|
|
13282
13424
|
"wallet_registration_activate",
|
|
13283
13425
|
"wallet_registration_near_provisioning",
|
|
13284
13426
|
"wallet_add_signer_intent",
|
|
@@ -13334,9 +13476,64 @@ async function handleWalletRegistration(ctx) {
|
|
|
13334
13476
|
publishableKeyAuth: ctx.opts.publishableKeyAuth,
|
|
13335
13477
|
walletProjection: ctx.opts.walletProjection
|
|
13336
13478
|
},
|
|
13337
|
-
sourceIp: resolveSourceIpFromFetchHeaders(ctx.request.headers) || void 0
|
|
13479
|
+
sourceIp: resolveSourceIpFromFetchHeaders(ctx.request.headers) || void 0,
|
|
13480
|
+
signingSessionSeal: ctx.opts.signingSessionSeal
|
|
13338
13481
|
};
|
|
13339
|
-
|
|
13482
|
+
let response;
|
|
13483
|
+
switch (route.id) {
|
|
13484
|
+
case "wallet_registration_setup":
|
|
13485
|
+
response = await handleRouterApiWalletRegistrationSetup(common);
|
|
13486
|
+
break;
|
|
13487
|
+
case "wallet_registration_respond":
|
|
13488
|
+
response = await handleRouterApiWalletRegistrationRespond(common);
|
|
13489
|
+
break;
|
|
13490
|
+
case "wallet_registration_near_admission":
|
|
13491
|
+
response = await handleRouterApiWalletRegistrationNearAdmission(common);
|
|
13492
|
+
break;
|
|
13493
|
+
case "wallet_registration_activate":
|
|
13494
|
+
response = await handleRouterApiWalletRegistrationActivate(common);
|
|
13495
|
+
break;
|
|
13496
|
+
case "wallet_registration_near_provisioning":
|
|
13497
|
+
response = await handleRouterApiWalletRegistrationNearProvisioning(common);
|
|
13498
|
+
break;
|
|
13499
|
+
case "wallet_add_signer_intent":
|
|
13500
|
+
response = await handleRouterApiWalletAddSignerIntent(common);
|
|
13501
|
+
break;
|
|
13502
|
+
case "wallet_add_signer_start":
|
|
13503
|
+
response = await handleRouterApiWalletAddSignerStart(common);
|
|
13504
|
+
break;
|
|
13505
|
+
case "wallet_add_signer_ecdsa_derivation_respond":
|
|
13506
|
+
response = await handleRouterApiWalletAddSignerEcdsaDerivationRespond(common);
|
|
13507
|
+
break;
|
|
13508
|
+
case "wallet_add_signer_ecdsa_activation":
|
|
13509
|
+
response = await handleRouterApiWalletAddSignerEcdsaActivation(common);
|
|
13510
|
+
break;
|
|
13511
|
+
case "wallet_add_signer_finalize":
|
|
13512
|
+
response = await handleRouterApiWalletAddSignerFinalize(common);
|
|
13513
|
+
break;
|
|
13514
|
+
case "wallet_add_auth_method_intent":
|
|
13515
|
+
response = await handleRouterApiWalletAddAuthMethodIntent(common);
|
|
13516
|
+
break;
|
|
13517
|
+
case "wallet_add_auth_method_email_otp_challenge":
|
|
13518
|
+
response = await handleRouterApiWalletAddAuthMethodEmailOtpChallenge(common);
|
|
13519
|
+
break;
|
|
13520
|
+
case "wallet_add_auth_method_start":
|
|
13521
|
+
response = await handleRouterApiWalletAddAuthMethodStart(common);
|
|
13522
|
+
break;
|
|
13523
|
+
case "wallet_add_auth_method_finalize":
|
|
13524
|
+
response = await handleRouterApiWalletAddAuthMethodFinalize(common);
|
|
13525
|
+
break;
|
|
13526
|
+
case "wallet_revoke_auth_method":
|
|
13527
|
+
response = await handleRouterApiWalletRevokeAuthMethod(common);
|
|
13528
|
+
break;
|
|
13529
|
+
case "wallet_ecdsa_key_facts_inventory":
|
|
13530
|
+
response = await handleRouterApiWalletEcdsaKeyFactsInventory(common);
|
|
13531
|
+
break;
|
|
13532
|
+
default:
|
|
13533
|
+
response = await handleRouterApiWalletNearImplicitAccountFund(common);
|
|
13534
|
+
break;
|
|
13535
|
+
}
|
|
13536
|
+
return toFetchRouteResponse(response);
|
|
13340
13537
|
}
|
|
13341
13538
|
//#endregion
|
|
13342
13539
|
//#region ../shared-ts/src/utils/errors.ts
|
|
@@ -55,6 +55,9 @@ function isEmailOtpWalletRegistrationActivateSuccessV2(result) {
|
|
|
55
55
|
function isPasskeyWalletRegistrationActivateSuccessV2(result) {
|
|
56
56
|
return result.authMethod.kind === "passkey";
|
|
57
57
|
}
|
|
58
|
+
function isPasskeyWalletRegistrationNearProvisioningSuccessV2(result) {
|
|
59
|
+
return result.authMethod.kind === "passkey";
|
|
60
|
+
}
|
|
58
61
|
function walletRegistrationRoutePolicyServices(input) {
|
|
59
62
|
const service = input.services.walletRegistration;
|
|
60
63
|
return {
|
|
@@ -1628,13 +1631,62 @@ async function handleRouterApiWalletRegistrationRespond(input) {
|
|
|
1628
1631
|
if (!session) return routeError(500, "internal", "wallet registration respond requires session signing");
|
|
1629
1632
|
const parsed = parseWalletRegistrationRespondRequest(input.body);
|
|
1630
1633
|
if (!parsed.ok) return routeError(400, parsed.code, parsed.message);
|
|
1634
|
+
const serverTiming = [];
|
|
1631
1635
|
const result = await input.services.walletRegistration.respondWalletRegistration({
|
|
1632
1636
|
...parsed.value,
|
|
1633
1637
|
verifier: session,
|
|
1634
1638
|
...registrationUserAgentFromHeaders(input.headers) ? { userAgent: registrationUserAgentFromHeaders(input.headers) } : {}
|
|
1635
|
-
}, traceContext.value ?? void 0);
|
|
1639
|
+
}, traceContext.value ?? void 0, serverTiming);
|
|
1640
|
+
return routeJson(result.ok ? 200 : 400, result, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
1641
|
+
}
|
|
1642
|
+
async function handleRouterApiWalletRegistrationNearAdmission(input) {
|
|
1643
|
+
const traceContext = parseRegistrationTraceContext(input.headers);
|
|
1644
|
+
if (!traceContext.ok) return routeError(400, "invalid_trace_id", traceContext.message);
|
|
1645
|
+
if (!isPlainObject(input.body)) return routeError(400, "invalid_body", "JSON body required");
|
|
1646
|
+
const session = input.services.session;
|
|
1647
|
+
if (!session) return routeError(500, "internal", "NEAR admission requires setup verification");
|
|
1648
|
+
const body = input.body;
|
|
1649
|
+
const registrationCeremonyId = String(body.registrationCeremonyId || "").trim();
|
|
1650
|
+
const signedSetup = String(body.signedSetup || "").trim();
|
|
1651
|
+
if (!registrationCeremonyId || !signedSetup) return routeError(400, "invalid_body", "registrationCeremonyId and signedSetup are required");
|
|
1652
|
+
const authority = parseWalletRegistrationAuthorityProof(body);
|
|
1653
|
+
if (!authority.ok) return routeError(400, authority.code, authority.message);
|
|
1654
|
+
const result = await input.services.walletRegistration.authorizeWalletRegistrationNearAdmission({
|
|
1655
|
+
registrationCeremonyId,
|
|
1656
|
+
signedSetup,
|
|
1657
|
+
authority: authority.value,
|
|
1658
|
+
verifier: session,
|
|
1659
|
+
...registrationUserAgentFromHeaders(input.headers) ? { userAgent: registrationUserAgentFromHeaders(input.headers) } : {}
|
|
1660
|
+
});
|
|
1636
1661
|
return routeJson(result.ok ? 200 : 400, result);
|
|
1637
1662
|
}
|
|
1663
|
+
function parseWalletRegistrationAuthorityProof(body) {
|
|
1664
|
+
const hasWebAuthn = Object.prototype.hasOwnProperty.call(body, "webauthn_registration");
|
|
1665
|
+
if (hasWebAuthn === Object.prototype.hasOwnProperty.call(body, "emailOtpRegistrationProof")) return {
|
|
1666
|
+
ok: false,
|
|
1667
|
+
code: "invalid_body",
|
|
1668
|
+
message: "exactly one registration authority proof is required"
|
|
1669
|
+
};
|
|
1670
|
+
if (hasWebAuthn) return {
|
|
1671
|
+
ok: true,
|
|
1672
|
+
value: {
|
|
1673
|
+
kind: "passkey",
|
|
1674
|
+
webauthnRegistration: body.webauthn_registration
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
const proof = normalizeEmailOtpRegistrationProof(body.emailOtpRegistrationProof);
|
|
1678
|
+
return proof ? {
|
|
1679
|
+
ok: true,
|
|
1680
|
+
value: {
|
|
1681
|
+
kind: "email_otp",
|
|
1682
|
+
emailOtpRegistrationProof: proof
|
|
1683
|
+
}
|
|
1684
|
+
} : {
|
|
1685
|
+
ok: false,
|
|
1686
|
+
code: "invalid_body",
|
|
1687
|
+
message: "emailOtpRegistrationProof is invalid"
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1638
1690
|
/**
|
|
1639
1691
|
* Parses respond's public body. `signedSetup` stays an opaque string here —
|
|
1640
1692
|
* the service verifies it; parsing its contents at the boundary would invite
|
|
@@ -1669,29 +1721,9 @@ function parseWalletRegistrationRespondRequest(body) {
|
|
|
1669
1721
|
code: "invalid_body",
|
|
1670
1722
|
message: "an Ed25519-only respond carries no ECDSA registration"
|
|
1671
1723
|
};
|
|
1672
|
-
const
|
|
1673
|
-
if (
|
|
1674
|
-
|
|
1675
|
-
code: "invalid_body",
|
|
1676
|
-
message: "exactly one registration authority proof is required"
|
|
1677
|
-
};
|
|
1678
|
-
let authority;
|
|
1679
|
-
if (hasWebAuthn) authority = {
|
|
1680
|
-
kind: "passkey",
|
|
1681
|
-
webauthnRegistration: body.webauthn_registration
|
|
1682
|
-
};
|
|
1683
|
-
else {
|
|
1684
|
-
const proof = normalizeEmailOtpRegistrationProof(body.emailOtpRegistrationProof);
|
|
1685
|
-
if (!proof) return {
|
|
1686
|
-
ok: false,
|
|
1687
|
-
code: "invalid_body",
|
|
1688
|
-
message: "emailOtpRegistrationProof is invalid"
|
|
1689
|
-
};
|
|
1690
|
-
authority = {
|
|
1691
|
-
kind: "email_otp",
|
|
1692
|
-
emailOtpRegistrationProof: proof
|
|
1693
|
-
};
|
|
1694
|
-
}
|
|
1724
|
+
const parsedAuthority = parseWalletRegistrationAuthorityProof(body);
|
|
1725
|
+
if (!parsedAuthority.ok) return parsedAuthority;
|
|
1726
|
+
const authority = parsedAuthority.value;
|
|
1695
1727
|
if (planKind === "near_ed25519") return {
|
|
1696
1728
|
ok: true,
|
|
1697
1729
|
value: {
|
|
@@ -1818,6 +1850,7 @@ async function handleRouterApiWalletRegistrationActivate(input) {
|
|
|
1818
1850
|
} catch {
|
|
1819
1851
|
return routeError(400, "invalid_body", "ECDSA activation facts are invalid");
|
|
1820
1852
|
}
|
|
1853
|
+
const serverTiming = [];
|
|
1821
1854
|
const result = await input.services.walletRegistration.activateWalletRegistration({
|
|
1822
1855
|
registrationCeremonyId,
|
|
1823
1856
|
signedSetup,
|
|
@@ -1827,7 +1860,7 @@ async function handleRouterApiWalletRegistrationActivate(input) {
|
|
|
1827
1860
|
...body.emailOtpEnrollment ? { emailOtpEnrollment: body.emailOtpEnrollment } : {},
|
|
1828
1861
|
...body.walletCustodyCommit !== void 0 ? { walletCustodyCommit: body.walletCustodyCommit } : {},
|
|
1829
1862
|
verifier: session
|
|
1830
|
-
}, traceContext.value ?? void 0);
|
|
1863
|
+
}, traceContext.value ?? void 0, serverTiming);
|
|
1831
1864
|
if (!result.ok) return routeJson(400, result);
|
|
1832
1865
|
if (!isEmailOtpWalletRegistrationActivateSuccessV2(result) && !isPasskeyWalletRegistrationActivateSuccessV2(result)) return routeError(500, "internal", "Wallet registration activation returned an invalid authority");
|
|
1833
1866
|
const projectionError = await projectActivatedWallet({
|
|
@@ -1836,7 +1869,7 @@ async function handleRouterApiWalletRegistrationActivate(input) {
|
|
|
1836
1869
|
signedSetup
|
|
1837
1870
|
});
|
|
1838
1871
|
if (projectionError) return routeError(500, "internal", `Wallet was created but its Console projection failed: ${projectionError}`);
|
|
1839
|
-
return routeJson(200, result);
|
|
1872
|
+
return routeJson(200, result, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
1840
1873
|
}
|
|
1841
1874
|
/**
|
|
1842
1875
|
* Refactor 94C. `POST /wallets/register/near-provisioning` — the non-blocking
|
|
@@ -1854,6 +1887,9 @@ async function handleRouterApiWalletRegistrationNearProvisioning(input) {
|
|
|
1854
1887
|
if (!registrationCeremonyId || !signedSetup || !idempotencyKey) return routeError(400, "invalid_body", "registrationCeremonyId, signedSetup and idempotencyKey are required");
|
|
1855
1888
|
const ed25519 = parseWalletRegistrationEd25519Finalize(body.ed25519);
|
|
1856
1889
|
if (!ed25519.ok) return routeError(400, ed25519.code, ed25519.message);
|
|
1890
|
+
const sessionSeal = parseRegistrationPreparedSessionSeal(body.sessionSeal);
|
|
1891
|
+
if (!sessionSeal.ok) return routeError(400, sessionSeal.code, sessionSeal.message);
|
|
1892
|
+
const serverTiming = [];
|
|
1857
1893
|
const result = await input.services.walletRegistration.completeWalletRegistrationNearProvisioning({
|
|
1858
1894
|
authorization: registrationContinuationAuthorization(input.headers),
|
|
1859
1895
|
registrationCeremonyId,
|
|
@@ -1863,9 +1899,110 @@ async function handleRouterApiWalletRegistrationNearProvisioning(input) {
|
|
|
1863
1899
|
emailOtpEnrollment: body.emailOtpEnrollment,
|
|
1864
1900
|
...body.walletCustodyCommit !== void 0 ? { walletCustodyCommit: body.walletCustodyCommit } : {},
|
|
1865
1901
|
verifier: session
|
|
1866
|
-
});
|
|
1902
|
+
}, serverTiming);
|
|
1867
1903
|
if (!result.ok) return routeJson(400, result);
|
|
1868
|
-
|
|
1904
|
+
if (sessionSeal.value) {
|
|
1905
|
+
if (!isPasskeyWalletRegistrationNearProvisioningSuccessV2(result)) return routeError(400, "invalid_body", "Registration session sealing requires a passkey authority");
|
|
1906
|
+
const sealStartedAt = Date.now();
|
|
1907
|
+
const sealed = await applyRegistrationPreparedSessionSeal({
|
|
1908
|
+
options: input.signingSessionSeal,
|
|
1909
|
+
request: sessionSeal.value,
|
|
1910
|
+
response: result
|
|
1911
|
+
});
|
|
1912
|
+
serverTiming.push(["near_finalize_session_seal", Math.max(0, Date.now() - sealStartedAt)]);
|
|
1913
|
+
if (!sealed.ok) return routeJson(503, {
|
|
1914
|
+
ok: false,
|
|
1915
|
+
code: sealed.code,
|
|
1916
|
+
message: sealed.message
|
|
1917
|
+
}, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
1918
|
+
return routeJson(200, {
|
|
1919
|
+
...result,
|
|
1920
|
+
sessionSeal: sealed.value
|
|
1921
|
+
}, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
1922
|
+
}
|
|
1923
|
+
return routeJson(200, result, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
|
|
1924
|
+
}
|
|
1925
|
+
function parseRegistrationPreparedSessionSeal(raw) {
|
|
1926
|
+
if (raw === void 0) return {
|
|
1927
|
+
ok: true,
|
|
1928
|
+
value: void 0
|
|
1929
|
+
};
|
|
1930
|
+
if (!isPlainObject(raw)) return {
|
|
1931
|
+
ok: false,
|
|
1932
|
+
code: "invalid_body",
|
|
1933
|
+
message: "sessionSeal must be an object"
|
|
1934
|
+
};
|
|
1935
|
+
if (Object.keys(raw).some((field) => ![
|
|
1936
|
+
"thresholdSessionId",
|
|
1937
|
+
"ciphertext",
|
|
1938
|
+
"keyVersion"
|
|
1939
|
+
].includes(field))) return {
|
|
1940
|
+
ok: false,
|
|
1941
|
+
code: "invalid_body",
|
|
1942
|
+
message: "sessionSeal has unknown fields"
|
|
1943
|
+
};
|
|
1944
|
+
const thresholdSessionId = String(raw.thresholdSessionId || "").trim();
|
|
1945
|
+
const ciphertext = String(raw.ciphertext || "").trim();
|
|
1946
|
+
const keyVersion = String(raw.keyVersion || "").trim();
|
|
1947
|
+
if (!thresholdSessionId || !ciphertext) return {
|
|
1948
|
+
ok: false,
|
|
1949
|
+
code: "invalid_body",
|
|
1950
|
+
message: "sessionSeal thresholdSessionId and ciphertext are required"
|
|
1951
|
+
};
|
|
1952
|
+
return {
|
|
1953
|
+
ok: true,
|
|
1954
|
+
value: {
|
|
1955
|
+
thresholdSessionId,
|
|
1956
|
+
ciphertext,
|
|
1957
|
+
...keyVersion ? { keyVersion } : {}
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
async function applyRegistrationPreparedSessionSeal(args) {
|
|
1962
|
+
if (!args.options) return {
|
|
1963
|
+
ok: false,
|
|
1964
|
+
code: "signing_session_seal_unavailable",
|
|
1965
|
+
message: "Signing-session sealing is unavailable"
|
|
1966
|
+
};
|
|
1967
|
+
const session = args.response.registrationEstablishedSession.session;
|
|
1968
|
+
const tokens = session.tokens;
|
|
1969
|
+
const ed25519 = tokens.kind === "near_ed25519" || tokens.kind === "near_ed25519_and_evm_family_ecdsa" ? tokens.ed25519 : null;
|
|
1970
|
+
if (!ed25519 || String(ed25519.thresholdSessionId) !== args.request.thresholdSessionId) return {
|
|
1971
|
+
ok: false,
|
|
1972
|
+
code: "invalid_session_seal",
|
|
1973
|
+
message: "Prepared seal does not match the established Ed25519 session"
|
|
1974
|
+
};
|
|
1975
|
+
const sealed = await args.options.service.applyServerSeal(args.request, {
|
|
1976
|
+
userId: String(session.walletId),
|
|
1977
|
+
session: {
|
|
1978
|
+
kind: "exact_wallet_session_operation_credential",
|
|
1979
|
+
curve: "ed25519",
|
|
1980
|
+
thresholdSessionId: args.request.thresholdSessionId,
|
|
1981
|
+
userId: String(session.walletId),
|
|
1982
|
+
expiresAtMs: session.expiresAtMs
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
if (!sealed.ok) return sealed;
|
|
1986
|
+
const keyVersion = String(sealed.keyVersion || "").trim();
|
|
1987
|
+
if (!keyVersion) return {
|
|
1988
|
+
ok: false,
|
|
1989
|
+
code: "invalid_session_seal",
|
|
1990
|
+
message: "Signing-session seal response is missing its key version"
|
|
1991
|
+
};
|
|
1992
|
+
return {
|
|
1993
|
+
ok: true,
|
|
1994
|
+
value: {
|
|
1995
|
+
ciphertext: sealed.ciphertext,
|
|
1996
|
+
keyVersion,
|
|
1997
|
+
expiresAtMs: Math.min(sealed.expiresAtMs ?? session.expiresAtMs, session.expiresAtMs),
|
|
1998
|
+
remainingUses: session.remainingUses
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
function ecdsaGatewayServerTimingHeaders(serverTiming) {
|
|
2003
|
+
if (serverTiming.length === 0) return void 0;
|
|
2004
|
+
const header = serverTiming.map(([name, durationMs]) => `${name};dur=${Math.max(0, durationMs).toFixed(1)}`).join(", ");
|
|
2005
|
+
return { "Server-Timing": header };
|
|
1869
2006
|
}
|
|
1870
2007
|
async function handleRouterApiWalletAddSignerStart(input) {
|
|
1871
2008
|
if (!isPlainObject(input.body)) return routeError(400, "invalid_body", "JSON body required");
|
|
@@ -2205,6 +2342,6 @@ function registrationContinuationAuthorization(headers) {
|
|
|
2205
2342
|
} : { kind: "registration_grant" };
|
|
2206
2343
|
}
|
|
2207
2344
|
//#endregion
|
|
2208
|
-
export { handleRouterApiWalletAddAuthMethodEmailOtpChallenge, handleRouterApiWalletAddAuthMethodFinalize, handleRouterApiWalletAddAuthMethodIntent, handleRouterApiWalletAddAuthMethodStart, handleRouterApiWalletAddSignerEcdsaActivation, handleRouterApiWalletAddSignerEcdsaDerivationRespond, handleRouterApiWalletAddSignerFinalize, handleRouterApiWalletAddSignerIntent, handleRouterApiWalletAddSignerStart, handleRouterApiWalletEcdsaKeyFactsInventory, handleRouterApiWalletNearImplicitAccountFund, handleRouterApiWalletRegistrationActivate, handleRouterApiWalletRegistrationNearProvisioning, handleRouterApiWalletRegistrationRespond, handleRouterApiWalletRegistrationSetup, handleRouterApiWalletRevokeAuthMethod };
|
|
2345
|
+
export { handleRouterApiWalletAddAuthMethodEmailOtpChallenge, handleRouterApiWalletAddAuthMethodFinalize, handleRouterApiWalletAddAuthMethodIntent, handleRouterApiWalletAddAuthMethodStart, handleRouterApiWalletAddSignerEcdsaActivation, handleRouterApiWalletAddSignerEcdsaDerivationRespond, handleRouterApiWalletAddSignerFinalize, handleRouterApiWalletAddSignerIntent, handleRouterApiWalletAddSignerStart, handleRouterApiWalletEcdsaKeyFactsInventory, handleRouterApiWalletNearImplicitAccountFund, handleRouterApiWalletRegistrationActivate, handleRouterApiWalletRegistrationNearAdmission, handleRouterApiWalletRegistrationNearProvisioning, handleRouterApiWalletRegistrationRespond, handleRouterApiWalletRegistrationSetup, handleRouterApiWalletRevokeAuthMethod };
|
|
2209
2346
|
|
|
2210
2347
|
//# sourceMappingURL=walletRegistrationRoutes.js.map
|