| import { NextResponse } from "next/server"; |
| import { cookies } from "next/headers"; |
| import { |
| exchangeOidcCode, |
| fetchOidcDiscovery, |
| getOidcRuntimeConfig, |
| getPublicOrigin, |
| pickOidcDisplayName, |
| pickOidcEmail, |
| verifyOidcIdToken, |
| } from "@/lib/auth/oidc"; |
| import { setDashboardAuthCookie } from "@/lib/auth/dashboardSession"; |
|
|
| function clearOidcCookies(cookieStore) { |
| cookieStore.delete("oidc_state"); |
| cookieStore.delete("oidc_nonce"); |
| cookieStore.delete("oidc_code_verifier"); |
| } |
|
|
| export async function GET(request) { |
| const url = new URL(request.url); |
| const error = url.searchParams.get("error"); |
| if (error) { |
| return NextResponse.redirect(new URL(`/login?error=${encodeURIComponent(error)}`, getPublicOrigin(request))); |
| } |
|
|
| const code = url.searchParams.get("code"); |
| const state = url.searchParams.get("state"); |
| if (!code || !state) { |
| return NextResponse.redirect(new URL("/login?error=oidc_missing_code", getPublicOrigin(request))); |
| } |
|
|
| const cookieStore = await cookies(); |
| const storedState = cookieStore.get("oidc_state")?.value; |
| const storedNonce = cookieStore.get("oidc_nonce")?.value; |
| const codeVerifier = cookieStore.get("oidc_code_verifier")?.value; |
|
|
| if (!storedState || !storedNonce || !codeVerifier || storedState !== state) { |
| clearOidcCookies(cookieStore); |
| return NextResponse.redirect(new URL("/login?error=oidc_invalid_state", getPublicOrigin(request))); |
| } |
|
|
| try { |
| const config = await getOidcRuntimeConfig(); |
| if (!config) { |
| clearOidcCookies(cookieStore); |
| return NextResponse.redirect(new URL("/login?error=oidc_not_configured", getPublicOrigin(request))); |
| } |
|
|
| const discovery = await fetchOidcDiscovery(config.issuerUrl); |
| const discoveredIssuer = discovery.issuer || config.issuerUrl; |
| const redirectUri = `${getPublicOrigin(request)}/api/auth/oidc/callback`; |
| const tokenData = await exchangeOidcCode({ |
| tokenEndpoint: discovery.token_endpoint, |
| clientId: config.clientId, |
| clientSecret: config.clientSecret, |
| code, |
| redirectUri, |
| codeVerifier, |
| }); |
|
|
| if (!tokenData.id_token) { |
| throw new Error("OIDC provider did not return an id_token"); |
| } |
|
|
| const payload = await verifyOidcIdToken({ |
| idToken: tokenData.id_token, |
| issuer: discoveredIssuer, |
| audience: config.clientId, |
| jwksUri: discovery.jwks_uri, |
| nonce: storedNonce, |
| }); |
|
|
| clearOidcCookies(cookieStore); |
| await setDashboardAuthCookie(cookieStore, request, { |
| oidc: true, |
| oidcSub: payload.sub || null, |
| oidcEmail: pickOidcEmail(payload) || null, |
| oidcName: pickOidcDisplayName(payload), |
| }); |
|
|
| return NextResponse.redirect(new URL("/dashboard", getPublicOrigin(request))); |
| } catch (error) { |
| clearOidcCookies(cookieStore); |
| return NextResponse.redirect(new URL(`/login?error=${encodeURIComponent(error.message || "oidc_callback_failed")}`, getPublicOrigin(request))); |
| } |
| } |
|
|