Data export for account holders (GDPR portability) #68

Closed
opened 2026-07-04 14:52:35 +02:00 by fable-5 · 1 comment
Collaborator

Context

Access/portability requests must be answerable by the user alone (security.md §Privacy).

Scope

Endpoint + settings UI: 'Export my data' — ZIP with profile JSON, list of memberships/grants, and the Markdown export of the personal pond plus owned shared ponds (reusing #65); rate-limited; generated as a job with download link (expiring).

Acceptance criteria

  • export contains profile + personal pond content (fixture verification)
  • another user's data never appears (memberships listed, foreign content not included)
  • link expires; re-request rate-limited

Technical notes

  • security.md §Privacy, #65 (reuse, do not duplicate).

Dependencies

Depends on #65.

Size: ~0.5 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 Access/portability requests must be answerable by the user alone (security.md §Privacy). ## Scope Endpoint + settings UI: 'Export my data' — ZIP with profile JSON, list of memberships/grants, and the Markdown export of the personal pond plus owned shared ponds (reusing #65); rate-limited; generated as a job with download link (expiring). ## Acceptance criteria - [ ] export contains profile + personal pond content (fixture verification) - [ ] another user's data never appears (memberships listed, foreign content not included) - [ ] link expires; re-request rate-limited ## Technical notes - security.md §Privacy, #65 (reuse, do not duplicate). ## Dependencies Depends on #65. **Size**: ~0.5 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 M6 — Import, export & attachments milestone 2026-07-04 14:52:35 +02:00
fable-5 added the
auth
backend
labels 2026-07-04 14:52:35 +02:00
Collaborator

Implemented in 462eca9 (pipeline green: all 7 CI/CD contexts, deployed to Test + promoted to Int).

Self-service GDPR data export, built on the existing conversion-job queue (#62) and the pond Markdown export (#65) — reused, not duplicated.

Endpoint & job

  • POST /users/me/data-export (@AuthenticatedOnly), rate-limited per account (3 / hour → 429 rate_limited). Enqueues a data_export conversion job; the client polls and downloads via the generic owner-scoped GET /jobs/:id / GET /jobs/:id/result.
  • The worker branches on kind === 'data_export', resolves DataExportService through a token (no DI cycle, mirroring the import pipeline), builds the ZIP and stores it with an expiresAt (24 h).

ZIP contents

  • profile.json — the caller's own account fields (the data-minimization set).
  • memberships.json — every role grant the user holds, grouped per pond with an owner flag: metadata only, no foreign content.
  • ponds/<slug>/… — Markdown + media for only the ponds the user owns (personal + owned shared), each routed through the shared read-permission filter.

Expiry & minimization

  • The download 404s once expiresAt passes; an hourly scheduled data-export-purge job drops the stored bytes (security.md §Privacy).

Acceptance criteria

  • export contains profile + personal pond content — verified by fixture (data-export.service.db.test.ts).
  • another user's data never appears — memberships list the foreign pond, but its TOP SECRET content never appears anywhere in the archive (asserted).
  • link expires; re-request rate-limited — expiry 404 + purge + 429 on the 4th request within the window.

Notable

  • Refactored ExportService.streamPondMarkdownZip into a shared appendPondMarkdown(archive, user, pond, prefix) used by both the pond ZIP and the data export (byte-identical for the #65 path, prefix='').
  • Web: shared pollJob / downloadJobResult between the document-export and data-export hooks (job-download.ts).
  • Schema: ConversionJob.expiresAt (+ migration 20260710120000_data_export_expiry).
  • Reviewed at medium (2 agents): 0 correctness findings; the one reuse finding (web poll/download duplication) is addressed.

No stage provisioning needed — no new sidecar or compose change (the queue and scheduler already exist on both stages).

Implemented in `462eca9` (pipeline green: all 7 CI/CD contexts, deployed to Test + promoted to Int). Self-service GDPR data export, built on the existing conversion-job queue (#62) and the pond Markdown export (#65) — reused, not duplicated. **Endpoint & job** - `POST /users/me/data-export` (`@AuthenticatedOnly`), rate-limited per account (3 / hour → `429 rate_limited`). Enqueues a `data_export` conversion job; the client polls and downloads via the generic owner-scoped `GET /jobs/:id` / `GET /jobs/:id/result`. - The worker branches on `kind === 'data_export'`, resolves `DataExportService` through a token (no DI cycle, mirroring the import pipeline), builds the ZIP and stores it with an `expiresAt` (24 h). **ZIP contents** - `profile.json` — the caller's own account fields (the data-minimization set). - `memberships.json` — every role grant the user holds, grouped per pond with an `owner` flag: **metadata only, no foreign content**. - `ponds/<slug>/…` — Markdown + media for **only the ponds the user owns** (personal + owned shared), each routed through the shared read-permission filter. **Expiry & minimization** - The download 404s once `expiresAt` passes; an hourly scheduled `data-export-purge` job drops the stored bytes (security.md §Privacy). **Acceptance criteria** - [x] export contains profile + personal pond content — verified by fixture (`data-export.service.db.test.ts`). - [x] another user's data never appears — memberships list the foreign pond, but its `TOP SECRET` content never appears anywhere in the archive (asserted). - [x] link expires; re-request rate-limited — expiry 404 + purge + `429` on the 4th request within the window. **Notable** - Refactored `ExportService.streamPondMarkdownZip` into a shared `appendPondMarkdown(archive, user, pond, prefix)` used by both the pond ZIP and the data export (byte-identical for the #65 path, `prefix=''`). - Web: shared `pollJob` / `downloadJobResult` between the document-export and data-export hooks (`job-download.ts`). - Schema: `ConversionJob.expiresAt` (+ migration `20260710120000_data_export_expiry`). - Reviewed at `medium` (2 agents): 0 correctness findings; the one reuse finding (web poll/download duplication) is addressed. No stage provisioning needed — no new sidecar or compose change (the queue and scheduler already exist on both stages).
Sign in to join this conversation.
No project
No Assignees
2 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#68
No description provided.