mirror of
https://github.com/fccview/cronmaster.git
synced 2026-01-01 02:19:03 -05:00
156 lines
4.7 KiB
TypeScript
156 lines
4.7 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { jwtVerify, createRemoteJWKSet } from "jose";
|
|
import {
|
|
createSession,
|
|
getSessionCookieName,
|
|
} from "@/app/_utils/session-utils";
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const appUrl = process.env.APP_URL || request.nextUrl.origin;
|
|
|
|
if (process.env.SSO_MODE !== "oidc") {
|
|
return NextResponse.redirect(`${appUrl}/login`);
|
|
}
|
|
|
|
let issuer = process.env.OIDC_ISSUER || "";
|
|
if (issuer && !issuer.endsWith("/")) {
|
|
issuer = `${issuer}/`;
|
|
}
|
|
const clientId = process.env.OIDC_CLIENT_ID || "";
|
|
if (!issuer || !clientId) {
|
|
return NextResponse.redirect(`${appUrl}/login`);
|
|
}
|
|
|
|
const code = request.nextUrl.searchParams.get("code");
|
|
const state = request.nextUrl.searchParams.get("state");
|
|
const savedState = request.cookies.get("oidc_state")?.value;
|
|
const verifier = request.cookies.get("oidc_verifier")?.value;
|
|
const nonce = request.cookies.get("oidc_nonce")?.value;
|
|
|
|
if (!code || !state || !savedState || state !== savedState || !verifier) {
|
|
if (process.env.DEBUGGER) {
|
|
console.log("[OIDC Callback] Missing or invalid parameters", {
|
|
hasCode: !!code,
|
|
hasState: !!state,
|
|
hasSavedState: !!savedState,
|
|
statesMatch: state === savedState,
|
|
hasVerifier: !!verifier,
|
|
});
|
|
}
|
|
return NextResponse.redirect(`${appUrl}/login`);
|
|
}
|
|
|
|
try {
|
|
const discoveryUrl = issuer.endsWith("/")
|
|
? `${issuer}.well-known/openid-configuration`
|
|
: `${issuer}/.well-known/openid-configuration`;
|
|
|
|
const discoveryRes = await fetch(discoveryUrl, { cache: "no-store" });
|
|
if (!discoveryRes.ok) {
|
|
if (process.env.DEBUGGER) {
|
|
console.log("[OIDC Callback] Discovery failed");
|
|
}
|
|
return NextResponse.redirect(`${appUrl}/login`);
|
|
}
|
|
|
|
const discovery = (await discoveryRes.json()) as {
|
|
token_endpoint: string;
|
|
jwks_uri: string;
|
|
issuer: string;
|
|
};
|
|
const tokenEndpoint = discovery.token_endpoint;
|
|
const jwksUri = discovery.jwks_uri;
|
|
const oidcIssuer = discovery.issuer;
|
|
|
|
const JWKS = createRemoteJWKSet(new URL(jwksUri));
|
|
|
|
const redirectUri = `${appUrl}/api/oidc/callback`;
|
|
const clientSecret = process.env.OIDC_CLIENT_SECRET;
|
|
const body = new URLSearchParams();
|
|
|
|
body.set("grant_type", "authorization_code");
|
|
body.set("code", code);
|
|
body.set("redirect_uri", redirectUri);
|
|
body.set("client_id", clientId);
|
|
body.set("code_verifier", verifier);
|
|
|
|
if (clientSecret) {
|
|
body.set("client_secret", clientSecret);
|
|
}
|
|
|
|
const tokenRes = await fetch(tokenEndpoint, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
body,
|
|
});
|
|
|
|
if (!tokenRes.ok) {
|
|
if (process.env.DEBUGGER) {
|
|
console.log("[OIDC Callback] Token request failed:", tokenRes.status);
|
|
}
|
|
return NextResponse.redirect(`${appUrl}/login`);
|
|
}
|
|
|
|
const token = (await tokenRes.json()) as { id_token?: string };
|
|
const idToken = token.id_token;
|
|
if (!idToken) {
|
|
if (process.env.DEBUGGER) {
|
|
console.log("[OIDC Callback] No id_token in response");
|
|
}
|
|
return NextResponse.redirect(`${appUrl}/login`);
|
|
}
|
|
|
|
let claims: { [key: string]: any };
|
|
try {
|
|
const { payload } = await jwtVerify(idToken, JWKS, {
|
|
issuer: oidcIssuer,
|
|
audience: clientId,
|
|
clockTolerance: 5,
|
|
});
|
|
claims = payload;
|
|
} catch (error) {
|
|
console.error("[OIDC Callback] ID Token validation failed:", error);
|
|
return NextResponse.redirect(`${appUrl}/login`);
|
|
}
|
|
|
|
if (nonce && claims.nonce && claims.nonce !== nonce) {
|
|
if (process.env.DEBUGGER) {
|
|
console.log("[OIDC Callback] Nonce mismatch");
|
|
}
|
|
return NextResponse.redirect(`${appUrl}/login`);
|
|
}
|
|
|
|
if (process.env.DEBUGGER) {
|
|
console.log("[OIDC Callback] Successfully authenticated user:", {
|
|
sub: claims.sub,
|
|
email: claims.email,
|
|
preferred_username: claims.preferred_username,
|
|
});
|
|
}
|
|
|
|
const sessionId = await createSession("oidc");
|
|
|
|
const response = NextResponse.redirect(`${appUrl}/`);
|
|
const cookieName = getSessionCookieName();
|
|
const isSecure =
|
|
process.env.NODE_ENV === "production" && process.env.HTTPS === "true";
|
|
|
|
response.cookies.set(cookieName, sessionId, {
|
|
httpOnly: true,
|
|
secure: isSecure,
|
|
sameSite: "lax",
|
|
path: "/",
|
|
maxAge: 30 * 24 * 60 * 60,
|
|
});
|
|
|
|
response.cookies.delete("oidc_verifier");
|
|
response.cookies.delete("oidc_state");
|
|
response.cookies.delete("oidc_nonce");
|
|
|
|
return response;
|
|
} catch (error) {
|
|
console.error("[OIDC Callback] Error:", error);
|
|
return NextResponse.redirect(`${appUrl}/login`);
|
|
}
|
|
}
|