mirror of
https://github.com/Sea-Haven-Industries/seahaven-door-unlock-api.git
synced 2026-09-30 03:43:11 +00:00
fix: use constant-time auth token comparison (INFRA-21) (#30)
Replace plain token !== secrets.authToken checks in the unlock and lockdown handlers with crypto.timingSafeEqual, guarding for unequal buffer lengths first (timingSafeEqual throws on different lengths). Prevents timing side-channel leakage of the auth token. Handler signatures, event shape, and return contract are unchanged.
This commit is contained in:
parent
0f27b2553a
commit
fe04a199de
2 changed files with 18 additions and 2 deletions
|
|
@ -2,9 +2,17 @@ import {
|
|||
SSMClient,
|
||||
GetParameterCommand,
|
||||
} from "@aws-sdk/client-ssm";
|
||||
import { timingSafeEqual } from "node:crypto";
|
||||
|
||||
const ssm = new SSMClient({});
|
||||
|
||||
function tokensMatch(provided: string, expected: string): boolean {
|
||||
const a = Buffer.from(provided);
|
||||
const b = Buffer.from(expected);
|
||||
// timingSafeEqual throws on unequal-length buffers; check length first.
|
||||
return a.length === b.length && timingSafeEqual(a, b);
|
||||
}
|
||||
|
||||
let cachedAuthToken: string | undefined;
|
||||
let cachedApiKey: string | undefined;
|
||||
|
||||
|
|
@ -105,7 +113,7 @@ export async function handler(event: {
|
|||
return xmlResponse(500, textScreenXml("Error", "Internal error"));
|
||||
}
|
||||
|
||||
if (token !== secrets.authToken) {
|
||||
if (!tokensMatch(token, secrets.authToken)) {
|
||||
console.log(JSON.stringify({ action: "lockdown", status: "rejected", reason: "invalid_token", sourceIp }));
|
||||
return { statusCode: 403, body: JSON.stringify({ error: "Forbidden" }) };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,17 @@ import {
|
|||
SSMClient,
|
||||
GetParameterCommand,
|
||||
} from "@aws-sdk/client-ssm";
|
||||
import { timingSafeEqual } from "node:crypto";
|
||||
|
||||
const ssm = new SSMClient({});
|
||||
|
||||
function tokensMatch(provided: string, expected: string): boolean {
|
||||
const a = Buffer.from(provided);
|
||||
const b = Buffer.from(expected);
|
||||
// timingSafeEqual throws on unequal-length buffers; check length first.
|
||||
return a.length === b.length && timingSafeEqual(a, b);
|
||||
}
|
||||
|
||||
let cachedAuthToken: string | undefined;
|
||||
let cachedApiKey: string | undefined;
|
||||
let cachedDoorId: string | undefined;
|
||||
|
|
@ -51,7 +59,7 @@ export async function handler(event: {
|
|||
return { statusCode: 500, body: JSON.stringify({ error: "Internal error" }) };
|
||||
}
|
||||
|
||||
if (token !== secrets.authToken) {
|
||||
if (!tokensMatch(token, secrets.authToken)) {
|
||||
console.log(JSON.stringify({ action: "unlock_attempt", status: "rejected", reason: "invalid_token", sourceIp }));
|
||||
return { statusCode: 403, body: JSON.stringify({ error: "Forbidden" }) };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue