import { SSMClient, GetParameterCommand, } from "@aws-sdk/client-ssm"; const ssm = new SSMClient({}); let cachedAuthToken: string | undefined; let cachedApiKey: string | undefined; const LOCKDOWN_PROFILES: Record = { bohemia: { id: "4b4a3e6b-c903-4cce-8cd6-288612bf0542", name: "Bohemia - Whole Building", linekey: 3 }, ronkonkoma: { id: "ff9876bc-c54f-472e-aef9-d2bffd4b7cf7", name: "Ronkonkoma - Whole Building", linekey: 4 }, }; const ELEMENTS_BASE_URL = "https://api.elementssecure.com/v1"; async function getParameter(name: string, decrypt: boolean): Promise { const res = await ssm.send( new GetParameterCommand({ Name: name, WithDecryption: decrypt }) ); return res.Parameter!.Value!; } async function loadSecrets() { const [authToken, apiKey] = await Promise.all([ cachedAuthToken ?? getParameter(process.env.AUTH_TOKEN_PARAM!, true), cachedApiKey ?? getParameter(process.env.ELEMENTS_API_KEY_PARAM!, true), ]); cachedAuthToken = authToken; cachedApiKey = apiKey; return { authToken, apiKey }; } async function getLockdownStatus(lockdownId: string, apiKey: string): Promise { const response = await fetch(`${ELEMENTS_BASE_URL}/lockdowns/${lockdownId}`, { headers: { "api-key": apiKey }, }); if (!response.ok) { const body = await response.text(); throw new Error(`Elements status check failed: ${response.status} - ${body}`); } const data = await response.json() as Record; return data.status === "active" || data.isActive === true; } async function setLockdown(lockdownId: string, apiKey: string, start: boolean): Promise { const action = start ? "start" : "stop"; const response = await fetch(`${ELEMENTS_BASE_URL}/lockdowns/${lockdownId}/${action}`, { method: "POST", headers: { "api-key": apiKey, "Content-Type": "application/json", }, body: JSON.stringify({}), }); if (!response.ok) { const body = await response.text(); throw new Error(`Elements ${action} failed: ${response.status} - ${body}`); } } // LED values: 0=off, 1=green, 2=green fast blink, 3=green slow blink, 4=red, 5=red fast blink, 6=red slow blink function ledColor(active: boolean): number { return active ? 4 : 1; } function yealinkExecuteXml(items: string[]): string { const body = items.map(uri => ` `).join("\n"); return `\n\n${body}\n`; } function xmlResponse(statusCode: number, body: string) { return { statusCode, headers: { "Content-Type": "application/xml" }, body, }; } function errorXml(message: string): string { return `\n\n Error\n ${message}\n`; } export async function handler(event: { queryStringParameters?: Record; rawPath?: string; requestContext?: { http?: { sourceIp?: string } }; }) { const sourceIp = event.requestContext?.http?.sourceIp ?? "unknown"; const token = event.queryStringParameters?.token; const profile = event.queryStringParameters?.profile; const path = event.rawPath ?? ""; if (!token) { console.log(JSON.stringify({ action: "lockdown", status: "rejected", reason: "missing_token", sourceIp })); return { statusCode: 403, body: JSON.stringify({ error: "Forbidden" }) }; } let secrets; try { secrets = await loadSecrets(); } catch (err) { console.error(JSON.stringify({ action: "lockdown", status: "error", reason: "ssm_failure", sourceIp, error: String(err) })); return xmlResponse(500, errorXml("Internal error")); } if (token !== secrets.authToken) { console.log(JSON.stringify({ action: "lockdown", status: "rejected", reason: "invalid_token", sourceIp })); return { statusCode: 403, body: JSON.stringify({ error: "Forbidden" }) }; } if (path === "/lockdown/status") { return handleStatus(secrets.apiKey, sourceIp); } return handleToggle(profile, secrets.apiKey, sourceIp); } async function handleStatus(apiKey: string, sourceIp: string) { try { const ledItems: string[] = []; for (const [key, config] of Object.entries(LOCKDOWN_PROFILES)) { const active = await getLockdownStatus(config.id, apiKey); ledItems.push(`Led:LINEKEY${config.linekey}=${ledColor(active)}`); console.log(JSON.stringify({ action: "lockdown_status", profile: key, active, sourceIp })); } return xmlResponse(200, yealinkExecuteXml(ledItems)); } catch (err) { console.error(JSON.stringify({ action: "lockdown_status", status: "error", sourceIp, error: String(err) })); return xmlResponse(502, errorXml("Unable to check lockdown status")); } } async function handleToggle(profile: string | undefined, apiKey: string, sourceIp: string) { if (!profile || !LOCKDOWN_PROFILES[profile]) { return xmlResponse(400, errorXml("Invalid profile")); } const config = LOCKDOWN_PROFILES[profile]; try { const wasActive = await getLockdownStatus(config.id, apiKey); await setLockdown(config.id, apiKey, !wasActive); const isActive = await getLockdownStatus(config.id, apiKey); console.log(JSON.stringify({ action: "lockdown_toggle", profile, wasActive, isActive, sourceIp, })); return xmlResponse(200, yealinkExecuteXml([ `Led:LINEKEY${config.linekey}=${ledColor(isActive)}`, ])); } catch (err) { console.error(JSON.stringify({ action: "lockdown_toggle", status: "error", profile, sourceIp, error: String(err) })); return xmlResponse(502, errorXml(`Lockdown error: ${config.name}`)); } }