UNPKG

wcz-layout

Version:

1 lines 7.08 kB
{"version":3,"file":"middleware.mjs","names":["createMiddleware","jose","permissions","getProviderByIssuer","getSessionUser","buildUser","hasPermission","TokenPayload","User","verifyBearer","token","Promise","provider","decodeJwt","iss","Error","payload","jwtVerify","jwks","issuer","audience","id","resolveUser","request","Request","authHeader","headers","get","startsWith","substring","length","error","Response","json","message","status","userMiddleware","server","user","next","context","authMiddleware","permissionKey","middleware","name","createMiddleware","z","validationMiddleware","schema","ZodType","T","server","next","request","json","result","safeParse","success","fieldErrors","flattenError","error","firstFieldName","Object","keys","firstErrorMessage","name","charAt","toUpperCase","slice","message","replace","toLowerCase","Response","status","context","data"],"sources":["../src/middleware/authMiddleware.ts","../src/middleware/validationMiddleware.ts","../src/middleware/csrfMiddleware.ts"],"sourcesContent":["import { createMiddleware } from \"@tanstack/react-start\";\nimport * as jose from \"jose\";\nimport type { permissions } from \"virtual:wcz-layout\";\nimport { getProviderByIssuer } from \"~/lib/auth/providers\";\nimport { getSessionUser } from \"~/lib/auth/user\";\nimport { buildUser, hasPermission } from \"~/lib/utils\";\nimport type { TokenPayload } from \"~/models/TokenPayload\";\nimport type { User } from \"~/models/User\";\n\n/**\n * Verifies a Bearer JWT from a sibling service. The unverified `iss` only selects\n * which provider to check against — the real check pins that provider's JWKS,\n * issuer and (where the provider has one) audience.\n */\nasync function verifyBearer(token: string): Promise<User> {\n const provider = getProviderByIssuer(jose.decodeJwt(token).iss);\n if (!provider) throw new Error(\"Unauthorized: Unknown token issuer\");\n\n const { payload } = await jose.jwtVerify(token, provider.jwks, {\n issuer: provider.issuer,\n audience: provider.audience,\n });\n return buildUser(payload as TokenPayload, provider.id);\n}\n\nasync function resolveUser(request: Request): Promise<User | null> {\n const authHeader = request.headers.get(\"Authorization\");\n\n if (authHeader?.startsWith(\"Bearer \")) {\n try {\n return await verifyBearer(authHeader.substring(\"Bearer \".length));\n } catch (error) {\n throw Response.json(\n { message: error instanceof Error ? error.message : \"Unauthorized: Invalid access token\" },\n { status: 401 },\n );\n }\n }\n\n return getSessionUser();\n}\n\n/**\n * Puts the caller in `context.user` — Bearer or cookie, `null` when anonymous —\n * without requiring one. Deliberately a single instance, not a factory:\n * `flattenMiddlewares` dedupes by object identity, so every middleware that\n * declares this one as a dependency shares one resolution per request.\n */\nexport const userMiddleware = createMiddleware().server<{ user: User | null }>(\n async ({ next, request }) => next({ context: { user: await resolveUser(request) } }),\n);\n\n/**\n * Server-function guard, the counterpart to `requireAuth` on routes.\n *\n * - `authMiddleware()` — 401 unless a user is signed in (cookie or Bearer).\n * - `authMiddleware(\"admin\")` — also 403 unless they hold that permission.\n */\nexport const authMiddleware = (permissionKey?: keyof typeof permissions) =>\n createMiddleware()\n .middleware([userMiddleware])\n .server<{ user: User }>(async ({ next, context: { user } }) => {\n if (!user)\n throw Response.json({ message: \"Unauthorized: User not signed in\" }, { status: 401 });\n\n if (permissionKey && !hasPermission(user, permissionKey))\n throw Response.json(\n { message: `Forbidden: User ${user.name} is not authorized to access this resource` },\n { status: 403 },\n );\n\n return next({ context: { user } });\n });\n","import { createMiddleware } from \"@tanstack/react-start\";\nimport { z } from \"zod\";\n\nexport const validationMiddleware = <T>(schema: z.ZodType<T>) =>\n createMiddleware().server(async ({ next, request }) => {\n const json = await request.json();\n const result = schema.safeParse(json);\n if (!result.success) {\n const { fieldErrors } = z.flattenError(result.error);\n const firstFieldName = Object.keys(fieldErrors)[0];\n const firstErrorMessage = fieldErrors[firstFieldName as keyof typeof fieldErrors]?.[0];\n\n if (firstFieldName && firstErrorMessage) {\n const name = firstFieldName.charAt(0).toUpperCase() + firstFieldName.slice(1);\n const message = firstErrorMessage.replace(/^Invalid input:\\s*/i, \"\").toLowerCase();\n return Response.json({ message: `${name} - ${message}` }, { status: 400 });\n }\n\n return Response.json({ message: \"Validation failed\" }, { status: 400 });\n }\n return await next({ context: { data: result.data } });\n });\n","import { createCsrfMiddleware } from \"@tanstack/react-start\";\n\nexport const csrfMiddleware = createCsrfMiddleware({\n filter: (ctx) => ctx.handlerType === \"serverFn\",\n});\n"],"mappings":";;;;;;;;;;;AAcA,eAAeS,aAAaC,OAA8B;CACxD,MAAME,WAAWT,oBAAoBF,KAAKY,UAAUH,KAAK,CAAC,CAACI,GAAG;CAC9D,IAAI,CAACF,UAAU,MAAM,IAAIG,MAAM,oCAAoC;CAEnE,MAAM,EAAEC,YAAY,MAAMf,KAAKgB,UAAUP,OAAOE,SAASM,MAAM;EAC7DC,QAAQP,SAASO;EACjBC,UAAUR,SAASQ;CACrB,CAAC;CACD,OAAOf,UAAUW,SAAyBJ,SAASS,EAAE;AACvD;AAEA,eAAeC,YAAYC,SAAwC;CACjE,MAAME,aAAaF,QAAQG,QAAQC,IAAI,eAAe;CAEtD,IAAIF,YAAYG,WAAW,SAAS,GAClC,IAAI;EACF,OAAO,MAAMnB,aAAagB,WAAWI,UAAU,CAAgB,CAAC;CAClE,SAASE,OAAO;EACd,MAAMC,SAASC,KACb,EAAEC,SAASH,iBAAiBhB,QAAQgB,MAAMG,UAAU,qCAAqC,GACzF,EAAEC,QAAQ,IAAI,CAChB;CACF;CAGF,OAAO/B,eAAe;AACxB;;;;;;;AAQA,MAAagC,iBAAiBpC,iBAAiB,CAAC,CAACqC,OAC/C,OAAO,EAAEE,MAAMhB,cAAcgB,KAAK,EAAEC,SAAS,EAAEF,MAAM,MAAMhB,YAAYC,OAAO,EAAE,EAAE,CAAC,CACrF;;;;;;;AAQA,MAAakB,kBAAkBC,kBAC7B1C,iBAAiB,CAAC,CACf2C,WAAW,CAACP,cAAc,CAAC,CAAC,CAC5BC,OAAuB,OAAO,EAAEE,MAAMC,SAAS,EAAEF,aAAa;CAC7D,IAAI,CAACA,MACH,MAAMN,SAASC,KAAK,EAAEC,SAAS,mCAAmC,GAAG,EAAEC,QAAQ,IAAI,CAAC;CAEtF,IAAIO,iBAAiB,CAACpC,cAAcgC,MAAMI,aAAa,GACrD,MAAMV,SAASC,KACb,EAAEC,SAAS,mBAAmBI,KAAKM,KAAI,4CAA6C,GACpF,EAAET,QAAQ,IAAI,CAChB;CAEF,OAAOI,KAAK,EAAEC,SAAS,EAAEF,KAAK,EAAE,CAAC;AACnC,CAAC;;;ACrEL,MAAaS,wBAA2BC,WACtCH,iBAAiB,CAAC,CAACM,OAAO,OAAO,EAAEC,MAAMC,cAAc;CACrD,MAAMC,OAAO,MAAMD,QAAQC,KAAK;CAChC,MAAMC,SAASP,OAAOQ,UAAUF,IAAI;CACpC,IAAI,CAACC,OAAOE,SAAS;EACnB,MAAM,EAAEC,gBAAgBZ,IAAEa,aAAaJ,OAAOK,KAAK;EACnD,MAAMC,iBAAiBC,OAAOC,KAAKL,WAAW,CAAC,CAAC;EAChD,MAAMM,oBAAoBN,YAAYG,eAA2C,GAAG;EAEpF,IAAIA,kBAAkBG,mBAAmB;GACvC,MAAMC,OAAOJ,eAAeK,OAAO,CAAC,CAAC,CAACC,YAAY,IAAIN,eAAeO,MAAM,CAAC;GAC5E,MAAMC,UAAUL,kBAAkBM,QAAQ,uBAAuB,EAAE,CAAC,CAACC,YAAY;GACjF,OAAOC,SAASlB,KAAK,EAAEe,SAAS,GAAGJ,KAAI,KAAMI,UAAU,GAAG,EAAEI,QAAQ,IAAI,CAAC;EAC3E;EAEA,OAAOD,SAASlB,KAAK,EAAEe,SAAS,oBAAoB,GAAG,EAAEI,QAAQ,IAAI,CAAC;CACxE;CACA,OAAO,MAAMrB,KAAK,EAAEsB,SAAS,EAAEC,MAAMpB,OAAOoB,KAAK,EAAE,CAAC;AACtD,CAAC;;;ACnBH,MAAa,iBAAiB,qBAAqB,EACjD,SAAS,QAAQ,IAAI,gBAAgB,WACvC,CAAC"}