{"version":3,"file":"auth.mjs","names":[],"sources":["../src/lib/auth/authHandlers.ts"],"sourcesContent":["import { buildUser } from \"~/lib/utils\";\nimport { getAppSession } from \"./session\";\n\n// These handlers run only as server route handlers, so their server-only deps are\n// imported dynamically to keep them out of the client bundle: `entra` pulls in\n// `@azure/msal-node`, and `@tanstack/react-start/server` pulls in the SSR renderer\n// (`react-dom/server`).\n\n/**\n * Server-side handlers for the OAuth2 Authorization Code (PKCE) flow. They are\n * mounted by thin route files (`/auth/login`, `/auth/callback`, `/auth/logout`)\n * in the consuming app and drive the full server-side Entra login — the browser\n * never loads an auth library.\n */\n\nconst redirect = (location: string): Response =>\n  new Response(null, { status: 302, headers: { Location: location } });\n\n/** The OAuth redirect URI, derived from the request origin. */\nconst resolveRedirectUri = (origin: string): string => `${origin}/auth/callback`;\n\n/**\n * Only same-origin paths are allowed, so `returnTo` cannot become an open\n * redirect. Backslash counts as a separator too: browsers normalize `/\\evil`\n * to the protocol-relative `//evil`.\n */\nconst sanitizeReturnTo = (value: string | null): string =>\n  value && /^\\/(?![/\\\\])/.test(value) ? value : \"/\";\n\n/** GET /auth/login — start the flow: stash PKCE + state, redirect to Entra. */\nexport async function handleLogin(): Promise<Response> {\n  const { getRequestUrl } = await import(\"@tanstack/react-start/server\");\n  const url = getRequestUrl();\n  const returnTo = sanitizeReturnTo(url.searchParams.get(\"returnTo\"));\n\n  const { cryptoProvider, buildAuthCodeUrl } = await import(\"./entra\");\n  const { verifier, challenge } = await cryptoProvider.generatePkceCodes();\n  const state = cryptoProvider.createNewGuid();\n\n  const session = await getAppSession();\n  await session.update({ pkceVerifier: verifier, state, returnTo });\n\n  const authUrl = await buildAuthCodeUrl({\n    redirectUri: resolveRedirectUri(url.origin),\n    state,\n    codeChallenge: challenge,\n  });\n  return redirect(authUrl);\n}\n\n/** GET /auth/callback — finish the flow: validate state, exchange code, set session. */\nexport async function handleCallback(): Promise<Response> {\n  const { getRequestUrl } = await import(\"@tanstack/react-start/server\");\n  const url = getRequestUrl();\n  const session = await getAppSession();\n  const { pkceVerifier, state: expectedState, returnTo = \"/\" } = session.data;\n\n  const error = url.searchParams.get(\"error\");\n  if (error) {\n    await session.clear();\n    return redirect(`/?auth_error=${encodeURIComponent(error)}`);\n  }\n\n  const code = url.searchParams.get(\"code\");\n  const state = url.searchParams.get(\"state\");\n  if (!code || !state || !pkceVerifier || state !== expectedState) {\n    await session.clear();\n    return redirect(\"/?auth_error=invalid_state\");\n  }\n\n  try {\n    const { exchangeCodeForSession } = await import(\"./entra\");\n    const { refreshToken, claims } = await exchangeCodeForSession({\n      code,\n      redirectUri: resolveRedirectUri(url.origin),\n      codeVerifier: pkceVerifier,\n    });\n\n    // Persist the refresh token + trimmed user; drop the spent transient values.\n    await session.update({\n      refreshToken,\n      user: buildUser(claims),\n      pkceVerifier: undefined,\n      state: undefined,\n      returnTo: undefined,\n    });\n\n    return redirect(returnTo);\n  } catch (error) {\n    const message = error instanceof Error ? error.message : \"token_exchange_failed\";\n    console.error(\"[auth] code exchange failed:\", message);\n    await session.clear();\n    return redirect(`/?auth_error=${encodeURIComponent(message)}`);\n  }\n}\n\n/** GET /auth/logout — clear the local session and sign out of the Entra session. */\nexport async function handleLogout(): Promise<Response> {\n  const { getRequestUrl } = await import(\"@tanstack/react-start/server\");\n  const url = getRequestUrl();\n  const session = await getAppSession();\n  await session.clear();\n  const { buildLogoutUrl } = await import(\"./entra\");\n  return redirect(buildLogoutUrl(`${url.origin}/`));\n}\n"],"mappings":";;;;;;;;;AAeA,MAAM,YAAY,aAChB,IAAI,SAAS,MAAM;CAAE,QAAQ;CAAK,SAAS,EAAE,UAAU,SAAS;AAAE,CAAC;;AAGrE,MAAM,sBAAsB,WAA2B,GAAG,OAAM;;;;;;AAOhE,MAAM,oBAAoB,UACxB,SAAS,eAAe,KAAK,KAAK,IAAI,QAAQ;;AAGhD,eAAsB,cAAiC;CACrD,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,MAAM,MAAM,cAAc;CAC1B,MAAM,WAAW,iBAAiB,IAAI,aAAa,IAAI,UAAU,CAAC;CAElE,MAAM,EAAE,gBAAgB,qBAAqB,MAAM,OAAO;CAC1D,MAAM,EAAE,UAAU,cAAc,MAAM,eAAe,kBAAkB;CACvE,MAAM,QAAQ,eAAe,cAAc;CAG3C,OAAM,MADgB,cAAc,EAAA,CACtB,OAAO;EAAE,cAAc;EAAU;EAAO;CAAS,CAAC;CAEhE,MAAM,UAAU,MAAM,iBAAiB;EACrC,aAAa,mBAAmB,IAAI,MAAM;EAC1C;EACA,eAAe;CACjB,CAAC;CACD,OAAO,SAAS,OAAO;AACzB;;AAGA,eAAsB,iBAAoC;CACxD,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,MAAM,MAAM,cAAc;CAC1B,MAAM,UAAU,MAAM,cAAc;CACpC,MAAM,EAAE,cAAc,OAAO,eAAe,WAAW,QAAQ,QAAQ;CAEvE,MAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;CAC1C,IAAI,OAAO;EACT,MAAM,QAAQ,MAAM;EACpB,OAAO,SAAS,gBAAgB,mBAAmB,KAAK,GAAG;CAC7D;CAEA,MAAM,OAAO,IAAI,aAAa,IAAI,MAAM;CACxC,MAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;CAC1C,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,gBAAgB,UAAU,eAAe;EAC/D,MAAM,QAAQ,MAAM;EACpB,OAAO,SAAS,4BAA4B;CAC9C;CAEA,IAAI;EACF,MAAM,EAAE,2BAA2B,MAAM,OAAO;EAChD,MAAM,EAAE,cAAc,WAAW,MAAM,uBAAuB;GAC5D;GACA,aAAa,mBAAmB,IAAI,MAAM;GAC1C,cAAc;EAChB,CAAC;EAGD,MAAM,QAAQ,OAAO;GACnB;GACA,MAAM,UAAU,MAAM;GACtB,cAAc,KAAA;GACd,OAAO,KAAA;GACP,UAAU,KAAA;EACZ,CAAC;EAED,OAAO,SAAS,QAAQ;CAC1B,SAAS,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;EACzD,QAAQ,MAAM,gCAAgC,OAAO;EACrD,MAAM,QAAQ,MAAM;EACpB,OAAO,SAAS,gBAAgB,mBAAmB,OAAO,GAAG;CAC/D;AACF;;AAGA,eAAsB,eAAkC;CACtD,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,MAAM,MAAM,cAAc;CAE1B,OAAM,MADgB,cAAc,EAAA,CACtB,MAAM;CACpB,MAAM,EAAE,mBAAmB,MAAM,OAAO;CACxC,OAAO,SAAS,eAAe,GAAG,IAAI,OAAM,EAAG,CAAC;AAClD"}