Files
curo1305 cfec3bb906 feat: Phase 4+5 — admin storage UI, backend proxy, CLAUDE.md enforcement
- backend/app/routers/storage_config.py: 5 admin-only endpoints proxying
  storage-service config + migration API (GET/PATCH/POST/DELETE)
- backend/app/main.py: register storage_config router
- frontend/src/api/client.ts: StorageStatus, MigrationStatus,
  StorageBackendConfig interfaces + 5 API functions
- frontend/src/pages/StorageAdminPage.tsx: full admin UI — backend health
  dot, driver selector (local/S3/WebDAV), conditional credential fields,
  Test & Migrate button, live 2s-poll migration progress bar, Cancel
- frontend/src/App.tsx: /admin/storage route (AdminRoute guard)
- CLAUDE.md: storage enforcement rule, updated Docker tables (6 services,
  3 volumes), §20 in merge checklist
- backend/CLAUDE.md, frontend/CLAUDE.md, doc-service/CLAUDE.md,
  ai-service/CLAUDE.md: updated to reflect storage-service integration
- tests/ALL_TESTS.md + tests/storage-service_tests.md: §20 (20 tests)
- backend/STATUS.md, frontend/STATUS.md: updated with new endpoints/routes
- changelog/2026-04-20_storage-service.md: full change log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 16:13:05 +02:00

9.0 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)
GET /api/users/me/groups List groups the current user belongs to (for share picker)

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) — superuser OR ai-service-admin member
PATCH /api/settings/ai Update AI provider / credentials — same access
POST /api/settings/ai/test Test AI connection — same access
GET /api/settings/documents/limits Doc service upload limits — superuser OR doc-service-admin member
PATCH /api/settings/documents/limits Update max PDF size — same access
GET /api/settings/system-prompts All editable system prompts — superuser OR ai-service-admin member
PATCH /api/settings/system-prompts/{id} Update system prompt — same access

Settings are persisted to the config bucket of storage-service:8020 via core/config_storage.py. All config I/O is async HTTP; no filesystem volumes are used.

Access to service-specific settings endpoints is enforced by get_service_admin(service_id) in deps.py — grants access to superusers OR members of the {service_id}-admin group.

Storage config (/api/admin)

Method Path Description
GET /api/admin/storage-config Current backend driver + health (proxied from storage-service)
PATCH /api/admin/storage-config Reconfigure backend without migration
POST /api/admin/storage-config/migrate Start async migration to a new backend
GET /api/admin/storage-config/migrate/status Poll migration progress
DELETE /api/admin/storage-config/migrate Cancel running migration

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-id header (UUID from users.id)
  • Strips hop-by-hop headers + content-length, accept-encoding, content-type
  • Returns Response (not StreamingResponse) 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 plugin list — no backend code changes required.

Service admin group bootstrap: On every startup, group_bootstrap.py creates a {service-id}-admin group for every registered service (idempotent). Admins add users to these groups via the Admin → Groups UI to delegate service-level administration.

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       /storage-    └── proxy →   health-check loop
                 /profile      config       doc-service:8001  (30s poll)
                             (proxy)
                                  │
                             storage-service:8020

Security notes

  • JWT stored in localStorage — XSS risk. Migration to httpOnly cookie planned.
  • No refresh token — after 8h the user must log in again.
  • Admin routes use get_current_admin dependency (checks role == "admin").
  • All backend routes require authentication except /api/auth/*.
  • backend-net is marked internal: true — containers on it cannot reach the internet directly.

Known limitations / not implemented

  • No refresh tokens — 8h hard expiry; adding refresh requires httpOnly cookie + rotation
  • No httpOnly cookie — JWT in localStorage is XSS-exposed
  • App permissions — no per-user, per-app access control. Currently all authenticated users can use all apps. Planned: user_app_permissions table, 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_memberships tables; admin CRUD; add/remove members
  • Generic plugin infrastructure: manifest contract, /api/plugins proxy router, check_plugin_access
  • App permissions registry: group_app_permissions table; AppsPage filtered by group grants
  • Doc sharing via group membership
  • App permissions registry: user_app_permissions (user_id, app_key); AppsPage filtered by grants
  • httpOnly cookie migration for JWT
  • Refresh token flow (paired with cookie migration)
  • Email verification on registration
  • Password reset flow
  • Rate limiting on auth endpoints