mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
fix(export): every whole-record export asks for a fresh proof
GET /api/export with type=all (the default), POST /api/export/health-record and GET /api/fhir/Patient/$everything hand over the whole record but took a live session alone, so a stolen browser session could still walk off with everything the full-backup gate was meant to protect. A browser session now needs a sign-in or re-proof within five minutes, with the second factor on an account that has one, and the doctor-report panel opens the re-proof dialog and retries. When a manager builds the report for a record they manage, their own session is the one asked. On a token the shipped app keeps working: health-record and $everything take the token as before, including a fhir:read token, and the recent-proof guard names both. GET /api/export?type=all, which no shipped client calls with a token, wants a step-up elevation there. Single-type exports are unchanged. The API reference now also describes the enrollment rule for accounts with a second factor and the passkey refusal it adds.
This commit is contained in:
1 parent
0692017f34
commit
a4d27c618e
13 files changed
+304
-76
No files matched your search
+50
-21
@@ -864,7 +864,11 @@ paths:
|
||||
`selection` is REQUIRED and carries the leaf inclusion list — membership is inclusion, absence is exclusion, and
|
||||
there is no server-side default. An unknown leaf id is a 422 that names it; the retired grouped `sections` shape
|
||||
is a 422 too, because accepting it would mean choosing a scope the caller did not express. Auth via cookie or
|
||||
Bearer; shared `export:<userId>` rate bucket (10/h). Strict validation: unknown keys 422."
|
||||
Bearer; shared `export:<userId>` rate bucket (10/h). Strict validation: unknown keys 422. A browser session
|
||||
needs a fresh proof as well as a live one: a sign-in or `POST /api/auth/reproof` within the last five minutes
|
||||
(with a second factor, on an account that has one). Without it the answer is 401 `auth.reproof.required`, with
|
||||
`meta.methods` naming the proofs the account can give. When a manager exports a record they manage, it is their
|
||||
own session that is asked. A Bearer caller is not asked for more than its token."
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -1489,7 +1493,10 @@ paths:
|
||||
Conditions, Encounters, AllergyIntolerances, FamilyMemberHistories and the DiagnosticReport (present when the
|
||||
window holds a vital sign) — flattened into one `searchset` Bundle. Scoped to the owner's SAVED report
|
||||
selection; an account that never saved one gets nothing. Auth: `fhir:read` scope. Offset paging via `_count`
|
||||
(≤200) / `_offset`."
|
||||
(≤200) / `_offset`. A browser session needs a fresh proof as well as a live one: a sign-in or `POST
|
||||
/api/auth/reproof` within the last five minutes (with a second factor, on an account that has one). Without it
|
||||
the answer is 401 `auth.reproof.required`, with `meta.methods` naming the proofs the account can give. A token,
|
||||
including a `fhir:read` one, is not asked for more."
|
||||
parameters:
|
||||
- in: query
|
||||
name: _count
|
||||
@@ -1524,7 +1531,13 @@ paths:
|
||||
notes ride their exported column. Shares the `export:<userId>` bucket with the encrypted variant (10 per hour)
|
||||
and audits every attempt as `export.download`. Cookie or Bearer auth; the caller is always resolved as
|
||||
themselves, so this download cannot be delegated to a shared record. Not module-gated: a disabled module's rows
|
||||
are still exported, because this is the account's own data leaving on request."
|
||||
are still exported, because this is the account's own data leaving on request. `type=all`, the default, is the
|
||||
whole record and asks for a fresh proof like the full backup; the single-type exports do not. A browser session
|
||||
needs a fresh proof as well as a live one: a sign-in or `POST /api/auth/reproof` within the last five minutes
|
||||
(with a second factor, on an account that has one). Without it the answer is 401 `auth.reproof.required`, with
|
||||
`meta.methods` naming the proofs the account can give. A Bearer caller asking for `type=all` presents an
|
||||
`X-Step-Up` elevation from `POST /api/auth/step-up` (a second-factor one on an account with a second factor) and
|
||||
receives 401 `auth.stepup.required` without it."
|
||||
parameters:
|
||||
- in: query
|
||||
name: format
|
||||
@@ -2172,14 +2185,18 @@ paths:
|
||||
/api/auth/step-up in the `X-Step-Up` header; a Bearer token alone is still refused.
|
||||
|
||||
|
||||
Adding a factor needs a fresh proof, not just a session. On the Bearer path the elevation is that proof (a
|
||||
password-proved one is enough). On the cookie path the request passes when the session signed in, or completed a
|
||||
second factor, within the last five minutes; otherwise the body must carry a proof in the shape POST
|
||||
/api/auth/step-up takes (`password`, a `totp` code from an already confirmed authenticator, or a passkey /
|
||||
security-key assertion begun at POST /api/auth/passkey/register-options). Without one the answer is 401
|
||||
`auth.reproof.required`; a proof that does not verify is 401 `auth.reproof.failed` and is audited. Proofs draw
|
||||
on the account's shared re-proof budget of five per fifteen minutes (429 once spent), the same budget as the
|
||||
step-up mint.
|
||||
Adding a factor needs a fresh proof, not just a session. On the Bearer path the elevation is that proof: a
|
||||
password-proved one is enough on an account without a second factor, and on an account that already has one the
|
||||
elevation must come from that factor or a passkey. On the cookie path, for an account without a second factor,
|
||||
the request passes when the session signed in, or completed a passkey sign-in, within the last five minutes;
|
||||
otherwise the body must carry a proof in the shape POST /api/auth/step-up takes (`password`, or a passkey
|
||||
assertion begun at POST /api/auth/passkey/register-options). For an account that already has a second factor,
|
||||
only that factor counts: a second factor completed within the last five minutes, or a `totp` code, security-key
|
||||
or passkey assertion in the body. A password or a young session (for example one that skipped the factor on a
|
||||
remembered browser) is not enough there. Without one the answer is 401 `auth.reproof.required` with
|
||||
`meta.methods` naming the proofs the account can give; a proof that does not verify is 401 `auth.reproof.failed`
|
||||
and is audited. Proofs draw on the account's shared re-proof budget of five per fifteen minutes (429 once
|
||||
spent), the same budget as the step-up mint.
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
@@ -2316,14 +2333,18 @@ paths:
|
||||
refused.
|
||||
|
||||
|
||||
Adding a factor needs a fresh proof, not just a session. On the Bearer path the elevation is that proof (a
|
||||
password-proved one is enough). On the cookie path the request passes when the session signed in, or completed a
|
||||
second factor, within the last five minutes; otherwise the body must carry a proof in the shape POST
|
||||
/api/auth/step-up takes (`password`, a `totp` code from an already confirmed authenticator, or a passkey /
|
||||
security-key assertion begun at POST /api/auth/passkey/register-options). Without one the answer is 401
|
||||
`auth.reproof.required`; a proof that does not verify is 401 `auth.reproof.failed` and is audited. Proofs draw
|
||||
on the account's shared re-proof budget of five per fifteen minutes (429 once spent), the same budget as the
|
||||
step-up mint.
|
||||
Adding a factor needs a fresh proof, not just a session. On the Bearer path the elevation is that proof: a
|
||||
password-proved one is enough on an account without a second factor, and on an account that already has one the
|
||||
elevation must come from that factor or a passkey. On the cookie path, for an account without a second factor,
|
||||
the request passes when the session signed in, or completed a passkey sign-in, within the last five minutes;
|
||||
otherwise the body must carry a proof in the shape POST /api/auth/step-up takes (`password`, or a passkey
|
||||
assertion begun at POST /api/auth/passkey/register-options). For an account that already has a second factor,
|
||||
only that factor counts: a second factor completed within the last five minutes, or a `totp` code, security-key
|
||||
or passkey assertion in the body. A password or a young session (for example one that skipped the factor on a
|
||||
remembered browser) is not enough there. Without one the answer is 401 `auth.reproof.required` with
|
||||
`meta.methods` naming the proofs the account can give; a proof that does not verify is 401 `auth.reproof.failed`
|
||||
and is audited. Proofs draw on the account's shared re-proof budget of five per fifteen minutes (429 once
|
||||
spent), the same budget as the step-up mint.
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
@@ -3453,7 +3474,9 @@ paths:
|
||||
|
||||
**Enrollment arm.** Any other body must be a full factor proof (the same shape POST /api/auth/step-up takes): a
|
||||
password, a TOTP code, or a completed passkey / security-key assertion. Only then are registration options
|
||||
issued.
|
||||
issued. On an account that already has a second factor a password is refused before it is checked, with 422
|
||||
`auth.reproof.too_weak` and `meta.methods` naming the proofs that work: a passkey added on the password alone
|
||||
would sign in without the second factor from then on.
|
||||
|
||||
|
||||
Which factor you prove changes the session, and the direction is not the obvious one: a strong proof stamps the
|
||||
@@ -3500,7 +3523,13 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a4
|
||||
"422":
|
||||
description: "`auth.reproof.too_weak`: a password on an account that already has a second factor. `meta.methods` names
|
||||
the proofs that would be accepted. Refused before the password is checked, so it spends no attempt."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"429": *a5
|
||||
/api/auth/passkey/register-verify:
|
||||
post:
|
||||
|
||||
Reference in new issue
Block a user