@beignet/core
Version:
Core framework primitives for Beignet
90 lines • 3.22 kB
JavaScript
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