feat: implement user profile management and oauth/traefik integration

User Profile (US-004):
- Add authenticated profile endpoints (GET/PUT /users/me)
- Add avatar upload with file validation (PNG/JPEG, max 2MB)
- Create frontend profile page with edit form and avatar upload
- Update app shell to link to profile page

OAuth/Traefik Integration:
- Externalize all Authentik URLs to environment variables
- Add domain configuration (API_DOMAIN, WEB_DOMAIN, AUTHENTIK_DOMAIN)
- Create docker-compose.traefik.yml for reverse proxy deployment
- Update OAuth redirect/callback URLs to use configured domains
- Add VITE_APP_URL for frontend public URL configuration

Quality gates: pytest (50 passed), ruff, mypy, npm test (12 passed), typecheck, lint, build
This commit is contained in:
Fusion
2026-05-17 23:17:10 +02:00
parent 56f440db1b
commit 577b052c05
32 changed files with 1153 additions and 16 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-17
@@ -0,0 +1,65 @@
## Context
The current `docker-compose.yml` is a standalone development setup without reverse proxy support. The API has hardcoded Authentik URLs in `config.py` (`https://authentik.local/...`) which makes it impossible to deploy in real environments without code changes. The frontend also hardcodes `VITE_API_URL=http://localhost:8000`.
For production deployment, the platform needs to work behind an existing Traefik reverse proxy (common in self-hosted stacks) and have all external service endpoints fully configurable.
## Goals / Non-Goals
**Goals:**
- Make all Authentik URLs configurable via environment variables (no hardcoded defaults).
- Make OAuth redirect/callback URLs configurable and domain-aware.
- Create `docker-compose.traefik.yml` for deployment behind an existing Traefik instance.
- Support configuring the public web domain, API domain, and Authentik domain via env vars.
- Ensure both development (`docker-compose.yml`) and traefik modes work correctly.
**Non-Goals:**
- Setting up Traefik itself (assumes existing Traefik instance).
- Authentik installation/configuration (assumes existing Authentik instance).
- SSL certificate management (handled by Traefik).
- Changing the authentication flow or token logic.
## Decisions
1. **Remove all hardcoded URLs from `config.py` and require env vars**
- Rationale: Deployment environments have different domains. Hardcoded values are a deployment blocker.
- Alternative: Keep defaults and override in prod. Rejected because defaults mask configuration errors.
2. **Use `API_DOMAIN` and `WEB_DOMAIN` env vars for constructing public URLs**
- Rationale: Centralizes domain configuration and makes it easy to switch between dev/prod.
- `API_PUBLIC_URL` will default to `http://${API_DOMAIN}` or can be overridden.
- `WEB_PUBLIC_URL` will default to `http://${WEB_DOMAIN}` or can be overridden.
3. **Create a separate `docker-compose.traefik.yml` instead of modifying the existing one**
- Rationale: The existing `docker-compose.yml` is for standalone development. Traefik deployment is a different topology.
- Alternative: Use compose profiles or overrides. Rejected to keep each file simple and explicit.
4. **Add `VITE_APP_URL` for the frontend so it knows its public URL**
- Rationale: OAuth redirect URI needs to be absolute and must match the public web URL.
- Frontend will use this for login redirect if needed.
5. **Use Traefik labels for routing instead of ports**
- Rationale: Standard Traefik pattern - services are discovered via Docker labels.
- No port mappings exposed; Traefik handles all ingress.
## Risks / Trade-offs
- **[Missing env vars cause startup failures]** -> Document all required variables in `.env.example` and add validation in config.py.
- **[OAuth redirect URI mismatch]** -> Ensure the redirect URI configured in Authentik matches the env-configured callback URL exactly.
- **[Local development still works]** -> Keep `docker-compose.yml` unchanged for dev; traefik file is additive.
- **[Cookie secure flag]** -> Ensure `cookie_secure` property in config reads from env properly for HTTPS deployments.
## Migration Plan
1. Update `config.py` to read all Authentik URLs from environment with no defaults.
2. Update `auth.py` to construct redirect/callback URLs from env-configured domains.
3. Update `.env.example` with all new variables.
4. Create `docker-compose.traefik.yml` with Traefik labels.
5. Test that both `docker-compose.yml` (dev) and `docker-compose.traefik.yml` (prod) work.
Rollback:
- Revert config.py and auth.py changes; remove `docker-compose.traefik.yml`.
## Open Questions
- Should we add a startup health check that validates all required env vars are set?
@@ -0,0 +1,28 @@
## Why
The current setup hardcodes Authentik URLs in the API config and provides only a basic docker-compose.yml without reverse proxy support. For production deployment, the platform needs to integrate with an existing Traefik reverse proxy and have all external service URLs fully configurable via environment variables.
## What Changes
- **Externalize all Authentik and domain configuration** to environment variables (no more hardcoded URLs in config.py).
- **Add `docker-compose.traefik.yml`** for deployment behind an existing Traefik instance with all domain names as env vars.
- **Update `.env.example`** to document all new environment variables for both development and traefik modes.
- **Add proxy web name configuration** for the frontend to know its public URL.
- **Verify OAuth callback URLs work correctly** with configurable domains.
## Capabilities
### New Capabilities
- `traefik-deployment`: Docker Compose setup for deploying behind an existing Traefik reverse proxy with environment-based domain configuration.
### Modified Capabilities
- `docker-infrastructure`: Add traefik deployment mode and externalize all domain/service URLs.
- `auth-oauth`: Make Authentik URLs and callback URLs fully environment-configurable instead of hardcoded.
## Impact
- `apps/api/src/config.py`: Remove hardcoded Authentik URLs, read from environment.
- `apps/api/src/api/auth.py`: Use configurable redirect/callback URLs.
- `.env.example`: Add all new environment variables.
- `docker-compose.traefik.yml`: New file for traefik deployment.
- Frontend may need `VITE_APP_URL` or similar for OAuth redirect.
@@ -0,0 +1,29 @@
## MODIFIED Requirements
### Requirement: OAuth2/OIDC Flow
The system SHALL support OAuth2/OIDC authentication via Authentik with fully configurable endpoints.
#### Scenario: User login
- GIVEN a user clicks the login button
- WHEN the frontend redirects to Authentik authorization endpoint
- THEN the redirect URI SHALL be constructed from environment-configured domains
- AND the Authentik authorize URL SHALL be read from environment variables
#### Scenario: Token exchange and validation
- GIVEN Authentik has redirected with authorization code
- WHEN the callback endpoint receives the code
- THEN it exchanges the code for provider tokens at the configured token URL
- AND verifies token signature using the configured JWKS URL
- AND validates the issuer and audience from environment configuration
### Requirement: Session Security
The system SHALL protect sessions using httpOnly cookies with environment-aware secure defaults.
#### Scenario: Cookie attributes in production
- GIVEN successful authentication behind Traefik with HTTPS
- WHEN cookies are set
- THEN access_token cookie SHALL be httpOnly
- AND access_token cookie SHALL have Secure flag based on environment
- AND access_token cookie SHALL have SameSite based on environment
@@ -0,0 +1,24 @@
## MODIFIED Requirements
### Requirement: Docker Compose Setup
The system SHALL provide Docker Compose configurations for both development and traefik deployment.
#### Scenario: Development compose file
- GIVEN the development environment
- THEN `docker-compose.yml` SHALL define all platform services for local development
#### Scenario: Traefik compose file
- GIVEN the production deployment
- THEN `docker-compose.traefik.yml` SHALL define all platform services behind Traefik
- AND no ports SHALL be exposed directly (all traffic through Traefik)
### Requirement: Environment Configuration
The system SHALL document all required environment variables for both development and traefik deployment modes.
#### Scenario: Environment setup
- GIVEN a new developer or operator
- WHEN they set up the project
- THEN `.env.example` SHALL document all variables for both modes
- AND variables SHALL include domain configuration for traefik mode
@@ -0,0 +1,31 @@
## ADDED Requirements
### Requirement: Traefik Docker Compose
The system SHALL provide a `docker-compose.traefik.yml` for deployment behind an existing Traefik reverse proxy.
#### Scenario: Service labels
- GIVEN the traefik deployment configuration
- WHEN services are started
- THEN `docker-compose.traefik.yml` SHALL define Traefik Docker labels for each service
- AND all routing rules SHALL use configurable domain names
#### Scenario: Environment variables
- GIVEN the traefik deployment configuration
- WHEN configuring the deployment
- THEN all domain names SHALL be configurable via environment variables
- AND the proxy web name SHALL be configurable via environment variable
### Requirement: Environment Configuration
The system SHALL document all required environment variables for traefik deployment.
#### Scenario: Required variables
- GIVEN a new deployment
- WHEN setting up environment variables
- THEN `.env.example` SHALL document:
- `API_DOMAIN` - domain for API service
- `WEB_DOMAIN` - domain for web frontend
- `AUTHENTIK_DOMAIN` - domain for Authentik instance
- `PROXY_WEB_NAME` - name for web proxy service
- All Authentik OIDC configuration variables
@@ -0,0 +1,25 @@
## 1. Externalize Authentik and domain configuration
- [x] 1.1 Update `apps/api/src/config.py` to read all Authentik URLs from environment variables with no hardcoded defaults.
- [x] 1.2 Add `API_PUBLIC_URL`, `WEB_PUBLIC_URL`, and related domain env vars to config.py.
- [x] 1.3 Update `apps/api/src/api/auth.py` to construct OAuth redirect/callback URLs from configured domains.
- [x] 1.4 Update `.env.example` with all new environment variables for Authentik and domain configuration.
## 2. Create Traefik deployment compose file
- [x] 2.1 Create `docker-compose.traefik.yml` with all services configured for Traefik reverse proxy.
- [x] 2.2 Add Traefik Docker labels to all services with configurable domain-based routing rules.
- [x] 2.3 Ensure no ports are exposed directly in traefik mode (all through Traefik).
- [x] 2.4 Add `PROXY_WEB_NAME` and other traefik-specific env vars to `.env.example`.
## 3. Frontend configuration
- [x] 3.1 Update frontend to support configurable public URL for OAuth redirect.
- [x] 3.2 Update `apps/web/.env.example` or relevant config with `VITE_APP_URL`.
## 4. Verification and testing
- [x] 4.1 Run backend quality gates (`pytest`, `ruff`, `mypy`).
- [x] 4.2 Run frontend quality gates (`npm test`, `typecheck`, `lint`, `build`).
- [x] 4.3 Validate `docker-compose config` works for both compose files.
- [x] 4.4 Update this tasks file with completed checkboxes.
+58
View File
@@ -0,0 +1,58 @@
## Context
The user-profile spec requires authenticated users to view and update their profile (name, email, avatar). The User model already has `email`, `name`, and `avatar_url` fields. The auth system provides cookie-based JWT authentication. This change connects those pieces into a working profile management flow.
## Goals / Non-Goals
**Goals:**
- Provide `GET /users/me` to retrieve the current user's profile.
- Provide `PUT /users/me` to update name and email with validation.
- Provide `POST /users/me/avatar` to upload an avatar image (PNG/JPG, max 2MB).
- Store uploaded avatars locally under `apps/api/uploads/avatars/`.
- Add a frontend `/profile` page with edit form and avatar upload UI.
- Update the app shell to link to the profile page.
**Non-Goals:**
- Social features or public profile pages.
- External avatar providers (Gravatar, etc.).
- Image resizing or cropping.
## Decisions
1. **Add a dedicated `/users` router instead of extending `/auth/me`**
- Rationale: cleaner separation of concerns; auth routes handle login/logout, user routes handle profile data.
- Alternative: extend `/auth/me` to support PUT. Rejected to keep auth router focused.
2. **Use `UploadFile` from FastAPI for avatar uploads**
- Rationale: standard FastAPI pattern, handles multipart parsing and streaming.
- Alternative: raw bytes in JSON body. Rejected as it complicates client and server.
3. **Store avatars as files locally, not in the database**
- Rationale: keeps the database lightweight; files are served statically.
- Alternative: bytea/blob column. Rejected for performance and simplicity.
4. **Use a simple form-based profile page in the frontend**
- Rationale: consistent with existing project pages and forms.
- Alternative: modal or inline editing. Rejected to keep implementation straightforward.
## Risks / Trade-offs
- **[File storage path]** -> use an environment-configurable upload directory; default to `apps/api/uploads/avatars`.
- **[Filename collisions]** -> use UUID-based filenames to avoid collisions.
- **[Unauthorized access to avatars]** -> for now, serve via static mount; later can add auth if needed.
- **[Frontend state sync]** -> after profile update, refresh auth context so the app shell shows updated name.
## Migration Plan
1. Create backend users router with GET/PUT/avatar endpoints.
2. Register router in main.py.
3. Create frontend profile page, API methods, and routing.
4. Update app shell with profile link.
5. Run quality gates (pytest, mypy, ruff, typecheck, lint).
Rollback:
- Remove users router and frontend page; no database changes needed.
## Open Questions
- Should old avatars be deleted on new upload? (Yes, to avoid disk bloat.)
+24
View File
@@ -0,0 +1,24 @@
## Why
The platform has authentication but users cannot view or edit their own profile information. Implementing profile management is essential for personalization and account management.
## What Changes
- Add authenticated user profile API endpoints (read, update, avatar upload).
- Add a frontend profile page with editable form and avatar upload.
- Validate avatar file type and size on upload.
- Store avatars locally and update the user's avatar_url.
## Capabilities
### New Capabilities
- `user-profile-management`: Users can view and edit their profile (name, email) and upload an avatar.
### Modified Capabilities
- `auth-oauth`: Extend /auth/me or add dedicated /users/me endpoint for richer profile data.
## Impact
- Backend changes in `apps/api/src/api/` (new users router) and storage for avatars.
- Frontend changes in `apps/web/src/` (new profile page, API methods, routing).
- No schema migrations required (avatar_url already exists on User model).
@@ -0,0 +1,50 @@
# User Profile Management Specification
## Purpose
Manage user profiles including personal information and avatar.
## Requirements
### Requirement: Profile Retrieval
The system SHALL allow users to view their profile.
#### Scenario: View profile
- GIVEN an authenticated user
- WHEN they access the profile page
- THEN their name, email, and avatar are displayed
### Requirement: Profile Updates
The system SHALL allow users to update their profile.
#### Scenario: Update name and email
- GIVEN an authenticated user
- WHEN they submit profile changes
- THEN the system validates the input
- AND updates the user record
### Requirement: Avatar Upload
The system SHALL support local avatar storage.
#### Scenario: Upload avatar
- GIVEN an authenticated user
- WHEN they upload an image file
- THEN the system validates the file type and size
- AND stores it locally
- AND updates the user's avatar URL
## Dependencies
- auth-oauth (authenticated users)
- Database models: User
## Quality Gates
- `pytest` must pass
- `mypy .` must pass
- `ruff check .` must pass
- `npm run typecheck` must pass
- `npm run lint` must pass
+28
View File
@@ -0,0 +1,28 @@
## 1. Backend profile API
- [x] 1.1 Create `apps/api/src/api/users.py` with `GET /users/me`, `PUT /users/me`, and `POST /users/me/avatar` endpoints.
- [x] 1.2 Add Pydantic schemas for `UserProfileResponse` and `UserProfileUpdate`.
- [x] 1.3 Implement avatar upload: validate file type (image/png, image/jpeg), max 2MB, save to `uploads/avatars/` with UUID filename, update `avatar_url`.
- [x] 1.4 Register users router in `apps/api/src/main.py`.
- [x] 1.5 Add backend tests for profile read, update, and avatar upload.
## 2. Frontend profile page
- [x] 2.1 Create `apps/web/src/api/profile.ts` with API methods for getProfile, updateProfile, and uploadAvatar.
- [x] 2.2 Create `apps/web/src/pages/profile.tsx` with profile display, edit form (name, email), and avatar upload.
- [x] 2.3 Add `/profile` route in `apps/web/src/router.tsx`.
- [x] 2.4 Update `apps/web/src/components/app-shell.tsx` to link to `/profile` from the user chip.
- [x] 2.5 Update `apps/web/src/types.ts` to include `avatar_url` in `SessionUser` if needed.
- [ ] 2.6 Add frontend tests for profile page rendering and interactions.
## 3. Verification and OpenSpec tracking
- [x] 3.1 Run backend checks (`pytest`, `ruff check src tests`, `mypy src`) and fix findings.
- [x] 3.2 Run frontend checks (`npm test`, `npm run typecheck`, `npm run lint`, `npm run build`) and fix findings.
- [x] 3.3 Update this tasks file with completed checkboxes and document blockers/follow-ups.
## Blockers / Follow-ups
- No blocking issues remain for this change.
- Frontend tests for profile page (task 2.6) were skipped to keep the change focused; existing tests pass (12/12). Profile page tests can be added in a follow-up.
- Vite deprecation warnings from `vite:react-babel` plugin are non-blocking and pre-existing.