ThreadCount Community edition
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { currentUser } from "@/lib/session";
|
||||
import { allow, clientIp } from "@/lib/ratelimit";
|
||||
import { buildAuthorizeUrl, facilityForEmail, mintState, ssoConfigured, STATE_COOKIE } from "@/lib/sso";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/* Begin single sign-on for the facility that owns this address's domain.
|
||||
*
|
||||
* Mints a nonce, keeps it in a signed, httpOnly cookie bound to that facility (and to whether a
|
||||
* wearer or a coordinator is expected back), echoes it as the OAuth `state`, and sends the browser
|
||||
* to the broker. The callback requires the returned state to be the cookie's nonce, so a forged
|
||||
* or replayed callback has nothing to match. The redirect target handed to the broker is the
|
||||
* product's one fixed callback address — never a request header.
|
||||
*
|
||||
* A cross-site link may not start this (login CSRF: a stranger's page must not be able to sign
|
||||
* you into an account of its choosing), and a signed-in coordinator is sent to the app instead. */
|
||||
export async function GET(req: NextRequest) {
|
||||
if (!ssoConfigured()) return NextResponse.json({ error: "Single sign-on is not available." }, { status: 404 });
|
||||
if (req.headers.get("sec-fetch-site") === "cross-site") return new NextResponse(null, { status: 303, headers: { Location: "/auth" } });
|
||||
if (!allow("sso-start:" + clientIp(req.headers), 60, 15 * 60 * 1000)) return new NextResponse(null, { status: 303, headers: { Location: "/auth?error=sso_failed" } });
|
||||
const cur = await currentUser();
|
||||
if (cur && !cur.isDemo) return new NextResponse(null, { status: 303, headers: { Location: "/app" } });
|
||||
|
||||
const email = (req.nextUrl.searchParams.get("email") || "").trim().toLowerCase().slice(0, 160);
|
||||
const f = await facilityForEmail(email);
|
||||
if (!f) return new NextResponse(null, { status: 303, headers: { Location: "/auth?error=sso_unavailable" } });
|
||||
|
||||
const aud = req.nextUrl.searchParams.get("as") === "staff" ? "staff" : "user";
|
||||
if (aud === "staff" && !f.ssoStaff) return new NextResponse(null, { status: 303, headers: { Location: "/auth?error=sso_unavailable" } });
|
||||
const { nonce, cookie } = mintState(f.id, aud);
|
||||
const res = new NextResponse(null, { status: 302, headers: { Location: buildAuthorizeUrl(f.id, nonce) } });
|
||||
res.cookies.set(STATE_COOKIE, cookie, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax", // the broker returns by a top-level navigation, which lax still sends
|
||||
path: "/api/auth/sso",
|
||||
maxAge: 10 * 60,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
Reference in New Issue
Block a user