ThreadCount Community edition

Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
This commit is contained in:
ThreadCount
2026-09-13 08:45:19 +10:00
commit 1bc2de655a
505 changed files with 56223 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { clientIp } from "@/lib/ratelimit";
import { safeOpsNext, verifyAccessJwt } from "@/lib/ops/cfAccess";
import { logOperatorEvent, setOpsCookie } from "@/lib/ops/session";
export const dynamic = "force-dynamic";
/* Single sign-on entry. Reached when a browser arrives with a Cloudflare Access assertion but no
* operator session (the sign-in page hands off here). The assertion is verified fail-closed
* (lib/ops/cfAccess.ts), the verified email is mapped to an EXISTING, ACTIVE operator, and the
* ordinary tc_ops session is minted, marked `sso`. Never creates an operator: an email Access
* admits but the console does not know falls back to the password door. Every failure lands on
* /ops/login?sso=failed — never a bypass, never a loop (the sign-in page does not re-trigger SSO
* when ?sso is present).
*
* Relative Location on purpose: an absolute URL built from the request would carry the origin
* nginx sees (127.0.0.1:3000), not the public host. */
function seeOther(location: string): NextResponse {
return new NextResponse(null, { status: 303, headers: { Location: location } });
}
export async function GET(req: NextRequest) {
const failed = seeOther("/ops/login?sso=failed");
const next = safeOpsNext(req.nextUrl.searchParams.get("next"));
const ip = clientIp(req.headers);
const email = await verifyAccessJwt(req.headers.get("cf-access-jwt-assertion"));
if (!email) return failed;
const op = await prisma.operator.findUnique({
where: { email },
select: { id: true, inactive: true, passwordHash: true },
});
if (!op || op.inactive) {
// The trail cannot name an operator it does not have; the address goes in the detail, since
// "who Access let in that the console refused" is the fact worth keeping.
console.warn("[ops sso] no active operator for", email, "from", ip);
return failed;
}
await setOpsCookie(op.id, op.passwordHash, true);
logOperatorEvent({ operatorId: op.id, action: "ops:signin.sso", ip });
prisma.operator.update({ where: { id: op.id }, data: { lastSeenAt: new Date() } }).catch(() => {});
return seeOther(next);
}