mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
fix(auth): count a passkey as the second factor on the managed-profile acts
An account whose only strong credential is a primary passkey was refused auth.stepup.mfa_not_enrolled on every action behind requireFreshMfa, so it could not create, edit, hand over or delete a managed profile or manage its guardians, although a passkey sign-in or re-proof already stamps the session the gate reads. requireFreshMfa now defaults to the any-possession proof source: a passkey makes the gate reachable, and a passkey re-proof through the dialog clears it. The refusal offers passkey in meta.methods, never the password. hasSecondFactorEnrolled keeps its narrow meaning on purpose. It decides whether the conditional gates apply at all (account deletion, the data reset, the password change, the export, offering manage access, the recent-proof branches and the native Bearer rules), so none of those change, and password sign-in still challenges only TOTP and security keys. The MFA-management routes keep the second-factor source. The decision and its limits are written beside FreshFactorProofSource. Copy, the OpenAPI description and the sharing and key-rotation docs say "second factor or passkey" where this gate applies. Refs #959
This commit is contained in:
1 parent
d9b6061d28
commit
792fc89a93
17 files changed
+381
-63
No files matched your search
@@ -21687,9 +21687,10 @@ paths:
|
||||
"401":
|
||||
description: "No fresh second-factor proof (`meta.errorCode: auth.stepup.required`, with `meta.methods` naming the
|
||||
proofs that clear it at POST /api/auth/reproof), or none enrolled at all (`meta.errorCode:
|
||||
auth.stepup.mfa_not_enrolled`) — this gate is unconditional, so an account without a second factor cannot
|
||||
create a managed profile until it enrols one. The ordinary unauthenticated 401 arrives here too, including
|
||||
for a Bearer caller, whose transport this gate does not read."
|
||||
auth.stepup.mfa_not_enrolled`) — this gate is unconditional, so an account with neither a second factor nor
|
||||
a passkey cannot create a managed profile until it enrols one. A passkey counts: an account whose only
|
||||
strong credential is a passkey is offered `passkey` in `meta.methods`. The ordinary unauthenticated 401
|
||||
arrives here too, including for a Bearer caller, whose transport this gate does not read."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -34618,8 +34619,9 @@ components:
|
||||
Guardian reading it: they decide how the profile's own reminders are worded and when its day starts. Strict — an
|
||||
unexpected field is a 422 rather than an ignored key. Cookie transport only and step-up gated
|
||||
(`requireFreshMfa`, unconditional): a Bearer caller cannot mint a credential-less person and a permanent
|
||||
management relationship, and an account with no second factor enrolled is refused rather than waved through.
|
||||
Rate-limited to ten an hour per caller."
|
||||
management relationship, and an account with no second factor enrolled is refused rather than waved through. A
|
||||
primary passkey counts as that factor here: a passkey sign-in or a passkey re-proof at POST /api/auth/reproof
|
||||
stamps the session. Rate-limited to ten an hour per caller."
|
||||
UpdateManagedProfileRequest:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -43,12 +43,12 @@ under `v1` (the synthetic id assigned to the existing `ENCRYPTION_KEY`).
|
||||
retained.
|
||||
3. **Re-encrypt the stored data under the new key.** Open **Admin →
|
||||
Encryption** and press **Rotate now**. The button needs an admin account
|
||||
with a second factor, confirmed recently; it queues one background run
|
||||
with a second factor or a passkey, confirmed recently; it queues one background run
|
||||
(`POST /api/admin/encryption/rotate`) on the app's own worker, so it
|
||||
works on the published image with nothing else installed. The same page
|
||||
shows the per-column counts and the result of the last run.
|
||||
|
||||
Without a second factor on any admin account, run the rotation script
|
||||
Without a second factor or a passkey on any admin account, run the rotation script
|
||||
instead. It imports the application code and the generated database
|
||||
client, so it runs from a source checkout of the same release, not
|
||||
inside the production container (the image has no package manager and
|
||||
|
||||
@@ -189,9 +189,11 @@ no password, no passkey and no e-mail address, and it is never presented
|
||||
as something to log in as.
|
||||
|
||||
- It is created from **Settings → Shared access**, under a fresh second
|
||||
factor. An account with no second factor enrolled is refused rather than
|
||||
waved through, because the act creates a permanent management
|
||||
relationship over a person who cannot object to it.
|
||||
factor. A passkey counts as one: a guardian who signs in with a passkey,
|
||||
or confirms it in the dialog the app opens, gets through. An account with
|
||||
neither a second factor nor a passkey is refused rather than waved
|
||||
through, because the act creates a permanent management relationship
|
||||
over a person who cannot object to it.
|
||||
- Creation takes a display name, optionally a real date of birth, and the
|
||||
language and timezone the record's own days and reminders are measured
|
||||
in. A year on its own is never turned into the first of January. The
|
||||
@@ -356,10 +358,11 @@ Four operational notes:
|
||||
and no address to look it up by.
|
||||
- **The second-factor requirement is not the same on both features, and
|
||||
the difference is deliberate.** Creating a managed profile, deleting
|
||||
one and inviting a second guardian require a second factor
|
||||
unconditionally: an account with none enrolled is refused and told to
|
||||
set one up, because those acts create or end a permanent relationship
|
||||
over somebody who cannot object. Offering manage access on an ordinary
|
||||
invitation asks for a fresh factor only from an account that has one, so
|
||||
one, handing it over and inviting a second guardian require a second
|
||||
factor unconditionally, and a passkey counts as one there: an account
|
||||
with neither is refused and told to set one up, because those acts
|
||||
create or end a permanent relationship over somebody who cannot object.
|
||||
Offering manage access on an ordinary invitation asks for a fresh factor
|
||||
only from an account with TOTP or a security key, so
|
||||
an instance where nobody has enrolled a factor can still use every level
|
||||
of ordinary sharing. All of them are browser-only either way.
|
||||
Reference in new issue
Block a user