import { hmacSha256Hex, timingSafeEqualStrings } from "../crypto";
const SLACK_MAX_SKEW_SECONDS = 5 * 60;
const LINEAR_MAX_SKEW_MS = 60_000;
const MS_PER_SECOND = 1000;
/** Signature header sent with every outbound integration payload: `sha256=<hex HMAC of body>`. */
export async function signPayload(secret: string, body: string): Promise<string> {
return `sha256=${await hmacSha256Hex(secret, body)}`;
}
/** GitHub style `X-Hub-Signature-256` / our own `X-Gitfor-Signature-256`. */
export async function verifySha256Signature(secret: string, body: string, header: string | null): Promise<boolean> {
if (!header || !secret) return false;
return timingSafeEqualStrings(await signPayload(secret, body), header.trim());
}
/** Slack request signing: `v0=` HMAC of `v0:<timestamp>:<body>`, rejecting stale timestamps to stop replays. */
export async function verifySlackSignature(input: {
signingSecret: string;
body: string;
timestamp: string | null;
signature: string | null;
nowMs?: number;
}): Promise<boolean> {
if (!input.signingSecret || !input.timestamp || !input.signature) return false;
if (!/^\d{1,12}$/.test(input.timestamp)) return false;
const now = Math.floor((input.nowMs ?? Date.now()) / MS_PER_SECOND);
if (Math.abs(now - Number(input.timestamp)) > SLACK_MAX_SKEW_SECONDS) return false;
const expected = `v0=${await hmacSha256Hex(input.signingSecret, `v0:${input.timestamp}:${input.body}`)}`;
return timingSafeEqualStrings(expected, input.signature.trim());
}
/** Linear webhooks: hex HMAC of the raw body in `Linear-Signature`, plus a `webhookTimestamp` within a minute. */
export async function verifyLinearSignature(input: {
secret: string;
body: string;
signature: string | null;
nowMs?: number;
}): Promise<boolean> {