wcz-layout
Version:
1 lines • 6.09 kB
Source Map (JSON)
{"version":3,"file":"user-BQiWoQk1.mjs","names":["User","serverEnv","AppSessionData","refreshToken","user","pkceVerifier","state","returnTo","getAppSession","useSession","getSession","name","password","SESSION_SECRET","cookie","httpOnly","sameSite","secure","path","createServerFn","createServerOnlyFn","scopes","definedScopes","queryClient","User","getAppSession","getSessionUser","method","handler","Promise","session","data","user","getUser","import","meta","env","SSR","ensureQueryData","queryKey","queryFn","staleTime","Infinity","getAccessToken","scopeKey","refreshToken","Error","acquireDelegatedToken","accessToken","update"],"sources":["../src/lib/auth/session.ts","../src/lib/auth/user.ts"],"sourcesContent":["import type { User } from \"~/models/User\";\r\nimport { serverEnv } from \"~/env\";\r\n\r\n/**\r\n * Data persisted in the sealed (encrypted, httpOnly) session cookie.\r\n *\r\n * Kept deliberately small so it never approaches the ~4 KB single-cookie limit:\r\n * the bulky MSAL token cache is NOT stored (serialized it runs to several KB once\r\n * group claims are included, which forces cookie chunking). Instead we keep only\r\n * the refresh token — which the server uses to mint delegated access tokens on\r\n * demand — and a trimmed user with permissions already resolved. The browser sees\r\n * neither: the cookie is httpOnly and encrypted with `SESSION_SECRET`.\r\n */\r\nexport interface AppSessionData {\r\n /** Refresh token used to acquire delegated access tokens server-side. */\r\n refreshToken?: string;\r\n /** Serializable signed-in user (profile + granted permission keys). */\r\n user?: User;\r\n /** Transient PKCE verifier, only present between /auth/login and /auth/callback. */\r\n pkceVerifier?: string;\r\n /** Transient CSRF state, only present between /auth/login and /auth/callback. */\r\n state?: string;\r\n /** Where to send the user after a successful login. */\r\n returnTo?: string;\r\n}\r\n\r\n/**\r\n * Returns the request-scoped session manager. Must be called inside a server\r\n * context (server function, server route handler, or SSR), where the request\r\n * cookies are available.\r\n */\r\nexport const getAppSession = async () => {\r\n // Dynamic import: `@tanstack/react-start/server` is the server entry and drags\r\n // in the SSR renderer (`react-dom/server`). Keep it out of the client graph —\r\n // this helper is only ever called server-side, and all callers already await it.\r\n // Aliased off the `use*` name so it isn't mistaken for a React hook (it's a\r\n // server session helper, not a hook).\r\n const { useSession: getSession } = await import(\"@tanstack/react-start/server\");\r\n return getSession<AppSessionData>({\r\n name: \"wcz-auth\",\r\n password: serverEnv.SESSION_SECRET,\r\n cookie: {\r\n httpOnly: true,\r\n sameSite: \"lax\",\r\n secure: true,\r\n path: \"/\",\r\n },\r\n });\r\n};\r\n","import { createServerFn, createServerOnlyFn } from \"@tanstack/react-start\";\r\nimport { scopes as definedScopes } from \"virtual:wcz-layout\";\r\nimport { queryClient } from \"~/lib/queryClient\";\r\nimport type { User } from \"~/models/User\";\r\nimport { getAppSession } from \"./session\";\r\n\r\n/**\r\n * Reads the signed-in user from the session cookie, or null. As a server function\r\n * it runs in-process when called on the server (SSR, middleware) and as an RPC\r\n * when called from the client — so it doubles as the client `queryFn`.\r\n */\r\nexport const getSessionUser = createServerFn({ method: \"GET\" }).handler(\r\n async (): Promise<User | null> => {\r\n const session = await getAppSession();\r\n return session.data.user ?? null;\r\n },\r\n);\r\n\r\nexport const getUser = (): Promise<User | null> => {\r\n if (import.meta.env.SSR) return getSessionUser();\r\n\r\n return queryClient.ensureQueryData({\r\n queryKey: [\"auth\", \"user\"],\r\n queryFn: () => getSessionUser(),\r\n staleTime: Infinity,\r\n });\r\n};\r\n\r\n/**\r\n * Server-only token acquisition: a delegated access token for the given API\r\n * scope, minted from the user's session refresh token. Entra rotates the refresh\r\n * token on each use, so the rotated token is persisted back to the session. Use\r\n * inside server functions and middleware — it is stripped from the client bundle\r\n * and throws if called there.\r\n */\r\nexport const getAccessToken = createServerOnlyFn(\r\n async (scopeKey: keyof typeof definedScopes): Promise<string> => {\r\n const session = await getAppSession();\r\n if (!session.data.refreshToken) throw new Error(\"No active session. User not signed in.\");\r\n\r\n // Dynamic import so `entra` (and its `@azure/msal-node` dependency) stays out\r\n // of the client graph — this module also exports the isomorphic `getUser`.\r\n const { acquireDelegatedToken } = await import(\"./entra\");\r\n const { accessToken, refreshToken } = await acquireDelegatedToken({\r\n refreshToken: session.data.refreshToken,\r\n scopes: definedScopes[scopeKey],\r\n });\r\n\r\n if (refreshToken !== session.data.refreshToken) await session.update({ refreshToken });\r\n return accessToken;\r\n },\r\n);\r\n"],"mappings":";;;;;;;;;;AA+BA,MAAaQ,gBAAgB,YAAY;CAMvC,MAAM,EAAEC,YAAYC,eAAe,MAAM,OAAO;CAChD,OAAOA,WAA2B;EAChCC,MAAM;EACNC,UAAUX,YAAUY;EACpBC,QAAQ;GACNC,UAAU;GACVC,UAAU;GACVC,QAAQ;GACRC,MAAM;EACR;CACF,CAAC;AACH;;;;;;;;ACrCA,MAAaQ,iBAAiBP,eAAe,EAAEQ,QAAQ,MAAM,CAAC,CAAC,CAACC,QAC9D,YAAkC;CAEhC,QAAOE,MADeL,cAAc,EAAA,CACrBM,KAAKC,QAAQ;AAC9B,CACF;AAEA,MAAaC,gBAAsC;CACjD,IAAIC,OAAOC,KAAKC,IAAIC,KAAK,OAAOX,eAAe;CAE/C,OAAOH,YAAYe,gBAAgB;EACjCC,UAAU,CAAC,QAAQ,MAAM;EACzBC,eAAed,eAAe;EAC9Be,WAAWC;CACb,CAAC;AACH;;;;;;;;AASA,MAAaC,iBAAiBvB,mBAC5B,OAAOwB,aAA0D;CAC/D,MAAMd,UAAU,MAAML,cAAc;CACpC,IAAI,CAACK,QAAQC,KAAKc,cAAc,MAAM,IAAIC,MAAM,wCAAwC;CAIxF,MAAM,EAAEC,0BAA0B,MAAM,OAAO;CAC/C,MAAM,EAAEC,aAAaH,iBAAiB,MAAME,sBAAsB;EAChEF,cAAcf,QAAQC,KAAKc;EAC3BxB,QAAQC,OAAcsB;CACxB,CAAC;CAED,IAAIC,iBAAiBf,QAAQC,KAAKc,cAAc,MAAMf,QAAQmB,OAAO,EAAEJ,aAAa,CAAC;CACrF,OAAOG;AACT,CACF"}