mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
feat(security): let an operator require a second factor
Wire the previously-unused mfaEnforced flag into a real policy. An admin can require a second factor instance-wide (an AppSettings toggle in the General section) or per user (a toggle in user management); the effective requirement is the OR of the two. When required and the account has no active factor — no confirmed TOTP, no registered security key, and no primary passkey — the user is walled behind a forced-enrollment interstitial after sign-in, with only the security settings and sign-out reachable until a factor is enrolled. The gate is surfaced like the onboarding gate: a hint cookie the proxy reads without a DB round-trip, recomputed from the database on every sign-in and on /api/auth/me so a tampered cookie self-corrects. Nobody is locked out — the enrollment surface stays reachable, and toggling the policy off (or the disable-mfa CLI) is the operator's recovery path.
This commit is contained in:
1 parent
135bb04f0d
commit
c8bef61344
22 files changed
+431
-8
No files matched your search
@@ -241,6 +241,23 @@ export function proxy(request: NextRequest) {
|
||||
if (onboardingPending && !isOnboardingSurface) {
|
||||
return NextResponse.redirect(new URL("/onboarding", request.url));
|
||||
}
|
||||
|
||||
// v1.23 — admin-enforced MFA forced-enrollment gate. When the operator
|
||||
// requires a second factor and the account has none, `/api/auth/me` (and
|
||||
// every auth surface) sets `hl_mfa_enroll=required`; the proxy reads it
|
||||
// without a DB round-trip — mirroring the onboarding hint — and walls the
|
||||
// app behind the enrollment surface. Only `/enroll-mfa` and
|
||||
// `/settings/security` stay reachable so the user can actually enrol;
|
||||
// logout + the enrollment APIs are `/api/*` and never page-gated here.
|
||||
// Onboarding takes precedence (the branch above already returned), so a
|
||||
// brand-new account finishes onboarding first.
|
||||
const mfaEnrollRequired =
|
||||
request.cookies.get("hl_mfa_enroll")?.value === "required";
|
||||
const isMfaEnrollSurface =
|
||||
pathname === "/enroll-mfa" || pathname.startsWith("/settings/security");
|
||||
if (mfaEnrollRequired && !isMfaEnrollSurface && !isOnboardingSurface) {
|
||||
return NextResponse.redirect(new URL("/enroll-mfa", request.url));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate or propagate x-request-id for request correlation
|
||||
|
||||
Reference in new issue
Block a user