Files
HealthLog/src/components/settings/access-section.tsx
T
Marc-André Bombeck 8617f809f0 feat(sharing): hand a managed profile over to the person it describes
A guardian with a fresh second factor mints a one-time handover link
(hlp_ token, HMAC hash stored only, 1/7/14 days, at most one open per
profile, withdrawable) with a proposed access level per guardian: end,
view only or manage, view only by default. The link is shown once with a
copy button and a QR code; nothing is mailed. The panel sits at the end
of each managed-profile row and hints when the person is under 16.

The person opens /claim/<token>, sees a minimal preview (name, expiry,
who keeps which access, no health data) and chooses a username, a
required email and a password under the registration policy. The claim
is one transaction under the managed-profile lock: the token is
consumed, the credentials are set, the marker is cleared, every pending
invitation the record offered is withdrawn, each guardian's access is
set to its proposal, the disclaimer, onboarding and AI consent are
reset, and every step is audited on the record and on each guardian's
own account. A failure anywhere leaves the profile managed and the link
unused. Each guardian is notified on their own channels.

The new owner then sees each guardian's access on the first screen after
signing in and decides finally; until then the proposal holds, and the
decision waits at the top of Shared access. A guardian whose access
moved since the claim is left alone. The same screen points the owner
at their own notification channels, since reminders no longer reach the
guardians.

Both anonymous routes refuse a single-sign-on-only instance, refuse a
live session before spending the per-address rate limit, check the link
before probing names, and answer one 404 for every unusable token. The
token travels in request bodies only; hlp_ joins the redaction and
secret-shape rules and /claim/ the path-secret registry. Usernames
starting with managed- are reserved. The session-surface and acting-
account guards list the new surfaces with their reasons.
2026-10-08 11:18:14 +02:00

60 lines
2.9 KiB
TypeScript

"use client";
import { GrantInviteCard } from "@/components/settings/access/grant-invite-card";
import { HandoverDecisionCard } from "@/components/settings/access/handover-decision";
import { GrantsGivenCard } from "@/components/settings/access/grants-given-card";
import { GrantsReceivedCard } from "@/components/settings/access/grants-received-card";
import { ManagedProfileCard } from "@/components/settings/access/managed-profile-card";
import { RecordActivityCard } from "@/components/settings/access/record-activity-card";
/**
* v1.36.0 — Settings → "Shared access".
*
* One section for one concept: who else can open this health record, and whose
* this account may open. Split across two pages it would be two half-answers —
* the settings IA rule this repository keeps rediscovering — so both directions
* and the activity that follows from them live in one column, in the order
* somebody reads them:
*
* 1. Invite — the act.
* 2. Records you look after — the other act, and the one that CREATES a
* record rather than opening one that already exists.
* 3. Who has access — the standing state, and the way to end it.
* 4. What I can open — the same, from the other side.
* 5. When it was opened — what actually happened.
*
* v1.37.0 put the managed-profile card second rather than in a settings area
* of its own. A managed profile is a record with no login, reachable only
* through the people who look after it — which is the same question this
* section already answers twice, from a third side. Splitting it out would
* have made "who may open which record" two half-answers on two pages, which
* is the settings-IA rule below in miniature.
*
* Distinct from Settings → "Health record" → sharing links, which mints a
* time-boxed read-only link to a REPORT for a clinician. This section is about
* another ACCOUNT on this instance being able to open the live record, which is
* a standing relationship rather than a document handed over once.
*
* Every card here reads and writes routes that refuse while the browser is
* acting on somebody else's record, and the section is unreachable in that
* state because the whole Settings entry drops out of the nav. Grant management
* is the one surface a delegate must have no reach into at all: a delegate who
* could invite, widen or transfer would be re-delegating access nobody granted
* them.
*/
export function AccessSection() {
return (
<div className="space-y-4">
{/* v1.42 (#959) — only for an account that claimed a managed profile
and has not decided its former Guardians' access yet; it comes first
because it is the one open question on this page. */}
<HandoverDecisionCard />
<GrantInviteCard />
<ManagedProfileCard />
<GrantsGivenCard />
<GrantsReceivedCard />
<RecordActivityCard />
</div>
);
}