26 lines · 1.4 KB
import { hmacSha256Hex, timingSafeEqualStrings } from "../crypto";

/**
 * Accepts the signing schemes of the common forges: GitHub / Bitbucket (`X-Hub-Signature-256: sha256=<hmac>`),
 * Gitea / Forgejo / Gogs (hex HMAC), GitLab (`X-Gitlab-Token` equal to the secret), and a bearer token or
 * `?secret=` for anything else.
 */
export async function verifyMirrorHook(secret: string, request: Request, body: string): Promise<boolean> {
  const headers = request.headers;
  const hex = await hmacSha256Hex(secret, body);
  const prefixed = [headers.get("x-hub-signature-256"), headers.get("x-hub-signature")];
  for (const header of prefixed) {
    if (header && timingSafeEqualStrings(`sha256=${hex}`, header.trim())) return true;
  }
  const plain = [headers.get("x-gitea-signature"), headers.get("x-forgejo-signature"), headers.get("x-gogs-signature")];
  for (const header of plain) {
    if (header && timingSafeEqualStrings(hex, header.trim().toLowerCase())) return true;
  }
  const authorization = headers.get("authorization") ?? "";
  const bearer = authorization.toLowerCase().startsWith("bearer ")
    ? authorization.slice("bearer ".length).trim()
    : null;
  const tokens = [headers.get("x-gitlab-token"), bearer, new URL(request.url).searchParams.get("secret")];
  return tokens.some((token) => token !== null && token !== "" && timingSafeEqualStrings(secret, token));
}