Commit b84a893
fix(gdpr): lift paid-plan gate on Full Organization Reset (Article 17)
Pre-fix: the only self-serve right-to-erasure path in the dashboard was
the "Reset Everything" button under Settings → Danger Zone, and it was
gated behind `_require_active_paid_plan(user, db)` on the backend +
admin-feature check on the frontend. Free-tier admins who wanted to
exercise their GDPR Article 17 right had no in-app path — they had to
file a GitHub issue and wait for operator intervention. /legal/privacy
§6 explicitly promised the action was self-serve, so the gate was both
a compliance hazard and a contradiction of our own published policy.
Right-to-erasure is a legal obligation we cannot gate behind a paid
subscription. Lifted the gate.
Done split, not blanket-lift: the sibling `/settings/danger/wipe-logs`
endpoint is operator-convenience (selective stream + MCP-activity log
purge while keeping nodes/cameras/settings intact) — that's not a
right-to-erasure operation and stays Pro / Pro Plus.
Changes:
Backend
- `app/api/cameras.py::full_reset` — removed the
`_require_active_paid_plan(user, db)` call. Docstring rewritten
to explicitly call out the GDPR Article 17 framing + why the gate
was wrong + what stays paid-only (wipe-logs).
- `app/api/cameras.py::wipe_stream_logs` — docstring tightened to
explain why this one IS still paid-only and to point at full-reset
as the Free-tier erasure path.
- `tests/test_cameras.py` — flipped the negative test:
`test_full_reset_requires_paid_plan_in_db_not_just_jwt` (expected
403 on free_org) replaced with `test_full_reset_works_on_free_plan`
(expected 200 on free_org). Companion `…_works_on_pro_plus_too`
pins the happy path didn't regress.
- `tests/test_gdpr.py::test_full_reset_clears_every_org_scoped_table`
— dropped the `_require_active_paid_plan` monkeypatch; the helper
no longer needs stubbing because it's no longer called.
Frontend
- `SettingsPage.jsx` — per-item gating instead of section-level:
Full Organization Reset is always shown (with a paragraph
explicitly identifying it as the GDPR Article 17 right-to-erasure
action); Wipe All Logs is locked behind a 🔒 Pro / Pro Plus badge
+ Upgrade button for free-tier admins.
- `index.css` — added `.danger-item-locked` + `.plan-locked-badge`
rules for the per-item lock state. Kept legacy `.danger-locked`
class for back-compat.
- `LegalPage.jsx` §6 — added explicit "Reset Everything is
available on every plan, including Free" sentence so the privacy
policy matches the now-actual behaviour.
Docs
- `docs/Plans.jsx` — split the "Danger-zone tools" row into two:
Full reset = Yes/Yes/Yes (every plan), Selective log wipe =
—/Yes/Yes (Pro+).
- `docs/Dashboard.jsx` — Danger Zone bullet rewritten with the same
split.
- `AGENTS.md` — API Routes table for both endpoints now spells out
the plan tier ("every plan" vs "Pro/Pro Plus") + the framing
rationale.
Refreshed `backend/static/` from a clean build. 612 backend tests
pass; frontend build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent faf4300 commit b84a893
16 files changed
Lines changed: 143 additions & 62 deletions
File tree
- backend
- app/api
- static
- assets
- tests
- frontend/src
- pages
- docs
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
338 | 338 | | |
339 | 339 | | |
340 | 340 | | |
341 | | - | |
342 | | - | |
| 341 | + | |
| 342 | + | |
343 | 343 | | |
344 | 344 | | |
345 | 345 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
785 | 785 | | |
786 | 786 | | |
787 | 787 | | |
788 | | - | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
789 | 800 | | |
790 | 801 | | |
791 | 802 | | |
| |||
817 | 828 | | |
818 | 829 | | |
819 | 830 | | |
820 | | - | |
| 831 | + | |
821 | 832 | | |
822 | 833 | | |
823 | 834 | | |
| |||
835 | 846 | | |
836 | 847 | | |
837 | 848 | | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
838 | 860 | | |
839 | | - | |
840 | 861 | | |
841 | 862 | | |
842 | 863 | | |
| |||
Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
| 36 | + | |
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
440 | 440 | | |
441 | 441 | | |
442 | 442 | | |
443 | | - | |
444 | | - | |
445 | | - | |
446 | | - | |
447 | | - | |
448 | | - | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
449 | 455 | | |
450 | 456 | | |
451 | 457 | | |
452 | | - | |
453 | | - | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
454 | 462 | | |
455 | 463 | | |
456 | | - | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
457 | 468 | | |
458 | 469 | | |
459 | 470 | | |
| |||
0 commit comments