mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-09-30 12:23:12 +00:00
80 lines
2.3 KiB
JavaScript
80 lines
2.3 KiB
JavaScript
|
|
import crypto from "node:crypto";
|
||
|
|
import {
|
||
|
|
SecretsManagerClient,
|
||
|
|
GetSecretValueCommand,
|
||
|
|
} from "@aws-sdk/client-secrets-manager";
|
||
|
|
import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";
|
||
|
|
|
||
|
|
const secrets = new SecretsManagerClient();
|
||
|
|
const lambda = new LambdaClient();
|
||
|
|
|
||
|
|
const EXPENSE_PROCESSOR_FN = process.env.EXPENSE_PROCESSOR_FN;
|
||
|
|
const EXPENSE_SIGNING_SECRET_NAME = process.env.EXPENSE_SIGNING_SECRET_NAME;
|
||
|
|
|
||
|
|
let cachedSigningSecret;
|
||
|
|
async function getSigningSecret() {
|
||
|
|
if (cachedSigningSecret) return cachedSigningSecret;
|
||
|
|
const { SecretString } = await secrets.send(
|
||
|
|
new GetSecretValueCommand({ SecretId: EXPENSE_SIGNING_SECRET_NAME })
|
||
|
|
);
|
||
|
|
cachedSigningSecret = SecretString;
|
||
|
|
return cachedSigningSecret;
|
||
|
|
}
|
||
|
|
|
||
|
|
function verifySignature(body, timestamp, signature, secret) {
|
||
|
|
if (!timestamp || !signature) return false;
|
||
|
|
const ts = Number(timestamp);
|
||
|
|
if (!Number.isFinite(ts)) return false;
|
||
|
|
if (Math.abs(Date.now() / 1000 - ts) > 300) return false;
|
||
|
|
|
||
|
|
const base = `v0:${timestamp}:${body}`;
|
||
|
|
const expected =
|
||
|
|
"v0=" + crypto.createHmac("sha256", secret).update(base).digest("hex");
|
||
|
|
|
||
|
|
return crypto.timingSafeEqual(
|
||
|
|
Buffer.from(expected),
|
||
|
|
Buffer.from(signature)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export const handler = async (event) => {
|
||
|
|
let body = event.body || "";
|
||
|
|
if (event.isBase64Encoded) {
|
||
|
|
body = Buffer.from(body, "base64").toString("utf-8");
|
||
|
|
}
|
||
|
|
|
||
|
|
const headers = Object.fromEntries(
|
||
|
|
Object.entries(event.headers || {}).map(([k, v]) => [k.toLowerCase(), v])
|
||
|
|
);
|
||
|
|
const timestamp = headers["x-slack-request-timestamp"] || "";
|
||
|
|
const signature = headers["x-slack-signature"] || "";
|
||
|
|
|
||
|
|
const secret = await getSigningSecret();
|
||
|
|
if (!verifySignature(body, timestamp, signature, secret)) {
|
||
|
|
console.log("Signature verification failed");
|
||
|
|
return { statusCode: 401, body: "unauthorized" };
|
||
|
|
}
|
||
|
|
|
||
|
|
const payload = JSON.parse(body);
|
||
|
|
|
||
|
|
if (payload.type === "url_verification") {
|
||
|
|
return {
|
||
|
|
statusCode: 200,
|
||
|
|
headers: { "Content-Type": "text/plain" },
|
||
|
|
body: payload.challenge || "",
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
if (payload.type === "event_callback") {
|
||
|
|
await lambda.send(
|
||
|
|
new InvokeCommand({
|
||
|
|
FunctionName: EXPENSE_PROCESSOR_FN,
|
||
|
|
InvocationType: "Event",
|
||
|
|
Payload: JSON.stringify(payload.event),
|
||
|
|
})
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return { statusCode: 200, body: "" };
|
||
|
|
};
|