Midday / apps /dashboard /src /utils /teller.ts
Jules
Final deployment with all fixes and verified content
c09f67c
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 };
};