Introduces a manifest contract so feature containers self-describe their settings (JSON Schema + access rules). Backend and frontend gain generic plugin proxy and dynamic Extensions UI with zero feature-specific code. Doc-service is the first plugin consumer: exposes /plugin/manifest and /plugin/settings, adds a watchdog-based file watcher that auto-ingests PDFs from a mounted directory, maps subfolders to categories, supports AI-suggested folder/filename (user-confirmed), and enforces a no-remove policy. Access is gated by is_superuser or doc-service-admin group. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.5 KiB
Backend — Status
What it is
Central FastAPI gateway. Handles authentication, user management, admin settings, and proxies feature-service traffic. It is the only container that has host-level port exposure (8000, internal) — all browser traffic arrives via the Vite/nginx frontend proxy.
Port: 8000 (on backend-net, no direct host binding in prod).
Database: PostgreSQL 16 (postgres_data named volume).
Current functionality
Auth (/api/auth)
| Method | Path | Description |
|---|---|---|
POST |
/api/auth/register |
Create account; password policy enforced (uppercase, special char, no "test") |
POST |
/api/auth/login |
OAuth2 password flow; returns RS256 JWT (8-hour expiry) |
JWT signing uses a 4096-bit RSA key pair (RS256). Keys are generated by scripts/generate_jwt_keys.py and stored in backend/.env (gitignored). Token stored in localStorage on the client.
Users (/api/users)
| Method | Path | Description |
|---|---|---|
GET |
/api/users/me |
Current user info |
GET |
/api/users/me/preferences |
User's dashboard preferences (app_ids list) |
PATCH |
/api/users/me/preferences |
Update pinned app IDs (max 50; validated as safe slugs) |
Profile (/api/profile)
| Method | Path | Description |
|---|---|---|
GET |
/api/profile |
Fetch profile (separate profiles table) |
PUT |
/api/profile |
Update profile fields |
Admin (/api/admin)
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/users |
List all users (admin only) |
PATCH |
/api/admin/users/{id} |
Update user (role, active flag) |
Groups (/api/admin/groups)
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/groups |
List all groups with member count |
POST |
/api/admin/groups |
Create a new group |
GET |
/api/admin/groups/{id} |
Get group detail with member list |
PATCH |
/api/admin/groups/{id} |
Update group name / description |
DELETE |
/api/admin/groups/{id} |
Delete group (cascades memberships) |
POST |
/api/admin/groups/{id}/members/{user_id} |
Add user to group |
DELETE |
/api/admin/groups/{id}/members/{user_id} |
Remove user from group |
Services (/api/services)
| Method | Path | Description |
|---|---|---|
GET |
/api/services |
Returns health status of all registered feature services |
A background task (service_health.py) polls each service's /health endpoint every 30 s and stores the result in memory. The first check runs immediately on startup. Any authenticated user may call GET /api/services; the frontend uses it to drive app card visibility.
Settings (/api/settings)
| Method | Path | Description |
|---|---|---|
GET |
/api/settings/ai |
AI service config (masked — API keys redacted) |
PATCH |
/api/settings/ai |
Update AI provider / credentials |
POST |
/api/settings/ai/test |
Test AI connection (proxies a minimal /chat call) |
GET |
/api/settings/documents/limits |
Doc service upload limits |
PATCH |
/api/settings/documents/limits |
Update max PDF size |
Settings are persisted to JSON files on the app_config Docker named volume and read by the respective feature services.
Feature proxies
All /api/documents/* and /api/documents/categories/* requests are transparently proxied to doc-service:8001 via httpx.AsyncClient. The proxy:
- Validates the JWT (
get_current_user) - Injects
x-user-idheader (UUID fromusers.id) - Strips hop-by-hop headers +
content-length,accept-encoding,content-type - Returns
Response(notStreamingResponse) to avoid content-length/chunked conflicts
Plugin system (/api/plugins)
Generic extension/plugin infrastructure — zero feature-specific code in backend. Feature containers self-describe via GET /plugin/manifest.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/plugins |
user | List plugins accessible to current user |
GET |
/api/plugins/{id}/manifest |
user | Cached manifest for a plugin (404 if not accessible) |
GET |
/api/plugins/{id}/settings |
user | Proxy to feature GET /plugin/settings |
PATCH |
/api/plugins/{id}/settings |
user | Proxy to feature PATCH /plugin/settings |
Access is controlled by the manifest: allow_superuser for admins; required_groups for group members. check_plugin_access(plugin_id, user, db) in deps.py enforces this.
During each health poll, service_health.py also fetches GET /plugin/manifest from healthy services and caches it. New feature containers that expose /plugin/manifest automatically appear in the Extensions sidebar — no backend code changes required.
Database models
| Model | Table | Notes |
|---|---|---|
User |
users |
email, hashed_password, role (user|admin), is_active, dashboard_app_ids (JSON) |
Profile |
profiles |
one-to-one with User; full_name, phone, etc. |
Group |
groups |
name (unique), description, created_at |
GroupMembership |
group_memberships |
group_id + user_id (unique pair); joined_at |
Alembic migrations in backend/alembic/versions/ — version table: alembic_version.
Architecture
Browser (port 5173 dev / 80 prod)
│
└── Vite dev proxy / nginx
│
└── /api/* → backend:8000 (FastAPI)
│
┌───────────┼────────────┬──────────────┐
/auth /settings /documents/* /services
/users (JSON │ │
/admin volume) └── proxy → health-check loop
/profile doc-service:8001 (30s poll)
Security notes
- JWT stored in
localStorage— XSS risk. Migration tohttpOnlycookie planned. - No refresh token — after 8h the user must log in again.
- Admin routes use
get_current_admindependency (checksrole == "admin"). - All backend routes require authentication except
/api/auth/*. backend-netis markedinternal: true— containers on it cannot reach the internet directly.
Known limitations / not implemented
- No refresh tokens — 8h hard expiry; adding refresh requires
httpOnlycookie + rotation - No
httpOnlycookie — JWT inlocalStorageis XSS-exposed - App permissions — no per-user, per-app access control. Currently all authenticated users can use all apps. Planned:
user_app_permissionstable, admin UI to grant/revoke - Groups / sharing — groups + memberships exist; app permission hooks not yet wired up
- Email verification — accounts are active immediately after registration
- Password reset — no flow implemented
Future work
- Groups system:
groups,group_membershipstables; admin CRUD; add/remove members - Generic plugin infrastructure: manifest contract,
/api/pluginsproxy router,check_plugin_access - App permissions registry:
group_app_permissionstable; AppsPage filtered by group grants - Doc sharing via group membership
- App permissions registry:
user_app_permissions (user_id, app_key); AppsPage filtered by grants httpOnlycookie migration for JWT- Refresh token flow (paired with cookie migration)
- Email verification on registration
- Password reset flow
- Rate limiting on auth endpoints