UNPKG

wcz-layout

Version:

1 lines 6.85 kB
{"version":3,"file":"auth.mjs","names":["buildUser","getAppSession","redirect","location","Response","status","headers","Location","resolveRedirectUri","origin","sanitizeReturnTo","value","startsWith","handleLogin","Promise","getRequestUrl","url","returnTo","searchParams","get","cryptoProvider","buildAuthCodeUrl","verifier","challenge","generatePkceCodes","state","createNewGuid","session","update","pkceVerifier","authUrl","redirectUri","codeChallenge","handleCallback","expectedState","data","error","clear","encodeURIComponent","code","exchangeCodeForSession","refreshToken","claims","codeVerifier","user","undefined","message","Error","console","handleLogout","buildLogoutUrl"],"sources":["../src/lib/auth/authHandlers.ts"],"sourcesContent":["import { buildUser } from \"~/lib/utils\";\r\nimport { getAppSession } from \"./session\";\r\n\r\n// These handlers run only as server route handlers, so their server-only deps are\r\n// imported dynamically to keep them out of the client bundle: `entra` pulls in\r\n// `@azure/msal-node`, and `@tanstack/react-start/server` pulls in the SSR renderer\r\n// (`react-dom/server`).\r\n\r\n/**\r\n * Server-side handlers for the OAuth2 Authorization Code (PKCE) flow. They are\r\n * mounted by thin route files (`/auth/login`, `/auth/callback`, `/auth/logout`)\r\n * in the consuming app and drive the full server-side Entra login — the browser\r\n * never loads an auth library.\r\n */\r\n\r\nconst redirect = (location: string): Response =>\r\n new Response(null, { status: 302, headers: { Location: location } });\r\n\r\n/** The OAuth redirect URI, derived from the request origin. */\r\nconst resolveRedirectUri = (origin: string): string => `${origin}/auth/callback`;\r\n\r\n/** Only same-origin paths are allowed, so `returnTo` cannot become an open redirect. */\r\nconst sanitizeReturnTo = (value: string | null): string =>\r\n value?.startsWith(\"/\") && !value.startsWith(\"//\") ? value : \"/\";\r\n\r\n/** GET /auth/login — start the flow: stash PKCE + state, redirect to Entra. */\r\nexport async function handleLogin(): Promise<Response> {\r\n const { getRequestUrl } = await import(\"@tanstack/react-start/server\");\r\n const url = getRequestUrl();\r\n const returnTo = sanitizeReturnTo(url.searchParams.get(\"returnTo\"));\r\n\r\n const { cryptoProvider, buildAuthCodeUrl } = await import(\"./entra\");\r\n const { verifier, challenge } = await cryptoProvider.generatePkceCodes();\r\n const state = cryptoProvider.createNewGuid();\r\n\r\n const session = await getAppSession();\r\n await session.update({ pkceVerifier: verifier, state, returnTo });\r\n\r\n const authUrl = await buildAuthCodeUrl({\r\n redirectUri: resolveRedirectUri(url.origin),\r\n state,\r\n codeChallenge: challenge,\r\n });\r\n return redirect(authUrl);\r\n}\r\n\r\n/** GET /auth/callback — finish the flow: validate state, exchange code, set session. */\r\nexport async function handleCallback(): Promise<Response> {\r\n const { getRequestUrl } = await import(\"@tanstack/react-start/server\");\r\n const url = getRequestUrl();\r\n const session = await getAppSession();\r\n const { pkceVerifier, state: expectedState, returnTo = \"/\" } = session.data;\r\n\r\n const error = url.searchParams.get(\"error\");\r\n if (error) {\r\n await session.clear();\r\n return redirect(`/?auth_error=${encodeURIComponent(error)}`);\r\n }\r\n\r\n const code = url.searchParams.get(\"code\");\r\n const state = url.searchParams.get(\"state\");\r\n if (!code || !state || !pkceVerifier || state !== expectedState) {\r\n await session.clear();\r\n return redirect(\"/?auth_error=invalid_state\");\r\n }\r\n\r\n try {\r\n const { exchangeCodeForSession } = await import(\"./entra\");\r\n const { refreshToken, claims } = await exchangeCodeForSession({\r\n code,\r\n redirectUri: resolveRedirectUri(url.origin),\r\n codeVerifier: pkceVerifier,\r\n });\r\n\r\n // Persist the refresh token + trimmed user; drop the spent transient values.\r\n await session.update({\r\n refreshToken,\r\n user: buildUser(claims),\r\n pkceVerifier: undefined,\r\n state: undefined,\r\n returnTo: undefined,\r\n });\r\n\r\n return redirect(returnTo);\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : \"token_exchange_failed\";\r\n console.error(\"[auth] code exchange failed:\", message);\r\n await session.clear();\r\n return redirect(`/?auth_error=${encodeURIComponent(message)}`);\r\n }\r\n}\r\n\r\n/** GET /auth/logout — clear the local session and sign out of the Entra session. */\r\nexport async function handleLogout(): Promise<Response> {\r\n const { getRequestUrl } = await import(\"@tanstack/react-start/server\");\r\n const url = getRequestUrl();\r\n const session = await getAppSession();\r\n await session.clear();\r\n const { buildLogoutUrl } = await import(\"./entra\");\r\n return redirect(buildLogoutUrl(`${url.origin}/`));\r\n}\r\n"],"mappings":";;;;;;;;;;AAeA,MAAME,YAAYC,aAChB,IAAIC,SAAS,MAAM;CAAEC,QAAQ;CAAKC,SAAS,EAAEC,UAAUJ,SAAS;AAAE,CAAC;;AAGrE,MAAMK,sBAAsBC,WAA2B,GAAGA,OAAM;;AAGhE,MAAMC,oBAAoBC,UACxBA,OAAOC,WAAW,GAAG,KAAK,CAACD,MAAMC,WAAW,IAAI,IAAID,QAAQ;;AAG9D,eAAsBE,cAAiC;CACrD,MAAM,EAAEE,kBAAkB,MAAM,OAAO;CACvC,MAAMC,MAAMD,cAAc;CAC1B,MAAME,WAAWP,iBAAiBM,IAAIE,aAAaC,IAAI,UAAU,CAAC;CAElE,MAAM,EAAEC,gBAAgBC,qBAAqB,MAAM,OAAO;CAC1D,MAAM,EAAEC,UAAUC,cAAc,MAAMH,eAAeI,kBAAkB;CACvE,MAAMC,QAAQL,eAAeM,cAAc;CAG3C,OAAMC,MADgB1B,cAAc,EAAA,CACtB2B,OAAO;EAAEC,cAAcP;EAAUG;EAAOR;CAAS,CAAC;CAOhE,OAAOf,SAAS4B,MALMT,iBAAiB;EACrCU,aAAavB,mBAAmBQ,IAAIP,MAAM;EAC1CgB;EACAO,eAAeT;CACjB,CAAC,CACsB;AACzB;;AAGA,eAAsBU,iBAAoC;CACxD,MAAM,EAAElB,kBAAkB,MAAM,OAAO;CACvC,MAAMC,MAAMD,cAAc;CAC1B,MAAMY,UAAU,MAAM1B,cAAc;CACpC,MAAM,EAAE4B,cAAcJ,OAAOS,eAAejB,WAAW,QAAQU,QAAQQ;CAEvE,MAAMC,QAAQpB,IAAIE,aAAaC,IAAI,OAAO;CAC1C,IAAIiB,OAAO;EACT,MAAMT,QAAQU,MAAM;EACpB,OAAOnC,SAAS,gBAAgBoC,mBAAmBF,KAAK,GAAG;CAC7D;CAEA,MAAMG,OAAOvB,IAAIE,aAAaC,IAAI,MAAM;CACxC,MAAMM,QAAQT,IAAIE,aAAaC,IAAI,OAAO;CAC1C,IAAI,CAACoB,QAAQ,CAACd,SAAS,CAACI,gBAAgBJ,UAAUS,eAAe;EAC/D,MAAMP,QAAQU,MAAM;EACpB,OAAOnC,SAAS,4BAA4B;CAC9C;CAEA,IAAI;EACF,MAAM,EAAEsC,2BAA2B,MAAM,OAAO;EAChD,MAAM,EAAEC,cAAcC,WAAW,MAAMF,uBAAuB;GAC5DD;GACAR,aAAavB,mBAAmBQ,IAAIP,MAAM;GAC1CkC,cAAcd;EAChB,CAAC;EAGD,MAAMF,QAAQC,OAAO;GACnBa;GACAG,MAAM5C,UAAU0C,MAAM;GACtBb,cAAcgB,KAAAA;GACdpB,OAAOoB,KAAAA;GACP5B,UAAU4B,KAAAA;EACZ,CAAC;EAED,OAAO3C,SAASe,QAAQ;CAC1B,SAASmB,OAAO;EACd,MAAMU,UAAUV,iBAAiBW,QAAQX,MAAMU,UAAU;EACzDE,QAAQZ,MAAM,gCAAgCU,OAAO;EACrD,MAAMnB,QAAQU,MAAM;EACpB,OAAOnC,SAAS,gBAAgBoC,mBAAmBQ,OAAO,GAAG;CAC/D;AACF;;AAGA,eAAsBG,eAAkC;CACtD,MAAM,EAAElC,kBAAkB,MAAM,OAAO;CACvC,MAAMC,MAAMD,cAAc;CAE1B,OAAMY,MADgB1B,cAAc,EAAA,CACtBoC,MAAM;CACpB,MAAM,EAAEa,mBAAmB,MAAM,OAAO;CACxC,OAAOhD,SAASgD,eAAe,GAAGlC,IAAIP,OAAM,EAAG,CAAC;AAClD"}