UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

90 lines 3.22 kB
import { AuthUnauthorizedError } from "../../ports/index.js"; function rawRequestHeaders(req) { const record = {}; req.headers.forEach((value, key) => { record[key.toLowerCase()] = value; }); return record; } async function parseAuthHeaders(schema, req) { const raw = rawRequestHeaders(req); if (!schema) { return { ok: true, headers: raw }; } const result = await schema["~standard"].validate(raw); if (result.issues) { return { ok: false }; } return { ok: true, headers: result.value }; } /** * Create route-scoped authentication hooks. * * The outer call binds the app context; the inner call takes auth options and * infers the added context from `resolve`: * * ```ts * const auth = createAuthHooks<AppContext>()({ * resolve: ({ ctx }) => (ctx.auth ? { user: ctx.auth.user } : null), * }); * ``` * * Use `auth.required()` on routes that require an authenticated actor and * `auth.optional()` where handlers can use auth when present. The returned * route hooks enrich handler `ctx`; business authorization still belongs in * feature policies or use cases. * * Declare a `headers` schema when credentials live in request headers. The * hook validates the raw lowercase header record itself, so `resolve` receives * typed headers without contract casts and a `required()` hook rejects * missing or malformed credentials with a framework-owned 401. * * @returns A function that takes auth options and returns public, optional, * and required route-hook factories. */ export function createAuthHooks() { return (options) => { const name = options.name ?? "auth"; const toAuthArgs = (args, headers) => ({ req: args.req, ctx: args.ctx, contract: args.contract, path: args.path, query: args.query, headers, body: args.body, }); return { public: () => ({ name: `${name}.public`, resolve: () => undefined, }), optional: () => ({ name: `${name}.optional`, resolve: async (args) => { const parsed = await parseAuthHeaders(options.headers, args.req); if (!parsed.ok) { return undefined; } const additions = await options.resolve(toAuthArgs(args, parsed.headers)); return additions ?? undefined; }, }), required: () => ({ name: `${name}.required`, resolve: async (args) => { const parsed = await parseAuthHeaders(options.headers, args.req); if (!parsed.ok) { throw new AuthUnauthorizedError(); } const additions = await options.resolve(toAuthArgs(args, parsed.headers)); if (!additions) { throw new AuthUnauthorizedError(); } return additions; }, }), }; }; } //# sourceMappingURL=auth.js.map