Protect sync pull with approved devices

This commit is contained in:
2026-05-08 18:20:01 -04:00
parent b4b862517a
commit 3544f644b1
4 changed files with 444 additions and 1 deletions
+37
View File
@@ -13,6 +13,12 @@ const RATE_LIMIT_WINDOW_SECONDS = 60;
export type ApiHandler = () => Promise<Response>;
export type AuthenticatedApiHandler = (context: AuthContext) => Promise<Response>;
const APPROVED_DEVICE_QUERY = `
SELECT device_id
FROM user_devices
WHERE user_id = ? AND device_id = ? AND approval_status = 'approved' AND revoked_at IS NULL
`;
export async function withPublicApiControls(
request: Request,
env: Env,
@@ -103,6 +109,37 @@ export async function withAuthenticatedApiControls(
}
}
export async function withApprovedDeviceApiControls(
request: Request,
env: Env,
route: string,
allowedMethods: readonly string[],
handler: AuthenticatedApiHandler,
): Promise<Response> {
return withAuthenticatedApiControls(request, env, route, allowedMethods, async (context) => {
if (context.deviceId === undefined) {
return jsonResponse({ error: "device_context_required" }, 403, {
"Cache-Control": "no-store",
});
}
const row = await env.ELY_DB.prepare(APPROVED_DEVICE_QUERY)
.bind(context.userId, context.deviceId)
.first<ApprovedDeviceRow>();
if (row === null) {
return jsonResponse({ error: "device_not_approved" }, 403, {
"Cache-Control": "no-store",
});
}
return handler(context);
});
}
interface ApprovedDeviceRow {
device_id: unknown;
}
function rateLimitKey(environment: string, route: string): string {
return `${environment}:${route}`;
}