Files
fusion/plugins/fusion-plugin-even-cards/src/routes/auth.ts
Fusion 302c378c2f feat(FN-3739): add fusion-plugin-even-cards workspace plugin
- Add a new fusion-plugin-even-cards package with typed card formatting utilities and board/task card generation
- Implement authenticated plugin routes for board card endpoints and plugin registration wiring
- Add unit tests for auth handling, board routes, and card formatting behavior
- Document plugin in PLUGIN_AUTHORING guide and register it in pnpm workspace

Fusion-Task-Id: FN-3739
2026-05-08 12:46:12 -07:00

40 lines
1.4 KiB
TypeScript

import { createHash, timingSafeEqual } from "node:crypto";
import type { PluginContext, PluginRouteResponse } from "@fusion/plugin-sdk";
function toDigest(value: string): Buffer {
return createHash("sha256").update(value).digest();
}
function readBearer(headers: Record<string, string | string[] | undefined>): string | undefined {
const auth = headers.authorization ?? headers.Authorization;
const header = Array.isArray(auth) ? auth[0] : auth;
if (!header) return undefined;
const match = header.match(/^Bearer\s+(.+)$/i);
return match?.[1]?.trim() || undefined;
}
export function requireApiKey(
ctx: PluginContext,
req: { headers: Record<string, string | string[] | undefined> },
): { ok: true } | { ok: false; response: PluginRouteResponse } {
const expected = typeof ctx.settings.apiKey === "string" ? ctx.settings.apiKey.trim() : "";
if (!expected) {
return { ok: false, response: { status: 503, body: { error: "plugin not configured" } } };
}
const provided = readBearer(req.headers ?? {});
if (!provided) {
return { ok: false, response: { status: 401, body: { error: "unauthorized" } } };
}
const expectedDigest = toDigest(expected);
const providedDigest = toDigest(provided);
const valid = timingSafeEqual(expectedDigest, providedDigest);
if (!valid) {
return { ok: false, response: { status: 401, body: { error: "unauthorized" } } };
}
return { ok: true };
}