feat: security hardening and polish for product release #5

Merged
patillacode merged 26 commits from feature/security-and-polish into main 2026-04-20 18:11:48 +02:00
Owner

Security hardening, publishing flow redesign, and polish

Security (9 fixes)

  • HSTS + Permissions-Policy headers added to all responses; HSTS only sent when SECURE_COOKIES=true
  • Session versioning: session_version column on User; token format updated to include version; stale tokens
    rejected on every request
  • Session invalidation on password change: admin reset and self-change both increment session_version,
    logging out all other devices; self-change re-issues cookie so the current session stays valid
  • Login CSRF: double-submit cookie pattern on GET /loginPOST /login; requests without a matching token get
    403
  • Image magic byte validation: upload endpoint validates actual file bytes, not the client-supplied Content-Type
  • TRUST_PROXY setting: opt-in X-Forwarded-For support for correct IP detection behind nginx/Caddy/Traefik; rate limiter uses real IP when enabled
  • CSP nonces: per-request nonces replace unsafe-inline on script-src; Google Fonts and esm.sh (Tiptap) whitelisted
  • Image access control: /uploads/{user_id}/{filename} requires auth or a valid ?share_token linked to an entry owned by that user
  • Share token revocation: DELETE /journal/{date}/share endpoint clears the token; images embedded in shared entries are rewritten to include ?share_token so they load for unauthenticated viewers

Publishing flow redesign

Replaced the fragile share popup with a clean two-action model:

  • Publish/Unpublish toggle in the toolbar: primary action, clear state label
  • Copy link icon button: appears inline only when the entry is published, copies directly to clipboard with a
    toast; no popup, no URL input field
  • Both buttons work dynamically without page reload
  • Mobile nav button mirrors the same publish toggle

UI & calendar polish

  • Calendar now distinguishes four day states: today (dot below number), written (accent strikethrough),
    shared/published (accent ring), active/current (solid fill); states stack, active overrides all
  • CSS split into modular component files

i18n

  • All templates fully wired to the translation system (account page, admin users table, journal toolbar)
  • New keys: account, publish, unpublish, copy_link, change_password, admin_role, user_role, stop_sharing,
    delete_confirm

Docs & CI

  • README rewritten with inline Docker Compose quick-start
  • CONFIGURATION.md added with full prose-style config reference
  • CI pipeline updated to run tests on every push to main, build and push Docker image only on version tags (v*)
### Security hardening, publishing flow redesign, and polish **Security (9 fixes)** - `HSTS` + `Permissions-Policy` headers added to all responses; `HSTS` only sent when `SECURE_COOKIES=true` - Session versioning: `session_version` column on `User`; token format updated to include version; stale tokens rejected on every request - Session invalidation on password change: admin reset and self-change both increment `session_version`, logging out all other devices; self-change re-issues cookie so the current session stays valid - Login `CSRF`: double-submit cookie pattern on `GET /login` → `POST /login`; requests without a matching token get 403 - Image magic byte validation: upload endpoint validates actual file bytes, not the client-supplied `Content-Type` - `TRUST_PROXY` setting: opt-in `X-Forwarded-For` support for correct IP detection behind nginx/Caddy/Traefik; rate limiter uses real IP when enabled - CSP nonces: per-request nonces replace unsafe-inline on script-src; Google Fonts and esm.sh (Tiptap) whitelisted - Image access control: `/uploads/{user_id}/{filename}` requires auth or a valid `?share_token` linked to an entry owned by that user - Share token revocation: `DELETE /journal/{date}/share` endpoint clears the token; images embedded in shared entries are rewritten to include `?share_token` so they load for unauthenticated viewers **Publishing flow redesign** Replaced the fragile share popup with a clean two-action model: - Publish/Unpublish toggle in the toolbar: primary action, clear state label - Copy link icon button: appears inline only when the entry is published, copies directly to clipboard with a toast; no popup, no URL input field - Both buttons work dynamically without page reload - Mobile nav button mirrors the same publish toggle **UI & calendar polish** - Calendar now distinguishes four day states: today (dot below number), written (accent strikethrough), shared/published (accent ring), active/current (solid fill); states stack, active overrides all - CSS split into modular component files **i18n** - All templates fully wired to the translation system (account page, admin users table, journal toolbar) - New keys: account, publish, unpublish, copy_link, change_password, admin_role, user_role, stop_sharing, delete_confirm **Docs & CI** - README rewritten with inline Docker Compose quick-start - `CONFIGURATION.md` added with full prose-style config reference - CI pipeline updated to run tests on every push to main, build and push Docker image only on version tags (v*)
- Custom 404/500 error pages with logo and styled layout
- Security headers middleware (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
- CSRF protection on all HTML form POST endpoints via HMAC session token
- In-memory rate limiter on login (10 attempts per 15-minute window)
- Self-service password change at /account for regular users
- Account nav link added to authenticated layout
- Web manifest name fields filled in (Piruetas)
- Meta description added to base template; OG tags on share page
- 40 tests passing (13 new tests covering all new features)
Extracted main.css (562 lines) into a component-based structure:
tokens, reset, layout, responsive, and per-component files under
components/. Adopted CUBE CSS-inspired block__element naming for
element-level classes, applying renames across all templates and
calendar.js. Single base.css entry point via @import.
- editor.js: show toast on image upload and share fetch failures
- journal.py: remove dead word_count from EntrySaveRequest (server recalculates)
- auth.py: restrict locale redirect to path-only to prevent open redirect via Referer
ci: run tests on all branches and PRs
All checks were successful
Test and publish Docker image / test (push) Successful in 46s
Test and publish Docker image / test (pull_request) Successful in 45s
Test and publish Docker image / build (push) Has been skipped
Test and publish Docker image / build (pull_request) Has been skipped
927a38b203
patillacode deleted branch feature/security-and-polish 2026-04-21 16:51:18 +02:00
Sign in to join this conversation.
No reviewers
No labels
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
patillacode/piruetas!5
No description provided.