feat: Private notes enforcement (is_public filtering + API key auth) #178

Closed
opened 2026-03-15 01:53:15 +00:00 by forgejo_admin · 0 comments

Lineage

plan-pal-e-docs → Phase F6 (Private Notes Enforcement)

Repo

forgejo_admin/pal-e-docs

User Story

As a pal-e-docs user
I want private notes to be hidden from anonymous requests
So that journal entries and private thoughts are not exposed to unauthenticated visitors

Context

is_public field exists on every note (default True) but is never filtered in any endpoint. list_notes(), search_notes(), and semantic_search() all return private notes to anyone. This is a security gap — the "I have a private thought" pattern (journal notes under Private project) depends on privacy enforcement. The fix: add a simple API key header (X-PaleDocs-Token) that pal-e-app passes from server-side fetches. If present and valid → show all notes. If absent → hide is_public=false notes.

File Targets

Files to modify:

  • src/pal_e_docs/routes/notes.py — add is_public filtering to list_notes (line 346), _build_filter_clauses (line 157), _keyword_search (line 193), _semantic_search_notes (line 293); add auth dependency to check X-PaleDocs-Token header
  • src/pal_e_docs/schemas.py — add is_public: bool to NoteSearchResult (line 174) and SemanticSearchResult (line 186)
  • src/pal_e_docs/config.py — add PAL_E_DOCS_API_KEY setting (optional, defaults to None = no enforcement)

Files NOT to touch:

  • src/pal_e_docs/routes/boards.py — board privacy is out of scope
  • alembic/ — no migration needed, is_public column already exists

Acceptance Criteria

  • When I GET /notes without X-PaleDocs-Token header, then notes with is_public=false are excluded
  • When I GET /notes with valid X-PaleDocs-Token header, then all notes including private are returned
  • When I GET /notes/search?q=... without auth, then private notes are excluded from keyword, semantic, and hybrid results
  • When I GET /notes/{slug} for a private note without auth, then 404 is returned
  • When I GET /notes/{slug} for a private note with valid auth, then the note is returned
  • When PAL_E_DOCS_API_KEY is not set, all notes are visible (backwards compatible)

Test Expectations

  • Unit test: list_notes excludes is_public=false when unauthenticated
  • Unit test: search endpoints exclude private notes when unauthenticated
  • Unit test: get_note returns 404 for private note when unauthenticated
  • Unit test: all endpoints return private notes when X-PaleDocs-Token matches
  • Run command: cd ~/pal-e-docs && .venv/bin/pytest tests/ -v

Constraints

  • Follow existing route patterns in routes/notes.py
  • Use FastAPI Depends() for the auth check — create a reusable dependency
  • API key comparison must be constant-time (use secrets.compare_digest)
  • Don't break any existing tests — is_public filtering should be opt-in (only active when PAL_E_DOCS_API_KEY env var is set)
  • The semantic_search endpoint at /notes/semantic-search (line 511) also needs filtering

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • pal-e-docs — project this affects
  • Phase F6 frontend (pal-e-app) depends on this
### Lineage `plan-pal-e-docs` → Phase F6 (Private Notes Enforcement) ### Repo `forgejo_admin/pal-e-docs` ### User Story As a pal-e-docs user I want private notes to be hidden from anonymous requests So that journal entries and private thoughts are not exposed to unauthenticated visitors ### Context `is_public` field exists on every note (default True) but is never filtered in any endpoint. list_notes(), search_notes(), and semantic_search() all return private notes to anyone. This is a security gap — the "I have a private thought" pattern (journal notes under Private project) depends on privacy enforcement. The fix: add a simple API key header (`X-PaleDocs-Token`) that pal-e-app passes from server-side fetches. If present and valid → show all notes. If absent → hide is_public=false notes. ### File Targets Files to modify: - `src/pal_e_docs/routes/notes.py` — add is_public filtering to list_notes (line 346), _build_filter_clauses (line 157), _keyword_search (line 193), _semantic_search_notes (line 293); add auth dependency to check X-PaleDocs-Token header - `src/pal_e_docs/schemas.py` — add `is_public: bool` to NoteSearchResult (line 174) and SemanticSearchResult (line 186) - `src/pal_e_docs/config.py` — add PAL_E_DOCS_API_KEY setting (optional, defaults to None = no enforcement) Files NOT to touch: - `src/pal_e_docs/routes/boards.py` — board privacy is out of scope - `alembic/` — no migration needed, is_public column already exists ### Acceptance Criteria - [ ] When I GET /notes without X-PaleDocs-Token header, then notes with is_public=false are excluded - [ ] When I GET /notes with valid X-PaleDocs-Token header, then all notes including private are returned - [ ] When I GET /notes/search?q=... without auth, then private notes are excluded from keyword, semantic, and hybrid results - [ ] When I GET /notes/{slug} for a private note without auth, then 404 is returned - [ ] When I GET /notes/{slug} for a private note with valid auth, then the note is returned - [ ] When PAL_E_DOCS_API_KEY is not set, all notes are visible (backwards compatible) ### Test Expectations - [ ] Unit test: list_notes excludes is_public=false when unauthenticated - [ ] Unit test: search endpoints exclude private notes when unauthenticated - [ ] Unit test: get_note returns 404 for private note when unauthenticated - [ ] Unit test: all endpoints return private notes when X-PaleDocs-Token matches - Run command: `cd ~/pal-e-docs && .venv/bin/pytest tests/ -v` ### Constraints - Follow existing route patterns in routes/notes.py - Use FastAPI Depends() for the auth check — create a reusable dependency - API key comparison must be constant-time (use secrets.compare_digest) - Don't break any existing tests — is_public filtering should be opt-in (only active when PAL_E_DOCS_API_KEY env var is set) - The semantic_search endpoint at /notes/semantic-search (line 511) also needs filtering ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `pal-e-docs` — project this affects - Phase F6 frontend (pal-e-app) depends on this
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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
forgejo_admin/pal-e-api#178
No description provided.