UNPKG

wcz-layout

Version:

72 lines (71 loc) 3.59 kB
import { n as getProviderByIssuer } from "./providers-Dbl95AxR.mjs"; import { f as getSessionUser, i as buildUser, s as hasPermission } from "./utils-KaM5VMTJ.mjs"; import { z as z$1 } from "zod"; import { createCsrfMiddleware, createMiddleware } from "@tanstack/react-start"; import * as jose from "jose"; //#region src/middleware/authMiddleware.ts /** * Verifies a Bearer JWT from a sibling service. The unverified `iss` only selects * which provider to check against — the real check pins that provider's JWKS, * issuer and (where the provider has one) audience. */ async function verifyBearer(token) { const provider = getProviderByIssuer(jose.decodeJwt(token).iss); if (!provider) throw new Error("Unauthorized: Unknown token issuer"); const { payload } = await jose.jwtVerify(token, provider.jwks, { issuer: provider.issuer, audience: provider.audience }); return buildUser(payload, provider.id); } async function resolveUser(request) { const authHeader = request.headers.get("Authorization"); if (authHeader?.startsWith("Bearer ")) try { return await verifyBearer(authHeader.substring(7)); } catch (error) { throw Response.json({ message: error instanceof Error ? error.message : "Unauthorized: Invalid access token" }, { status: 401 }); } return getSessionUser(); } /** * Puts the caller in `context.user` — Bearer or cookie, `null` when anonymous — * without requiring one. Deliberately a single instance, not a factory: * `flattenMiddlewares` dedupes by object identity, so every middleware that * declares this one as a dependency shares one resolution per request. */ const userMiddleware = createMiddleware().server(async ({ next, request }) => next({ context: { user: await resolveUser(request) } })); /** * Server-function guard, the counterpart to `requireAuth` on routes. * * - `authMiddleware()` — 401 unless a user is signed in (cookie or Bearer). * - `authMiddleware("admin")` — also 403 unless they hold that permission. */ const authMiddleware = (permissionKey) => createMiddleware().middleware([userMiddleware]).server(async ({ next, context: { user } }) => { if (!user) throw Response.json({ message: "Unauthorized: User not signed in" }, { status: 401 }); if (permissionKey && !hasPermission(user, permissionKey)) throw Response.json({ message: `Forbidden: User ${user.name} is not authorized to access this resource` }, { status: 403 }); return next({ context: { user } }); }); //#endregion //#region src/middleware/validationMiddleware.ts const validationMiddleware = (schema) => createMiddleware().server(async ({ next, request }) => { const json = await request.json(); const result = schema.safeParse(json); if (!result.success) { const { fieldErrors } = z$1.flattenError(result.error); const firstFieldName = Object.keys(fieldErrors)[0]; const firstErrorMessage = fieldErrors[firstFieldName]?.[0]; if (firstFieldName && firstErrorMessage) { const name = firstFieldName.charAt(0).toUpperCase() + firstFieldName.slice(1); const message = firstErrorMessage.replace(/^Invalid input:\s*/i, "").toLowerCase(); return Response.json({ message: `${name} - ${message}` }, { status: 400 }); } return Response.json({ message: "Validation failed" }, { status: 400 }); } return await next({ context: { data: result.data } }); }); //#endregion //#region src/middleware/csrfMiddleware.ts const csrfMiddleware = createCsrfMiddleware({ filter: (ctx) => ctx.handlerType === "serverFn" }); //#endregion export { authMiddleware, csrfMiddleware, userMiddleware, validationMiddleware }; //# sourceMappingURL=middleware.mjs.map