68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
import { createHmac, randomBytes } from "node:crypto";
|
|
|
|
import type { NextcloudTalkWebhookHeaders } from "./types.js";
|
|
|
|
const SIGNATURE_HEADER = "x-nextcloud-talk-signature";
|
|
const RANDOM_HEADER = "x-nextcloud-talk-random";
|
|
const BACKEND_HEADER = "x-nextcloud-talk-backend";
|
|
|
|
/**
|
|
* Verify the HMAC-SHA256 signature of an incoming webhook request.
|
|
* Signature is calculated as: HMAC-SHA256(random + body, secret)
|
|
*/
|
|
export function verifyNextcloudTalkSignature(params: {
|
|
signature: string;
|
|
random: string;
|
|
body: string;
|
|
secret: string;
|
|
}): boolean {
|
|
const { signature, random, body, secret } = params;
|
|
if (!signature || !random || !secret) return false;
|
|
|
|
const expected = createHmac("sha256", secret)
|
|
.update(random + body)
|
|
.digest("hex");
|
|
|
|
if (signature.length !== expected.length) return false;
|
|
let result = 0;
|
|
for (let i = 0; i < signature.length; i++) {
|
|
result |= signature.charCodeAt(i) ^ expected.charCodeAt(i);
|
|
}
|
|
return result === 0;
|
|
}
|
|
|
|
/**
|
|
* Extract webhook headers from an incoming request.
|
|
*/
|
|
export function extractNextcloudTalkHeaders(
|
|
headers: Record<string, string | string[] | undefined>,
|
|
): NextcloudTalkWebhookHeaders | null {
|
|
const getHeader = (name: string): string | undefined => {
|
|
const value = headers[name] ?? headers[name.toLowerCase()];
|
|
return Array.isArray(value) ? value[0] : value;
|
|
};
|
|
|
|
const signature = getHeader(SIGNATURE_HEADER);
|
|
const random = getHeader(RANDOM_HEADER);
|
|
const backend = getHeader(BACKEND_HEADER);
|
|
|
|
if (!signature || !random || !backend) return null;
|
|
|
|
return { signature, random, backend };
|
|
}
|
|
|
|
/**
|
|
* Generate signature headers for an outbound request to Nextcloud Talk.
|
|
*/
|
|
export function generateNextcloudTalkSignature(params: { body: string; secret: string }): {
|
|
random: string;
|
|
signature: string;
|
|
} {
|
|
const { body, secret } = params;
|
|
const random = randomBytes(32).toString("hex");
|
|
const signature = createHmac("sha256", secret)
|
|
.update(random + body)
|
|
.digest("hex");
|
|
return { random, signature };
|
|
}
|