refactor: share handshake auth helper builders

This commit is contained in:
Peter Steinberger 2026-03-13 21:33:28 +00:00
parent 6ecc184637
commit 4674fbf923
2 changed files with 45 additions and 37 deletions

View File

@ -10,24 +10,21 @@ import {
shouldSkipBackendSelfPairing,
} from "./handshake-auth-helpers.js";
function createRateLimiter(): AuthRateLimiter {
return {
check: () => ({ allowed: true, remaining: 1, retryAfterMs: 0 }),
reset: () => {},
recordFailure: () => {},
size: () => 0,
prune: () => {},
dispose: () => {},
};
}
describe("handshake auth helpers", () => {
it("pins browser-origin loopback clients to the synthetic rate-limit ip", () => {
const rateLimiter: AuthRateLimiter = {
check: () => ({ allowed: true, remaining: 1, retryAfterMs: 0 }),
reset: () => {},
recordFailure: () => {},
size: () => 0,
prune: () => {},
dispose: () => {},
};
const browserRateLimiter: AuthRateLimiter = {
check: () => ({ allowed: true, remaining: 1, retryAfterMs: 0 }),
reset: () => {},
recordFailure: () => {},
size: () => 0,
prune: () => {},
dispose: () => {},
};
const rateLimiter = createRateLimiter();
const browserRateLimiter = createRateLimiter();
const resolved = resolveHandshakeBrowserSecurityContext({
requestOrigin: "https://app.example",
clientIp: "127.0.0.1",

View File

@ -91,6 +91,23 @@ function resolveSignatureToken(connectParams: ConnectParams): string | null {
);
}
function buildUnauthorizedHandshakeContext(params: {
authProvided: AuthProvidedKind;
canRetryWithDeviceToken: boolean;
recommendedNextStep:
| "retry_with_device_token"
| "update_auth_configuration"
| "update_auth_credentials"
| "wait_then_retry"
| "review_auth_configuration";
}) {
return {
authProvided: params.authProvided,
canRetryWithDeviceToken: params.canRetryWithDeviceToken,
recommendedNextStep: params.recommendedNextStep,
};
}
export function resolveDeviceSignaturePayloadVersion(params: {
device: {
id: string;
@ -104,7 +121,7 @@ export function resolveDeviceSignaturePayloadVersion(params: {
nonce: string;
}): "v3" | "v2" | null {
const signatureToken = resolveSignatureToken(params.connectParams);
const payloadV3 = buildDeviceAuthPayloadV3({
const basePayload = {
deviceId: params.device.id,
clientId: params.connectParams.client.id,
clientMode: params.connectParams.client.mode,
@ -113,6 +130,9 @@ export function resolveDeviceSignaturePayloadVersion(params: {
signedAtMs: params.signedAtMs,
token: signatureToken,
nonce: params.nonce,
};
const payloadV3 = buildDeviceAuthPayloadV3({
...basePayload,
platform: params.connectParams.client.platform,
deviceFamily: params.connectParams.client.deviceFamily,
});
@ -120,16 +140,7 @@ export function resolveDeviceSignaturePayloadVersion(params: {
return "v3";
}
const payloadV2 = buildDeviceAuthPayload({
deviceId: params.device.id,
clientId: params.connectParams.client.id,
clientMode: params.connectParams.client.mode,
role: params.role,
scopes: params.scopes,
signedAtMs: params.signedAtMs,
token: signatureToken,
nonce: params.nonce,
});
const payloadV2 = buildDeviceAuthPayload(basePayload);
if (verifyDeviceSignature(params.device.publicKey, payloadV2, params.device.signature)) {
return "v2";
}
@ -171,41 +182,41 @@ export function resolveUnauthorizedHandshakeContext(params: {
authProvided === "token" &&
!params.connectAuth?.deviceToken;
if (canRetryWithDeviceToken) {
return {
return buildUnauthorizedHandshakeContext({
authProvided,
canRetryWithDeviceToken,
recommendedNextStep: "retry_with_device_token",
};
});
}
switch (params.failedAuth.reason) {
case "token_missing":
case "token_missing_config":
case "password_missing":
case "password_missing_config":
return {
return buildUnauthorizedHandshakeContext({
authProvided,
canRetryWithDeviceToken,
recommendedNextStep: "update_auth_configuration",
};
});
case "token_mismatch":
case "password_mismatch":
case "device_token_mismatch":
return {
return buildUnauthorizedHandshakeContext({
authProvided,
canRetryWithDeviceToken,
recommendedNextStep: "update_auth_credentials",
};
});
case "rate_limited":
return {
return buildUnauthorizedHandshakeContext({
authProvided,
canRetryWithDeviceToken,
recommendedNextStep: "wait_then_retry",
};
});
default:
return {
return buildUnauthorizedHandshakeContext({
authProvided,
canRetryWithDeviceToken,
recommendedNextStep: "review_auth_configuration",
};
});
}
}