mirror of
https://github.com/Sea-Haven-Industries/seahaven-door-unlock-api.git
synced 2026-09-30 12:53:12 +00:00
63 lines
1.9 KiB
TypeScript
63 lines
1.9 KiB
TypeScript
|
|
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;
|
||
|
|
|
||
|
|
async function getAuthToken(): Promise<string> {
|
||
|
|
if (cachedAuthToken) return cachedAuthToken;
|
||
|
|
const res = await ssm.send(
|
||
|
|
new GetParameterCommand({
|
||
|
|
Name: process.env.AUTH_TOKEN_PARAM!,
|
||
|
|
WithDecryption: true,
|
||
|
|
})
|
||
|
|
);
|
||
|
|
cachedAuthToken = res.Parameter!.Value!;
|
||
|
|
return cachedAuthToken;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* API Gateway HTTP API (payload v2.0) REQUEST Lambda authorizer.
|
||
|
|
*
|
||
|
|
* Validates the SAME `?token=` query-string parameter the Yealink XML Browser
|
||
|
|
* keys already send (type-17 keys issue a plain GET and cannot send headers or
|
||
|
|
* a POST body), so this authorizer is transparent to the phones. An
|
||
|
|
* unauthenticated or wrong-token request is now rejected at the gateway with
|
||
|
|
* 401/403 before any handler Lambda is invoked.
|
||
|
|
*
|
||
|
|
* Returns the simple-response shape ({ isAuthorized }) which the routes are
|
||
|
|
* configured for (enableSimpleResponses: true).
|
||
|
|
*/
|
||
|
|
export async function handler(event: {
|
||
|
|
queryStringParameters?: Record<string, string>;
|
||
|
|
}): Promise<{ isAuthorized: boolean }> {
|
||
|
|
const token = event.queryStringParameters?.token;
|
||
|
|
if (!token) {
|
||
|
|
return { isAuthorized: false };
|
||
|
|
}
|
||
|
|
|
||
|
|
let expected: string;
|
||
|
|
try {
|
||
|
|
expected = await getAuthToken();
|
||
|
|
} catch (err) {
|
||
|
|
console.error(
|
||
|
|
JSON.stringify({ action: "authorize", status: "error", reason: "ssm_failure", error: String(err) })
|
||
|
|
);
|
||
|
|
// Fail closed: deny if the token cannot be loaded.
|
||
|
|
return { isAuthorized: false };
|
||
|
|
}
|
||
|
|
|
||
|
|
return { isAuthorized: tokensMatch(token, expected) };
|
||
|
|
}
|