Account & Auth
513b7de (Jul 16, 2026)Prod: 513b7de (Jul 16, 2026)The single HDS app that owns authentication (sign-in, registration, password reset, MFA) and
account management (profile, preferences, security, connected apps, data rights, audit). It is a fork
of Pryv’s app-web-user-account and is the successor to the retired app-web-auth3-hds — one deployment
serves both account hostnames on clean paths (no .html).
Your app does not build any of this itself. It links into the account app for the ceremony and the
account pages, and reads account-level preferences through hds-lib.
What it owns — don’t rebuild these
Section titled “What it owns — don’t rebuild these”| Path | Purpose |
|---|---|
/signin | Sign-in (username or email + password) |
/register | Account creation |
/reset-password, /change-password | Password recovery / change |
/mfa-challenge | Inline MFA |
/auth, /oauth2-authorize | The Pryv auth ceremony / OAuth2 entry |
/account/{profile,preferences,security,apps,data,audit-access/:id} | Account management pages |
/cmc-accept (alias /cmc/approve), /cmc-scope-update | CMC consent hand-offs (below) |
A custom HDS app should build none of register / reset / change-password / profile / preferences — link to the account app instead.
Linking in — the hand-off contract
Section titled “Linking in — the hand-off contract”Send the user to an account path and pass these optional query params (all carried across the app’s
internal /signin bounce, so a signed-out deep-link keeps them):
username— a sign-in hint; the calling app usually already knows who the user is, so the sign-in form pre-fills it.backUrl+backLabel— render a← Back to {backLabel}affordance that returns the user to your app when they’re done.pryvServiceInfoUrl— optional and normally omitted. Each deployment already knows the Pryv platform it serves (from asettings.jsonserved next to the bundle). The param stays authoritative when present (the auth ceremony passes it so one deployment can serve a caller on another platform), but apps linking in are not expected to send it.
Settings vs. profile — where preferences live
Section titled “Settings vs. profile — where preferences live”Two distinct stores, both via hds-lib:
HDSSettings— per-app. Bound to an app’s baseStream, so each app gets its own private copy (e.g.theme, which is legitimately per-app).HDSProfile— account-level, shared across every HDS app. Holds identity (displayName, avatar, …) and the account preferences every app should agree on:preferredLocales,timezone,dateFormat,unitSystem. These live on the account’s ownprofile-preferencesstream, which is what makes them interoperable. The account app owns writing them.
Read a shared preference through resolveAccountPreference(key) (from hds-lib), never by reading one
store directly. Its precedence is: a genuinely stored profile value → the legacy per-app HDSSettings
value → the profile default (incl. browser inference). This is what lets an app honour an account-level
choice while still working for users who only ever set a per-app value.
CMC consent hand-off
Section titled “CMC consent hand-off”A custom app needs no CMC consent UI of its own. /cmc-accept is the canonical path a requester points
@pryv/cmc.requestAccept at; CmcApprove is app-agnostic and drives sign-in + consent + accept/decline.
Requester recipe:
import { requestAccept } from '@pryv/cmc';
requestAccept({ authUrl: 'https://account.hds.ngo/cmc-accept', // the account app owns the consent UI capabilityUrl, // the offer to read (anonymous readOffer resolves it) scopeStreamId, // optional — the stream scope being granted pryvApi, accessName, // optional — a label for the created access returnUrl, // optional — where to send the result in redirect mode mode, // 'popup' (default) | 'redirect'});Result return: the account app returns { ok, dataGrantApiEndpoint?, acceptEventId?, reason? } —
- popup mode:
window.opener.postMessage({ type: 'cmc-accept-result', ...res }); - redirect mode: navigates to
returnUrlwith?cmcAcceptResult=<json>.
/cmc-scope-update mirrors the same shape for updating an existing grant’s scope.
Scope note (accept vs. revoke): /cmc-accept is accept/decline of a pending offer, and
/cmc-scope-update updates a grant’s scope. Revoking an existing grant is a different action and lives
in the account UI at /account/audit-access (accesses.delete), not on these hand-off routes.
Security properties to know (all inherent to @pryv/cmc, not HDS choices): in redirect mode the result
carries a token-bearing dataGrantApiEndpoint on the URL query; popup mode posts with targetOrigin: '*'
(there is no trustworthy requester web origin to pin); upstream’s requestAccept listener does not validate
ev.origin. The requester is the intended recipient of the grant, so these are by design — but do not log or
forward the result URL.
Tech stack
Section titled “Tech stack”- React 19 + TypeScript + Vite + Tailwind + React Router 7.
- Pryv is reached only through
hds-lib(never a directpryvimport). - Published as a static SPA on GitHub Pages; each deployment writes its own
settings.json(dev/demo vs. production platform).