Add registration control, admin user management, and security hardening
- Registration auto-closes after first user; admin can toggle from /admin/users - Admin user management view with user list and delete - Password confirmation on registration form - Password change in Settings (PUT /api/auth/password) - Session cookie hardening: HttpOnly, SameSite=Lax, optional Secure flag - Startup warning when SECRET_KEY is default - Production deployment docs: reverse proxy, rate limiting, CSP headers - Fix assist prompt to preserve markdown headings in target sections - Simplify prod compose networking Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+39
-10
@@ -12,7 +12,7 @@
|
||||
> Include file-level details in the commit body when the change is non-trivial.
|
||||
|
||||
## Last Updated
|
||||
2026-02-11 — Phase 5.2: Tiptap Inline-Preview Editor + Layout Improvements
|
||||
2026-02-12 — Phase 5.3: Registration Control, User Management, Security Hardening
|
||||
|
||||
## Project Overview
|
||||
Fabled Assistant is a self-hosted note-taking and task-tracking application with
|
||||
@@ -76,6 +76,12 @@ for AI-assisted features.
|
||||
`build_context()` returns `(messages, context_meta)` tuple; metadata includes
|
||||
auto-found note IDs/titles sent to frontend via SSE `context` event before streaming.
|
||||
Multi-word search splits terms into per-word ILIKE with AND logic (not adjacent match).
|
||||
- **Reverse proxy required for production:** The app does not terminate TLS. A
|
||||
reverse proxy (Nginx/Traefik/Caddy) must sit in front of port 5000. Do not
|
||||
expose the app directly to the internet.
|
||||
- **Registration auto-closes:** After the first user registers (admin), registration
|
||||
is closed by default. The admin can toggle it from `/admin/users`. The setting is
|
||||
stored as the admin user's `registration_open` setting key.
|
||||
- **No blocking long-running operations:** Any potentially slow operation (model
|
||||
pulls, LLM calls, URL fetching, etc.) must never block app startup or freeze the
|
||||
UI. Backend uses SSE streaming for incremental responses (chat messages and model
|
||||
@@ -91,6 +97,10 @@ for AI-assisted features.
|
||||
`CREATE TABLE IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`, and `DO $$ BEGIN
|
||||
CREATE TYPE ... EXCEPTION WHEN duplicate_object` to allow safe re-runs and
|
||||
fresh database creation.
|
||||
- **Session cookie security:** `HttpOnly` and `SameSite=Lax` always set. `Secure`
|
||||
flag controlled by `SECURE_COOKIES` env var (enable when behind TLS).
|
||||
- **Default SECRET_KEY warning:** App logs a WARNING on startup if the default
|
||||
`dev-secret-change-me` key is in use.
|
||||
|
||||
### High-Level Component Diagram
|
||||
```
|
||||
@@ -199,7 +209,7 @@ fabledassistant/
|
||||
│ ├── __init__.py
|
||||
│ ├── app.py # Quart app factory: SPA via 404 handler, JSON 404/500 for API, request logging middleware
|
||||
│ ├── auth.py # Auth decorators: login_required, admin_required, get_current_user_id
|
||||
│ ├── config.py # Config from env vars + Docker secrets file support (_read_secret)
|
||||
│ ├── config.py # Config from env vars + Docker secrets file support (_read_secret) + SECURE_COOKIES flag
|
||||
│ ├── models/
|
||||
│ │ ├── __init__.py # async_session factory, Base, imports all models
|
||||
│ │ ├── user.py # User model (id, username, email, password_hash, role, created_at)
|
||||
@@ -210,13 +220,13 @@ fabledassistant/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── api.py # /api blueprint with /health endpoint (public)
|
||||
│ │ ├── auth.py # /api/auth blueprint: register, login, logout, me, status
|
||||
│ │ ├── admin.py # /api/admin blueprint: backup (user/full), restore (admin only)
|
||||
│ │ ├── admin.py # /api/admin blueprint: backup, restore, user management, registration toggle (admin only)
|
||||
│ │ ├── chat.py # /api/chat blueprint: conversations CRUD, SSE message streaming (all @login_required)
|
||||
│ │ ├── notes.py # /api/notes CRUD + wikilinks + backlinks (all @login_required)
|
||||
│ │ ├── tasks.py # /api/tasks CRUD + PATCH status (all @login_required)
|
||||
│ │ └── settings.py # /api/settings GET/PUT — per-user settings (@login_required)
|
||||
│ ├── services/
|
||||
│ │ ├── auth.py # Auth business logic: hash_password, verify_password, create_user, authenticate, get_user_by_id
|
||||
│ │ ├── auth.py # Auth business logic: hash_password, verify_password, create_user, authenticate, get_user_by_id, is_registration_open, list_users, delete_user, set_registration_open
|
||||
│ │ ├── backup.py # Backup/restore: export_full_backup, export_user_backup, restore_full_backup
|
||||
│ │ ├── notes.py # CRUD with user_id isolation, is_task filter, convert, backlinks, search_notes_for_context
|
||||
│ │ ├── llm.py # Ollama interaction: build_context with user_id, streaming, URL fetching
|
||||
@@ -268,7 +278,8 @@ fabledassistant/
|
||||
│ │ └── markdownSerializer.ts # Tiptap JSON → markdown serializer: handles all StarterKit nodes + marks
|
||||
│ ├── views/
|
||||
│ │ ├── LoginView.vue # Login form with error display, link to register
|
||||
│ │ ├── RegisterView.vue # Register form (username, password, email), first-user setup
|
||||
│ │ ├── RegisterView.vue # Register form with password confirmation; shows "closed" message when registration disabled
|
||||
│ │ ├── UserManagementView.vue # Admin user management: registration toggle, user list with delete
|
||||
│ │ ├── ChatView.vue # Dedicated /chat page: responsive sidebar (overlay on mobile), bubble messages, note picker, context pills
|
||||
│ │ ├── HomeView.vue # Landing page: recent notes + tasks + recent chats
|
||||
│ │ ├── SettingsView.vue # Settings page: assistant name, model catalog, data export/restore (admin)
|
||||
@@ -294,7 +305,7 @@ fabledassistant/
|
||||
│ │ ├── PaginationBar.vue # Prev/next + page numbers
|
||||
│ │ └── ToastNotification.vue # Fixed-position toast container with close button, warning support
|
||||
│ └── router/
|
||||
│ └── index.ts # Routes: /, /login, /register, /notes/*, /tasks/*, /chat, /chat/:id, /settings; beforeEach auth guard
|
||||
│ └── index.ts # Routes: /, /login, /register, /notes/*, /tasks/*, /chat, /chat/:id, /settings, /admin/users; beforeEach auth guard
|
||||
└── public/
|
||||
```
|
||||
|
||||
@@ -303,13 +314,18 @@ fabledassistant/
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/health` | Health check (public) |
|
||||
| GET | `/api/auth/status` | Check if any users exist (public) |
|
||||
| POST | `/api/auth/register` | Register new user (first user becomes admin) |
|
||||
| GET | `/api/auth/status` | Check if any users exist + registration open status (public) |
|
||||
| POST | `/api/auth/register` | Register new user (first user becomes admin; returns 403 if registration closed) |
|
||||
| POST | `/api/auth/login` | Login with username/password |
|
||||
| POST | `/api/auth/logout` | Logout (clear session) |
|
||||
| GET | `/api/auth/me` | Get current user info |
|
||||
| PUT | `/api/auth/password` | Change password (body: `{current_password, new_password}`) |
|
||||
| GET | `/api/admin/backup` | Export backup (`?scope=user` for own data, full requires admin) |
|
||||
| POST | `/api/admin/restore` | Restore from JSON backup (admin only) |
|
||||
| GET | `/api/admin/users` | List all users (admin only) |
|
||||
| DELETE | `/api/admin/users/:id` | Delete a user (admin only, cannot delete self) |
|
||||
| GET | `/api/admin/registration` | Get registration open/closed status (admin only) |
|
||||
| PUT | `/api/admin/registration` | Toggle registration open/closed (admin only, body: `{open: bool}`) |
|
||||
| GET | `/api/notes` | List notes (params: `q`, `tag`, `sort`, `order`, `limit`, `offset`; defaults to `is_task=false` — plain notes only; `?is_task=true` for tasks, `?all=true` for everything) |
|
||||
| POST | `/api/notes` | Create note (body: `{title, body, status?, priority?, due_date?}` — tags auto-extracted) |
|
||||
| GET | `/api/notes/tags` | List all tags from notes table (param: `q` for filter) |
|
||||
@@ -583,6 +599,16 @@ When adding a new migration, follow these conventions:
|
||||
- [x] **Accept trailing newline:** Accepted AI suggestions always end with a newline for clean separation
|
||||
- [x] **Wider content area:** Max-width increased from 720px to 960px across all views
|
||||
|
||||
### Phase 5.3 — Registration Control, User Management, Security Hardening ✓
|
||||
- [x] **Registration auto-closes:** After the first user registers (admin), registration is closed by default
|
||||
- [x] **Registration toggle:** Admin can open/close registration from `/admin/users`
|
||||
- [x] **Password confirmation:** Register form requires password confirmation with inline validation
|
||||
- [x] **Admin user management:** `/admin/users` view with user list and delete (admin only)
|
||||
- [x] **Session cookie hardening:** `HttpOnly`, `SameSite=Lax` always on; `Secure` via `SECURE_COOKIES` env var
|
||||
- [x] **Default SECRET_KEY warning:** Logs WARNING on startup if default key is in use
|
||||
- [x] **Password change:** `PUT /api/auth/password` endpoint + Settings UI section
|
||||
- [x] **Production deployment docs:** README documents reverse proxy, rate limiting, CSP headers, SECRET_KEY, registration behavior
|
||||
|
||||
### Future / Stretch
|
||||
- Tagging/labeling system with LLM-suggested tags
|
||||
- Calendar/timeline view for tasks
|
||||
@@ -598,7 +624,7 @@ When adding a new migration, follow these conventions:
|
||||
- To reset database: `docker compose down -v && docker compose up --build`
|
||||
|
||||
## Current Status
|
||||
**Phase:** Phase 5.2 complete. Tiptap Inline-Preview Editor + Layout Improvements.
|
||||
**Phase:** Phase 5.3 complete. Registration Control, User Management, Security Hardening.
|
||||
- Full note-taking and task-tracking CRUD with markdown, wikilinks, backlinks, tags
|
||||
- **Tiptap WYSIWYG editor** with inline formatting preview, markdown round-trip, paste handling
|
||||
- **Tag/wikilink autocomplete** via `@tiptap/suggestion` with heading disambiguation
|
||||
@@ -611,7 +637,10 @@ When adding a new migration, follow these conventions:
|
||||
- Context pills with promote/exclude controls; note picker in chat input
|
||||
- Save assistant messages as notes (LLM-titled, chat-tagged); summarize conversations as notes
|
||||
- Dedicated `/chat` page with responsive sidebar (overlay on mobile)
|
||||
- Settings page: assistant name, model catalog, data export/restore (admin)
|
||||
- Settings page: assistant name, model catalog, password change, data export/restore (admin)
|
||||
- **Registration control**: auto-closes after first user, admin toggle, password confirmation
|
||||
- **Admin user management**: `/admin/users` with user list + delete
|
||||
- **Session cookie hardening**: HttpOnly, SameSite=Lax, optional Secure flag
|
||||
- **App-wide layout fix**: navbar always visible, all views fit within viewport
|
||||
- **Responsive design**: hamburger menu, mobile touch targets, responsive breakpoints
|
||||
- **Docker Swarm production stack** with secrets, network isolation, health checks, resource limits
|
||||
|
||||
Reference in New Issue
Block a user