mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
feat(security): require fresh verification before destructive account actions
Account deletion and the keep-account data reset now demand a recent second-factor re-verification for accounts that have a second factor active; single-factor accounts are unaffected and keep the typed- confirmation-only contract. The gate runs through requireFreshMfaIfEnrolled, which is cookie-only by construction — a Bearer transport for an MFA-enrolled account cannot satisfy step-up and surfaces the auth.stepup.required envelope so the UI can launch a re-verification flow.
This commit is contained in:
1 parent
6d053c3667
commit
01689f77bc
4 files changed
+166
-4
No files matched your search
@@ -557,6 +557,34 @@ export async function requireFreshMfa(
|
||||
return { ...sessionData, mfaVerifiedAt: verifiedAt };
|
||||
}
|
||||
|
||||
/**
|
||||
* v1.23 — conditional step-up for destructive account actions.
|
||||
*
|
||||
* Resolves the caller with the standard `requireAuth()` (cookie OR Bearer).
|
||||
* For an account WITHOUT a confirmed second factor the caller passes straight
|
||||
* through — a single-factor user is intentionally unaffected, so account
|
||||
* deletion / data reset keeps its existing typed-confirmation-only contract.
|
||||
* For an account WITH MFA active (`totpConfirmedAt` set) it additionally runs
|
||||
* `requireFreshMfa`, which is cookie-only by construction: an MFA-enrolled
|
||||
* account's Bearer transport carries no `mfaVerifiedAt` and therefore cannot
|
||||
* satisfy step-up, surfacing `StepUpRequiredError` (401,
|
||||
* `errorCode: "auth.stepup.required"`) so the UI launches a re-verification.
|
||||
*
|
||||
* Gating only the MFA-enrolled cohort keeps the boundary structural: a
|
||||
* hijacked live cookie session for an MFA user cannot nuke the record without
|
||||
* a fresh factor, while users who never opted into MFA are not forced through
|
||||
* a flow they have no way to complete.
|
||||
*/
|
||||
export async function requireFreshMfaIfEnrolled(
|
||||
maxAgeSeconds: number,
|
||||
): Promise<AuthContext> {
|
||||
const auth = await requireAuth();
|
||||
if (auth.user.totpConfirmedAt) {
|
||||
await requireFreshMfa(maxAgeSeconds);
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report unhandled errors to GlitchTip (fire-and-forget).
|
||||
* Uses dynamic import to avoid circular dependencies and startup cost.
|
||||
|
||||
Reference in new issue
Block a user