mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
fix(sharing): invitations need a fresh proof and end with sign out everywhere
Offering access to the record took a live session alone, and an invitation outlives the session that sent it: whoever held a stolen session could invite their own account and keep a way into the record after the owner signed everything out. POST /api/account/grants now takes the recent-proof gate at every level (a sign-in or re-proof within five minutes on the browser, with the second factor on an account that has one; a step-up elevation on a token, which no shipped client uses here), and the invite form opens the re-proof dialog and sends again. Sign out everywhere now withdraws every invitation the account offered that nobody has accepted. Accepted grants are left standing on purpose: each is a person the owner chose, often a carer who still needs the record, and a sign-out button must not quietly cut them off. The response lists them as grantsKept and the sessions card shows each with a one-click End access.
This commit is contained in:
1 parent
a4d27c618e
commit
a294d7f684
22 files changed
+585
-65
No files matched your search
+60
-4
@@ -2621,7 +2621,10 @@ paths:
|
||||
(the OAuth connection, so it cannot mint a new access token), every programmatic token (connector, measurement
|
||||
and document tokens), and every clinician share link. Share links are revoked by default because a link made by
|
||||
whoever held a lost session or device would otherwise keep opening the record; send `keepShareLinks=1` to keep
|
||||
them. A Bearer caller keeps its own device login."
|
||||
them. Record-sharing invitations this account offered that nobody has accepted yet are withdrawn too, because an
|
||||
invitation sent from a stolen session would otherwise wait for the thief's own account. Accepted grants are not
|
||||
ended: the response lists them as `grantsKept` so the client can show who can still read the record and offer to
|
||||
end each one. A Bearer caller keeps its own device login."
|
||||
parameters:
|
||||
- in: query
|
||||
name: keepShareLinks
|
||||
@@ -19394,7 +19397,12 @@ paths:
|
||||
(`requireFreshMfaIfEnrolled`, cookie-only): an enrolled account without a fresh factor gets 401
|
||||
`auth.stepup.required`, and a Bearer caller gets 403 `sharing.invite.manage_browser_only` before anything else
|
||||
happens, because the gate it would hit resolves through the session cookie and would otherwise answer 'not
|
||||
authenticated' to a caller it had just authenticated."
|
||||
authenticated' to a caller it had just authenticated. Every invitation, at any level, also needs a fresh proof:
|
||||
a browser session must have signed in or used `POST /api/auth/reproof` within the last five minutes (with the
|
||||
second factor, on an account that has one), or it gets 401 `auth.reproof.required` with `meta.methods`; a Bearer
|
||||
caller presents an `X-Step-Up` elevation from `POST /api/auth/step-up` (a second-factor one on an account with a
|
||||
second factor) and gets 401 `auth.stepup.required` without it. `DELETE /api/auth/me/sessions` withdraws the
|
||||
invitations nobody has accepted yet."
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -19409,8 +19417,10 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AccountGrantEnvelope"
|
||||
"401":
|
||||
description: "Offering MANAGE from an MFA-enrolled account without a recent second-factor proof (`meta.errorCode:
|
||||
auth.stepup.required`). Re-prove the factor and retry; READ and WRITE invitations are never gated."
|
||||
description: "No recent proof. A browser session that has not signed in or re-proved within five minutes gets
|
||||
`meta.errorCode: auth.reproof.required` with `meta.methods`; re-prove at POST /api/auth/reproof and retry. A
|
||||
Bearer caller without a valid `X-Step-Up` elevation gets `auth.stepup.required`, as does offering MANAGE
|
||||
from an MFA-enrolled browser session without a recent second-factor proof."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -39023,11 +39033,57 @@ components:
|
||||
minimum: -9007199254740991
|
||||
maximum: 9007199254740991
|
||||
description: Clinician share links revoked; 0 when `keepShareLinks=1` was sent.
|
||||
pendingInvitesRevoked:
|
||||
type: integer
|
||||
minimum: -9007199254740991
|
||||
maximum: 9007199254740991
|
||||
description: "Record-sharing invitations this account offered that nobody had accepted yet, now withdrawn (`revokedBy:
|
||||
GRANTOR`)."
|
||||
grantsKept:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: The grant id, for DELETE /api/account/grants/{id}.
|
||||
account:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
displayName:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
required:
|
||||
- id
|
||||
- username
|
||||
- displayName
|
||||
additionalProperties: false
|
||||
access:
|
||||
type: string
|
||||
enum:
|
||||
- READ
|
||||
- WRITE
|
||||
- MANAGE
|
||||
required:
|
||||
- id
|
||||
- account
|
||||
- access
|
||||
additionalProperties: false
|
||||
description: Accepted grants this account gave that are still live. Signing out does not end them, because each is a
|
||||
person the owner chose and ending one silently could cut off a carer; they are listed so a client can show
|
||||
who can still read the record and offer to end each with DELETE /api/account/grants/{id}.
|
||||
required:
|
||||
- sessionsRevoked
|
||||
- accessTokensRevoked
|
||||
- connectorsRevoked
|
||||
- shareLinksRevoked
|
||||
- pendingInvitesRevoked
|
||||
- grantsKept
|
||||
additionalProperties: false
|
||||
SessionRevokeEnvelope:
|
||||
type: object
|
||||
|
||||
Reference in new issue
Block a user