File size: 1,582 Bytes
c09f67c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | import crypto from "node:crypto";
// https://teller.io/docs/api/webhooks#verifying-messages
export const validateTellerSignature = (params: {
signatureHeader: string | null;
text: string;
}): boolean => {
if (!params.signatureHeader) {
return false;
}
const { timestamp, signatures } = parseTellerSignatureHeader(
params.signatureHeader,
);
const threeMinutesAgo = Math.floor(Date.now() / 1000) - 3 * 60;
if (Number.parseInt(timestamp, 10) < threeMinutesAgo) {
return false;
}
// Ensure the text is used as a raw string
const signedMessage = `${timestamp}.${params.text}`;
const calculatedSignature = crypto
.createHmac("sha256", process.env.TELLER_SIGNING_SECRET!)
.update(signedMessage)
.digest("hex");
// Compare calculated signature with provided signatures
return signatures.includes(calculatedSignature);
};
export const parseTellerSignatureHeader = (
header: string,
): { timestamp: string; signatures: string[] } => {
const parts = header.split(",");
const timestampPart = parts.find((p) => p.startsWith("t="));
const signatureParts = parts.filter((p) => p.startsWith("v1="));
if (!timestampPart) {
throw new Error("No timestamp in Teller-Signature header");
}
const timestamp = timestampPart.split("=")[1];
const signatures = signatureParts
.map((p) => p.split("=")[1])
.filter((sig): sig is string => sig !== undefined);
if (!timestamp || signatures.some((sig) => !sig)) {
throw new Error("Invalid Teller-Signature header format");
}
return { timestamp, signatures };
};
|