feat: implement auth, projects, and frontend foundation

This commit is contained in:
2026-05-17 20:21:55 +00:00
parent e7819bfc82
commit 71d9fe6406
88 changed files with 10936 additions and 47 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-17
@@ -0,0 +1,3 @@
# auth-oauth
Implement OAuth2/OIDC authentication via Authentik with internal JWTs and DB-backed refresh tokens
@@ -0,0 +1,68 @@
## Context
The existing repository now includes core database infrastructure and user models, but authentication is not implemented yet. The target behavior is defined by `openspec/specs/auth-oauth/spec.md` and refined through approved decisions: Authentik OIDC as the identity provider, strict production cookies, internal JWT access tokens, and server-side refresh token storage with revocation.
The backend is Python-based with async SQLAlchemy and Alembic. This change must integrate with that stack while keeping trust boundaries explicit and supporting predictable local development.
## Goals / Non-Goals
**Goals:**
- Implement Authentik OAuth2/OIDC login and callback flow.
- Validate provider tokens via JWKS before creating local session credentials.
- Mint internal short-lived JWT access tokens for API authorization.
- Persist hashed opaque refresh tokens in DB with rotation and revocation.
- Provide explicit logout that invalidates refresh state and clears cookies.
- Enforce environment-aware cookie policy (strict in production, relaxed on localhost).
**Non-Goals:**
- RBAC policy engine and permission modeling.
- Multi-device session management UX.
- Additional social identity providers.
## Decisions
1. **Use OIDC code flow + Authentik JWKS validation before local minting**
- Rationale: prevents blind trust in token exchange payloads and centralizes signature/claim checks (`iss`, `aud`, `exp`).
- Alternative considered: trust exchange response without independent validation. Rejected due to weaker security posture.
2. **Issue internal JWT access tokens instead of forwarding provider access tokens**
- Rationale: stable internal contract, decoupled claim shape, simpler downstream authorization.
- Alternative considered: pass-through provider tokens. Rejected due to tighter coupling and reduced control over TTL/claims.
3. **Use DB-backed opaque refresh tokens with hash-at-rest + rotation**
- Rationale: supports immediate revocation on logout and tighter reuse detection.
- Alternative considered: stateless long-lived JWT refresh tokens. Rejected because revocation and replay handling are weaker.
4. **Environment-aware cookie policy with secure defaults**
- Production: `Secure=true`, `SameSite=strict`, `HttpOnly=true`.
- Local dev: `Secure=false`, `SameSite=lax`, `HttpOnly=true`.
- Rationale: preserves security in production while enabling localhost development without TLS.
5. **Add dedicated refresh token persistence model and migration**
- Table fields: `id`, `user_id`, `token_hash`, `expires_at`, `created_at`, `revoked_at`, `user_agent`, `ip_address`.
- Indexes: unique `token_hash`, plus `user_id` and `expires_at` indexes.
## Risks / Trade-offs
- **[JWKS endpoint/network failures]** -> Cache JWKS keys with bounded TTL and fail closed with `401` on unverifiable tokens.
- **[Clock skew affecting token validity]** -> Allow small validation leeway and keep server clock synchronized.
- **[Refresh token replay attempts]** -> Rotate per refresh, revoke reused chains, and force re-authentication.
- **[Cookie behavior differences across browsers/environments]** -> Centralize cookie option builder and test dev/prod permutations.
- **[Added implementation surface area]** -> Keep modules focused (provider client, JWT service, refresh store, routes) and maintain high test coverage.
## Migration Plan
1. Add new refresh token model and Alembic migration.
2. Add configuration for OIDC endpoints/client credentials/JWT secret/cookie mode.
3. Implement auth services (provider exchange + JWKS verification, JWT mint/verify, refresh store).
4. Implement routes: `/auth/login`, `/auth/callback`, `/auth/refresh`, `/auth/logout`, `/auth/me`.
5. Add/expand tests (unit + integration) for auth and token lifecycle.
6. Verify quality gates (`pytest`, `ruff`, `mypy`) and migration status.
Rollback:
- Revert route/service changes and run Alembic downgrade for refresh-token migration if deployment requires rollback.
## Open Questions
- None blocking for initial implementation.
- Optional follow-up: enforce single active refresh token per user-agent/device (currently out of scope).
@@ -0,0 +1,27 @@
## Why
The project has database foundations in place but still lacks production-ready user authentication. We need a secure OAuth2/OIDC integration with Authentik that issues internal session credentials and supports reliable logout and token revocation.
## What Changes
- Implement backend OAuth2/OIDC login and callback flow against Authentik.
- Verify Authentik-issued tokens via JWKS before minting local credentials.
- Mint short-lived internal JWT access tokens and store refresh tokens server-side.
- Add refresh-token rotation, revocation, and explicit logout semantics.
- Apply environment-aware secure cookie policy (strict in production, relaxed for localhost development).
- Add auth endpoints, supporting services, and test coverage for auth flows.
## Capabilities
### New Capabilities
- `auth-session-tokens`: Internal JWT access-token issuance, opaque refresh-token storage, rotation, and revocation.
### Modified Capabilities
- `auth-oauth`: Extend OAuth/OIDC behavior to require JWKS validation, internal JWT minting, cookie policy by environment, and DB-backed refresh lifecycle.
## Impact
- Affected backend modules in `apps/api/src` (config, models, auth services, API routes).
- New database schema object for refresh-token persistence and an accompanying migration.
- New environment variables for OIDC/JWT/cookie settings.
- Frontend auth integration points for login/logout/me/refresh behavior.
@@ -0,0 +1,62 @@
## MODIFIED Requirements
### Requirement: OAuth2/OIDC Flow
The system SHALL support OAuth2/OIDC authentication via Authentik and SHALL validate Authentik-issued tokens via JWKS before creating local sessions.
#### Scenario: User login
- GIVEN a user clicks the login button
- WHEN the frontend redirects to Authentik authorization endpoint
- THEN the user authenticates with Authentik
- AND Authentik redirects back with authorization code
#### 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
- AND verifies token signature and claims using Authentik JWKS
- AND upserts the local user account
- AND mints internal access and refresh tokens
### Requirement: Session Security
The system SHALL protect sessions using httpOnly cookies and SHALL apply secure cookie defaults by environment.
#### Scenario: Cookie attributes in production
- GIVEN successful authentication in production
- WHEN cookies are set
- THEN access_token cookie SHALL be httpOnly
- AND access_token cookie SHALL have Secure flag
- AND access_token cookie SHALL have SameSite=strict
- AND refresh_token cookie SHALL have the same attributes
#### Scenario: Cookie attributes in localhost development
- GIVEN successful authentication in localhost development
- WHEN cookies are set
- THEN access_token cookie SHALL be httpOnly
- AND access_token cookie SHALL have Secure=false
- AND access_token cookie SHALL have SameSite=lax
- AND refresh_token cookie SHALL have the same attributes
### Requirement: Token Refresh
The system SHALL support automatic token refresh with server-side refresh token storage, rotation, and revocation.
#### Scenario: Access token expiration
- GIVEN a user has an expired access token
- WHEN the user makes an authenticated request that can refresh
- THEN the system validates the refresh token against non-expired, non-revoked DB state
- AND rotates the refresh token
- AND issues a new internal access token
#### Scenario: Refresh token reuse detection
- GIVEN a refresh token has already been rotated or revoked
- WHEN it is presented again to the refresh endpoint
- THEN the system rejects the request with unauthorized status
- AND invalidates the token chain for the session
### Requirement: Session Termination
The system SHALL support explicit logout with refresh token invalidation.
#### Scenario: User logout
- GIVEN an authenticated user
- WHEN the user clicks logout
- THEN all auth cookies are cleared
- AND the refresh token is invalidated in server-side storage
@@ -0,0 +1,38 @@
## 1. Configuration and schema foundation
- [x] 1.1 Add auth/OIDC/JWT/cookie settings to backend config with environment-aware defaults.
- [x] 1.2 Add refresh token SQLAlchemy model and relationships to user model.
- [x] 1.3 Add Alembic migration for refresh token table and indexes.
- [x] 1.4 Add/adjust tests that fail first for config and refresh-token model metadata.
## 2. Auth provider and token services
- [x] 2.1 Implement Authentik OIDC client helpers for login URL build and callback token exchange.
- [x] 2.2 Implement JWKS-based token verification helper for provider tokens.
- [x] 2.3 Implement internal JWT mint/verify helper with configured TTL.
- [x] 2.4 Implement refresh token store service (hashing, create, rotate, revoke, reuse detection).
- [x] 2.5 Add unit tests for provider verification, JWT helpers, cookie options, and refresh lifecycle.
## 3. Auth HTTP endpoints
- [x] 3.1 Implement `GET /auth/login` redirect endpoint.
- [x] 3.2 Implement `GET /auth/callback` with state validation, token exchange, user upsert, and cookie set.
- [x] 3.3 Implement `POST /auth/refresh` with DB validation and rotation.
- [x] 3.4 Implement `POST /auth/logout` to revoke refresh state and clear cookies.
- [x] 3.5 Implement `GET /auth/me` returning authenticated user payload via internal JWT.
- [x] 3.6 Add integration tests for callback, refresh rotation, logout, and unauthorized cases.
## 4. Verification and OpenSpec tracking
- [x] 4.1 Run auth-focused and full backend checks (`pytest`, `ruff check`, `mypy`) and fix findings.
- [x] 4.2 Run migration verification against local Postgres and confirm current revision.
- [x] 4.3 Update this task list with completed checkboxes and note any blockers/follow-ups.
## Blockers / Follow-ups
- No blocking items remain for this change.
## Runtime verification
- `DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter .venv/bin/alembic -c alembic.ini upgrade head` succeeded.
- `DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter .venv/bin/alembic -c alembic.ini current` returned `0002_refresh_tokens (head)`.