83 lines
3.2 KiB
TypeScript
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;
|
|
}
|