diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eec328..212327e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Documented the viewer-token create/revoke UI design and no-account + read-only viewer boundary. - Added incident-detail wrapped-key delivery revocation for account-owned wrapped-key metadata. - Added incident-scoped sharing-grant management for active contact keys, diff --git a/README.md b/README.md index fc99d4b..3255f0c 100644 --- a/README.md +++ b/README.md @@ -256,12 +256,12 @@ do not imply production readiness or public `/v1` API readiness. The server currently confirms bearer session auth, browser-cookie auth routes, `POST /v1/auth/register`, `POST /v1/auth/email/verify`, `GET /v1/account`, -`POST /v1/account/password`, owner-scoped incident list/detail routes, contact -public-key routes, sharing-grant routes, and wrapped-key routes. Current -`open-proofline/server` documents authenticated -`GET /v1/incidents`, and this client parses that response shape in live mode. -Mock mode uses prototype incident records only and must not be treated as -backend truth. +`POST /v1/account/password`, owner-scoped incident list/detail routes, +viewer-token create/revoke routes, contact public-key routes, sharing-grant +routes, and wrapped-key routes. Current `open-proofline/server` documents +authenticated `GET /v1/incidents`, and this client parses that response shape +in live mode. Mock mode uses prototype incident records only and must not be +treated as backend truth. Authenticated users can open the account profile route to review safe account metadata and change their password through `POST /v1/account/password`. The @@ -282,6 +282,13 @@ verification request body, and clears the fragment from the address bar. Raw verification tokens must not be logged, persisted, screenshotted, copied into issue drafts, or sent to analytics. +Viewer-token create/revoke UI is documented as a design boundary in +[Viewer Token UI Design](docs/viewer-token-ui-design.md). The intended +web-client viewer will replace the current server-rendered incident viewer +while preserving the same viewer-token system for no-account, read-only access +to unencrypted incident data. That path is separate from future notification +delivery and trusted-contact account/key flows. + The client must not log session tokens, Authorization headers, request bodies, uploaded bytes, plaintext, raw keys, raw media keys, contact private keys, wrapped-key ciphertext, verification credentials, object keys, stored paths, diff --git a/SECURITY.md b/SECURITY.md index 2f09c16..c91cd5b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,9 +3,9 @@ Proofline Web Client is experimental and not production-ready. Do not report real secrets, raw session tokens, browser session cookies, CSRF -tokens, Authorization headers, request bodies, plaintext, raw keys, wrapped-key -ciphertext, verification credentials, private deployment details, or user -safety data in public issues. +tokens, raw viewer tokens, token-bearing viewer links, Authorization headers, +request bodies, plaintext, raw keys, wrapped-key ciphertext, verification +credentials, private deployment details, or user safety data in public issues. Backend security issues may belong in [`open-proofline/server`](https://github.com/open-proofline/server). Web-client diff --git a/docs/README.md b/docs/README.md index a5f986a..fee92b3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,6 +8,8 @@ source of truth remains `open-proofline/server`. client modes. - [Security model](security-model.md): implemented controls, non-controls, and browser auth review areas. +- [Viewer token UI design](viewer-token-ui-design.md): owner create/revoke + design and the planned no-account read-only viewer boundary. - [Browser security headers](browser-security-headers.md): static-host header guidance and credentialed CORS review notes. - [Supply chain review](supply-chain.md) diff --git a/docs/api-client.md b/docs/api-client.md index 769c71a..3676a84 100644 --- a/docs/api-client.md +++ b/docs/api-client.md @@ -40,6 +40,8 @@ From current `open-proofline/server` docs and route registration: - `POST /v1/incidents` - `GET /v1/incidents` - `GET /v1/incidents/{incident_id}` +- `POST /v1/incidents/{incident_id}/incident-tokens` +- `POST /v1/incident-tokens/{token_id}/revoke` - `POST /v1/incidents/{incident_id}/deletion` - `GET /v1/incidents/{incident_id}/deletion` - `POST /v1/contact-public-keys` @@ -77,6 +79,31 @@ The UI maps password-change failures to fixed safe messages and does not log or persist passwords, request bodies, session tokens, Authorization headers, browser session cookies, or CSRF token values. +## Viewer Token UI Boundary + +Current `open-proofline/server` documents authenticated owner-scoped +viewer-token creation and revocation: + +- `POST /v1/incidents/{incident_id}/incident-tokens` +- `POST /v1/incident-tokens/{token_id}/revoke` + +The create route returns the raw viewer token only once and stores only a token +hash on the server. The web-client design for this flow is documented in +[Viewer Token UI Design](viewer-token-ui-design.md). Runtime client methods are +not implemented in this repository yet. + +The current server also serves a token-scoped read-only incident viewer. The +intended web-client direction is to replace that surface while using the same +viewer-token authority model for no-account notification contacts. That viewer +path is distinct from the future trusted-contact account system and must not +imply browser decryption, key unwrapping, playable export, notification +delivery, emergency dispatch, or public production readiness. + +No incident-token list or read route is currently documented for long-term +management UI. Until such a backend contract exists, the web client should not +promise a durable token table or post-reload revocation workflow for tokens +created outside the current browser flow. + ## Frontend Metadata Boundary Incident detail parsing keeps browser state focused on public-safe metadata. diff --git a/docs/security-model.md b/docs/security-model.md index 9a6e810..f67ce0c 100644 --- a/docs/security-model.md +++ b/docs/security-model.md @@ -48,6 +48,25 @@ persisted in browser storage, screenshotted, copied into public issue drafts, included in analytics, or exposed in UI beyond the transient browser URL fragment needed to complete verification. +## Viewer Token Boundary + +Viewer tokens are bearer secrets for no-account, read-only incident access. +The planned web-client viewer should replace the current server-rendered viewer +while preserving the same server token semantics. It may show only unencrypted +incident data that the backend intentionally exposes to token holders, such as +safe status, check-in, current-location, or device-state fields when those +fields are part of the viewer payload. + +Viewer-token UI must not persist raw tokens, viewer links, or token-bearing +paths in browser storage. It must not send raw token values to analytics, logs, +public issues, PR text, or error messages. Invalid, expired, missing, and +revoked token states should collapse into generic viewer errors. + +Viewer-token access is separate from the future trusted-contact account system. +Trusted contacts require their own accept/decline flow, account identity, +client-side private-key creation, public-key storage, encrypted evidence access +design, and key-custody threat model. + ## Browser Cookie Auth And CSRF The implemented live client supports bearer-token auth and explicit diff --git a/docs/threat-model.md b/docs/threat-model.md index 9814593..fe8e224 100644 --- a/docs/threat-model.md +++ b/docs/threat-model.md @@ -9,6 +9,8 @@ in `open-proofline/server`. - Browser session cookies when cookie auth mode is enabled. - CSRF tokens for cookie-authenticated unsafe requests. - Raw email-verification tokens carried in verification URL fragments. +- Raw viewer tokens and token-bearing viewer links returned once after owner + creation. - Account metadata visible to the authenticated user. - Incident, stream, chunk, contact public-key, sharing-grant, and wrapped-key metadata. @@ -32,6 +34,9 @@ in `open-proofline/server`. - Browser local storage can retain credentials after a session should be gone. - Verification URL fragments can be exposed by screenshots, browser extensions, debugging tools, copied issue text, or analytics if handled carelessly. +- Viewer tokens or token-bearing links can be exposed through clipboard history, + logs, analytics, referrers, screenshots, issue drafts, or persistent browser + storage if the one-time success state is mishandled. - Registration UI wording could expose account-existence state if it diverges from the server's generic verification-required response. - Cookie-auth mode could accidentally mix bearer and cookie credentials, @@ -50,5 +55,6 @@ in `open-proofline/server`. Recording, decryption, key escrow, break-glass access, trusted-contact decryption, payment processing, public-production account portal claims, -emergency notifications, and playable media export are out of scope until -explicitly designed and reviewed. +emergency notifications, notification delivery channels, trusted-contact +account/key flows, and playable media export are out of scope until explicitly +designed and reviewed. diff --git a/docs/viewer-token-ui-design.md b/docs/viewer-token-ui-design.md new file mode 100644 index 0000000..1033076 --- /dev/null +++ b/docs/viewer-token-ui-design.md @@ -0,0 +1,185 @@ +# Viewer Token UI Design + +This note scopes the planned browser UI for owner-created viewer tokens and +the future web-client read-only viewer. The backend source of truth remains +`open-proofline/server`. + +## Goals + +- Let an authenticated incident owner create a read-only viewer token for one + incident. +- Show the raw viewer token or token-bearing viewer link exactly once, with + explicit copy and clear controls. +- Let the owner revoke a known viewer token by server-issued token ID. +- Keep no-account viewer access separate from the account-based trusted-contact + system. +- Keep the web-client viewer limited to read-only unencrypted incident data, + such as safe incident status, latest check-in, current location, or device + state when those fields are present in the server viewer contract. + +## Current Server Contract + +Current `open-proofline/server` docs and route registration confirm: + +- `POST /v1/incidents/{incident_id}/incident-tokens` +- `POST /v1/incident-tokens/{token_id}/revoke` + +The create route is owner-scoped, returns the raw token only in the creation +response, stores only a SHA-256 token hash in the configured metadata backend, +and sends `Cache-Control: no-store`. `expires_at` is optional. If omitted, the +server applies its configured default token lifetime, currently 24 hours unless +the deployment changes `SAFE_DEFAULT_INCIDENT_TOKEN_TTL`. Sending `null` +requests a token that remains valid until revoked. + +The revoke route disables a token by token ID. There is currently no documented +incident-token list or read route for long-term browser management UI. Until +that route exists, the web client should not promise a durable token table or +post-reload revoke workflow for tokens created elsewhere. + +The server still provides the current token-scoped `/i/{token}` incident +viewer. The intended product direction is for the web-client viewer to replace +that surface while preserving the same viewer-token authority model. + +## Access Models + +Viewer-token access is the no-account contact path. A contact may receive an +email, SMS, Messenger, Signal, or other notification that leads to read-only +incident viewer access. Notification delivery is not part of this design note. +The token grants read-only access to the server-defined viewer payload and must +not imply account access, trusted-contact status, emergency dispatch, or +encrypted evidence decryption. + +Trusted contacts are a separate account-based system. That future flow requires +a Proofline account, an accept or decline step after the owner adds a contact by +email or username, client-side private key creation, public key storage, and +client support for viewing or downloading encrypted evidence. That flow is not +implemented by viewer-token UI and needs its own design, backend contract, and +key-custody threat model. + +## Owner Create UI + +The authenticated incident detail view should expose a "Viewer access" section +only after the user is signed in and the incident detail is loaded. + +The create form should include: + +- a short label for the intended recipient or purpose; +- an expiry control with the server default as the recommended path; +- an explicit option for no expiry until revoked, with warning copy; +- a submit button that names the action without implying notifications are sent. + +The form must not accept raw token input. It should not mention or reveal server +storage paths, object keys, private deployment details, contact private keys, +wrapped-key ciphertext, raw media keys, plaintext, or request bodies. + +Recommended create copy: + +- Button: "Create viewer link" +- Default expiry helper: "Uses the server default expiry unless changed." +- No-expiry warning: "This link remains valid until revoked. Use this only for + a reviewed contact path." +- Success heading: "Viewer link created" +- One-time warning: "Copy it now. Proofline will not show this link again after + you clear it." + +## One-Time Token Handling + +The raw viewer token is a bearer secret. The browser may hold it only in +component memory for the active success state. + +Required handling: + +- do not store the raw token in `localStorage`, `sessionStorage`, IndexedDB, URL + query parameters, persisted TanStack Query cache, service-worker cache, logs, + analytics, telemetry, screenshots, issue drafts, or error messages; +- do not send the raw token to any route other than the intended viewer route; +- do not include raw token values in test fixtures, public docs, issue bodies, + PR text, or changelog entries; +- clear the raw token when the user dismisses the success state, navigates away, + logs out, changes auth mode, or resets session state; +- copy only from a user gesture and report generic copy failures; +- prefer copying a token-bearing viewer link over showing the bare token, while + still treating the full link as the same secret. + +The success panel should use an accessible status region, keep warning text next +to the copy action, and provide an explicit "Clear link" control. After clear, +the UI may keep non-secret creation metadata such as label, token ID, created +time, and expiry in volatile browser state, but must not reconstruct or reveal +the raw token again. + +## Revoke UI + +Revocation should be available only for token IDs the browser knows. With the +current server contract, that means tokens created during the current browser +flow. A durable management table requires a future list/read backend route that +returns non-secret token metadata. + +The revoke action should: + +- require explicit confirmation for active tokens; +- identify tokens by label, created time, and expiry rather than raw token; +- call `POST /v1/incident-tokens/{token_id}/revoke`; +- remove or mark the local row revoked after success; +- keep failures generic, for example "Viewer access could not be revoked."; +- treat missing, already revoked, unauthorized, and ownership-boundary failures + as non-revealing UI errors. + +Revoke copy must not claim clawback. A recipient who already copied data or kept +an open page may have seen read-only incident data before revocation. + +## Web-Client Viewer Target + +The replacement web-client viewer should consume the same viewer-token system +but remain separate from authenticated owner UI. + +Viewer route behavior should be designed before implementation: + +- accept a token-bearing route without storing the token in browser storage; +- request only the server-defined read-only viewer payload; +- show unencrypted incident status data that the server intentionally exposes to + token holders; +- avoid encrypted evidence decryption, key unwrapping, playable export, + account login, trusted-contact private-key handling, and emergency response + claims; +- use no-store cache behavior and conservative browser headers at deployment; +- collapse invalid, expired, missing, and revoked token states into generic + errors that do not reveal token validity. + +If the replacement viewer needs a different route shape or link origin from the +current server `/i/{token}` path, that must be resolved with server and +deployment docs before runtime implementation. + +## Error And Empty States + +Create errors should be short and generic: + +- incident not found or not owned: "Viewer access could not be created."; +- invalid expiry: "Enter a valid expiry time or use the default."; +- auth expired: "Sign in again to create viewer access."; +- network/server failure: "Viewer access could not be created."; + +Revoke errors should also stay generic: + +- unknown, already revoked, or not owned: "Viewer access could not be revoked."; +- auth expired: "Sign in again to revoke viewer access."; +- network/server failure: "Viewer access could not be revoked."; + +The UI should include `role="status"` for success and loading states and +`role="alert"` for blocking errors. Buttons should have stable disabled states +while requests are pending. + +## Follow-Up Implementation Questions + +Before runtime implementation, decide: + +- the exact web-client viewer route and configured public viewer origin; +- whether owner create UI may create a server `/i/{token}` link during the + transition or must wait for the web-client viewer route; +- whether the server should add a non-secret incident-token list/read route for + long-term revocation management; +- which viewer payload fields are intended for no-account notification contacts, + especially current location and device state; +- whether notification delivery channels are server, client, or separate + service responsibilities; +- how deployment will keep token-bearing paths out of access logs, referrers, + analytics, and public issue text.