feat: add optional OIDC authentication

This commit is contained in:
2026-06-05 17:34:03 +08:00
parent 09f752c8cf
commit 20654d9756
8 changed files with 382 additions and 2 deletions

81
proxy.ts Normal file
View File

@@ -0,0 +1,81 @@
import { getToken } from "next-auth/jwt";
import { NextResponse, type NextRequest } from "next/server";
import { getAuthMode, getRequiredAuthSecret, isAuthRoute, isProtectedPath } from "@/lib/auth-config";
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
if (!isProtectedPath(pathname)) return NextResponse.next();
const authMode = getAuthMode();
if (!authMode.enabled && !authMode.error) return NextResponse.next();
if (authMode.error) {
return authConfigErrorResponse(request, authMode.error);
}
const token = await getToken({
req: request,
secret: getRequiredAuthSecret(),
});
if (token) return NextResponse.next();
if (pathname.startsWith("/api/")) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const signInUrl = new URL("/api/auth/signin/oidc", request.url);
signInUrl.searchParams.set("callbackUrl", request.nextUrl.href);
return NextResponse.redirect(signInUrl);
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|icon.svg).*)"],
};
function authConfigErrorResponse(request: NextRequest, error: string) {
if (request.nextUrl.pathname.startsWith("/api/") && !isAuthRoute(request.nextUrl.pathname)) {
return NextResponse.json({ error }, { status: 500 });
}
return new NextResponse(authErrorHtml(error), {
status: 500,
headers: {
"content-type": "text/html; charset=utf-8",
},
});
}
function authErrorHtml(error: string): string {
return `<!doctype html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Authentication Configuration Error</title>
<style>
body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #0f172a; color: #e2e8f0; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
main { max-width: 640px; padding: 32px; }
h1 { margin: 0 0 12px; font-size: 22px; }
p { color: #94a3b8; line-height: 1.6; }
code { color: #f8fafc; }
</style>
</head>
<body>
<main>
<h1>OIDC 配置不完整</h1>
<p><code>${escapeHtml(error)}</code></p>
<p>请补全 OIDC 配置,或移除全部 OIDC 相关变量以保持开放访问。</p>
</main>
</body>
</html>`;
}
function escapeHtml(value: string): string {
return value
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}