Collab token issuance and connection authentication #34

Closed
opened 2026-07-04 14:52:14 +02:00 by fable-5 · 2 comments
Collaborator

Context

Connections must be authorized by the API — the collab server never talks to session cookies (ADR 0003/0007).

Scope

API: GET /pages/:id/collab-token returns a JWT (≤60 s TTL, claims: userId, pageId, mode rw/ro) signed with the shared env key, using the interim access service (real permissions land in #53). Collab: onAuthenticate verifies signature/expiry/pageId match, stores user context on the connection, enforces ro by dropping inbound document messages. Shared claim schema in packages/shared.

Acceptance criteria

  • valid token connects; expired/tampered/mismatched-page tokens are rejected (integration tests)
  • ro connections receive updates but their document writes are discarded server-side (test with two clients)
  • token endpoint requires authentication and logs issuance at debug level without the token value

Technical notes

  • ADR 0007 (the only JWTs in the system), realtime-collaboration.md sequence diagram.

Dependencies

Depends on #23, #33.

Size: ~1 day


Conventions: English code/comments, clear human-readable code, no hard-coded UI strings (ADR 0012, add de and en), permission checks only via the shared guard (docs/architecture/permissions.md). Read the referenced ADRs before starting.

## Context Connections must be authorized by the API — the collab server never talks to session cookies (ADR 0003/0007). ## Scope API: `GET /pages/:id/collab-token` returns a JWT (≤60 s TTL, claims: userId, pageId, mode rw/ro) signed with the shared env key, using the interim access service (real permissions land in #53). Collab: `onAuthenticate` verifies signature/expiry/pageId match, stores user context on the connection, enforces `ro` by dropping inbound document messages. Shared claim schema in `packages/shared`. ## Acceptance criteria - [ ] valid token connects; expired/tampered/mismatched-page tokens are rejected (integration tests) - [ ] `ro` connections receive updates but their document writes are discarded server-side (test with two clients) - [ ] token endpoint requires authentication and logs issuance at debug level without the token value ## Technical notes - ADR 0007 (the only JWTs in the system), realtime-collaboration.md sequence diagram. ## Dependencies Depends on #23, #33. **Size**: ~1 day --- *Conventions: English code/comments, clear human-readable code, no hard-coded UI strings (ADR 0012, add `de` **and** `en`), permission checks only via the shared guard (docs/architecture/permissions.md). Read the referenced ADRs before starting.*
fable-5 added this to the M3 — Real-time collaboration & history milestone 2026-07-04 14:52:14 +02:00
fable-5 added the
auth
backend
collab
labels 2026-07-04 14:52:14 +02:00
Owner

▎ Verified on Test and Int (commit d4ebcfc, pipeline green — all CI + CD jobs incl. Int promotion).

▎ - Both dorfteich-{test,int}-collab-1 and -api-1 are Up (healthy) after restarting with the new shared COLLAB_TOKEN_SECRET.
▎ - GET /pages/:id/collab-token returns 401 unauthenticated (route deployed + guarded).
▎ - Full authenticated loop on Test (fixture-user → create page → token): 200, mode: rw, expiresInSeconds: 60, a valid 3-part HS256 JWT.

▎ Implementation notes:
▎ - The token is the only JWT in the system (ADR 0007), HS256 via node:crypto. Sign/verify live in packages/shared/src/token-crypto.ts behind the @dorfteich/shared/token-crypto subpath export (with typesVersions for the api's node10 resolution) so the web bundle never pulls in node:crypto; browser-safe schemas/types stay in the barrel.
▎ - collab onAuthenticate verifies signature/expiry, requires claims.pageId === documentName, and enforces ro via Hocuspocus' connectionConfig.readOnly. Read-only enforcement is covered by a two-client integration test (@hocuspocus/provider).
▎ - COLLAB_TOKEN_SECRET is shared by api and collab (must match); set per stage in each .env, required in the stage compose.

▎ Verified on Test and Int (commit d4ebcfc, pipeline green — all CI + CD jobs incl. Int promotion). ▎ ▎ - Both dorfteich-{test,int}-collab-1 and -api-1 are Up (healthy) after restarting with the new shared COLLAB_TOKEN_SECRET. ▎ - GET /pages/:id/collab-token returns 401 unauthenticated (route deployed + guarded). ▎ - Full authenticated loop on Test (fixture-user → create page → token): 200, mode: rw, expiresInSeconds: 60, a valid 3-part HS256 JWT. ▎ ▎ Implementation notes: ▎ - The token is the only JWT in the system (ADR 0007), HS256 via node:crypto. Sign/verify live in packages/shared/src/token-crypto.ts behind the @dorfteich/shared/token-crypto subpath export (with typesVersions for the api's node10 resolution) so the web bundle never pulls in node:crypto; browser-safe schemas/types stay in the barrel. ▎ - collab onAuthenticate verifies signature/expiry, requires claims.pageId === documentName, and enforces ro via Hocuspocus' connectionConfig.readOnly. Read-only enforcement is covered by a two-client integration test (@hocuspocus/provider). ▎ - COLLAB_TOKEN_SECRET is shared by api and collab (must match); set per stage in each .env, required in the stage compose.
Collaborator

Reposting the verification note cleanly under the model account — the earlier comment was pasted manually and picked up formatting artifacts (leading quote bars).

Verified on Test and Int (commit d4ebcfc, pipeline green — all CI + CD jobs incl. Int promotion).

  • Both dorfteich-{test,int}-collab-1 and -api-1 are Up (healthy) after restarting with the new shared COLLAB_TOKEN_SECRET.
  • GET /pages/:id/collab-token returns 401 unauthenticated (route deployed + guarded).
  • Full authenticated loop on Test (fixture-user → create page → token): 200, mode: rw, expiresInSeconds: 60, a valid 3-part HS256 JWT.

Implementation notes:

  • The token is the only JWT in the system (ADR 0007), HS256 via node:crypto. Sign/verify live in packages/shared/src/token-crypto.ts behind the @dorfteich/shared/token-crypto subpath export (with typesVersions for the api's node10 resolution) so the web bundle never pulls in node:crypto; browser-safe schemas/types stay in the barrel.
  • collab onAuthenticate verifies signature/expiry, requires claims.pageId === documentName, and enforces ro via Hocuspocus' connectionConfig.readOnly. Read-only enforcement is covered by a two-client integration test (@hocuspocus/provider).
  • COLLAB_TOKEN_SECRET is shared by api and collab (must match); set per stage in each .env, required in the stage compose.
_Reposting the verification note cleanly under the model account — the earlier comment was pasted manually and picked up formatting artifacts (leading `▎` quote bars)._ Verified on Test and Int (commit d4ebcfc, pipeline green — all CI + CD jobs incl. Int promotion). - Both `dorfteich-{test,int}-collab-1` and `-api-1` are Up (healthy) after restarting with the new shared `COLLAB_TOKEN_SECRET`. - `GET /pages/:id/collab-token` returns 401 unauthenticated (route deployed + guarded). - Full authenticated loop on Test (fixture-user → create page → token): 200, `mode: rw`, `expiresInSeconds: 60`, a valid 3-part HS256 JWT. Implementation notes: - The token is the only JWT in the system (ADR 0007), HS256 via `node:crypto`. Sign/verify live in `packages/shared/src/token-crypto.ts` behind the `@dorfteich/shared/token-crypto` subpath export (with `typesVersions` for the api's node10 resolution) so the web bundle never pulls in `node:crypto`; browser-safe schemas/types stay in the barrel. - collab `onAuthenticate` verifies signature/expiry, requires `claims.pageId === documentName`, and enforces `ro` via Hocuspocus' `connectionConfig.readOnly`. Read-only enforcement is covered by a two-client integration test (`@hocuspocus/provider`). - `COLLAB_TOKEN_SECRET` is shared by api and collab (must match); set per stage in each `.env`, required in the stage compose.
Sign in to join this conversation.
No project
No Assignees
3 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: stwaidele/dorfteich#34
No description provided.