Files
gutscheintracker/app/api/auth/oidc/callback/route.ts
T

83 lines
3.2 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { getDb } from "@/lib/db";
import { createSession } from "@/lib/session";
import { setSessionCookie } from "@/lib/set-session-cookie";
import {
discover,
verifyCallbackCode,
STATE_COOKIE,
NONCE_COOKIE,
VERIFIER_COOKIE,
} from "@/lib/oidc";
import { getOidcSettings } from "@/lib/integration-settings";
function clearCookie(res: NextResponse, name: string) {
res.cookies.set(name, "", { path: "/", maxAge: 0 });
}
/** OIDC-Rücksprung: prüft State/Code, verknüpft oder erstellt das Konto, meldet an. Öffentlich. */
export async function GET(req: NextRequest) {
const appUrl = (process.env.APP_URL ?? "").replace(/\/$/, "") || req.nextUrl.origin;
const params = req.nextUrl.searchParams;
const code = params.get("code");
const state = params.get("state");
const expectedState = req.cookies.get(STATE_COOKIE)?.value;
const nonce = req.cookies.get(NONCE_COOKIE)?.value;
const verifier = req.cookies.get(VERIFIER_COOKIE)?.value;
const deny = (reason: string) => {
const res = NextResponse.redirect(new URL(`/login?sso=${reason}`, appUrl));
clearCookie(res, STATE_COOKIE);
clearCookie(res, NONCE_COOKIE);
clearCookie(res, VERIFIER_COOKIE);
return res;
};
if (!code || !state || !expectedState || state !== expectedState || !nonce || !verifier) {
return deny("ungueltig");
}
const { issuer, clientId, clientSecret, signingAlg } = await getOidcSettings();
if (!issuer || !clientId || !clientSecret) return deny("nicht-konfiguriert");
let verified;
try {
const doc = await discover(issuer);
verified = await verifyCallbackCode(
doc,
clientId,
clientSecret,
`${appUrl}/api/auth/oidc/callback`,
code,
verifier,
nonce,
signingAlg
);
} catch {
return deny("pruefung-fehlgeschlagen");
}
const db = getDb();
let user = await db.user.findUnique({ where: { oidcSubject: verified.subject } });
if (!user) {
const byMail = await db.user.findUnique({ where: { email: verified.email } });
if (byMail) {
user = await db.user.update({ where: { id: byMail.id }, data: { oidcSubject: verified.subject } });
} else {
const invite = await db.invite.findUnique({ where: { email: verified.email } });
const invited = invite && invite.acceptedAt === null && invite.expiresAt.getTime() > Date.now() ? invite : null;
user = await db.user.create({
data: { name: verified.name, email: verified.email, role: invited?.role ?? "member", oidcSubject: verified.subject },
});
if (invited) {
if (invited.householdId !== null) {
await db.householdMember.create({ data: { householdId: invited.householdId, userId: user.id, role: "member" } });
}
await db.invite.update({ where: { id: invited.id }, data: { acceptedAt: new Date() } });
}
}
}
const token = await createSession({ uid: user.id, name: user.name, role: user.role, tokenVersion: user.tokenVersion });
const res = NextResponse.redirect(new URL("/", appUrl));
setSessionCookie(res, token);
clearCookie(res, STATE_COOKIE);
clearCookie(res, NONCE_COOKIE);
clearCookie(res, VERIFIER_COOKIE);
return res;
}