Archive mobile-terms-gate + sync spec delta

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-05-08 02:02:24 +02:00
parent 067e2ebd0b
commit 1e43e96732
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
4 changed files with 17 additions and 1 deletions

View file

@ -0,0 +1,29 @@
## Why
Mobile API requests authenticated via OAuth2 bearer tokens currently bypass the Terms gate. Web users with a stale `users.terms_version` are redirected to `/auth/accept-terms` by the journal root loader (`apps/journal/app/root.tsx:58`), but mobile clients hitting `/api/v1/*` with the same stale version get a normal 200 response. The Terms gate (defined in `journal-auth/spec.md`) is meant to apply to **all** authenticated requests, not only cookie-session traffic.
## What Changes
- Extend the bearer-token API chokepoint (`requireApiUser` in `apps/journal/app/lib/api-guard.server.ts`) to compare the authenticated user's `termsVersion` against the current `TERMS_VERSION`. On mismatch, return a structured **HTTP 403** with `{ error, code: "TERMS_OUTDATED", currentTermsVersion }` so the mobile app can surface its own UI (or open the web Terms page in a webview).
- Add `TERMS_OUTDATED` to `packages/api/src/errors.ts` `ERROR_CODES` so clients have a stable code to switch on.
- Spec delta on `journal-auth`: add a requirement that the Terms gate also applies to bearer-token API auth, with the structured-error contract as a scenario.
Web cookie-session behaviour is unchanged (still a redirect via the root loader). Only the API chokepoint changes.
## Capabilities
### New Capabilities
(none)
### Modified Capabilities
- `journal-auth`: adds a requirement that the Terms-version gate applies to bearer-token API requests, returning HTTP 403 with a structured `terms_outdated` payload instead of a redirect.
## Impact
- **Code**: one function (`requireApiUser`) gains a terms check; one constant added to `@trails-cool/api`.
- **Clients**: the mobile app needs to handle `403 { code: "TERMS_OUTDATED" }` (out of scope for this change — tracked separately on the mobile side).
- **Tests**: unit test for `requireApiUser` covering stale / current / null `termsVersion`.
- **Specs**: 1 spec delta on `journal-auth` (added requirement).
- **Out of scope**: mobile client UI for the new error; any change to web cookie-session behaviour.

View file

@ -0,0 +1,17 @@
## ADDED Requirements
### Requirement: Terms gate applies to bearer-token API requests
Authenticated API requests carrying an OAuth2 bearer token SHALL be subject to the same Terms-version gate as cookie-session requests. When the authenticated user's stored `terms_version` is NULL or differs from the current `TERMS_VERSION`, the API chokepoint (`requireApiUser`) SHALL reject the request with HTTP 403 and a structured JSON body `{ error, code: "TERMS_OUTDATED", currentTermsVersion: <current> }` instead of returning the resource. Cookie-session web traffic continues to be handled by the root-loader redirect to `/auth/accept-terms`; only the API path uses the structured 403.
#### Scenario: Stale bearer token receives structured 403
- **WHEN** a request to `/api/v1/*` arrives with a valid bearer token whose user has `terms_version` NULL or different from the current `TERMS_VERSION`
- **THEN** the server responds with HTTP 403 and JSON `{ error: <message>, code: "TERMS_OUTDATED", currentTermsVersion: <current> }`
- **AND** the requested resource is not returned
#### Scenario: Up-to-date bearer token is allowed through
- **WHEN** a request to `/api/v1/*` arrives with a valid bearer token whose user's `terms_version` matches the current `TERMS_VERSION`
- **THEN** the request proceeds normally
#### Scenario: Anonymous API request still 401
- **WHEN** a request to `/api/v1/*` arrives without a bearer token (or with an invalid one)
- **THEN** the server responds with HTTP 401 `UNAUTHORIZED` as before — the Terms check only applies after authentication succeeds

View file

@ -0,0 +1,13 @@
## 1. Implementation
- [x] 1.1 Add `TERMS_OUTDATED: "TERMS_OUTDATED"` to `ERROR_CODES` in `packages/api/src/errors.ts`.
- [x] 1.2 Extend `requireApiUser` in `apps/journal/app/lib/api-guard.server.ts` to compare the authenticated user's `termsVersion` to `TERMS_VERSION` (from `~/lib/legal`). On mismatch (or NULL), throw a `Response` with status 403 and JSON body `{ error, code: ERROR_CODES.TERMS_OUTDATED, currentTermsVersion: TERMS_VERSION }`.
## 2. Tests
- [x] 2.1 Add `apps/journal/app/lib/api-guard.server.test.ts` covering: stale `termsVersion` → 403 with `TERMS_OUTDATED` + `currentTermsVersion`; matching `termsVersion` → returns user; null `termsVersion` → 403; no auth → 401 (unchanged).
## 3. Verification
- [x] 3.1 Run `pnpm typecheck && pnpm lint && pnpm test` — all green.
- [x] 3.2 Open draft PR against `main` (#366).