@civic/auth 0.13.0-beta.1 → 0.13.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ import { clearAuthCookies, NextjsCookieStorage } from "../nextjs/cookies.js";
7
7
  import { AuthFlowCookie, CodeVerifier, UserStorage, } from "../shared/lib/types.js";
8
8
  import { revalidatePath } from "next/cache.js";
9
9
  import { NextResponse } from "next/server.js";
10
- import { getOriginUrl, redirectWithBasePath, sanitizeReturnUrl, } from "./utils.js";
10
+ import { getOriginUrl, prependBasePath, redirectWithBasePath, sanitizeReturnUrl, } from "./utils.js";
11
11
  const logger = loggers.nextjs.handlers.auth;
12
12
  class AuthError extends Error {
13
13
  status;
@@ -62,7 +62,9 @@ const createCivicAuth = (request, config) => {
62
62
  redirectUrl: absoluteCallbackUrl,
63
63
  oauthServer: resolvedConfig.oauthServer,
64
64
  postLogoutRedirectUrl: absoluteLogoutCallbackUrl,
65
- loginSuccessUrl: request.url,
65
+ // Note: Do NOT use request.url here - during callback, that would be the callback URL itself,
66
+ // causing an infinite redirect loop in iframe mode fallbacks.
67
+ loginSuccessUrl: resolvedConfig.loginSuccessUrl,
66
68
  });
67
69
  return {
68
70
  civicAuth,
@@ -193,36 +195,35 @@ async function handleCallback(request, config) {
193
195
  headers: Object.fromEntries(request.headers.entries()),
194
196
  url: request.url.toString(),
195
197
  };
196
- // Use CivicAuth's smart callback handler
197
- // Note: CivicAuth.handleCallback reads loginSuccessUrl from state (injected by login handler)
198
- // with fallback to config.loginSuccessUrl, so we don't need to pass frontendUrl option
198
+ // Resolve frontendUrl BEFORE calling handleCallback so it's available for iframe completion HTML.
199
+ // Priority: loginSuccessUrl from state (injected by login handler) > config loginSuccessUrl > "/"
200
+ // Note: We trust the state mechanism here - the login handler should have injected the deep link
201
+ // destination into state. We don't read the cookie here because:
202
+ // 1. If cookies work in callback, they should have worked in login handler to inject into state
203
+ // 2. If cookies don't work in callback (SameSite restrictions), reading them is pointless anyway
204
+ // 3. The whole point of using state was to survive cookie-less callback scenarios
205
+ let frontendUrl;
206
+ // Get loginSuccessUrl from state (should have been injected by login handler from deep link cookie)
207
+ const loginSuccessUrlFromState = CivicAuth.getLoginSuccessUrl(urlDetectionRequest, appUrl);
208
+ if (loginSuccessUrlFromState) {
209
+ logger.debug("[CALLBACK_HANDLER] Using loginSuccessUrl from state", {
210
+ loginSuccessUrlFromState,
211
+ });
212
+ frontendUrl = loginSuccessUrlFromState;
213
+ }
214
+ // Final fallback to config loginSuccessUrl
215
+ if (!frontendUrl) {
216
+ frontendUrl = resolvedConfigs.loginSuccessUrl || "/";
217
+ }
218
+ // Use CivicAuth's smart callback handler with resolved frontendUrl
199
219
  const result = await civicAuth.handleCallback({
200
220
  code,
201
221
  state,
202
222
  req: handleCallbackRequest,
223
+ }, {
224
+ // Pass the resolved frontendUrl - this is critical for iframe completion HTML
225
+ frontendUrl: prependBasePath(frontendUrl, config.basePath || ""),
203
226
  });
204
- // Fallback: If state was corrupted/lost and CivicAuth redirected to the default loginSuccessUrl,
205
- // check if we have a deep link cookie as a backup. This provides resilience against state loss.
206
- const cookieStorage = new NextjsCookieStorage(resolvedConfigs.cookies?.tokens ?? {});
207
- if (resolvedConfigs.deepLinkHandling !== "disabled" && result.redirectTo) {
208
- const defaultLoginSuccessUrl = resolvedConfigs.loginSuccessUrl || "/";
209
- const deepLinkFromCookie = await cookieStorage.get(AuthFlowCookie.RETURN_URL);
210
- // If redirecting to default and we have a cookie, use the cookie value
211
- if (deepLinkFromCookie && result.redirectTo === defaultLoginSuccessUrl) {
212
- // Re-validate the cookie value to guard against tampering (defense-in-depth)
213
- const originUrl = getOriginUrl(request, resolvedConfigs);
214
- const sanitized = sanitizeReturnUrl(deepLinkFromCookie, originUrl);
215
- if (sanitized) {
216
- // Don't prepend basePath here - redirectWithBasePath will handle it
217
- logger.debug("[CALLBACK_HANDLER] State missing loginSuccessUrl, using cookie fallback", { deepLinkFromCookie, sanitized });
218
- result.redirectTo = sanitized;
219
- }
220
- else {
221
- logger.warn("[CALLBACK_HANDLER] Rejected invalid fallback cookie value", { deepLinkFromCookie });
222
- // Keep the default loginSuccessUrl
223
- }
224
- }
225
- }
226
227
  // Helper to clear the deep link cookie on successful auth
227
228
  // Always clear the cookie to handle stale cookies from previous sessions
228
229
  const clearDeepLinkCookie = (response) => {
@@ -1 +1 @@
1
- {"version":3,"file":"routeHandler.js","sourceRoot":"","sources":["../../src/nextjs/routeHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { CivicAuth, type UrlDetectionRequest } from \"@civic/auth/server\";\nimport { LOGOUT_SUCCESS_TEXT } from \"@/constants.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport { displayModeFromState } from \"@/lib/oauth.js\";\nimport type { AuthConfig } from \"@/nextjs/config.js\";\nimport { resolveAuthConfig } from \"@/nextjs/config.js\";\nimport { clearAuthCookies, NextjsCookieStorage } from \"@/nextjs/cookies.js\";\nimport {\n AuthFlowCookie,\n CodeVerifier,\n UserStorage,\n} from \"@/shared/lib/types.js\";\nimport { revalidatePath } from \"next/cache.js\";\nimport type { NextRequest } from \"next/server.js\";\nimport { NextResponse } from \"next/server.js\";\nimport {\n getOriginUrl,\n redirectWithBasePath,\n sanitizeReturnUrl,\n} from \"./utils.js\";\n\nconst logger = loggers.nextjs.handlers.auth;\n\nclass AuthError extends Error {\n constructor(\n message: string,\n public readonly status: number = 401,\n ) {\n super(message);\n this.name = \"AuthError\";\n }\n}\n\n/**\n * Helper to convert NextRequest to UrlDetectionRequest for framework-agnostic URL handling\n */\nconst toUrlDetectionRequest = (request: NextRequest): UrlDetectionRequest => ({\n url: request.url,\n headers: Object.fromEntries(request.headers.entries()),\n searchParams: {\n get: (name: string) => request.nextUrl.searchParams.get(name),\n },\n cookies: {\n get: (name: string) => request.cookies.get(name),\n },\n});\n\n/**\n * Helper to create CivicAuth instance for a request\n * Now handles appUrl detection for proxy environments\n */\nconst createCivicAuth = (request: NextRequest, config: AuthConfig) => {\n const resolvedConfig = resolveAuthConfig(config);\n const cookieStorage = new NextjsCookieStorage({\n ...resolvedConfig.cookies?.tokens,\n [UserStorage.USER]: resolvedConfig.cookies?.user,\n });\n\n // Convert to framework-agnostic request format\n const urlDetectionRequest = toUrlDetectionRequest(request);\n\n // Get appUrl from client (for proxy environments)\n const clientAppUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n\n // Use baseUrl from config, then client appUrl, then request origin\n // This matches the main branch priority: config > client > request\n const appUrl =\n resolvedConfig.baseUrl ||\n clientAppUrl ||\n new URL(urlDetectionRequest.url).origin;\n\n // Build absolute URLs using detected appUrl or request origin\n const absoluteCallbackUrl = resolvedConfig.callbackUrl.startsWith(\"http\")\n ? resolvedConfig.callbackUrl\n : CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n resolvedConfig.callbackUrl,\n appUrl,\n );\n const absoluteLogoutCallbackUrl = resolvedConfig.logoutCallbackUrl.startsWith(\n \"http\",\n )\n ? resolvedConfig.logoutCallbackUrl\n : CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n resolvedConfig.logoutCallbackUrl,\n appUrl,\n );\n\n const civicAuth = new CivicAuth(cookieStorage, {\n disableRefresh: resolvedConfig.disableRefresh,\n clientId: resolvedConfig.clientId,\n redirectUrl: absoluteCallbackUrl,\n oauthServer: resolvedConfig.oauthServer,\n postLogoutRedirectUrl: absoluteLogoutCallbackUrl,\n loginSuccessUrl: request.url,\n });\n\n return {\n civicAuth,\n cookieStorage,\n appUrl, // Return appUrl for use in other functions\n urlDetectionRequest, // Return for use in handlers\n };\n};\n\n/**\n * Injects loginSuccessUrl into an existing base64-encoded state string, or creates\n * a new state if none exists. This allows the deep link destination (computed by\n * middleware) to be passed through the OAuth flow via the existing loginSuccessUrl\n * mechanism.\n */\nfunction injectLoginSuccessUrlIntoState(\n frontendState: string | null,\n loginSuccessUrl: string,\n): string {\n let stateObj: Record<string, unknown> = {};\n\n if (frontendState) {\n try {\n // Decode the existing state\n const jsonString = atob(frontendState);\n stateObj = JSON.parse(jsonString);\n } catch (error) {\n logger.warn(\n \"[LOGIN_HANDLER] Failed to parse existing state, creating new state\",\n { error },\n );\n // Continue with empty stateObj - we'll still add loginSuccessUrl\n }\n }\n\n // Set loginSuccessUrl in state (overrides any existing value for fullUrl mode)\n stateObj.loginSuccessUrl = loginSuccessUrl;\n\n // Encode and return the state\n return btoa(JSON.stringify(stateObj));\n}\n\n/**\n * Login handler - backend OAuth login initiation endpoint\n * Uses CivicAuth.buildLoginUrl()\n */\nasync function handleLogin(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n try {\n let frontendState = request.nextUrl.searchParams.get(\"state\");\n\n // Store appUrl in cookie if provided as query parameter\n const appUrlFromQuery = request.nextUrl.searchParams.get(\"appUrl\");\n const cookieStorage = new NextjsCookieStorage(\n resolvedConfigs.cookies?.tokens ?? {},\n );\n\n if (appUrlFromQuery) {\n await cookieStorage.set(CodeVerifier.APP_URL, appUrlFromQuery);\n }\n\n // Read the deep link cookie (set by middleware with deepLinkHandling already applied)\n // and inject it into the OAuth state's loginSuccessUrl field. This ensures the\n // destination survives the OAuth flow even when cookies aren't available in the\n // callback (e.g., due to SameSite restrictions in Chromium iframes).\n // Note: We don't delete the cookie here because:\n // 1. The login handler may be called multiple times (iframe preload, mode switching)\n // 2. We only want to consume the cookie on successful auth (handled in callback)\n if (resolvedConfigs.deepLinkHandling !== \"disabled\") {\n const deepLinkDestination = await cookieStorage.get(\n AuthFlowCookie.RETURN_URL,\n );\n if (deepLinkDestination) {\n // Re-validate the cookie value to guard against tampering (defense-in-depth)\n const originUrl = getOriginUrl(request, resolvedConfigs);\n const sanitized = sanitizeReturnUrl(deepLinkDestination, originUrl);\n\n if (sanitized) {\n // Inject the destination into state.loginSuccessUrl\n // Don't prepend basePath here - the callback handler's redirectWithBasePath will handle it\n logger.debug(\n \"[LOGIN_HANDLER] Found deep link cookie, injecting into state.loginSuccessUrl\",\n { deepLinkDestination, sanitized },\n );\n frontendState = injectLoginSuccessUrlIntoState(\n frontendState,\n sanitized,\n );\n } else {\n logger.warn(\n \"[LOGIN_HANDLER] Rejected invalid deep link cookie value\",\n { deepLinkDestination },\n );\n }\n }\n }\n\n const { civicAuth } = createCivicAuth(request, resolvedConfigs);\n\n const url = await civicAuth.buildLoginUrl({\n state: frontendState || undefined,\n });\n\n logger.info(\"[LOGIN_HANDLER] Redirecting to OAuth login URL\", {\n loginUrl: url.toString(),\n });\n\n return NextResponse.redirect(url.toString());\n } catch (error) {\n logger.error(\"[LOGIN_HANDLER] Backend login error:\", error);\n\n // Clean up deep link cookie on error to prevent stale cookies\n try {\n const errorCookieStorage = new NextjsCookieStorage(\n resolvedConfigs.cookies?.tokens ?? {},\n );\n await errorCookieStorage.delete(AuthFlowCookie.RETURN_URL);\n } catch (cookieError) {\n logger.warn(\n \"[LOGIN_HANDLER] Failed to clean up deep link cookie on error\",\n { cookieError },\n );\n }\n\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n return NextResponse.redirect(\n CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n \"/?error=login_failed\",\n appUrl,\n ),\n );\n }\n}\n\nasync function handleRefresh(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n try {\n const { civicAuth } = createCivicAuth(request, resolvedConfigs);\n\n await civicAuth.refreshTokens();\n\n logger.info(\"[REFRESH_HANDLER] Tokens refreshed successfully\");\n\n return NextResponse.json({\n status: \"success\",\n message: \"Tokens refreshed\",\n });\n } catch (error) {\n logger.error(\"[REFRESH_HANDLER] Token refresh error:\", error);\n return NextResponse.json(\n { error: \"Token refresh failed\" },\n { status: 500 },\n );\n }\n}\n\nasync function handleCallback(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n const code = request.nextUrl.searchParams.get(\"code\");\n const state = request.nextUrl.searchParams.get(\"state\");\n const error = request.nextUrl.searchParams.get(\"error\");\n\n if (error) {\n logger.error(\"OAuth error in callback:\", error);\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n return NextResponse.redirect(\n CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n \"/?error=oauth_error\",\n appUrl,\n ),\n );\n }\n\n if (!code || !state) throw new AuthError(\"Bad parameters\", 400);\n\n try {\n const { civicAuth, appUrl, urlDetectionRequest } = createCivicAuth(\n request,\n resolvedConfigs,\n );\n\n // Convert NextRequest to the format expected by handleCallback\n const handleCallbackRequest = {\n headers: Object.fromEntries(request.headers.entries()),\n url: request.url.toString(),\n };\n\n // Use CivicAuth's smart callback handler\n // Note: CivicAuth.handleCallback reads loginSuccessUrl from state (injected by login handler)\n // with fallback to config.loginSuccessUrl, so we don't need to pass frontendUrl option\n const result = await civicAuth.handleCallback({\n code,\n state,\n req: handleCallbackRequest,\n });\n\n // Fallback: If state was corrupted/lost and CivicAuth redirected to the default loginSuccessUrl,\n // check if we have a deep link cookie as a backup. This provides resilience against state loss.\n const cookieStorage = new NextjsCookieStorage(\n resolvedConfigs.cookies?.tokens ?? {},\n );\n if (resolvedConfigs.deepLinkHandling !== \"disabled\" && result.redirectTo) {\n const defaultLoginSuccessUrl = resolvedConfigs.loginSuccessUrl || \"/\";\n const deepLinkFromCookie = await cookieStorage.get(\n AuthFlowCookie.RETURN_URL,\n );\n\n // If redirecting to default and we have a cookie, use the cookie value\n if (deepLinkFromCookie && result.redirectTo === defaultLoginSuccessUrl) {\n // Re-validate the cookie value to guard against tampering (defense-in-depth)\n const originUrl = getOriginUrl(request, resolvedConfigs);\n const sanitized = sanitizeReturnUrl(deepLinkFromCookie, originUrl);\n\n if (sanitized) {\n // Don't prepend basePath here - redirectWithBasePath will handle it\n logger.debug(\n \"[CALLBACK_HANDLER] State missing loginSuccessUrl, using cookie fallback\",\n { deepLinkFromCookie, sanitized },\n );\n result.redirectTo = sanitized;\n } else {\n logger.warn(\n \"[CALLBACK_HANDLER] Rejected invalid fallback cookie value\",\n { deepLinkFromCookie },\n );\n // Keep the default loginSuccessUrl\n }\n }\n }\n\n // Helper to clear the deep link cookie on successful auth\n // Always clear the cookie to handle stale cookies from previous sessions\n const clearDeepLinkCookie = (response: NextResponse) => {\n const cookieConfig =\n resolvedConfigs.cookies?.tokens?.[AuthFlowCookie.RETURN_URL];\n response.cookies.set(AuthFlowCookie.RETURN_URL, \"\", {\n path: cookieConfig?.path ?? resolvedConfigs.basePath ?? \"/\",\n httpOnly: cookieConfig?.httpOnly ?? true,\n secure: cookieConfig?.secure ?? true,\n sameSite: cookieConfig?.sameSite ?? \"strict\",\n maxAge: 0, // Immediately expire the cookie\n });\n logger.debug(\n \"[CALLBACK_HANDLER] Clearing deep link cookie after successful auth\",\n );\n return response;\n };\n\n if (result.redirectTo) {\n const response = redirectWithBasePath(\n config,\n CivicAuth.toAbsoluteUrl(urlDetectionRequest, result.redirectTo, appUrl),\n );\n return clearDeepLinkCookie(response);\n }\n\n if (result.content) {\n // Handle both string content and object content\n if (typeof result.content === \"string\") {\n const response = new NextResponse(result.content, {\n status: 200,\n headers: { \"Content-Type\": \"text/html\" },\n });\n return clearDeepLinkCookie(response);\n } else {\n // Object content (JSON response)\n return NextResponse.json(result.content);\n }\n }\n\n // Fallback redirect\n const response = NextResponse.redirect(\n CivicAuth.toAbsoluteUrl(urlDetectionRequest, \"/\", appUrl),\n );\n return clearDeepLinkCookie(response);\n } catch (error) {\n logger.error(\"[CALLBACK_HANDLER] OAuth callback error:\", error);\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n return NextResponse.redirect(\n CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n \"/?error=callback_failed\",\n appUrl,\n ),\n );\n }\n}\n\nconst revalidateUrlPath = async (url: string) => {\n try {\n const path = new URL(url).pathname;\n revalidatePath(path);\n } catch (error) {\n logger.warn(\"Failed to revalidate path after logout:\", error);\n }\n};\n\nexport async function handleLogout(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n // Get framework-agnostic request for URL utilities\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n\n // Read the state from the query parameters\n const state = request.nextUrl.searchParams.get(\"state\");\n const clientLogoutRedirectUrl =\n request.nextUrl.searchParams.get(\"logoutRedirectUrl\");\n\n try {\n logger.info(\"[LOGOUT_HANDLER] Backend logout endpoint called\");\n\n // If client provided a logoutRedirectUrl, override the config\n let configToUse = resolvedConfigs;\n if (clientLogoutRedirectUrl) {\n configToUse = {\n ...resolvedConfigs,\n logoutCallbackUrl: clientLogoutRedirectUrl,\n };\n logger.info(\"[LOGOUT_HANDLER] Overriding logout callback URL\", {\n original: resolvedConfigs.logoutCallbackUrl,\n override: clientLogoutRedirectUrl,\n });\n }\n\n const { civicAuth } = createCivicAuth(request, configToUse);\n\n // Always redirect to OAuth logout (like main branch)\n // Don't validate session - even invalid local sessions should hit OAuth logout\n logger.info(\"[LOGOUT_HANDLER] Processing logout request\", {\n state: !!state,\n });\n\n // Always redirect to OAuth logout endpoint (like main branch)\n // Client-side iframe logic will handle completion and redirect appropriately\n const logoutUrl = await civicAuth.buildLogoutRedirectUrl({\n state: state || undefined,\n });\n\n try {\n await clearAuthCookies(resolvedConfigs);\n } catch (error) {\n logger.error(\"[LOGOUT_HANDLER] Error clearing tokens:\", error);\n }\n\n // Remove state parameter from logout URL to prevent it from appearing in frontend URL\n const cleanLogoutUrl = new URL(logoutUrl);\n cleanLogoutUrl.searchParams.delete(\"state\");\n\n logger.info(\"[LOGOUT_HANDLER] Redirecting to OAuth logout endpoint\", {\n logoutUrl: cleanLogoutUrl.toString(),\n });\n\n return NextResponse.redirect(cleanLogoutUrl.toString());\n } catch (error) {\n logger.error(\"[LOGOUT_HANDLER] Logout error:\", error);\n // If logout URL generation fails, clear tokens and redirect to home\n await clearAuthCookies(resolvedConfigs);\n\n const fallbackUrl =\n clientLogoutRedirectUrl || resolvedConfigs.logoutCallbackUrl;\n const finalFallbackUrl = CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n fallbackUrl,\n appUrl,\n );\n\n return NextResponse.redirect(finalFallbackUrl);\n }\n}\n\n/**\n * Clear session handler - clears all auth cookies server-side.\n * Called by client in parallel with logout iframe to quickly clear HttpOnly cookies.\n *\n * This is part of a parallel logout strategy:\n * - Client calls clearsession (this endpoint) AND loads logout iframe simultaneously\n * - Both requests are sent while cookies are still present\n * - clearsession clears HttpOnly cookies quickly (this endpoint)\n * - logout iframe handles OAuth provider logout (slower, but had cookies when initiated)\n *\n * This ensures cookies are cleared fast (preventing race conditions if user refreshes)\n * while still performing OAuth provider logout.\n */\nasync function handleClearSession(\n _request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n try {\n logger.info(\"[CLEARSESSION_HANDLER] Clearing session cookies\");\n\n // Clear all auth cookies immediately\n await clearAuthCookies(resolvedConfigs);\n\n logger.info(\"[CLEARSESSION_HANDLER] Session cleared successfully\");\n\n return NextResponse.json({\n status: \"success\",\n message: \"Session cleared\",\n });\n } catch (error) {\n logger.error(\"[CLEARSESSION_HANDLER] Error clearing session:\", error);\n return NextResponse.json(\n { error: \"Failed to clear session\" },\n { status: 500 },\n );\n }\n}\n\nexport async function handleLogoutCallback(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n try {\n logger.info(\n \"[LOGOUT_CALLBACK_HANDLER] Backend logout callback endpoint called\",\n );\n\n // Clear authentication cookies\n await clearAuthCookies(resolvedConfigs);\n\n // Get framework-agnostic request and create CivicAuth instance\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const { civicAuth } = createCivicAuth(request, resolvedConfigs);\n\n // Get the state parameter for iframe detection\n const state = request.nextUrl.searchParams.get(\"state\");\n\n // If this is an iframe request, return HTML with logout success signal\n if (state && displayModeFromState(state, \"iframe\") === \"iframe\") {\n // For iframe mode, include the post-logout redirect URL in the HTML\n const postLogoutRedirectUrl =\n civicAuth.getPostLogoutRedirectUrl(urlDetectionRequest);\n const response = new NextResponse(\n `<html lang=\"en\"><span style=\"display:none\">${LOGOUT_SUCCESS_TEXT}<a href=\"${postLogoutRedirectUrl}\" rel=\"civic-auth-post-logout-redirect-url\"></a></span></html>`,\n );\n response.headers.set(\"Content-Type\", \"text/html; charset=utf-8\");\n logger.info(\n \"[LOGOUT_CALLBACK_HANDLER] Returning iframe logout success HTML\",\n { postLogoutRedirectUrl },\n );\n return response;\n }\n\n // For non-iframe requests, redirect to the logout callback URL or post-logout URL\n const redirectUrl = civicAuth.getPostLogoutRedirectUrl(urlDetectionRequest);\n logger.info(\n \"[LOGOUT_CALLBACK_HANDLER] Redirecting to logout callback URL\",\n {\n logoutCallbackUrl: resolvedConfigs.logoutCallbackUrl,\n redirectUrl,\n },\n );\n\n // Revalidate the redirect path to update authentication state in server components\n await revalidateUrlPath(redirectUrl);\n return redirectWithBasePath(config, redirectUrl);\n } catch (error) {\n logger.error(\"[LOGOUT_CALLBACK_HANDLER] Logout callback error:\", error);\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n return redirectWithBasePath(\n config,\n CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n resolvedConfigs.logoutCallbackUrl,\n appUrl,\n ),\n );\n }\n}\n\n/**\n * Creates an authentication handler for Next.js API routes\n *\n * Usage:\n * ```ts\n * // app/api/auth/[...civicauth]/route.ts\n * import { handler } from '@civic/auth/nextjs'\n * export const GET = handler({\n * // optional config overrides\n * })\n * export const POST = handler({\n * // optional config overrides\n * })\n * ```\n */\nexport const handler =\n (authConfig = {}) =>\n async (request: NextRequest): Promise<NextResponse> => {\n const config = resolveAuthConfig(authConfig);\n logger.debug(\"routeHandler: Auth route handler called\", config);\n try {\n const pathname = request.nextUrl.pathname;\n const pathSegments = pathname.split(\"/\");\n const lastSegment = pathSegments[pathSegments.length - 1];\n\n switch (lastSegment) {\n case \"challenge\":\n case \"login\":\n return await handleLogin(request, config);\n case \"callback\":\n return await handleCallback(request, config);\n case \"refresh\":\n return await handleRefresh(request, config);\n case \"logout\":\n return await handleLogout(request, config);\n case \"clearsession\":\n return await handleClearSession(request, config);\n case \"logoutcallback\":\n return await handleLogoutCallback(request, config);\n case \"user\":\n return await handleUser(request, config);\n default:\n throw new AuthError(`Invalid auth route: ${pathname}`, 404);\n }\n } catch (error) {\n logger.error(\"Auth handler error:\", error);\n\n const status = error instanceof AuthError ? error.status : 500;\n const message =\n error instanceof Error ? error.message : \"Authentication failed\";\n\n const response = NextResponse.json({ error: message }, { status });\n\n await clearAuthCookies(config);\n return response;\n }\n };\n\n/**\n * User endpoint - returns current user data as JSON\n * Uses CivicAuth.isLoggedIn() and getUser()\n */\nasync function handleUser(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n logger.debug(\n \"routeHandler: [USER_HANDLER] User endpoint called\",\n resolvedConfigs,\n );\n try {\n const { civicAuth } = createCivicAuth(request, resolvedConfigs);\n\n const isLoggedIn = await civicAuth.isLoggedIn();\n\n if (!isLoggedIn) {\n const statusCode = request.nextUrl.searchParams.get(\n \"optimisticRehydration\",\n )\n ? 202\n : 401;\n return NextResponse.json(\n { error: \"Not authenticated\" },\n { status: statusCode },\n );\n }\n\n const user = await civicAuth.getUser();\n\n return NextResponse.json({ user });\n } catch (error) {\n logger.error(\"[USER_HANDLER] User endpoint error:\", error);\n return NextResponse.json(\n { error: \"Internal server error\" },\n { status: 500 },\n );\n }\n}\n"]}
1
+ {"version":3,"file":"routeHandler.js","sourceRoot":"","sources":["../../src/nextjs/routeHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { CivicAuth, type UrlDetectionRequest } from \"@civic/auth/server\";\nimport { LOGOUT_SUCCESS_TEXT } from \"@/constants.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport { displayModeFromState } from \"@/lib/oauth.js\";\nimport type { AuthConfig } from \"@/nextjs/config.js\";\nimport { resolveAuthConfig } from \"@/nextjs/config.js\";\nimport { clearAuthCookies, NextjsCookieStorage } from \"@/nextjs/cookies.js\";\nimport {\n AuthFlowCookie,\n CodeVerifier,\n UserStorage,\n} from \"@/shared/lib/types.js\";\nimport { revalidatePath } from \"next/cache.js\";\nimport type { NextRequest } from \"next/server.js\";\nimport { NextResponse } from \"next/server.js\";\nimport {\n getOriginUrl,\n prependBasePath,\n redirectWithBasePath,\n sanitizeReturnUrl,\n} from \"./utils.js\";\n\nconst logger = loggers.nextjs.handlers.auth;\n\nclass AuthError extends Error {\n constructor(\n message: string,\n public readonly status: number = 401,\n ) {\n super(message);\n this.name = \"AuthError\";\n }\n}\n\n/**\n * Helper to convert NextRequest to UrlDetectionRequest for framework-agnostic URL handling\n */\nconst toUrlDetectionRequest = (request: NextRequest): UrlDetectionRequest => ({\n url: request.url,\n headers: Object.fromEntries(request.headers.entries()),\n searchParams: {\n get: (name: string) => request.nextUrl.searchParams.get(name),\n },\n cookies: {\n get: (name: string) => request.cookies.get(name),\n },\n});\n\n/**\n * Helper to create CivicAuth instance for a request\n * Now handles appUrl detection for proxy environments\n */\nconst createCivicAuth = (request: NextRequest, config: AuthConfig) => {\n const resolvedConfig = resolveAuthConfig(config);\n const cookieStorage = new NextjsCookieStorage({\n ...resolvedConfig.cookies?.tokens,\n [UserStorage.USER]: resolvedConfig.cookies?.user,\n });\n\n // Convert to framework-agnostic request format\n const urlDetectionRequest = toUrlDetectionRequest(request);\n\n // Get appUrl from client (for proxy environments)\n const clientAppUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n\n // Use baseUrl from config, then client appUrl, then request origin\n // This matches the main branch priority: config > client > request\n const appUrl =\n resolvedConfig.baseUrl ||\n clientAppUrl ||\n new URL(urlDetectionRequest.url).origin;\n\n // Build absolute URLs using detected appUrl or request origin\n const absoluteCallbackUrl = resolvedConfig.callbackUrl.startsWith(\"http\")\n ? resolvedConfig.callbackUrl\n : CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n resolvedConfig.callbackUrl,\n appUrl,\n );\n const absoluteLogoutCallbackUrl = resolvedConfig.logoutCallbackUrl.startsWith(\n \"http\",\n )\n ? resolvedConfig.logoutCallbackUrl\n : CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n resolvedConfig.logoutCallbackUrl,\n appUrl,\n );\n\n const civicAuth = new CivicAuth(cookieStorage, {\n disableRefresh: resolvedConfig.disableRefresh,\n clientId: resolvedConfig.clientId,\n redirectUrl: absoluteCallbackUrl,\n oauthServer: resolvedConfig.oauthServer,\n postLogoutRedirectUrl: absoluteLogoutCallbackUrl,\n // Note: Do NOT use request.url here - during callback, that would be the callback URL itself,\n // causing an infinite redirect loop in iframe mode fallbacks.\n loginSuccessUrl: resolvedConfig.loginSuccessUrl,\n });\n\n return {\n civicAuth,\n cookieStorage,\n appUrl, // Return appUrl for use in other functions\n urlDetectionRequest, // Return for use in handlers\n };\n};\n\n/**\n * Injects loginSuccessUrl into an existing base64-encoded state string, or creates\n * a new state if none exists. This allows the deep link destination (computed by\n * middleware) to be passed through the OAuth flow via the existing loginSuccessUrl\n * mechanism.\n */\nfunction injectLoginSuccessUrlIntoState(\n frontendState: string | null,\n loginSuccessUrl: string,\n): string {\n let stateObj: Record<string, unknown> = {};\n\n if (frontendState) {\n try {\n // Decode the existing state\n const jsonString = atob(frontendState);\n stateObj = JSON.parse(jsonString);\n } catch (error) {\n logger.warn(\n \"[LOGIN_HANDLER] Failed to parse existing state, creating new state\",\n { error },\n );\n // Continue with empty stateObj - we'll still add loginSuccessUrl\n }\n }\n\n // Set loginSuccessUrl in state (overrides any existing value for fullUrl mode)\n stateObj.loginSuccessUrl = loginSuccessUrl;\n\n // Encode and return the state\n return btoa(JSON.stringify(stateObj));\n}\n\n/**\n * Login handler - backend OAuth login initiation endpoint\n * Uses CivicAuth.buildLoginUrl()\n */\nasync function handleLogin(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n try {\n let frontendState = request.nextUrl.searchParams.get(\"state\");\n\n // Store appUrl in cookie if provided as query parameter\n const appUrlFromQuery = request.nextUrl.searchParams.get(\"appUrl\");\n const cookieStorage = new NextjsCookieStorage(\n resolvedConfigs.cookies?.tokens ?? {},\n );\n\n if (appUrlFromQuery) {\n await cookieStorage.set(CodeVerifier.APP_URL, appUrlFromQuery);\n }\n\n // Read the deep link cookie (set by middleware with deepLinkHandling already applied)\n // and inject it into the OAuth state's loginSuccessUrl field. This ensures the\n // destination survives the OAuth flow even when cookies aren't available in the\n // callback (e.g., due to SameSite restrictions in Chromium iframes).\n // Note: We don't delete the cookie here because:\n // 1. The login handler may be called multiple times (iframe preload, mode switching)\n // 2. We only want to consume the cookie on successful auth (handled in callback)\n if (resolvedConfigs.deepLinkHandling !== \"disabled\") {\n const deepLinkDestination = await cookieStorage.get(\n AuthFlowCookie.RETURN_URL,\n );\n if (deepLinkDestination) {\n // Re-validate the cookie value to guard against tampering (defense-in-depth)\n const originUrl = getOriginUrl(request, resolvedConfigs);\n const sanitized = sanitizeReturnUrl(deepLinkDestination, originUrl);\n\n if (sanitized) {\n // Inject the destination into state.loginSuccessUrl\n // Don't prepend basePath here - the callback handler's redirectWithBasePath will handle it\n logger.debug(\n \"[LOGIN_HANDLER] Found deep link cookie, injecting into state.loginSuccessUrl\",\n { deepLinkDestination, sanitized },\n );\n frontendState = injectLoginSuccessUrlIntoState(\n frontendState,\n sanitized,\n );\n } else {\n logger.warn(\n \"[LOGIN_HANDLER] Rejected invalid deep link cookie value\",\n { deepLinkDestination },\n );\n }\n }\n }\n\n const { civicAuth } = createCivicAuth(request, resolvedConfigs);\n\n const url = await civicAuth.buildLoginUrl({\n state: frontendState || undefined,\n });\n\n logger.info(\"[LOGIN_HANDLER] Redirecting to OAuth login URL\", {\n loginUrl: url.toString(),\n });\n\n return NextResponse.redirect(url.toString());\n } catch (error) {\n logger.error(\"[LOGIN_HANDLER] Backend login error:\", error);\n\n // Clean up deep link cookie on error to prevent stale cookies\n try {\n const errorCookieStorage = new NextjsCookieStorage(\n resolvedConfigs.cookies?.tokens ?? {},\n );\n await errorCookieStorage.delete(AuthFlowCookie.RETURN_URL);\n } catch (cookieError) {\n logger.warn(\n \"[LOGIN_HANDLER] Failed to clean up deep link cookie on error\",\n { cookieError },\n );\n }\n\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n return NextResponse.redirect(\n CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n \"/?error=login_failed\",\n appUrl,\n ),\n );\n }\n}\n\nasync function handleRefresh(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n try {\n const { civicAuth } = createCivicAuth(request, resolvedConfigs);\n\n await civicAuth.refreshTokens();\n\n logger.info(\"[REFRESH_HANDLER] Tokens refreshed successfully\");\n\n return NextResponse.json({\n status: \"success\",\n message: \"Tokens refreshed\",\n });\n } catch (error) {\n logger.error(\"[REFRESH_HANDLER] Token refresh error:\", error);\n return NextResponse.json(\n { error: \"Token refresh failed\" },\n { status: 500 },\n );\n }\n}\n\nasync function handleCallback(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n const code = request.nextUrl.searchParams.get(\"code\");\n const state = request.nextUrl.searchParams.get(\"state\");\n const error = request.nextUrl.searchParams.get(\"error\");\n\n if (error) {\n logger.error(\"OAuth error in callback:\", error);\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n return NextResponse.redirect(\n CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n \"/?error=oauth_error\",\n appUrl,\n ),\n );\n }\n\n if (!code || !state) throw new AuthError(\"Bad parameters\", 400);\n\n try {\n const { civicAuth, appUrl, urlDetectionRequest } = createCivicAuth(\n request,\n resolvedConfigs,\n );\n\n // Convert NextRequest to the format expected by handleCallback\n const handleCallbackRequest = {\n headers: Object.fromEntries(request.headers.entries()),\n url: request.url.toString(),\n };\n\n // Resolve frontendUrl BEFORE calling handleCallback so it's available for iframe completion HTML.\n // Priority: loginSuccessUrl from state (injected by login handler) > config loginSuccessUrl > \"/\"\n // Note: We trust the state mechanism here - the login handler should have injected the deep link\n // destination into state. We don't read the cookie here because:\n // 1. If cookies work in callback, they should have worked in login handler to inject into state\n // 2. If cookies don't work in callback (SameSite restrictions), reading them is pointless anyway\n // 3. The whole point of using state was to survive cookie-less callback scenarios\n\n let frontendUrl: string | undefined;\n\n // Get loginSuccessUrl from state (should have been injected by login handler from deep link cookie)\n const loginSuccessUrlFromState = CivicAuth.getLoginSuccessUrl(\n urlDetectionRequest,\n appUrl,\n );\n if (loginSuccessUrlFromState) {\n logger.debug(\"[CALLBACK_HANDLER] Using loginSuccessUrl from state\", {\n loginSuccessUrlFromState,\n });\n frontendUrl = loginSuccessUrlFromState;\n }\n\n // Final fallback to config loginSuccessUrl\n if (!frontendUrl) {\n frontendUrl = resolvedConfigs.loginSuccessUrl || \"/\";\n }\n\n // Use CivicAuth's smart callback handler with resolved frontendUrl\n const result = await civicAuth.handleCallback(\n {\n code,\n state,\n req: handleCallbackRequest,\n },\n {\n // Pass the resolved frontendUrl - this is critical for iframe completion HTML\n frontendUrl: prependBasePath(frontendUrl, config.basePath || \"\"),\n },\n );\n\n // Helper to clear the deep link cookie on successful auth\n // Always clear the cookie to handle stale cookies from previous sessions\n const clearDeepLinkCookie = (response: NextResponse) => {\n const cookieConfig =\n resolvedConfigs.cookies?.tokens?.[AuthFlowCookie.RETURN_URL];\n response.cookies.set(AuthFlowCookie.RETURN_URL, \"\", {\n path: cookieConfig?.path ?? resolvedConfigs.basePath ?? \"/\",\n httpOnly: cookieConfig?.httpOnly ?? true,\n secure: cookieConfig?.secure ?? true,\n sameSite: cookieConfig?.sameSite ?? \"strict\",\n maxAge: 0, // Immediately expire the cookie\n });\n logger.debug(\n \"[CALLBACK_HANDLER] Clearing deep link cookie after successful auth\",\n );\n return response;\n };\n\n if (result.redirectTo) {\n const response = redirectWithBasePath(\n config,\n CivicAuth.toAbsoluteUrl(urlDetectionRequest, result.redirectTo, appUrl),\n );\n return clearDeepLinkCookie(response);\n }\n\n if (result.content) {\n // Handle both string content and object content\n if (typeof result.content === \"string\") {\n const response = new NextResponse(result.content, {\n status: 200,\n headers: { \"Content-Type\": \"text/html\" },\n });\n return clearDeepLinkCookie(response);\n } else {\n // Object content (JSON response)\n return NextResponse.json(result.content);\n }\n }\n\n // Fallback redirect\n const response = NextResponse.redirect(\n CivicAuth.toAbsoluteUrl(urlDetectionRequest, \"/\", appUrl),\n );\n return clearDeepLinkCookie(response);\n } catch (error) {\n logger.error(\"[CALLBACK_HANDLER] OAuth callback error:\", error);\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n return NextResponse.redirect(\n CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n \"/?error=callback_failed\",\n appUrl,\n ),\n );\n }\n}\n\nconst revalidateUrlPath = async (url: string) => {\n try {\n const path = new URL(url).pathname;\n revalidatePath(path);\n } catch (error) {\n logger.warn(\"Failed to revalidate path after logout:\", error);\n }\n};\n\nexport async function handleLogout(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n // Get framework-agnostic request for URL utilities\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n\n // Read the state from the query parameters\n const state = request.nextUrl.searchParams.get(\"state\");\n const clientLogoutRedirectUrl =\n request.nextUrl.searchParams.get(\"logoutRedirectUrl\");\n\n try {\n logger.info(\"[LOGOUT_HANDLER] Backend logout endpoint called\");\n\n // If client provided a logoutRedirectUrl, override the config\n let configToUse = resolvedConfigs;\n if (clientLogoutRedirectUrl) {\n configToUse = {\n ...resolvedConfigs,\n logoutCallbackUrl: clientLogoutRedirectUrl,\n };\n logger.info(\"[LOGOUT_HANDLER] Overriding logout callback URL\", {\n original: resolvedConfigs.logoutCallbackUrl,\n override: clientLogoutRedirectUrl,\n });\n }\n\n const { civicAuth } = createCivicAuth(request, configToUse);\n\n // Always redirect to OAuth logout (like main branch)\n // Don't validate session - even invalid local sessions should hit OAuth logout\n logger.info(\"[LOGOUT_HANDLER] Processing logout request\", {\n state: !!state,\n });\n\n // Always redirect to OAuth logout endpoint (like main branch)\n // Client-side iframe logic will handle completion and redirect appropriately\n const logoutUrl = await civicAuth.buildLogoutRedirectUrl({\n state: state || undefined,\n });\n\n try {\n await clearAuthCookies(resolvedConfigs);\n } catch (error) {\n logger.error(\"[LOGOUT_HANDLER] Error clearing tokens:\", error);\n }\n\n // Remove state parameter from logout URL to prevent it from appearing in frontend URL\n const cleanLogoutUrl = new URL(logoutUrl);\n cleanLogoutUrl.searchParams.delete(\"state\");\n\n logger.info(\"[LOGOUT_HANDLER] Redirecting to OAuth logout endpoint\", {\n logoutUrl: cleanLogoutUrl.toString(),\n });\n\n return NextResponse.redirect(cleanLogoutUrl.toString());\n } catch (error) {\n logger.error(\"[LOGOUT_HANDLER] Logout error:\", error);\n // If logout URL generation fails, clear tokens and redirect to home\n await clearAuthCookies(resolvedConfigs);\n\n const fallbackUrl =\n clientLogoutRedirectUrl || resolvedConfigs.logoutCallbackUrl;\n const finalFallbackUrl = CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n fallbackUrl,\n appUrl,\n );\n\n return NextResponse.redirect(finalFallbackUrl);\n }\n}\n\n/**\n * Clear session handler - clears all auth cookies server-side.\n * Called by client in parallel with logout iframe to quickly clear HttpOnly cookies.\n *\n * This is part of a parallel logout strategy:\n * - Client calls clearsession (this endpoint) AND loads logout iframe simultaneously\n * - Both requests are sent while cookies are still present\n * - clearsession clears HttpOnly cookies quickly (this endpoint)\n * - logout iframe handles OAuth provider logout (slower, but had cookies when initiated)\n *\n * This ensures cookies are cleared fast (preventing race conditions if user refreshes)\n * while still performing OAuth provider logout.\n */\nasync function handleClearSession(\n _request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n try {\n logger.info(\"[CLEARSESSION_HANDLER] Clearing session cookies\");\n\n // Clear all auth cookies immediately\n await clearAuthCookies(resolvedConfigs);\n\n logger.info(\"[CLEARSESSION_HANDLER] Session cleared successfully\");\n\n return NextResponse.json({\n status: \"success\",\n message: \"Session cleared\",\n });\n } catch (error) {\n logger.error(\"[CLEARSESSION_HANDLER] Error clearing session:\", error);\n return NextResponse.json(\n { error: \"Failed to clear session\" },\n { status: 500 },\n );\n }\n}\n\nexport async function handleLogoutCallback(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n try {\n logger.info(\n \"[LOGOUT_CALLBACK_HANDLER] Backend logout callback endpoint called\",\n );\n\n // Clear authentication cookies\n await clearAuthCookies(resolvedConfigs);\n\n // Get framework-agnostic request and create CivicAuth instance\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const { civicAuth } = createCivicAuth(request, resolvedConfigs);\n\n // Get the state parameter for iframe detection\n const state = request.nextUrl.searchParams.get(\"state\");\n\n // If this is an iframe request, return HTML with logout success signal\n if (state && displayModeFromState(state, \"iframe\") === \"iframe\") {\n // For iframe mode, include the post-logout redirect URL in the HTML\n const postLogoutRedirectUrl =\n civicAuth.getPostLogoutRedirectUrl(urlDetectionRequest);\n const response = new NextResponse(\n `<html lang=\"en\"><span style=\"display:none\">${LOGOUT_SUCCESS_TEXT}<a href=\"${postLogoutRedirectUrl}\" rel=\"civic-auth-post-logout-redirect-url\"></a></span></html>`,\n );\n response.headers.set(\"Content-Type\", \"text/html; charset=utf-8\");\n logger.info(\n \"[LOGOUT_CALLBACK_HANDLER] Returning iframe logout success HTML\",\n { postLogoutRedirectUrl },\n );\n return response;\n }\n\n // For non-iframe requests, redirect to the logout callback URL or post-logout URL\n const redirectUrl = civicAuth.getPostLogoutRedirectUrl(urlDetectionRequest);\n logger.info(\n \"[LOGOUT_CALLBACK_HANDLER] Redirecting to logout callback URL\",\n {\n logoutCallbackUrl: resolvedConfigs.logoutCallbackUrl,\n redirectUrl,\n },\n );\n\n // Revalidate the redirect path to update authentication state in server components\n await revalidateUrlPath(redirectUrl);\n return redirectWithBasePath(config, redirectUrl);\n } catch (error) {\n logger.error(\"[LOGOUT_CALLBACK_HANDLER] Logout callback error:\", error);\n const urlDetectionRequest = toUrlDetectionRequest(request);\n const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);\n return redirectWithBasePath(\n config,\n CivicAuth.toAbsoluteUrl(\n urlDetectionRequest,\n resolvedConfigs.logoutCallbackUrl,\n appUrl,\n ),\n );\n }\n}\n\n/**\n * Creates an authentication handler for Next.js API routes\n *\n * Usage:\n * ```ts\n * // app/api/auth/[...civicauth]/route.ts\n * import { handler } from '@civic/auth/nextjs'\n * export const GET = handler({\n * // optional config overrides\n * })\n * export const POST = handler({\n * // optional config overrides\n * })\n * ```\n */\nexport const handler =\n (authConfig = {}) =>\n async (request: NextRequest): Promise<NextResponse> => {\n const config = resolveAuthConfig(authConfig);\n logger.debug(\"routeHandler: Auth route handler called\", config);\n try {\n const pathname = request.nextUrl.pathname;\n const pathSegments = pathname.split(\"/\");\n const lastSegment = pathSegments[pathSegments.length - 1];\n\n switch (lastSegment) {\n case \"challenge\":\n case \"login\":\n return await handleLogin(request, config);\n case \"callback\":\n return await handleCallback(request, config);\n case \"refresh\":\n return await handleRefresh(request, config);\n case \"logout\":\n return await handleLogout(request, config);\n case \"clearsession\":\n return await handleClearSession(request, config);\n case \"logoutcallback\":\n return await handleLogoutCallback(request, config);\n case \"user\":\n return await handleUser(request, config);\n default:\n throw new AuthError(`Invalid auth route: ${pathname}`, 404);\n }\n } catch (error) {\n logger.error(\"Auth handler error:\", error);\n\n const status = error instanceof AuthError ? error.status : 500;\n const message =\n error instanceof Error ? error.message : \"Authentication failed\";\n\n const response = NextResponse.json({ error: message }, { status });\n\n await clearAuthCookies(config);\n return response;\n }\n };\n\n/**\n * User endpoint - returns current user data as JSON\n * Uses CivicAuth.isLoggedIn() and getUser()\n */\nasync function handleUser(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n logger.debug(\n \"routeHandler: [USER_HANDLER] User endpoint called\",\n resolvedConfigs,\n );\n try {\n const { civicAuth } = createCivicAuth(request, resolvedConfigs);\n\n const isLoggedIn = await civicAuth.isLoggedIn();\n\n if (!isLoggedIn) {\n const statusCode = request.nextUrl.searchParams.get(\n \"optimisticRehydration\",\n )\n ? 202\n : 401;\n return NextResponse.json(\n { error: \"Not authenticated\" },\n { status: statusCode },\n );\n }\n\n const user = await civicAuth.getUser();\n\n return NextResponse.json({ user });\n } catch (error) {\n logger.error(\"[USER_HANDLER] User endpoint error:\", error);\n return NextResponse.json(\n { error: \"Internal server error\" },\n { status: 500 },\n );\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"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"}
@@ -371,10 +371,9 @@ export class CivicAuth {
371
371
  newSearchParams.delete("sameDomainCallback");
372
372
  newSearchParams.delete("appUrl");
373
373
  newSearchParams.delete("loginSuccessUrl");
374
- // Use preserved deep link if available and valid, otherwise fall back to cleaned callback URL
375
- const cleanedSearch = newSearchParams.toString();
376
- const fallbackUrl = `${currentUrl.pathname}${cleanedSearch ? "?" + cleanedSearch : ""}${currentUrl.hash}`;
377
- const redirectUrl = loginSuccessUrl || fallbackUrl;
374
+ // Use preserved deep link if available and valid, otherwise fall back to loginSuccessUrl or "/"
375
+ // Note: Do NOT fall back to currentUrl.pathname as that's the callback URL, which would cause a loop
376
+ const redirectUrl = loginSuccessUrl || this.authConfig.loginSuccessUrl || "/";
378
377
  return {
379
378
  content: {
380
379
  success: true,
@@ -424,6 +423,10 @@ export class CivicAuth {
424
423
  return { redirectTo: frontendUrl };
425
424
  }
426
425
  else {
426
+ // Return JSON when no redirect destination is available.
427
+ // This is intentional for API-style callbacks and cross-origin scenarios
428
+ // where cookies aren't accessible to read config. The JSON response allows
429
+ // clients (e.g., vanilla JS SDK) to handle the success case programmatically.
427
430
  return { content: { success: true, user } };
428
431
  }
429
432
  }
@@ -520,19 +523,9 @@ export class CivicAuth {
520
523
  if (frontendUrl) {
521
524
  return { redirectTo: frontendUrl };
522
525
  }
523
- // Server-side fallback: if no frontend URL is configured but we have a postLogoutRedirectUrl,
524
- // redirect there instead of returning JSON content
525
- if (this.authConfig.postLogoutRedirectUrl) {
526
- return { redirectTo: this.authConfig.postLogoutRedirectUrl };
527
- }
528
- // Absolute fallback: return success as JSON content if no other conditions are met.
529
- // This could happen if no loginSuccessUrl or postLogoutRedirectUrl is configured.
530
- return {
531
- content: {
532
- success: true,
533
- user,
534
- },
535
- };
526
+ // Absolute fallback: redirect to loginSuccessUrl or "/"
527
+ // Never return JSON for browser navigation - that would display raw JSON to the user
528
+ return { redirectTo: this.authConfig.loginSuccessUrl || "/" };
536
529
  }
537
530
  /**
538
531
  * Generate HTML content for iframe completion that sends postMessage to parent
@@ -541,10 +534,10 @@ export class CivicAuth {
541
534
  const escapedUser = JSON.stringify(user).replace(/'/g, "\\'");
542
535
  const clientId = this.authConfig.clientId;
543
536
  // Determine fallback redirect URL
544
- const fallbackUrl = frontendUrl ||
545
- this.authConfig.redirectUrl ||
546
- this.authConfig.postLogoutRedirectUrl ||
547
- "/";
537
+ // Note: redirectUrl is the OAuth callback URL - it should NEVER be used as a post-login
538
+ // destination as it would cause an infinite redirect loop in iframe mode.
539
+ // postLogoutRedirectUrl is semantically incorrect for login success scenarios.
540
+ const fallbackUrl = frontendUrl || this.authConfig.loginSuccessUrl || "/";
548
541
  return `
549
542
  <!DOCTYPE html>
550
543
  <html>
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,OAAO,IAAI,iBAAiB,EAC5B,SAAS,IAAI,mBAAmB,GACjC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AACxF,OAAO,EACL,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,SAAS,EAAmB,MAAM,MAAM,CAAC;AAClD,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAgC1C,uCAAuC;AACvC,MAAM,QAAQ,GAAG,CACf,IAAS,EACT,GAAM,EACM,EAAE;IACd,MAAM,MAAM,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;IAC1B,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,oBAAoB,GAAG,CAAC,GAA0B,EAAW,EAAE;IACnE,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IAC3B,OAAO,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC;AACrD,CAAC,CAAC;AAEF;;;;GAIG;AACH,SAAS,iBAAiB,CACxB,MAA6B;IAE7B,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAmB,CAAC;IACjE,IAAI,CAAC,WAAW,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAElC,qCAAqC;IACrC,MAAM,6BAA6B,GAAG;QACpC,GAAI,WAAiB;QACrB,EAAE,EAAE,WAAW,CAAC,GAAG;KACpB,CAAC;IAEF,0EAA0E;IAC1E,OAAO,QAAQ,CACb,CAAC,GAAG,4BAA4B,EAAE,GAAG,SAAS,CAAC,EAC/C,6BAA6B,CACnB,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,SAAS;IAGT;IACA;IAHX,aAAa,GAAkC,IAAI,CAAC;IACpD,YACW,OAAsB,EACtB,UAAsB;QADtB,YAAO,GAAP,OAAO,CAAe;QACtB,eAAU,GAAV,UAAU,CAAY;IAC9B,CAAC;IAEJ,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,IAAI,mBAAmB,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YACxE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACpE,IAAI,CAAC,aAAa,GAAG,MAAM,4BAA4B,CAAC,KAAK,CAC3D;YACE,GAAG,IAAI,CAAC,UAAU;YAClB,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,EACD,IAAI,CAAC,OAAO,CACb,CAAC;QACF,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IACD;;;OAGG;IACH,KAAK,CAAC,OAAO;QAGX,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAE9C,IAAI,CAAC;YACH,iDAAiD;YACjD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,uBAAuB,EAAE,CAAC;YACzD,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,CAAC;gBAC5B,OAAO,IAAI,CAAC;YACd,CAAC;YAED,qEAAqE;YACrE,OAAO,iBAAiB,CAAI,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,SAAS;QACb,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAE9C,IAAI,CAAC;YACH,mDAAmD;YACnD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,uBAAuB,EAAE,CAAC;YAEzD,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,CAAC;gBAC5B,OAAO,IAAI,CAAC;YACd,CAAC;YAED,uEAAuE;YACvE,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvD,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAC;YACnE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,sBAAsB,CAC1B,IAAY,EACZ,KAAa;QAEb,OAAO,sBAAsB,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5E,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,uBAAuB,EAAE,CAAC;QACzD,OAAO,OAAO,EAAE,aAAa,IAAI,KAAK,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,aAAa,CAAC,OAInB;QACC,OAAO,aAAa,CAClB;YACE,GAAG,IAAI,CAAC,UAAU;YAClB,MAAM,EAAE,OAAO,EAAE,MAAM;YACvB,KAAK,EAAE,OAAO,EAAE,KAAK;YACrB,KAAK,EAAE,OAAO,EAAE,KAAK;YACrB,SAAS,EAAE,QAAQ;YACnB,UAAU,EAAE,UAAU,EAAE;SACzB,EACD,IAAI,CAAC,OAAO,CACb,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,sBAAsB,CAAC,OAG5B;QACC,gEAAgE;QAChE,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;YAC7B,gEAAgE;YAChE,yEAAyE;YACzE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;YAC5D,MAAM,SAAS,GAAG,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;YACxE,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;YAE1E,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAE5C,6DAA6D;YAC7D,IAAI,IAAI,CAAC,UAAU,CAAC,qBAAqB,EAAE,CAAC;gBAC1C,SAAS,CAAC,YAAY,CAAC,GAAG,CACxB,mBAAmB,EACnB,IAAI,CAAC,UAAU,CAAC,qBAAqB,CACtC,CAAC;YACJ,CAAC;YAED,4BAA4B;YAC5B,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;gBACnB,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACrD,CAAC;YAED,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,qEAAqE;QACrE,IAAI,CAAC;YACH,gFAAgF;YAChF,uEAAuE;YACvE,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAEvD,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,kDAAkD;gBAClD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;oBAC9B,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;gBACtE,CAAC;gBAED,wDAAwD;gBACxD,yDAAyD;gBAEzD,MAAM,SAAS,GAAG,MAAM,sBAAsB,CAAC;oBAC7C,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ;oBAClC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,qBAAqB,IAAI,GAAG;oBACzD,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;oBAChE,WAAW,EAAE,IAAI,CAAC,WAAW;iBAC9B,CAAC,CAAC;gBAEH,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,kEAAkE;YAClE,OAAO,CAAC,IAAI,CACV,sFAAsF,EACtF,KAAK,CACN,CAAC;QACJ,CAAC;QAED,4FAA4F;QAC5F,OAAO,sBAAsB,CAC3B;YACE,GAAG,IAAI,CAAC,UAAU;YAClB,MAAM,EAAE,OAAO,EAAE,MAAM;YACvB,KAAK,EAAE,OAAO,EAAE,KAAK;SACtB,EACD,IAAI,CAAC,OAAO,CACb,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,aAAa;QACjB,OAAO,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QACf,OAAO,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAED;;;OAGG;IAEH;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,KAAa;QAC/B,IAAI,CAAC;YACH,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,CAAC,CAAC,CAAC;YAClD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,oBAAoB,CACzB,OAA4B,EAC5B,SAAiB;QAEjB,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvD,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,SAAS,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,qBAAqB,CAC1B,OAA4B,EAC5B,UAAkB,EAClB,SAAiB;QAEjB,4EAA4E;QAC5E,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC;QAC3D,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,SAAS,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC;QAED,8BAA8B;QAC9B,OAAO,SAAS,CAAC,oBAAoB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC5D,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,OAA4B;QAC3C,OAAO,SAAS,CAAC,qBAAqB,CACpC,OAAO,EACP,YAAY,CAAC,OAAO,EACpB,QAAQ,CACT,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,kBAAkB,CACvB,OAA4B,EAC5B,OAAuB;QAEvB,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,eAAe,GACnB,wBAAwB,CAAC,KAAK,CAAC;YAC/B,SAAS,CAAC,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QAE7D,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC;IAC5E,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,aAAa,CAClB,OAA4B,EAC5B,GAAW,EACX,MAAsB;QAEtB,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,OAAO,GAAG,CAAC;QACb,CAAC;QAED,0FAA0F;QAC1F,MAAM,OAAO,GAAG,MAAM,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;QACtD,OAAO,IAAI,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,wBAAwB,CAAC,OAA4B;QACnD,iEAAiE;QACjE,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACxD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,CAAC,eAAe,IAAI,GAAG,CAAC;QAE9D,+CAA+C;QAC/C,MAAM,kBAAkB,GAAG,yBAAyB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC1E,IAAI,kBAAkB,EAAE,CAAC;YACvB,OAAO,cAAc,CAAC;QACxB,CAAC;QAED,gDAAgD;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,IAAI,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC;QAC9C,CAAC;QAED,6BAA6B;QAC7B,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,KAAK,CAAC,cAAc,CAClB,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAwB,EAC1C,OAGC;QAKD,oDAAoD;QACpD,IAAI,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,0EAA0E;gBAC1E,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;gBAClD,IAAI,IAAI,GAAgB,IAAI,CAAC;gBAE7B,IAAI,iBAAiB,EAAE,CAAC;oBACtB,wDAAwD;oBACxD,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;oBAC5B,OAAO,CAAC,GAAG,CACT,qDAAqD,EACrD,CAAC,CAAC,IAAI,CACP,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACN,8DAA8D;oBAC9D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;oBAC9D,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;oBACjC,OAAO,CAAC,GAAG,CACT,mDAAmD,EACnD,CAAC,CAAC,IAAI,CACP,CAAC;gBACJ,CAAC;gBAED,gDAAgD;gBAChD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;gBAE1C,oFAAoF;gBACpF,oFAAoF;gBACpF,MAAM,kBAAkB,GACtB,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;gBACjD,MAAM,eAAe,GAAG,kBAAkB;oBACxC,CAAC,CAAC,iBAAiB,CAAC,kBAAkB,EAAE,UAAU,CAAC,MAAM,CAAC;oBAC1D,CAAC,CAAC,IAAI,CAAC;gBAET,MAAM,eAAe,GAAG,IAAI,eAAe,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;gBAC/D,eAAe,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;gBAC7C,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACjC,eAAe,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;gBAE1C,8FAA8F;gBAC9F,MAAM,aAAa,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC;gBACjD,MAAM,WAAW,GAAG,GAAG,UAAU,CAAC,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;gBAC1G,MAAM,WAAW,GAAG,eAAe,IAAI,WAAW,CAAC;gBACnD,OAAO;oBACL,OAAO,EAAE;wBACP,OAAO,EAAE,IAAI;wBACb,WAAW;qBACiC;iBAC/C,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;gBACrD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAED,mDAAmD;QACnD,IAAI,MAA6B,CAAC;QAClC,IAAI,IAAiB,CAAC;QAEtB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACxD,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,kEAAkE;YAClE,MAAM,mBAAmB,GACvB,KAAK,YAAY,KAAK;gBACtB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,oCAAoC,CAAC,CAAC;YAE/D,IAAI,mBAAmB,EAAE,CAAC;gBACxB,+EAA+E;gBAC/E,IAAI,CAAC;oBACH,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;oBAElD,IAAI,iBAAiB,EAAE,CAAC;wBACtB,4DAA4D;wBAC5D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;wBAClC,MAAM,6BAA6B,GACjC,wBAAwB,CAAC,KAAK,CAAC,CAAC;wBAClC,MAAM,WAAW,GACf,OAAO,EAAE,WAAW;4BACpB,6BAA6B;4BAC7B,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC;wBAElC,8EAA8E;wBAC9E,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;wBAChE,MAAM,qBAAqB,GAAG,gBAAgB,KAAK,QAAQ,CAAC;wBAE5D,IACE,qBAAqB;4BACrB,CAAC,IAAI,CAAC,UAAU,CAAC,sBAAsB;4BACvC,IAAI;4BACJ,WAAW,EACX,CAAC;4BACD,qEAAqE;4BACrE,MAAM,cAAc,GAAG,IAAI,CAAC,4BAA4B,CACtD,IAAI,EACJ,WAAW,CACZ,CAAC;4BACF,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;wBACrC,CAAC;wBAED,IAAI,WAAW,EAAE,CAAC;4BAChB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;wBACrC,CAAC;6BAAM,CAAC;4BACN,OAAO,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;wBAC9C,CAAC;oBACH,CAAC;gBACH,CAAC;gBAAC,OAAO,cAAc,EAAE,CAAC;oBACxB,OAAO,CAAC,IAAI,CACV,wCAAwC,EACxC,cAAc,CACf,CAAC;oBACF,sDAAsD;gBACxD,CAAC;gBAED,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;gBAChE,MAAM,qBAAqB,GAAG,gBAAgB,KAAK,QAAQ,CAAC;gBAE5D,IAAI,qBAAqB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC;oBACrE,uDAAuD;oBACvD,MAAM,6BAA6B,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;oBACtE,MAAM,WAAW,GACf,OAAO,EAAE,WAAW;wBACpB,6BAA6B;wBAC7B,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC;oBAElC,MAAM,WAAW,GAAG,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC;oBAClC,MAAM,cAAc,GAAG,IAAI,CAAC,8BAA8B,CACxD,WAAW,EACX,WAAW,CACZ,CAAC;oBACF,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;gBACrC,CAAC;gBAED,gFAAgF;gBAChF,OAAO;oBACL,OAAO,EAAE,oDAAoD,2BAA2B,uBAAuB;iBAChH,CAAC;YACJ,CAAC;YAED,wBAAwB;YACxB,MAAM,KAAK,CAAC;QACd,CAAC;QAED,gDAAgD;QAChD,MAAM,6BAA6B,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;QAEtE,sFAAsF;QACtF,MAAM,WAAW,GACf,OAAO,EAAE,WAAW;YACpB,6BAA6B;YAC7B,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC;QAElC,yDAAyD;QACzD,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAChE,MAAM,qBAAqB,GAAG,gBAAgB,KAAK,QAAQ,CAAC;QAE5D,2DAA2D;QAC3D,kEAAkE;QAClE,MAAM,mBAAmB,GACvB,qBAAqB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,sBAAsB,CAAC;QAEnE,MAAM,kBAAkB,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,UAAU,CAAC;QACxE,MAAM,eAAe,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,QAAQ,CAAC;QACnE,MAAM,YAAY,GAChB,OAAO,EAAE,WAAW,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QAE3E,qGAAqG;QACrG,wCAAwC;QACxC,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAClD,MAAM,QAAQ,GACZ,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAChE,MAAM,yBAAyB,GAC7B,QAAQ;YACR,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAElE,wEAAwE;QACxE,yFAAyF;QACzF,IACE,mBAAmB;YACnB,IAAI;YACJ,WAAW;YACX,CAAC,yBAAyB,EAC1B,CAAC;YACD,MAAM,cAAc,GAAG,IAAI,CAAC,4BAA4B,CACtD,IAAI,EACJ,WAAW,CACZ,CAAC;YACF,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;QACrC,CAAC;QAED,sEAAsE;QACtE,IACE,mBAAmB;YACnB,IAAI;YACJ,WAAW;YACX,yBAAyB,EACzB,CAAC;YACD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;QACrC,CAAC;QAED,sEAAsE;QACtE,IAAI,kBAAkB,IAAI,WAAW,EAAE,CAAC;YACtC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;QACrC,CAAC;QAED,sEAAsE;QACtE,8FAA8F;QAC9F,IAAI,eAAe,IAAI,IAAI,IAAI,WAAW,EAAE,CAAC;YAC3C,IAAI,yBAAyB,EAAE,CAAC;gBAC9B,OAAO,CAAC,GAAG,CACT,sEAAsE,CACvE,CAAC;gBACF,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;YACrC,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CACT,mFAAmF,CACpF,CAAC;gBACF,MAAM,cAAc,GAAG,IAAI,CAAC,4BAA4B,CACtD,IAAI,EACJ,WAAW,CACZ,CAAC;gBACF,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;YACrC,CAAC;QACH,CAAC;QAED,2DAA2D;QAC3D,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO;gBACL,OAAO,EAAE;oBACP,OAAO,EAAE,IAAI;oBACb,IAAI;iBACL;aACF,CAAC;QACJ,CAAC;QAED,kFAAkF;QAClF,2BAA2B;QAC3B,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;QACrC,CAAC;QAED,8FAA8F;QAC9F,mDAAmD;QACnD,IAAI,IAAI,CAAC,UAAU,CAAC,qBAAqB,EAAE,CAAC;YAC1C,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,qBAAqB,EAAE,CAAC;QAC/D,CAAC;QAED,oFAAoF;QACpF,kFAAkF;QAClF,OAAO;YACL,OAAO,EAAE;gBACP,OAAO,EAAE,IAAI;gBACb,IAAI;aACL;SACF,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,4BAA4B,CAClC,IAAU,EACV,WAAoB;QAEpB,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAE1C,kCAAkC;QAClC,MAAM,WAAW,GACf,WAAW;YACX,IAAI,CAAC,UAAU,CAAC,WAAW;YAC3B,IAAI,CAAC,UAAU,CAAC,qBAAqB;YACrC,GAAG,CAAC;QAEN,OAAO;;;;;;;;;qFAS0E,WAAW;YACpF,2BAA2B;;;;;;;;;;;0BAWb,WAAW;iCACJ,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;;;;;;;;;;4BAYhC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;;0BAE1B,WAAW;iCACJ,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;mCAezB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;;;;;KAOzD,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,8BAA8B,GAAG,CACvC,WAAmB,EACnB,WAAoB,EACZ,EAAE;QACV,MAAM,mBAAmB,GAAG,WAAW;YACrC,CAAC,CAAC,oBAAoB,kBAAkB,CAAC,WAAW,CAAC,EAAE;YACvD,CAAC,CAAC,EAAE,CAAC;QAEP,OAAO;;;;;;;;;;;2BAWgB,WAAW,qEAAqE,mBAAmB;;;;;;;;;;;;;;CAc7H,CAAC;IACA,CAAC,CAAC;CACH","sourcesContent":["import {\n type OAuthTokens,\n type User,\n type EmptyObject,\n type UnknownObject,\n type OIDCTokenResponseBody,\n tokenKeys,\n} from \"@/types.js\";\nimport type { AuthConfig } from \"@/server/config.js\";\nimport {\n getUser as getUserFromShared,\n getTokens as getTokensFromShared,\n} from \"@/shared/lib/session.js\";\nimport { clearTokens as clearTokensUtil } from \"@/shared/lib/util.js\";\nimport { resolveOAuthAccessCode } from \"@/server/login.js\";\nimport { buildLoginUrl } from \"@/server/login.js\";\nimport { buildLogoutRedirectUrl } from \"@/server/logout.js\";\nimport {\n TOKEN_EXCHANGE_SUCCESS_TEXT,\n TOKEN_EXCHANGE_TRIGGER_TEXT,\n} from \"@/constants.js\";\nimport { refreshTokens } from \"@/server/refresh.js\";\nimport { getVersion } from \"@/shared/index.js\";\nimport { ServerAuthenticationResolver } from \"@/server/ServerAuthenticationResolver.js\";\nimport {\n DEFAULT_AUTH_SERVER,\n JWT_PAYLOAD_KNOWN_CLAIM_KEYS,\n} from \"@/constants.js\";\nimport type { AuthenticationResolver } from \"@/services/types.js\";\nimport { displayModeFromState, loginSuccessUrlFromState } from \"@/lib/oauth.js\";\nimport { decodeJwt, type JWTPayload } from \"jose\";\nimport {\n generateOauthLogoutUrl,\n getBackendEndpoints,\n resolveEndpointUrl,\n sanitizeReturnUrl,\n} from \"@/shared/lib/util.js\";\nimport { CodeVerifier } from \"@/shared/lib/types.js\";\nimport type { CookieStorage } from \"./index.js\";\nimport { loggers } from \"@/lib/logger.js\";\n\n// Generic request interface for framework-agnostic URL detection\nexport type UrlDetectionRequest = {\n url: string;\n headers: Record<string, string | string[] | undefined>;\n searchParams: {\n get(name: string): string | null;\n };\n cookies: {\n get(name: string): { value: string } | undefined;\n };\n};\n\nexport type HandleCallbackRequest = {\n headers: {\n [key: string]: string | string[] | undefined;\n referer?: string;\n origin?: string;\n \"user-agent\"?: string;\n accept?: string;\n \"sec-fetch-dest\"?: string;\n };\n url?: string;\n};\n\nexport type HandleCallbackParams = {\n code: string;\n state: string;\n req: HandleCallbackRequest;\n};\n\n// Function to omit keys from an object\nconst omitKeys = <K extends keyof T, T extends Record<string, unknown>>(\n keys: K[],\n obj: T,\n): Omit<T, K> => {\n const result = { ...obj };\n keys.forEach((key) => {\n delete result[key];\n });\n return result;\n};\n\n/**\n * Helper to detect if this is a same-domain callback request (for iframe workaround)\n */\nconst isSameDomainCallback = (req: HandleCallbackRequest): boolean => {\n if (!req.url) return false;\n return req.url.includes(\"sameDomainCallback=true\");\n};\n\n/**\n * Extract user information directly from OIDC tokens\n * @param tokens The OIDC tokens response\n * @returns The user object or null if no valid ID token\n */\nfunction getUserFromTokens<T extends UnknownObject = EmptyObject>(\n tokens: OIDCTokenResponseBody,\n): User<T> | null {\n if (!tokens.id_token) return null;\n\n const parsedToken = decodeJwt(tokens.id_token) as JWTPayload & T;\n if (!parsedToken.sub) return null;\n\n // set the user ID from the token sub\n const userWithAdditionalTokenFields = {\n ...(parsedToken as T),\n id: parsedToken.sub,\n };\n\n // Remove the token keys from the user object to stop it getting too large\n return omitKeys(\n [...JWT_PAYLOAD_KNOWN_CLAIM_KEYS, ...tokenKeys],\n userWithAdditionalTokenFields,\n ) as User<T>;\n}\n\n/**\n * CivicAuth is the main entry point for server-side authentication operations.\n * It provides a unified interface to all the authentication functions.\n */\nexport class CivicAuth {\n _authResolver: AuthenticationResolver | null = null;\n constructor(\n readonly storage: CookieStorage,\n readonly authConfig: AuthConfig,\n ) {}\n\n get oauthServer(): string {\n return this.authConfig.oauthServer || DEFAULT_AUTH_SERVER;\n }\n\n async getAuthResolver(): Promise<AuthenticationResolver> {\n if (this._authResolver) {\n loggers.server.debug(\"Reusing existing auth resolver\", this.authConfig);\n return Promise.resolve(this._authResolver);\n }\n loggers.server.debug(\"Creating new auth resolver\", this.authConfig);\n this._authResolver = await ServerAuthenticationResolver.build(\n {\n ...this.authConfig,\n oauthServer: this.oauthServer,\n },\n this.storage,\n );\n return this._authResolver;\n }\n /**\n * Gets the authenticated user with token validation\n * @returns The user object if authenticated, null otherwise\n */\n async getUser<\n T extends UnknownObject = EmptyObject,\n >(): Promise<User<T> | null> {\n const resolver = await this.getAuthResolver();\n\n try {\n // Validate the session before returning the user\n const session = await resolver.validateExistingSession();\n if (!session?.authenticated) {\n return null;\n }\n\n // If session is valid, use the shared implementation to get the user\n return getUserFromShared<T>(this.storage);\n } catch (error) {\n console.error(\"Token validation failed during getUser\", error);\n return null;\n }\n }\n\n /**\n * Gets the authentication tokens with token validation\n * @returns The tokens if authenticated, null otherwise\n */\n async getTokens(): Promise<OAuthTokens | null> {\n const resolver = await this.getAuthResolver();\n\n try {\n // Validate the session before returning the tokens\n const session = await resolver.validateExistingSession();\n\n if (!session?.authenticated) {\n return null;\n }\n\n // If session is valid, use the shared implementation to get the tokens\n const tokens = await getTokensFromShared(this.storage);\n return tokens;\n } catch (error) {\n console.error(\"❌ Token validation failed during getTokens\", error);\n return null;\n }\n }\n\n /**\n * Resolve an OAuth access code to a set of OIDC tokens\n * @param code The access code from the query parameter\n * @param state The OAuth state parameter\n * @returns OIDC tokens\n */\n async resolveOAuthAccessCode(\n code: string,\n state: string,\n ): Promise<OIDCTokenResponseBody> {\n return resolveOAuthAccessCode(code, state, this.storage, this.authConfig);\n }\n\n /**\n * Check if the user is currently logged in\n * @returns true if logged in, false otherwise\n */\n async isLoggedIn(): Promise<boolean> {\n const resolver = await this.getAuthResolver();\n const session = await resolver.validateExistingSession();\n return session?.authenticated ?? false;\n }\n\n /**\n * Build a login URL to redirect the user to\n * @param options Additional options for building the login URL\n * @returns The login URL\n */\n async buildLoginUrl(options?: {\n scopes?: string[];\n state?: string;\n nonce?: string;\n }): Promise<URL> {\n return buildLoginUrl(\n {\n ...this.authConfig,\n scopes: options?.scopes,\n state: options?.state,\n nonce: options?.nonce,\n framework: \"server\",\n sdkVersion: getVersion(),\n },\n this.storage,\n );\n }\n\n /**\n * Build a logout URL to redirect the user to\n * @param options Additional options for building the logout URL\n * @returns The logout URL\n */\n async buildLogoutRedirectUrl(options?: {\n scopes?: string[];\n state?: string;\n }): Promise<URL> {\n // Check if this is backend integration mode (loginUrl provided)\n if (this.authConfig.loginUrl) {\n // Backend integration mode: redirect to backend logout endpoint\n // This matches the vanilla client's logout logic for backend integration\n const backendUrl = new URL(this.authConfig.loginUrl).origin;\n const endpoints = getBackendEndpoints(this.authConfig.backendEndpoints);\n const backendLogoutUrl = resolveEndpointUrl(backendUrl, endpoints.logout);\n\n const logoutUrl = new URL(backendLogoutUrl);\n\n // Include logoutRedirectUrl as query parameter if configured\n if (this.authConfig.postLogoutRedirectUrl) {\n logoutUrl.searchParams.set(\n \"logoutRedirectUrl\",\n this.authConfig.postLogoutRedirectUrl,\n );\n }\n\n // Include state if provided\n if (options?.state) {\n logoutUrl.searchParams.set(\"state\", options.state);\n }\n\n return logoutUrl;\n }\n\n // Standard OAuth flow - redirect to OAuth provider's logout endpoint\n try {\n // Use the shared getTokens function directly - this bypasses session validation\n // since for logout we just need the raw ID token, not validated tokens\n const tokens = await getTokensFromShared(this.storage);\n\n if (tokens?.idToken) {\n // Ensure clientId is present for OAuth operations\n if (!this.authConfig.clientId) {\n throw new Error(\"clientId is required for OAuth logout operations\");\n }\n\n // We have access to the ID token from HTTP-only cookies\n // Build the logout URL manually using the shared utility\n\n const logoutUrl = await generateOauthLogoutUrl({\n clientId: this.authConfig.clientId,\n redirectUrl: this.authConfig.postLogoutRedirectUrl || \"/\",\n idToken: tokens.idToken,\n state: options?.state ?? Math.random().toString(36).substring(2),\n oauthServer: this.oauthServer,\n });\n\n return logoutUrl;\n }\n } catch (error) {\n // If direct token access fails, fall back to the generic function\n console.warn(\n \"❌ Could not get tokens directly from storage, falling back to generic logout method:\",\n error,\n );\n }\n\n // Fallback to the generic function for other storage types or when tokens aren't accessible\n return buildLogoutRedirectUrl(\n {\n ...this.authConfig,\n scopes: options?.scopes,\n state: options?.state,\n },\n this.storage,\n );\n }\n\n /**\n * Refresh the current set of OIDC tokens\n * @returns The refreshed tokens or null for backend flows where tokens are managed in HTTP-only cookies\n */\n async refreshTokens(): Promise<OIDCTokenResponseBody | null> {\n return refreshTokens(this.storage, this.authConfig);\n }\n\n /**\n * Clear all authentication tokens from storage\n */\n async clearTokens(): Promise<void> {\n return clearTokensUtil(this.storage);\n }\n\n /**\n * Framework-agnostic URL detection and resolution helpers\n * These methods handle proxy environments and can be used by any framework\n */\n\n /**\n * Try to URI decode a value, returning the original value on error\n */\n static tryUriDecode(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch (e) {\n console.error(\"Error decoding URI component:\", e);\n return value;\n }\n }\n\n /**\n * Get decoded query parameter from request\n */\n static getDecodedQueryParam(\n request: UrlDetectionRequest,\n paramName: string,\n ): string | null {\n const queryParam = request.searchParams.get(paramName);\n if (queryParam) {\n return CivicAuth.tryUriDecode(queryParam);\n }\n return null;\n }\n\n /**\n * Get value from cookie or query parameter (cookie takes precedence)\n */\n static getCookieOrQueryParam(\n request: UrlDetectionRequest,\n cookieName: string,\n queryName: string,\n ): string | null {\n // First check the cookie as it might have the full path with base directory\n const cookieValue = request.cookies.get(cookieName)?.value;\n if (cookieValue) {\n return CivicAuth.tryUriDecode(cookieValue);\n }\n\n // Fallback to query parameter\n return CivicAuth.getDecodedQueryParam(request, queryName);\n }\n\n /**\n * Get app URL from request (for proxy environment support)\n * Checks cookies first, then query parameters\n */\n static getAppUrl(request: UrlDetectionRequest): string | null {\n return CivicAuth.getCookieOrQueryParam(\n request,\n CodeVerifier.APP_URL,\n \"appUrl\",\n );\n }\n\n /**\n * Get login success URL with proper base URL handling\n * Extracts from state parameter or query parameters, resolves with baseUrl if provided\n */\n static getLoginSuccessUrl(\n request: UrlDetectionRequest,\n baseUrl?: string | null,\n ): string | null {\n const state = request.searchParams.get(\"state\");\n const loginSuccessUrl =\n loginSuccessUrlFromState(state) ||\n CivicAuth.getDecodedQueryParam(request, \"loginSuccessUrl\");\n\n if (!loginSuccessUrl) {\n return null;\n }\n\n return baseUrl ? new URL(loginSuccessUrl, baseUrl).href : loginSuccessUrl;\n }\n\n /**\n * Convert relative URL to absolute URL using appUrl for proxy environments\n */\n static toAbsoluteUrl(\n request: UrlDetectionRequest,\n url: string,\n appUrl?: string | null,\n ): string {\n if (url.startsWith(\"http\")) {\n return url;\n }\n\n // Use appUrl if available (for proxy environments), otherwise fall back to request origin\n const baseUrl = appUrl || new URL(request.url).origin;\n return new URL(url, baseUrl).href;\n }\n\n /**\n * Get post-logout redirect URL with proxy environment support\n */\n getPostLogoutRedirectUrl(request: UrlDetectionRequest): string {\n // Check if we have a target URL in the request (from middleware)\n const targetUrl = request.searchParams.get(\"targetUrl\");\n if (targetUrl) {\n return targetUrl;\n }\n\n const redirectTarget = this.authConfig.loginSuccessUrl ?? \"/\";\n\n // If loginSuccessUrl is absolute, use it as-is\n const isAbsoluteRedirect = /^(https?:\\/\\/|www\\.).+/i.test(redirectTarget);\n if (isAbsoluteRedirect) {\n return redirectTarget;\n }\n\n // Use appUrl from client for proxy environments\n const appUrl = CivicAuth.getAppUrl(request);\n if (appUrl) {\n return new URL(redirectTarget, appUrl).href;\n }\n\n // Fallback to request origin\n return new URL(request.url).origin;\n }\n\n /**\n * Smart callback handler that automatically detects frontend vs backend requests\n * and redirects appropriately. Use this instead of resolveOAuthAccessCode + manual redirect.\n *\n * @param params An object containing the authorization code, state, and the incoming request.\n * @param params.code The authorization code from query parameters.\n * @param params.state The OAuth state parameter.\n * @param params.req The incoming request object (e.g., from Express).\n * @param options Configuration options (frontendUrl override, apiResponse flag).\n * @returns Object with redirect information or HTML content for iframe completion.\n *\n * @example\n * ```javascript\n * app.get('/auth/callback', async (req, res) => {\n * const { code, state } = req.query;\n * // The request object 'req' is passed directly\n * const result = await req.civicAuth.handleCallback({ code, state, req });\n *\n * if (result.htmlContent) {\n * res.setHeader('Content-Type', 'text/html');\n * res.send(result.htmlContent);\n * } else if (result.redirectTo) {\n * res.redirect(result.redirectTo);\n * } else {\n * res.json({ success: true, user: result.user });\n * }\n * });\n * ```\n */\n async handleCallback(\n { code, state, req }: HandleCallbackParams,\n options?: {\n frontendUrl?: string;\n apiResponse?: boolean;\n },\n ): Promise<{\n redirectTo?: string;\n content?: string | { success: boolean; user?: User | null };\n }> {\n // Handle same-domain callback for iframe workaround\n if (isSameDomainCallback(req)) {\n try {\n // Check if user is already authenticated before attempting token exchange\n const isAlreadyLoggedIn = await this.isLoggedIn();\n let user: User | null = null;\n\n if (isAlreadyLoggedIn) {\n // User is already authenticated, get existing user data\n user = await this.getUser();\n console.log(\n \"User already authenticated in same-domain callback:\",\n !!user,\n );\n } else {\n // For same-domain callbacks, we should have access to cookies\n const tokens = await this.resolveOAuthAccessCode(code, state);\n user = getUserFromTokens(tokens);\n console.log(\n \"Completed token exchange in same-domain callback:\",\n !!user,\n );\n }\n\n // Return JSON response for same-domain callback\n const currentUrl = new URL(req.url || \"\");\n\n // Extract and sanitize loginSuccessUrl (deep link) BEFORE cleaning up search params\n // Sanitization prevents open redirect attacks via malicious URLs in the query param\n const rawLoginSuccessUrl =\n currentUrl.searchParams.get(\"loginSuccessUrl\");\n const loginSuccessUrl = rawLoginSuccessUrl\n ? sanitizeReturnUrl(rawLoginSuccessUrl, currentUrl.origin)\n : null;\n\n const newSearchParams = new URLSearchParams(currentUrl.search);\n newSearchParams.delete(\"sameDomainCallback\");\n newSearchParams.delete(\"appUrl\");\n newSearchParams.delete(\"loginSuccessUrl\");\n\n // Use preserved deep link if available and valid, otherwise fall back to cleaned callback URL\n const cleanedSearch = newSearchParams.toString();\n const fallbackUrl = `${currentUrl.pathname}${cleanedSearch ? \"?\" + cleanedSearch : \"\"}${currentUrl.hash}`;\n const redirectUrl = loginSuccessUrl || fallbackUrl;\n return {\n content: {\n success: true,\n redirectUrl,\n } as { success: boolean; redirectUrl: string },\n };\n } catch (error) {\n console.error(\"Same-domain callback failed:\", error);\n throw error;\n }\n }\n\n // Try to resolve the OAuth code and create session\n let tokens: OIDCTokenResponseBody;\n let user: User | null;\n\n try {\n tokens = await this.resolveOAuthAccessCode(code, state);\n user = getUserFromTokens(tokens);\n } catch (error) {\n // Check if this is a code verifier error and we're in iframe mode\n const isCodeVerifierError =\n error instanceof Error &&\n error.message.includes(\"Code verifier not found in storage\");\n\n if (isCodeVerifierError) {\n // First check if user is already authenticated before trying iframe workaround\n try {\n const isAlreadyLoggedIn = await this.isLoggedIn();\n\n if (isAlreadyLoggedIn) {\n // \"User already authenticated, skipping iframe workaround\",\n const user = await this.getUser();\n const loginSuccessUrlFromStateValue =\n loginSuccessUrlFromState(state);\n const frontendUrl =\n options?.frontendUrl ||\n loginSuccessUrlFromStateValue ||\n this.authConfig.loginSuccessUrl;\n\n // Check if this is an iframe context - if so, generate iframe completion HTML\n const stateDisplayMode = displayModeFromState(state, undefined);\n const isConfiguredForIframe = stateDisplayMode === \"iframe\";\n\n if (\n isConfiguredForIframe &&\n !this.authConfig.disableIframeDetection &&\n user &&\n frontendUrl\n ) {\n // Generating iframe completion HTML for already authenticated user\",\n const completionHtml = this.generateIframeCompletionHtml(\n user,\n frontendUrl,\n );\n return { content: completionHtml };\n }\n\n if (frontendUrl) {\n return { redirectTo: frontendUrl };\n } else {\n return { content: { success: true, user } };\n }\n }\n } catch (authCheckError) {\n console.warn(\n \"Failed to check authentication status:\",\n authCheckError,\n );\n // Continue with iframe workaround if auth check fails\n }\n\n const stateDisplayMode = displayModeFromState(state, undefined);\n const isConfiguredForIframe = stateDisplayMode === \"iframe\";\n\n if (isConfiguredForIframe && !this.authConfig.disableIframeDetection) {\n // Generate HTML that will trigger same-domain callback\n const loginSuccessUrlFromStateValue = loginSuccessUrlFromState(state);\n const frontendUrl =\n options?.frontendUrl ||\n loginSuccessUrlFromStateValue ||\n this.authConfig.loginSuccessUrl;\n\n const callbackUrl = req.url || \"\";\n const sameDomainHtml = this.generateSameDomainCallbackHtml(\n callbackUrl,\n frontendUrl,\n );\n return { content: sameDomainHtml };\n }\n\n // For non-iframe mode or when iframe detection is disabled, return trigger text\n return {\n content: `<html lang=\"en\"><body><span style=\"display:none\">${TOKEN_EXCHANGE_TRIGGER_TEXT}</span></body></html>`,\n };\n }\n\n // Re-throw other errors\n throw error;\n }\n\n // Extract loginSuccessUrl from state if present\n const loginSuccessUrlFromStateValue = loginSuccessUrlFromState(state);\n\n // Priority: options.frontendUrl > loginSuccessUrl from state > config loginSuccessUrl\n const frontendUrl =\n options?.frontendUrl ||\n loginSuccessUrlFromStateValue ||\n this.authConfig.loginSuccessUrl;\n\n // Priority 1: Check state for display mode configuration\n const stateDisplayMode = displayModeFromState(state, undefined);\n const isConfiguredForIframe = stateDisplayMode === \"iframe\";\n\n // Determine if this should be treated as an iframe request\n // Configuration (from state) takes precedence over auto-detection\n const shouldTreatAsIframe =\n isConfiguredForIframe && !this.authConfig.disableIframeDetection;\n\n const isTopLevelRedirect = req.headers[\"sec-fetch-dest\"] === \"document\";\n const isIframeRequest = req.headers[\"sec-fetch-dest\"] === \"iframe\";\n const isApiRequest =\n options?.apiResponse || req.headers.accept?.includes(\"application/json\");\n\n // Detect Safari or other browsers where iframe postMessage may fail due to cross-origin restrictions\n //TODO: Find a better way to detect this\n const userAgent = req.headers[\"user-agent\"] || \"\";\n const isSafari =\n userAgent.includes(\"Safari\") && !userAgent.includes(\"Chrome\");\n const isLikelyCrossOriginIframe =\n isSafari ||\n (userAgent.includes(\"WebKit\") && !userAgent.includes(\"Chrome\"));\n\n // Case 1: The request should be treated as iframe. Return HTML content.\n // Unless iframe detection is disabled via configuration OR we detect cross-origin issues\n if (\n shouldTreatAsIframe &&\n user &&\n frontendUrl &&\n !isLikelyCrossOriginIframe\n ) {\n const completionHtml = this.generateIframeCompletionHtml(\n user,\n frontendUrl,\n );\n return { content: completionHtml };\n }\n\n // Case 1b: Safari/cross-origin iframe case - redirect instead of HTML\n if (\n shouldTreatAsIframe &&\n user &&\n frontendUrl &&\n isLikelyCrossOriginIframe\n ) {\n return { redirectTo: frontendUrl };\n }\n\n // Case 2: The request is a top-level navigation. Return redirect URL.\n if (isTopLevelRedirect && frontendUrl) {\n return { redirectTo: frontendUrl };\n }\n\n // Case 2a: The request is from an iframe (detected by sec-fetch-dest)\n // Even if not configured for iframe in state, we should still generate iframe completion HTML\n if (isIframeRequest && user && frontendUrl) {\n if (isLikelyCrossOriginIframe) {\n console.log(\n \"Iframe request detected but cross-origin issues likely - redirecting\",\n );\n return { redirectTo: frontendUrl };\n } else {\n console.log(\n \"Generating iframe completion HTML for iframe request (detected by sec-fetch-dest)\",\n );\n const completionHtml = this.generateIframeCompletionHtml(\n user,\n frontendUrl,\n );\n return { content: completionHtml };\n }\n }\n\n // Case 3: The request is an API call. Return JSON content.\n if (isApiRequest) {\n return {\n content: {\n success: true,\n user,\n },\n };\n }\n\n // Fallback for older browsers or other contexts: if a frontend URL is configured,\n // assume a redirect to it.\n if (frontendUrl) {\n return { redirectTo: frontendUrl };\n }\n\n // Server-side fallback: if no frontend URL is configured but we have a postLogoutRedirectUrl,\n // redirect there instead of returning JSON content\n if (this.authConfig.postLogoutRedirectUrl) {\n return { redirectTo: this.authConfig.postLogoutRedirectUrl };\n }\n\n // Absolute fallback: return success as JSON content if no other conditions are met.\n // This could happen if no loginSuccessUrl or postLogoutRedirectUrl is configured.\n return {\n content: {\n success: true,\n user,\n },\n };\n }\n\n /**\n * Generate HTML content for iframe completion that sends postMessage to parent\n */\n private generateIframeCompletionHtml(\n user: User,\n frontendUrl?: string,\n ): string {\n const escapedUser = JSON.stringify(user).replace(/'/g, \"\\\\'\");\n const clientId = this.authConfig.clientId;\n\n // Determine fallback redirect URL\n const fallbackUrl =\n frontendUrl ||\n this.authConfig.redirectUrl ||\n this.authConfig.postLogoutRedirectUrl ||\n \"/\";\n\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <title>Authentication Complete</title>\n <meta charset=\"utf-8\">\n </head>\n <body> \n <!-- Success signal for SignalObserver -->\n <div id=\"civic-auth-success-signal\" style=\"display: none;\" data-user-info='${escapedUser}'>\n ${TOKEN_EXCHANGE_SUCCESS_TEXT}\n </div>\n \n <script>\n // Send postMessage to parent to resolve authentication promise\n if (window.parent && window.parent !== window) {\n try {\n window.parent.postMessage({\n type: 'auth_success',\n detail: 'Authentication successful',\n data: {\n user: ${escapedUser},\n redirectUrl: ${JSON.stringify(fallbackUrl)}\n }\n }, '*');\n } catch (error) {\n console.error('❌ Failed to send postMessage:', error);\n }\n\n // Also send civicloginApp format message for compatibility\n try {\n window.parent.postMessage({\n source: 'civicloginApp',\n type: 'auth_success',\n clientId: ${JSON.stringify(clientId)},\n data: {\n user: ${escapedUser},\n redirectUrl: ${JSON.stringify(fallbackUrl)}\n }\n }, '*');\n } catch (error) {\n console.error('❌ Failed to send civicloginApp message:', error);\n }\n } else {\n console.log('❌ Not in iframe context or no parent window');\n }\n \n // Fallback redirect after 500ms delay to handle cases where:\n // 1. postMessage fails or parent doesn't respond\n // 2. Not in iframe context\n // 3. Any other edge cases where the user gets stuck\n setTimeout(function() {\n var redirectTarget = ${JSON.stringify(fallbackUrl)};\n console.log('🔄 Executing fallback redirect to:', redirectTarget);\n window.location.href = redirectTarget;\n }, 500);\n </script>\n </body>\n </html>\n `;\n }\n\n /**\n * Generate HTML response that triggers same-domain callback for iframe workaround\n */\n private generateSameDomainCallbackHtml = (\n callbackUrl: string,\n frontendUrl?: string,\n ): string => {\n const loginSuccessSegment = frontendUrl\n ? `&loginSuccessUrl=${encodeURIComponent(frontendUrl)}`\n : \"\";\n\n return `<html lang=\"en\">\n <body>\n <span style=\"display:none\">\n <script>\n window.onload = function () {\n // Get the complete URL including origin and path\n // This ensures we capture any base path like /directory\n const appUrl = window.location.href.substring(\n 0,\n window.location.href.indexOf(\"/api/auth\")\n );\n fetch('${callbackUrl}&sameDomainCallback=true&appUrl=' + encodeURIComponent(appUrl) + '${loginSuccessSegment}').then((response) => {\n response.json().then((jsonResponse) => {\n // For login: Redirect back to the callback route, so Case 2 in handleTokenExchangeComplete will be triggered\n // For logout: Redirect to the postLogoutRedirectUrl\n if(jsonResponse.redirectUrl) {\n window.location.href = jsonResponse.redirectUrl;\n }\n });\n });\n };\n </script>\n </span>\n </body>\n</html>\n`;\n };\n}\n"]}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type OAuthTokens,\n type User,\n type EmptyObject,\n type UnknownObject,\n type OIDCTokenResponseBody,\n tokenKeys,\n} from \"@/types.js\";\nimport type { AuthConfig } from \"@/server/config.js\";\nimport {\n getUser as getUserFromShared,\n getTokens as getTokensFromShared,\n} from \"@/shared/lib/session.js\";\nimport { clearTokens as clearTokensUtil } from \"@/shared/lib/util.js\";\nimport { resolveOAuthAccessCode } from \"@/server/login.js\";\nimport { buildLoginUrl } from \"@/server/login.js\";\nimport { buildLogoutRedirectUrl } from \"@/server/logout.js\";\nimport {\n TOKEN_EXCHANGE_SUCCESS_TEXT,\n TOKEN_EXCHANGE_TRIGGER_TEXT,\n} from \"@/constants.js\";\nimport { refreshTokens } from \"@/server/refresh.js\";\nimport { getVersion } from \"@/shared/index.js\";\nimport { ServerAuthenticationResolver } from \"@/server/ServerAuthenticationResolver.js\";\nimport {\n DEFAULT_AUTH_SERVER,\n JWT_PAYLOAD_KNOWN_CLAIM_KEYS,\n} from \"@/constants.js\";\nimport type { AuthenticationResolver } from \"@/services/types.js\";\nimport { displayModeFromState, loginSuccessUrlFromState } from \"@/lib/oauth.js\";\nimport { decodeJwt, type JWTPayload } from \"jose\";\nimport {\n generateOauthLogoutUrl,\n getBackendEndpoints,\n resolveEndpointUrl,\n sanitizeReturnUrl,\n} from \"@/shared/lib/util.js\";\nimport { CodeVerifier } from \"@/shared/lib/types.js\";\nimport type { CookieStorage } from \"./index.js\";\nimport { loggers } from \"@/lib/logger.js\";\n\n// Generic request interface for framework-agnostic URL detection\nexport type UrlDetectionRequest = {\n url: string;\n headers: Record<string, string | string[] | undefined>;\n searchParams: {\n get(name: string): string | null;\n };\n cookies: {\n get(name: string): { value: string } | undefined;\n };\n};\n\nexport type HandleCallbackRequest = {\n headers: {\n [key: string]: string | string[] | undefined;\n referer?: string;\n origin?: string;\n \"user-agent\"?: string;\n accept?: string;\n \"sec-fetch-dest\"?: string;\n };\n url?: string;\n};\n\nexport type HandleCallbackParams = {\n code: string;\n state: string;\n req: HandleCallbackRequest;\n};\n\n// Function to omit keys from an object\nconst omitKeys = <K extends keyof T, T extends Record<string, unknown>>(\n keys: K[],\n obj: T,\n): Omit<T, K> => {\n const result = { ...obj };\n keys.forEach((key) => {\n delete result[key];\n });\n return result;\n};\n\n/**\n * Helper to detect if this is a same-domain callback request (for iframe workaround)\n */\nconst isSameDomainCallback = (req: HandleCallbackRequest): boolean => {\n if (!req.url) return false;\n return req.url.includes(\"sameDomainCallback=true\");\n};\n\n/**\n * Extract user information directly from OIDC tokens\n * @param tokens The OIDC tokens response\n * @returns The user object or null if no valid ID token\n */\nfunction getUserFromTokens<T extends UnknownObject = EmptyObject>(\n tokens: OIDCTokenResponseBody,\n): User<T> | null {\n if (!tokens.id_token) return null;\n\n const parsedToken = decodeJwt(tokens.id_token) as JWTPayload & T;\n if (!parsedToken.sub) return null;\n\n // set the user ID from the token sub\n const userWithAdditionalTokenFields = {\n ...(parsedToken as T),\n id: parsedToken.sub,\n };\n\n // Remove the token keys from the user object to stop it getting too large\n return omitKeys(\n [...JWT_PAYLOAD_KNOWN_CLAIM_KEYS, ...tokenKeys],\n userWithAdditionalTokenFields,\n ) as User<T>;\n}\n\n/**\n * CivicAuth is the main entry point for server-side authentication operations.\n * It provides a unified interface to all the authentication functions.\n */\nexport class CivicAuth {\n _authResolver: AuthenticationResolver | null = null;\n constructor(\n readonly storage: CookieStorage,\n readonly authConfig: AuthConfig,\n ) {}\n\n get oauthServer(): string {\n return this.authConfig.oauthServer || DEFAULT_AUTH_SERVER;\n }\n\n async getAuthResolver(): Promise<AuthenticationResolver> {\n if (this._authResolver) {\n loggers.server.debug(\"Reusing existing auth resolver\", this.authConfig);\n return Promise.resolve(this._authResolver);\n }\n loggers.server.debug(\"Creating new auth resolver\", this.authConfig);\n this._authResolver = await ServerAuthenticationResolver.build(\n {\n ...this.authConfig,\n oauthServer: this.oauthServer,\n },\n this.storage,\n );\n return this._authResolver;\n }\n /**\n * Gets the authenticated user with token validation\n * @returns The user object if authenticated, null otherwise\n */\n async getUser<\n T extends UnknownObject = EmptyObject,\n >(): Promise<User<T> | null> {\n const resolver = await this.getAuthResolver();\n\n try {\n // Validate the session before returning the user\n const session = await resolver.validateExistingSession();\n if (!session?.authenticated) {\n return null;\n }\n\n // If session is valid, use the shared implementation to get the user\n return getUserFromShared<T>(this.storage);\n } catch (error) {\n console.error(\"Token validation failed during getUser\", error);\n return null;\n }\n }\n\n /**\n * Gets the authentication tokens with token validation\n * @returns The tokens if authenticated, null otherwise\n */\n async getTokens(): Promise<OAuthTokens | null> {\n const resolver = await this.getAuthResolver();\n\n try {\n // Validate the session before returning the tokens\n const session = await resolver.validateExistingSession();\n\n if (!session?.authenticated) {\n return null;\n }\n\n // If session is valid, use the shared implementation to get the tokens\n const tokens = await getTokensFromShared(this.storage);\n return tokens;\n } catch (error) {\n console.error(\"❌ Token validation failed during getTokens\", error);\n return null;\n }\n }\n\n /**\n * Resolve an OAuth access code to a set of OIDC tokens\n * @param code The access code from the query parameter\n * @param state The OAuth state parameter\n * @returns OIDC tokens\n */\n async resolveOAuthAccessCode(\n code: string,\n state: string,\n ): Promise<OIDCTokenResponseBody> {\n return resolveOAuthAccessCode(code, state, this.storage, this.authConfig);\n }\n\n /**\n * Check if the user is currently logged in\n * @returns true if logged in, false otherwise\n */\n async isLoggedIn(): Promise<boolean> {\n const resolver = await this.getAuthResolver();\n const session = await resolver.validateExistingSession();\n return session?.authenticated ?? false;\n }\n\n /**\n * Build a login URL to redirect the user to\n * @param options Additional options for building the login URL\n * @returns The login URL\n */\n async buildLoginUrl(options?: {\n scopes?: string[];\n state?: string;\n nonce?: string;\n }): Promise<URL> {\n return buildLoginUrl(\n {\n ...this.authConfig,\n scopes: options?.scopes,\n state: options?.state,\n nonce: options?.nonce,\n framework: \"server\",\n sdkVersion: getVersion(),\n },\n this.storage,\n );\n }\n\n /**\n * Build a logout URL to redirect the user to\n * @param options Additional options for building the logout URL\n * @returns The logout URL\n */\n async buildLogoutRedirectUrl(options?: {\n scopes?: string[];\n state?: string;\n }): Promise<URL> {\n // Check if this is backend integration mode (loginUrl provided)\n if (this.authConfig.loginUrl) {\n // Backend integration mode: redirect to backend logout endpoint\n // This matches the vanilla client's logout logic for backend integration\n const backendUrl = new URL(this.authConfig.loginUrl).origin;\n const endpoints = getBackendEndpoints(this.authConfig.backendEndpoints);\n const backendLogoutUrl = resolveEndpointUrl(backendUrl, endpoints.logout);\n\n const logoutUrl = new URL(backendLogoutUrl);\n\n // Include logoutRedirectUrl as query parameter if configured\n if (this.authConfig.postLogoutRedirectUrl) {\n logoutUrl.searchParams.set(\n \"logoutRedirectUrl\",\n this.authConfig.postLogoutRedirectUrl,\n );\n }\n\n // Include state if provided\n if (options?.state) {\n logoutUrl.searchParams.set(\"state\", options.state);\n }\n\n return logoutUrl;\n }\n\n // Standard OAuth flow - redirect to OAuth provider's logout endpoint\n try {\n // Use the shared getTokens function directly - this bypasses session validation\n // since for logout we just need the raw ID token, not validated tokens\n const tokens = await getTokensFromShared(this.storage);\n\n if (tokens?.idToken) {\n // Ensure clientId is present for OAuth operations\n if (!this.authConfig.clientId) {\n throw new Error(\"clientId is required for OAuth logout operations\");\n }\n\n // We have access to the ID token from HTTP-only cookies\n // Build the logout URL manually using the shared utility\n\n const logoutUrl = await generateOauthLogoutUrl({\n clientId: this.authConfig.clientId,\n redirectUrl: this.authConfig.postLogoutRedirectUrl || \"/\",\n idToken: tokens.idToken,\n state: options?.state ?? Math.random().toString(36).substring(2),\n oauthServer: this.oauthServer,\n });\n\n return logoutUrl;\n }\n } catch (error) {\n // If direct token access fails, fall back to the generic function\n console.warn(\n \"❌ Could not get tokens directly from storage, falling back to generic logout method:\",\n error,\n );\n }\n\n // Fallback to the generic function for other storage types or when tokens aren't accessible\n return buildLogoutRedirectUrl(\n {\n ...this.authConfig,\n scopes: options?.scopes,\n state: options?.state,\n },\n this.storage,\n );\n }\n\n /**\n * Refresh the current set of OIDC tokens\n * @returns The refreshed tokens or null for backend flows where tokens are managed in HTTP-only cookies\n */\n async refreshTokens(): Promise<OIDCTokenResponseBody | null> {\n return refreshTokens(this.storage, this.authConfig);\n }\n\n /**\n * Clear all authentication tokens from storage\n */\n async clearTokens(): Promise<void> {\n return clearTokensUtil(this.storage);\n }\n\n /**\n * Framework-agnostic URL detection and resolution helpers\n * These methods handle proxy environments and can be used by any framework\n */\n\n /**\n * Try to URI decode a value, returning the original value on error\n */\n static tryUriDecode(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch (e) {\n console.error(\"Error decoding URI component:\", e);\n return value;\n }\n }\n\n /**\n * Get decoded query parameter from request\n */\n static getDecodedQueryParam(\n request: UrlDetectionRequest,\n paramName: string,\n ): string | null {\n const queryParam = request.searchParams.get(paramName);\n if (queryParam) {\n return CivicAuth.tryUriDecode(queryParam);\n }\n return null;\n }\n\n /**\n * Get value from cookie or query parameter (cookie takes precedence)\n */\n static getCookieOrQueryParam(\n request: UrlDetectionRequest,\n cookieName: string,\n queryName: string,\n ): string | null {\n // First check the cookie as it might have the full path with base directory\n const cookieValue = request.cookies.get(cookieName)?.value;\n if (cookieValue) {\n return CivicAuth.tryUriDecode(cookieValue);\n }\n\n // Fallback to query parameter\n return CivicAuth.getDecodedQueryParam(request, queryName);\n }\n\n /**\n * Get app URL from request (for proxy environment support)\n * Checks cookies first, then query parameters\n */\n static getAppUrl(request: UrlDetectionRequest): string | null {\n return CivicAuth.getCookieOrQueryParam(\n request,\n CodeVerifier.APP_URL,\n \"appUrl\",\n );\n }\n\n /**\n * Get login success URL with proper base URL handling\n * Extracts from state parameter or query parameters, resolves with baseUrl if provided\n */\n static getLoginSuccessUrl(\n request: UrlDetectionRequest,\n baseUrl?: string | null,\n ): string | null {\n const state = request.searchParams.get(\"state\");\n const loginSuccessUrl =\n loginSuccessUrlFromState(state) ||\n CivicAuth.getDecodedQueryParam(request, \"loginSuccessUrl\");\n\n if (!loginSuccessUrl) {\n return null;\n }\n\n return baseUrl ? new URL(loginSuccessUrl, baseUrl).href : loginSuccessUrl;\n }\n\n /**\n * Convert relative URL to absolute URL using appUrl for proxy environments\n */\n static toAbsoluteUrl(\n request: UrlDetectionRequest,\n url: string,\n appUrl?: string | null,\n ): string {\n if (url.startsWith(\"http\")) {\n return url;\n }\n\n // Use appUrl if available (for proxy environments), otherwise fall back to request origin\n const baseUrl = appUrl || new URL(request.url).origin;\n return new URL(url, baseUrl).href;\n }\n\n /**\n * Get post-logout redirect URL with proxy environment support\n */\n getPostLogoutRedirectUrl(request: UrlDetectionRequest): string {\n // Check if we have a target URL in the request (from middleware)\n const targetUrl = request.searchParams.get(\"targetUrl\");\n if (targetUrl) {\n return targetUrl;\n }\n\n const redirectTarget = this.authConfig.loginSuccessUrl ?? \"/\";\n\n // If loginSuccessUrl is absolute, use it as-is\n const isAbsoluteRedirect = /^(https?:\\/\\/|www\\.).+/i.test(redirectTarget);\n if (isAbsoluteRedirect) {\n return redirectTarget;\n }\n\n // Use appUrl from client for proxy environments\n const appUrl = CivicAuth.getAppUrl(request);\n if (appUrl) {\n return new URL(redirectTarget, appUrl).href;\n }\n\n // Fallback to request origin\n return new URL(request.url).origin;\n }\n\n /**\n * Smart callback handler that automatically detects frontend vs backend requests\n * and redirects appropriately. Use this instead of resolveOAuthAccessCode + manual redirect.\n *\n * @param params An object containing the authorization code, state, and the incoming request.\n * @param params.code The authorization code from query parameters.\n * @param params.state The OAuth state parameter.\n * @param params.req The incoming request object (e.g., from Express).\n * @param options Configuration options (frontendUrl override, apiResponse flag).\n * @returns Object with redirect information or HTML content for iframe completion.\n *\n * @example\n * ```javascript\n * app.get('/auth/callback', async (req, res) => {\n * const { code, state } = req.query;\n * // The request object 'req' is passed directly\n * const result = await req.civicAuth.handleCallback({ code, state, req });\n *\n * if (result.htmlContent) {\n * res.setHeader('Content-Type', 'text/html');\n * res.send(result.htmlContent);\n * } else if (result.redirectTo) {\n * res.redirect(result.redirectTo);\n * } else {\n * res.json({ success: true, user: result.user });\n * }\n * });\n * ```\n */\n async handleCallback(\n { code, state, req }: HandleCallbackParams,\n options?: {\n frontendUrl?: string;\n apiResponse?: boolean;\n },\n ): Promise<{\n redirectTo?: string;\n content?: string | { success: boolean; user?: User | null };\n }> {\n // Handle same-domain callback for iframe workaround\n if (isSameDomainCallback(req)) {\n try {\n // Check if user is already authenticated before attempting token exchange\n const isAlreadyLoggedIn = await this.isLoggedIn();\n let user: User | null = null;\n\n if (isAlreadyLoggedIn) {\n // User is already authenticated, get existing user data\n user = await this.getUser();\n console.log(\n \"User already authenticated in same-domain callback:\",\n !!user,\n );\n } else {\n // For same-domain callbacks, we should have access to cookies\n const tokens = await this.resolveOAuthAccessCode(code, state);\n user = getUserFromTokens(tokens);\n console.log(\n \"Completed token exchange in same-domain callback:\",\n !!user,\n );\n }\n\n // Return JSON response for same-domain callback\n const currentUrl = new URL(req.url || \"\");\n\n // Extract and sanitize loginSuccessUrl (deep link) BEFORE cleaning up search params\n // Sanitization prevents open redirect attacks via malicious URLs in the query param\n const rawLoginSuccessUrl =\n currentUrl.searchParams.get(\"loginSuccessUrl\");\n const loginSuccessUrl = rawLoginSuccessUrl\n ? sanitizeReturnUrl(rawLoginSuccessUrl, currentUrl.origin)\n : null;\n\n const newSearchParams = new URLSearchParams(currentUrl.search);\n newSearchParams.delete(\"sameDomainCallback\");\n newSearchParams.delete(\"appUrl\");\n newSearchParams.delete(\"loginSuccessUrl\");\n\n // Use preserved deep link if available and valid, otherwise fall back to loginSuccessUrl or \"/\"\n // Note: Do NOT fall back to currentUrl.pathname as that's the callback URL, which would cause a loop\n const redirectUrl =\n loginSuccessUrl || this.authConfig.loginSuccessUrl || \"/\";\n return {\n content: {\n success: true,\n redirectUrl,\n } as { success: boolean; redirectUrl: string },\n };\n } catch (error) {\n console.error(\"Same-domain callback failed:\", error);\n throw error;\n }\n }\n\n // Try to resolve the OAuth code and create session\n let tokens: OIDCTokenResponseBody;\n let user: User | null;\n\n try {\n tokens = await this.resolveOAuthAccessCode(code, state);\n user = getUserFromTokens(tokens);\n } catch (error) {\n // Check if this is a code verifier error and we're in iframe mode\n const isCodeVerifierError =\n error instanceof Error &&\n error.message.includes(\"Code verifier not found in storage\");\n\n if (isCodeVerifierError) {\n // First check if user is already authenticated before trying iframe workaround\n try {\n const isAlreadyLoggedIn = await this.isLoggedIn();\n\n if (isAlreadyLoggedIn) {\n // \"User already authenticated, skipping iframe workaround\",\n const user = await this.getUser();\n const loginSuccessUrlFromStateValue =\n loginSuccessUrlFromState(state);\n const frontendUrl =\n options?.frontendUrl ||\n loginSuccessUrlFromStateValue ||\n this.authConfig.loginSuccessUrl;\n\n // Check if this is an iframe context - if so, generate iframe completion HTML\n const stateDisplayMode = displayModeFromState(state, undefined);\n const isConfiguredForIframe = stateDisplayMode === \"iframe\";\n\n if (\n isConfiguredForIframe &&\n !this.authConfig.disableIframeDetection &&\n user &&\n frontendUrl\n ) {\n // Generating iframe completion HTML for already authenticated user\",\n const completionHtml = this.generateIframeCompletionHtml(\n user,\n frontendUrl,\n );\n return { content: completionHtml };\n }\n\n if (frontendUrl) {\n return { redirectTo: frontendUrl };\n } else {\n // Return JSON when no redirect destination is available.\n // This is intentional for API-style callbacks and cross-origin scenarios\n // where cookies aren't accessible to read config. The JSON response allows\n // clients (e.g., vanilla JS SDK) to handle the success case programmatically.\n return { content: { success: true, user } };\n }\n }\n } catch (authCheckError) {\n console.warn(\n \"Failed to check authentication status:\",\n authCheckError,\n );\n // Continue with iframe workaround if auth check fails\n }\n\n const stateDisplayMode = displayModeFromState(state, undefined);\n const isConfiguredForIframe = stateDisplayMode === \"iframe\";\n\n if (isConfiguredForIframe && !this.authConfig.disableIframeDetection) {\n // Generate HTML that will trigger same-domain callback\n const loginSuccessUrlFromStateValue = loginSuccessUrlFromState(state);\n const frontendUrl =\n options?.frontendUrl ||\n loginSuccessUrlFromStateValue ||\n this.authConfig.loginSuccessUrl;\n\n const callbackUrl = req.url || \"\";\n const sameDomainHtml = this.generateSameDomainCallbackHtml(\n callbackUrl,\n frontendUrl,\n );\n return { content: sameDomainHtml };\n }\n\n // For non-iframe mode or when iframe detection is disabled, return trigger text\n return {\n content: `<html lang=\"en\"><body><span style=\"display:none\">${TOKEN_EXCHANGE_TRIGGER_TEXT}</span></body></html>`,\n };\n }\n\n // Re-throw other errors\n throw error;\n }\n\n // Extract loginSuccessUrl from state if present\n const loginSuccessUrlFromStateValue = loginSuccessUrlFromState(state);\n\n // Priority: options.frontendUrl > loginSuccessUrl from state > config loginSuccessUrl\n const frontendUrl =\n options?.frontendUrl ||\n loginSuccessUrlFromStateValue ||\n this.authConfig.loginSuccessUrl;\n\n // Priority 1: Check state for display mode configuration\n const stateDisplayMode = displayModeFromState(state, undefined);\n const isConfiguredForIframe = stateDisplayMode === \"iframe\";\n\n // Determine if this should be treated as an iframe request\n // Configuration (from state) takes precedence over auto-detection\n const shouldTreatAsIframe =\n isConfiguredForIframe && !this.authConfig.disableIframeDetection;\n\n const isTopLevelRedirect = req.headers[\"sec-fetch-dest\"] === \"document\";\n const isIframeRequest = req.headers[\"sec-fetch-dest\"] === \"iframe\";\n const isApiRequest =\n options?.apiResponse || req.headers.accept?.includes(\"application/json\");\n\n // Detect Safari or other browsers where iframe postMessage may fail due to cross-origin restrictions\n //TODO: Find a better way to detect this\n const userAgent = req.headers[\"user-agent\"] || \"\";\n const isSafari =\n userAgent.includes(\"Safari\") && !userAgent.includes(\"Chrome\");\n const isLikelyCrossOriginIframe =\n isSafari ||\n (userAgent.includes(\"WebKit\") && !userAgent.includes(\"Chrome\"));\n\n // Case 1: The request should be treated as iframe. Return HTML content.\n // Unless iframe detection is disabled via configuration OR we detect cross-origin issues\n if (\n shouldTreatAsIframe &&\n user &&\n frontendUrl &&\n !isLikelyCrossOriginIframe\n ) {\n const completionHtml = this.generateIframeCompletionHtml(\n user,\n frontendUrl,\n );\n return { content: completionHtml };\n }\n\n // Case 1b: Safari/cross-origin iframe case - redirect instead of HTML\n if (\n shouldTreatAsIframe &&\n user &&\n frontendUrl &&\n isLikelyCrossOriginIframe\n ) {\n return { redirectTo: frontendUrl };\n }\n\n // Case 2: The request is a top-level navigation. Return redirect URL.\n if (isTopLevelRedirect && frontendUrl) {\n return { redirectTo: frontendUrl };\n }\n\n // Case 2a: The request is from an iframe (detected by sec-fetch-dest)\n // Even if not configured for iframe in state, we should still generate iframe completion HTML\n if (isIframeRequest && user && frontendUrl) {\n if (isLikelyCrossOriginIframe) {\n console.log(\n \"Iframe request detected but cross-origin issues likely - redirecting\",\n );\n return { redirectTo: frontendUrl };\n } else {\n console.log(\n \"Generating iframe completion HTML for iframe request (detected by sec-fetch-dest)\",\n );\n const completionHtml = this.generateIframeCompletionHtml(\n user,\n frontendUrl,\n );\n return { content: completionHtml };\n }\n }\n\n // Case 3: The request is an API call. Return JSON content.\n if (isApiRequest) {\n return {\n content: {\n success: true,\n user,\n },\n };\n }\n\n // Fallback for older browsers or other contexts: if a frontend URL is configured,\n // assume a redirect to it.\n if (frontendUrl) {\n return { redirectTo: frontendUrl };\n }\n\n // Absolute fallback: redirect to loginSuccessUrl or \"/\"\n // Never return JSON for browser navigation - that would display raw JSON to the user\n return { redirectTo: this.authConfig.loginSuccessUrl || \"/\" };\n }\n\n /**\n * Generate HTML content for iframe completion that sends postMessage to parent\n */\n private generateIframeCompletionHtml(\n user: User,\n frontendUrl?: string,\n ): string {\n const escapedUser = JSON.stringify(user).replace(/'/g, \"\\\\'\");\n const clientId = this.authConfig.clientId;\n\n // Determine fallback redirect URL\n // Note: redirectUrl is the OAuth callback URL - it should NEVER be used as a post-login\n // destination as it would cause an infinite redirect loop in iframe mode.\n // postLogoutRedirectUrl is semantically incorrect for login success scenarios.\n const fallbackUrl = frontendUrl || this.authConfig.loginSuccessUrl || \"/\";\n\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <title>Authentication Complete</title>\n <meta charset=\"utf-8\">\n </head>\n <body> \n <!-- Success signal for SignalObserver -->\n <div id=\"civic-auth-success-signal\" style=\"display: none;\" data-user-info='${escapedUser}'>\n ${TOKEN_EXCHANGE_SUCCESS_TEXT}\n </div>\n \n <script>\n // Send postMessage to parent to resolve authentication promise\n if (window.parent && window.parent !== window) {\n try {\n window.parent.postMessage({\n type: 'auth_success',\n detail: 'Authentication successful',\n data: {\n user: ${escapedUser},\n redirectUrl: ${JSON.stringify(fallbackUrl)}\n }\n }, '*');\n } catch (error) {\n console.error('❌ Failed to send postMessage:', error);\n }\n\n // Also send civicloginApp format message for compatibility\n try {\n window.parent.postMessage({\n source: 'civicloginApp',\n type: 'auth_success',\n clientId: ${JSON.stringify(clientId)},\n data: {\n user: ${escapedUser},\n redirectUrl: ${JSON.stringify(fallbackUrl)}\n }\n }, '*');\n } catch (error) {\n console.error('❌ Failed to send civicloginApp message:', error);\n }\n } else {\n console.log('❌ Not in iframe context or no parent window');\n }\n \n // Fallback redirect after 500ms delay to handle cases where:\n // 1. postMessage fails or parent doesn't respond\n // 2. Not in iframe context\n // 3. Any other edge cases where the user gets stuck\n setTimeout(function() {\n var redirectTarget = ${JSON.stringify(fallbackUrl)};\n console.log('🔄 Executing fallback redirect to:', redirectTarget);\n window.location.href = redirectTarget;\n }, 500);\n </script>\n </body>\n </html>\n `;\n }\n\n /**\n * Generate HTML response that triggers same-domain callback for iframe workaround\n */\n private generateSameDomainCallbackHtml = (\n callbackUrl: string,\n frontendUrl?: string,\n ): string => {\n const loginSuccessSegment = frontendUrl\n ? `&loginSuccessUrl=${encodeURIComponent(frontendUrl)}`\n : \"\";\n\n return `<html lang=\"en\">\n <body>\n <span style=\"display:none\">\n <script>\n window.onload = function () {\n // Get the complete URL including origin and path\n // This ensures we capture any base path like /directory\n const appUrl = window.location.href.substring(\n 0,\n window.location.href.indexOf(\"/api/auth\")\n );\n fetch('${callbackUrl}&sameDomainCallback=true&appUrl=' + encodeURIComponent(appUrl) + '${loginSuccessSegment}').then((response) => {\n response.json().then((jsonResponse) => {\n // For login: Redirect back to the callback route, so Case 2 in handleTokenExchangeComplete will be triggered\n // For logout: Redirect to the postLogoutRedirectUrl\n if(jsonResponse.redirectUrl) {\n window.location.href = jsonResponse.redirectUrl;\n }\n });\n });\n };\n </script>\n </span>\n </body>\n</html>\n`;\n };\n}\n"]}
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "@civic/auth:0.13.0-beta.1";
1
+ export declare const VERSION = "@civic/auth:0.13.0-beta.3";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // This is an auto-generated file. Do not edit.
2
- export const VERSION = "@civic/auth:0.13.0-beta.1";
2
+ export const VERSION = "@civic/auth:0.13.0-beta.3";
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAE/C,MAAM,CAAC,MAAM,OAAO,GAAG,2BAA2B,CAAC","sourcesContent":["// This is an auto-generated file. Do not edit.\n\nexport const VERSION = \"@civic/auth:0.13.0-beta.1\";\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAE/C,MAAM,CAAC,MAAM,OAAO,GAAG,2BAA2B,CAAC","sourcesContent":["// This is an auto-generated file. Do not edit.\n\nexport const VERSION = \"@civic/auth:0.13.0-beta.3\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civic/auth",
3
- "version": "0.13.0-beta.1",
3
+ "version": "0.13.0-beta.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -89,8 +89,8 @@
89
89
  "vite": "^5",
90
90
  "vite-plugin-dts": "^4.2.3",
91
91
  "vitest": "^2.1.8",
92
- "@repo/eslint-config": "0.0.0",
93
- "@repo/typescript-config": "0.0.0"
92
+ "@repo/typescript-config": "0.0.0",
93
+ "@repo/eslint-config": "0.0.0"
94
94
  },
95
95
  "peerDependencies": {
96
96
  "express": ">=4.0.0",