diff --git a/.env.example b/.env.example index 887ce83..66bb372 100644 --- a/.env.example +++ b/.env.example @@ -98,12 +98,9 @@ PADDLE_PUBLIC_KEY= PADDLE_BASE_URL= PADDLE_CONSOLE_URL= -OAUTH_JWT_KID=fotospiel-jwt -OAUTH_KEY_STORE= -OAUTH_REFRESH_ENFORCE_IP=true -OAUTH_REFRESH_ALLOW_SUBNET=false -OAUTH_REFRESH_MAX_ACTIVE=5 -OAUTH_REFRESH_AUDIT_RETENTION_DAYS=90 +# Sanctum / SPA auth +SANCTUM_STATEFUL_DOMAINS=localhost,localhost:3000 +SANCTUM_TOKEN_PREFIX= JOIN_TOKEN_FAILURE_LIMIT=10 JOIN_TOKEN_FAILURE_DECAY=5 JOIN_TOKEN_ACCESS_LIMIT=120 diff --git a/docs/deployment/oauth-key-rotation.md b/docs/deployment/oauth-key-rotation.md deleted file mode 100644 index ad04ff2..0000000 --- a/docs/deployment/oauth-key-rotation.md +++ /dev/null @@ -1,58 +0,0 @@ -# OAuth JWT Key Rotation Playbook (Dual-Key) - -## Purpose -Ensure marketing/tenant OAuth tokens remain valid during RSA key rotations by keeping the previous signing key available until all legacy tokens expire. - -## Prerequisites -- Environment variable `OAUTH_KEY_STORE` points to a shared filesystem (default `storage/app/oauth-keys`). -- `OAUTH_JWT_KID` set to the current signing key id. -- Application deploy tooling able to propagate `.env` changes promptly. -- Operations access to run artisan commands in the target environment. - -## Rotation Workflow - -1. **Review existing keys** - ```bash - php artisan oauth:list-keys - ``` - Confirm the `current` entry matches `OAUTH_JWT_KID`, note any legacy KIDs that should remain trusted until rotation completes. - -2. **Generate new key pair** - ```bash - php artisan oauth:rotate-keys --kid=fotospiel-jwt-$(date +%Y%m%d%H%M) - ``` - - The command now *copies* the existing key into the `/archive` folder but leaves it in-place for token verification. - - After the command, run `php artisan oauth:list-keys` again to verify both the old and new KIDs exist. - -3. **Update environment configuration** - - Set `OAUTH_JWT_KID` to the newly generated value. - - Deploy the updated config (restart queue workers/web instances if they cache config). - -4. **Smoke test issuance** - - Request a fresh OAuth token (PKCE flow) and inspect the JWT header — `kid` must match the new value. - - Use an existing token issued **before** the rotation to hit a tenant API route; it should continue to verify because the old key remains present. - -5. **Monitor** - - Watch application logs for `Invalid token` / `JWT public key not found` errors over the next 24h. - - Investigate any anomalies before pruning. - -## Pruning Legacy Keys -After the longest access-token + refresh-token lifetime (default: 30 days for refresh), prune the legacy signing directory. - -```bash -php artisan oauth:prune-keys --days=45 --force -``` - -- Use `--dry-run` first to see which directories would be removed. -- The prune command never deletes the `current` KID. -- Archived copies remain under `storage/app/oauth-keys/archive/...` for forensics. - -## Runbook Summary -| Step | Command | Outcome | -| --- | --- | --- | -| Inspect | `php artisan oauth:list-keys` | Inventory current + legacy keys | -| Rotate | `php artisan oauth:rotate-keys --kid=...` | Creates new key while keeping legacy key active | -| Verify | Issue new token + test old token | Ensures dual-key window works | -| Prune | `php artisan oauth:prune-keys --days=45` | Removes legacy key once safe | - -Document completion of `SEC-IO-01` in `docs/todo/security-hardening-epic.md` when the rotation runbook has been rehearsed in staging. diff --git a/docs/implementation-roadmap.md b/docs/implementation-roadmap.md index 61a99e0..3e9d6bf 100644 --- a/docs/implementation-roadmap.md +++ b/docs/implementation-roadmap.md @@ -97,8 +97,8 @@ Basierend auf aktueller Code-Analyse und Implementierung: ### Verbleibende Tasks 1. **Security Implementation (1 Tag)** - - Rate Limiting: 100/min tenant, 10/min oauth *(aktiv)* - - Token-Rotation in OAuthController *(KID-basierte Schlüssel & `oauth:rotate-keys`)* + - Rate Limiting: 100/min tenant API, 20/min tenant-auth login/exchange *(aktiv)* + - Token issuance handled by Sanctum PAT endpoints; rotation via PAT revocation *(aktiv)* - IP-Binding für Refresh Tokens *(konfigurierbar, Subnetzrelax optional)* ### Milestones diff --git a/docs/plan-superadmin-filament.md b/docs/plan-superadmin-filament.md index a0eecbd..d05f60b 100644 --- a/docs/plan-superadmin-filament.md +++ b/docs/plan-superadmin-filament.md @@ -626,7 +626,7 @@ class SuperAdminMiddleware 1. **Model & Migration:** PurchaseHistory, OAuthClient Tabellen 2. **TenantResource:** Erweiterung mit neuen Feldern, Relations, Actions 3. **PurchaseHistoryResource:** Vollständige CRUD mit Filtern und Export -4. **OAuthClientResource:** Management für OAuth-Clients +4. **PAT-Tooling:** Management für Tenant-PATs (TBD) 5. **Widgets:** Dashboard-Übersicht mit Charts und Stats 6. **Policies & Middleware:** Security für SuperAdmin-Funktionen 7. **Tests:** Feature-Tests für Credit-Management, Permissions diff --git a/docs/prp-addendum-2025-09-08-tenant-admin-pwa.md b/docs/prp-addendum-2025-09-08-tenant-admin-pwa.md index 2d9393f..a1b646a 100644 --- a/docs/prp-addendum-2025-09-08-tenant-admin-pwa.md +++ b/docs/prp-addendum-2025-09-08-tenant-admin-pwa.md @@ -29,7 +29,7 @@ This addendum supersedes tenant-facing Filament guidance in `fotospiel_prp.md`. ## API Surface (Tenant) -- Auth: `/oauth/authorize` (PKCE), `/oauth/token`, `/oauth/token/refresh`. +- Auth: `/api/v1/tenant-auth/login`, `/tenant-auth/exchange`, `/tenant-auth/logout`, `/tenant-auth/me`. - Entities: events, galleries, members, uploads, settings, purchases. - Conventions: pagination, filtering, 429 rate limits, trace IDs in errors. diff --git a/docs/prp/01-architecture.md b/docs/prp/01-architecture.md index 0637daa..a218be8 100644 --- a/docs/prp/01-architecture.md +++ b/docs/prp/01-architecture.md @@ -9,7 +9,7 @@ - DB: Single database, row-level multi-tenancy using `tenant_id` and policies. Components -- API (`/api/v1`) with OAuth2 PKCE for tenant apps; session auth for Super Admin. +- API (`/api/v1`) with Sanctum personal access tokens for tenant apps; session auth for Super Admin. - Media pipeline: upload -> scan -> transform (EXIF/orientation/sizes) -> store -> CDN. - Notifications: Web Push (VAPID); Capacitor push for iOS wrapper when needed. - Observability: request ID, structured logs, audit trails for admin actions. diff --git a/docs/prp/03-api.md b/docs/prp/03-api.md index 976aea9..91a8b71 100644 --- a/docs/prp/03-api.md +++ b/docs/prp/03-api.md @@ -2,7 +2,7 @@ - Base URL: `/api/v1` - Auth - - Tenant apps: OAuth2 Authorization Code + PKCE, refresh tokens. + - Tenant apps: Sanctum personal access tokens (PATs) issued via `/api/v1/tenant-auth/login` + abilities. - Super Admin: session-authenticated Filament (web only). - Common - Pagination: `page`, `per_page` (max 100). @@ -10,7 +10,7 @@ - Rate limits: per-tenant and per-device for tenant apps; 429 with `x-rate-limit-*` headers. Key Endpoints (abridged) -- Auth: `/oauth/authorize`, `/oauth/token`, `/oauth/token/refresh`. +- Auth: `/api/v1/tenant-auth/login`, `/tenant-auth/exchange`, `/tenant-auth/logout`, `/tenant-auth/me`. - Tenants (Super Admin only): list/read; no create via API for MVP. - Events (tenant): CRUD, publish, archive; unique by `tenant_id + slug`. - Photos (tenant): signed upload URL, create, list, moderate, feature. diff --git a/docs/prp/06-tenant-admin-pwa.md b/docs/prp/06-tenant-admin-pwa.md index 596160a..b2d9065 100644 --- a/docs/prp/06-tenant-admin-pwa.md +++ b/docs/prp/06-tenant-admin-pwa.md @@ -6,7 +6,7 @@ Packaging - Installable PWA (A2HS) with offline and background sync. Auth & Tenancy -- OAuth2 Authorization Code + PKCE; refresh tokens; secure storage (Keychain/Keystore). +- Sanctum personal access tokens via `/api/v1/tenant-auth/*`, persisted to IndexedDB/Keychain and refreshable through the session→token exchange for Google sign-ins. - Tokens carry `tenant_id` and roles; backend enforces scoping. Capabilities diff --git a/docs/prp/09-security-compliance.md b/docs/prp/09-security-compliance.md index 30614a0..4eccf56 100644 --- a/docs/prp/09-security-compliance.md +++ b/docs/prp/09-security-compliance.md @@ -9,8 +9,8 @@ ## 2025 Hardening Priorities -- **Identity & OAuth** — *Owner: Backend Platform* - Track JWT key rotation via `oauth:rotate-keys`, roll out dual-key support (old/new KID overlap), surface refresh-token revocation tooling, and extend IP/device binding rules for long-lived sessions. See `docs/deployment/oauth-key-rotation.md` for the rotation playbook. Filament now offers a refresh-token console with per-device revocation and audit history. +- **Identity & Token Management** — *Owner: Backend Platform* + Track Sanctum PAT issuance and revocation. Provide tooling to list/revoke active PATs per tenant admin and document forced re-login procedures for compromised devices. - **Guest Join Tokens** — *Owner: Guest Platform* Hash stored join tokens, add anomaly metrics (usage spikes, stale tokens), and tighten gallery/photo rate limits with visibility in storage dashboards. Join-token access is now logged to `event_join_token_events` with summaries surfaced in the Event admin modal. - **Public API Resilience** — *Owner: Core API* diff --git a/docs/prp/13-backend-authentication.md b/docs/prp/13-backend-authentication.md index afe260a..d2819aa 100644 --- a/docs/prp/13-backend-authentication.md +++ b/docs/prp/13-backend-authentication.md @@ -2,418 +2,128 @@ ## Overview -This document outlines the authentication requirements and implementation details for the Fotospiel tenant backend. The system uses OAuth2 with PKCE (Proof Key for Code Exchange) for secure authorization, providing tenant-specific access tokens for API operations. Additionally, session-based authentication is used for web interfaces like the checkout wizard, supporting both email and username login. +Tenant authentication now uses a hybrid Sanctum setup: -## Session-Based Authentication (Web/Checkout) +- **Tenant Admin PWA** obtains Laravel Sanctum personal access tokens (PATs) via first-party endpoints under `/api/v1/tenant-auth/*`. Tokens carry ability strings (e.g. `tenant-admin`, `tenant:{id}`) and are required for all `/api/v1/tenant/*` routes. +- **First-party web flows** (marketing checkout, Filament admin, Google OAuth sign-in) rely on the classic session guard. When a session-based login needs API access (e.g. inside the PWA shell after a Google callback) the frontend exchanges the session for a PAT using the same Sanctum endpoints. +- **Legacy compatibility** is preserved through `/api/v1/tenant/me`, which now proxies the Sanctum-backed data while keeping the historical payload shape consumed by older clients. -### Checkout Login Flow +All previous OAuth2/PKCE code paths, tables, and environment variables have been removed. No external authorization server is required. + +## Tenant Admin PAT Flow + +### Login +- **Endpoint**: `POST /api/v1/tenant-auth/login` +- **Body** (`application/json`): + ```json + { + "login": "tenant@example.com", + "password": "secret" + } + ``` + `login` accepts either an email address or username. Passwords are validated against the user record. +- **Response** (`200`): + ```json + { + "token": "plain-text-pat", + "token_type": "Bearer", + "abilities": ["tenant-admin", "tenant:42"], + "user": { + "id": 17, + "email": "tenant@example.com", + "name": "Tenant Admin", + "role": "tenant_admin", + "tenant_id": 42 + } + } + ``` +- **Failure codes**: + - `422` with `login` field errors for invalid credentials, unverified email addresses, or roles without tenant access. + - `429` throttle when the `tenant-auth` rate limit is exceeded. + +Every successful login revokes any previous `tenant-admin` PAT for that user before issuing a new token. PATs are plain-text once in the response; the hash is stored in `personal_access_tokens`. + +### Token Exchange (session → PAT) +- **Endpoint**: `POST /api/v1/tenant-auth/exchange` +- **Guards**: `web` session (cookies) with Sanctum stateful middleware. +- **Response**: identical payload to `/login`. +- **Usage**: The Tenant Admin PWA calls this after a browser session login (e.g. Google Socialite callback or marketing checkout user who opens the PWA shell) to synchronise state without a password prompt. + +### Token Introspection & Logout +- **`GET /api/v1/tenant-auth/me`** (auth:sanctum) returns the current user, tenant snapshot, and active abilities for the PAT. +- **`POST /api/v1/tenant-auth/logout`** invalidates the current PAT and clears it from storage. +- **Legacy Proxy**: `/api/v1/tenant/me` now delegates to the new controller to return the historical payload seen by existing tooling while relying on Sanctum for authentication. + +### Calling Tenant APIs +All tenant APIs continue to require the abilities enforced by middleware: +- `auth:sanctum` authenticates the PAT. +- `tenant.admin` gate checks that the user has `tenant-admin` or `super-admin` ability. +- `tenant.isolation` ensures the `tenant:{id}` ability matches the route tenant, guarding cross-tenant access. + +Requests must send the PAT in the `Authorization: Bearer {token}` header. Tokens have no server-side expiration (`sanctum.expiration = null`); clients should refresh proactively (e.g. re-login) when a token is revoked or rejected. + +## Session-Based Flows + +### Marketing Checkout Login - **Endpoint**: `POST /checkout/login` -- **Method**: POST -- **Content-Type**: `application/json` -- **Parameters**: - - `identifier`: Email or username (required, string) - - `password`: User's password (required, string) - - `remember`: Remember me flag (optional, boolean) - - `locale`: Language locale (optional, string, e.g., 'de') +- **Body**: `{ identifier, password, locale? }` +- **Remember behaviour**: the checkbox is hidden on the new Tenant Admin login screen but applied automatically – sessions persist via `Auth::login($user, true)`. +- Successful logins store a `redirect_intent` in the session when the flow originates from `/event-admin/*`, ensuring post-login navigation goes to `/event-admin/dashboard`. -**Authentication Logic**: -- Validate input using Laravel Validator. -- Search for user by email or username using Eloquent query: `User::where('email', $identifier)->orWhere('username', $identifier)->first()`. -- Verify password with `Hash::check()`. -- If valid, log in user with `Auth::login($user, $remember)` and regenerate session. -- Set `pending_purchase = true` if a package is selected (from session) and not already set, wrapped in DB transaction. -- Return JSON response with user data for AJAX handling in frontend. +### Tenant Admin PWA Login Screen +- The React shell renders `/event-admin/login` inside the PWA layout. It uses the same credentials form as `/checkout/login` but hits `/api/v1/tenant-auth/login` with fetch requests. +- After a successful PAT response, the token is persisted to both `localStorage` and `sessionStorage`; abilities are cached client-side to power feature flags. -**Response** (JSON, 200 OK): -```json -{ - "user": { - "id": 1, - "email": "user@example.com", - "name": "John Doe", - "pending_purchase": true - }, - "message": "Login erfolgreich" -} -``` +### Google Sign-In +- Socialite handles the Google redirect/callback under `/event-admin/auth/google`. +- On success the user is logged into the web session, their email is marked verified (if needed), and the browser redirects back into the PWA shell. +- The shell immediately calls `/api/v1/tenant-auth/exchange` to obtain a PAT, keeping the flow consistent with password logins. +- Failures redirect back to the login page with query-string error codes that the PWA surfaces via i18n strings. -**Error Response** (JSON, 422 Unprocessable Entity): -```json -{ - "errors": { - "identifier": ["Ungültige Anmeldedaten."] - } -} -``` +## Middleware & Guards -**Security**: -- CSRF protection via `web` middleware. -- Rate limiting recommended (add `throttle:6,1` middleware). -- Password hashing with Laravel's `Hash` facade. -- Session regeneration after login to prevent fixation attacks. +| Middleware | Purpose | +| --- | --- | +| `auth:sanctum` | Validates PATs issued by Sanctum. | +| `tenant.admin` | Ensures the authenticated user is a tenant admin or super admin. | +| `tenant.isolation` | Loads the tenant model by token ability and adds it to the request for downstream controllers. | +| `throttle:tenant-auth` | Rate limits login, exchange, and logout endpoints. | +| `EncryptCookies`, `AddQueuedCookiesToResponse`, `StartSession` | Applied to exchange routes so session cookies are available. | -### Integration with Standard Laravel Auth -- Leverages `AuthenticatedSessionController` for core logic where possible, but custom handling for identifier flexibility and checkout context. -- Compatible with Inertia.js for SPA responses. +## Security Considerations -## OAuth2 Authentication (API) +- PATs are hashed at rest (`personal_access_tokens`), revocation is handled via database deletes. +- Tokens are limited to one active entry per user (`tenant-admin` name). Issuing a new token automatically stairs old devices out. +- All auth endpoints return generic validation errors to avoid username enumeration. +- Session regeneration occurs on every successful login (password or Google) to prevent fixation. +- Frontend storage keeps a second copy of the PAT in `sessionStorage` to reduce exposure when a user clears only persistent storage. +- Rate limits: configure `tenant-auth` in `RouteServiceProvider` (default `10 requests / minute`). +- CSP / XSRF: stateful Sanctum middleware is configured with `SANCTUM_STATEFUL_DOMAINS` so SPA requests inherit `XSRF-TOKEN` cookies automatically. -### 1. Authorization Request -- **Endpoint**: `GET /api/v1/oauth/authorize` -- **Method**: GET (redirect from frontend) -- **Parameters**: - - `client_id`: Fixed client ID for tenant-admin-app (`tenant-admin-app`) - - `redirect_uri`: Frontend callback URL (e.g., `https://tenant-admin-app.example.com/auth/callback`) - - `response_type`: `code` - - `scope`: `tenant:read tenant:write` (tenant-specific scopes) - - `state`: CSRF protection state parameter - - `code_challenge`: PKCE challenge (SHA-256 hash of code verifier) - - `code_challenge_method`: `S256` +## Database & Infrastructure -**Response**: Redirect to frontend with authorization code and state parameters. +- OAuth tables (`oauth_clients`, `oauth_codes`, `refresh_tokens`) have been removed. Sanctum uses the stock `personal_access_tokens` migration. +- No background jobs are required for PAT issuance or rotation. +- Audit requirements should log `personal_access_tokens` changes via database auditing (e.g. telescope/horizon logs) if needed. -### 2. Token Exchange -- **Endpoint**: `POST /api/v1/oauth/token` -- **Method**: POST -- **Content-Type**: `application/x-www-form-urlencoded` -- **Parameters**: - - `grant_type`: `authorization_code` or `refresh_token` - - `client_id`: `tenant-admin-app` - - `code`: Authorization code (for initial exchange) - - `redirect_uri`: Same as authorization request - - `code_verifier`: PKCE verifier (plain text) - - `refresh_token`: For token refresh (optional) +## Configuration & Environment -**Response** (JSON): -```json -{ - "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "refresh_token": "def50200...", - "expires_in": 3600, - "token_type": "Bearer", - "scope": "tenant:read tenant:write" -} -``` +Set the following environment variables to support the hybrid flow: -### 3. Token Refresh -- **Endpoint**: `POST /api/v1/oauth/token` -- **Method**: POST -- **Content-Type**: `application/x-www-form-urlencoded` -- **Parameters**: - - `grant_type`: `refresh_token` - - `client_id`: `tenant-admin-app` - - `refresh_token`: Current refresh token +- `SANCTUM_STATEFUL_DOMAINS` – comma-separated list of domains running the Tenant Admin PWA (TWA, Capacitor, staging). Ensures cookies are considered first-party for the exchange endpoint. +- `SANCTUM_TOKEN_PREFIX` – optional prefix to enable secret-scanning detection. +- Remove the deprecated OAuth variables: + - `VITE_OAUTH_CLIENT_ID` + - `OAUTH_JWT_KID` + - `OAUTH_KEY_STORE` + - `OAUTH_REFRESH_*` -**Response**: Same as token exchange (new access and refresh tokens). +Google OAuth credentials (`GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`) remain for Socialite but only power the browser session login; token exchange happens through Sanctum. -### 4. Token Validation -- **Endpoint**: `GET /api/v1/tenant/me` -- **Method**: GET -- **Authorization**: `Bearer {access_token}` -- **Purpose**: Validate token and return tenant information +## Testing -**Response** (JSON): -```json -{ - "tenant_id": "tenant-uuid", - "tenant_name": "Event Photo Company", - "credits": 150, - "role": "admin", - "email": "admin@eventphoto.com" -} -``` +- Feature tests cover `/api/v1/tenant-auth/login`, `/exchange`, and `/logout` (`tests/Feature/Auth/TenantProfileApiTest.php`, `tests/Feature/TenantCreditsTest.php`). +- Frontend Playwright fixtures (`tests/e2e/utils/test-fixtures.ts`) provide helpers to obtain PATs when seeding test users. +- When adding new tenant API endpoints ensure coverage under Sanctum by using `->actingAs($user, 'sanctum')` in PHPUnit tests and by asserting abilities with `Sanctum::actingAs()` helpers where required. -## Security Requirements - -### PKCE Implementation -- Validate `code_challenge_method` is `S256` -- Store `code_challenge` and `code_verifier` association temporarily (Redis, 5-minute expiry) -- Verify SHA-256 hash of `code_verifier` matches stored `code_challenge` during token exchange -- Reject requests without valid PKCE parameters - -### State Validation -- Generate cryptographically secure state parameter -- Store state in session or Redis with 5-minute expiry -- Validate state parameter matches stored value during callback -- Reject requests with state mismatch (CSRF protection) - -### Token Security -- Access tokens: JWT with 1-hour expiry, signed with RS256 -- Refresh tokens: Secure random 128-character strings, stored in database -- Refresh tokens: Single-use or rotation (new refresh token with each refresh) -- Rate limiting on token endpoints (100 requests/hour per IP) -- IP binding for refresh tokens (optional for enhanced security) - -### Scopes and Permissions -- `tenant:read`: Access to tenant data (events, photos, members) -- `tenant:write`: Create/update/delete tenant resources -- `tenant:admin`: Full administrative access (user management, billing) -- Token claims include `tenant_id` and `scope` for authorization - -## Database Schema - -### oauth_clients Table -```sql -CREATE TABLE oauth_clients ( - id VARCHAR(255) PRIMARY KEY, - client_id VARCHAR(255) UNIQUE NOT NULL, - client_secret VARCHAR(255), - tenant_id BIGINT UNSIGNED NULL, - redirect_uris JSON NULL, - scopes JSON NULL, - is_active TINYINT(1) DEFAULT 1, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT oauth_clients_tenant_id_foreign FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE SET NULL -); -``` -```sql -CREATE TABLE oauth_clients ( - id VARCHAR(255) PRIMARY KEY, - client_id VARCHAR(255) UNIQUE NOT NULL, - client_secret VARCHAR(255), - redirect_uris TEXT, - scopes TEXT DEFAULT 'tenant:read tenant:write', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); -``` - -### oauth_codes Table (Temporary) -```sql -CREATE TABLE oauth_codes ( - id VARCHAR(255) PRIMARY KEY, - client_id VARCHAR(255) NOT NULL, - user_id VARCHAR(255) NOT NULL, - code VARCHAR(255) UNIQUE NOT NULL, - code_challenge VARCHAR(255) NOT NULL, - state VARCHAR(255), - redirect_uri VARCHAR(255), - scope TEXT, - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - INDEX idx_code (code), - INDEX idx_expires (expires_at) -); -``` - -### refresh_tokens Table -```sql -CREATE TABLE refresh_tokens ( - id VARCHAR(255) PRIMARY KEY, - tenant_id VARCHAR(255) NOT NULL, - client_id VARCHAR(255), - token VARCHAR(255) UNIQUE NOT NULL, - access_token VARCHAR(255), - scope TEXT, - ip_address VARCHAR(45), - user_agent TEXT, - expires_at TIMESTAMP, - revoked_at TIMESTAMP NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -``` -```sql -CREATE TABLE refresh_tokens ( - id VARCHAR(255) PRIMARY KEY, - tenant_id VARCHAR(255) NOT NULL, - token VARCHAR(255) UNIQUE NOT NULL, - access_token VARCHAR(255), - expires_at TIMESTAMP NOT NULL, - scope TEXT, - ip_address VARCHAR(45), - user_agent TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - revoked_at TIMESTAMP NULL, - INDEX idx_tenant (tenant_id), - INDEX idx_token (token), - INDEX idx_expires (expires_at) -); -``` - -### tenant_tokens Table (JWT Blacklist) -```sql -CREATE TABLE tenant_tokens ( - id VARCHAR(255) PRIMARY KEY, - tenant_id VARCHAR(255) NOT NULL, - jti VARCHAR(255) UNIQUE NOT NULL, -- JWT ID - token_type VARCHAR(50) NOT NULL, - expires_at TIMESTAMP NOT NULL, - revoked_at TIMESTAMP NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - INDEX idx_jti (jti), - INDEX idx_tenant (tenant_id), - INDEX idx_expires (expires_at) -); -``` - -## API Endpoints - -### Authentication Endpoints -| Endpoint | Method | Description | Authentication | -|----------|--------|-------------|----------------| -| `/oauth/authorize` | GET | Authorization request | None | -| `/oauth/token` | POST | Token exchange/refresh | None | -| `/api/v1/tenant/me` | GET | Validate token | Bearer Token | -| `/checkout/login` | POST | Session login for checkout (email/username) | None | - -### Protected Endpoints -All tenant API endpoints require `Authorization: Bearer {access_token}` header. - -#### Token Validation Middleware -```javascript -// Pseudocode -function validateTenantToken(req, res, next) { - const token = req.headers.authorization?.replace('Bearer ', ''); - if (!token) return res.status(401).json({ error: 'Missing token' }); - - try { - const decoded = jwt.verify(token, process.env.JWT_SECRET); - const { tenant_id, scope, exp, jti } = decoded; - - // Check if token is blacklisted - const blacklisted = await db.query('SELECT * FROM tenant_tokens WHERE jti = ? AND revoked_at IS NULL', [jti]); - if (blacklisted.length > 0) { - return res.status(401).json({ error: 'Token revoked' }); - } - - // Check expiry - if (Date.now() >= exp * 1000) { - return res.status(401).json({ error: 'Token expired' }); - } - - // Set tenant context - req.tenant = { id: tenant_id, scope }; - next(); - } catch (error) { - res.status(401).json({ error: 'Invalid token' }); - } -} -``` - -## Environment Variables - -### Backend (.env) -``` -JWT_SECRET=your-super-secret-jwt-signing-key -API_BASE_URL=https://api.fotospiel.com -OAUTH_CLIENT_SECRET=your-oauth-client-secret -DATABASE_URL=your-database-connection-string -REDIS_URL=redis://localhost:6379 -``` - -### Frontend (.env) -``` -VITE_API_URL=https://api.fotospiel.com -VITE_OAUTH_CLIENT_ID=tenant-admin-app -``` - -> **Hinweis:** Der Wert von `VITE_OAUTH_CLIENT_ID` dient jetzt als alleinige Quelle der Wahrheit für den Tenant-Admin-OAuth-Client. Der Seeder `OAuthClientSeeder` greift auf `config/services.php` zu, das wiederum diesen Env-Wert ausliest und passende Redirect-URIs generiert (`/event-admin/auth/callback` für DEV und APP_URL). Stimmt der Wert im Frontend nicht mit dem Seeder überein, schlägt der PKCE-Login mit `invalid_client` fehl. - -## Error Handling - -### Common Error Responses -```json -// 400 Bad Request -{ - "error": "invalid_request", - "error_description": "Missing required parameter: code" -} - -// 401 Unauthorized -{ - "error": "invalid_token", - "error_description": "Token signature invalid" -} - -// 403 Forbidden -{ - "error": "insufficient_scope", - "error_description": "Scope tenant:write required" -} - -// 422 Unprocessable Entity (Checkout Login) -{ - "errors": { - "identifier": ["Ungültige Anmeldedaten."] - } -} -``` - -## Implementation Notes - -### 1. PKCE Storage -- Use Redis for temporary code_challenge storage (5-minute TTL) -- Key format: `pkce:{code_challenge}:{client_id}` -- Value: JSON with `code_verifier`, `user_id`, `redirect_uri`, `scope` - -### 2. Refresh Token Rotation -- Issue new refresh token with each refresh -- Revoke old refresh token immediately -- Limit refresh tokens per tenant to 5 active - -### 3. Key Management -- RSA key pairs for signing are generated on demand and stored in storage/app/private.key (private) and storage/app/public.key (public). -- Treat the private key as a secret; rotate it alongside deploys that invalidate tenant tokens. - -### 4. Rate Limiting -- Authorization requests: 10/minute per IP -- Token exchanges: 5/minute per IP -- Token validation: 100/minute per tenant -- Checkout login: 6/minute per IP (add throttle middleware) - -### 5. Logging and Monitoring -- Log all authentication attempts (success/failure) -- Monitor token usage patterns -- Alert on unusual activity (multiple failed attempts, token anomalies) -- Track refresh token usage for security analysis -- Log checkout login attempts with identifier type (email/username) - -### 6. Database Cleanup -- Cron job to remove expired authorization codes (daily) -- Remove expired refresh tokens (weekly) -- Clean blacklisted tokens after expiry (daily) - -## Testing Requirements - -### Unit Tests -- PKCE generation and validation -- State parameter security -- Token signing and verification -- Scope validation middleware -- Checkout login with email and username - -### Integration Tests -- Complete OAuth2 flow (authorize → token → validate) -- Token refresh cycle -- Error scenarios (invalid code, expired tokens, state mismatch) -- Concurrent access testing -- Checkout login flow with pending_purchase - -### Security Tests -- CSRF protection validation -- PKCE bypass attempts -- Token replay attacks -- Rate limiting enforcement -- Username/email ambiguity handling - -## Deployment Considerations - -### 1. Secrets Management -- Store JWT secret and OAuth client secret in secure vault (AWS Secrets Manager, HashiCorp Vault) -- Rotate secrets every 90 days -- Use different secrets for dev/staging/production - -### 2. Certificate Management -- Use Let's Encrypt or commercial SSL certificates -- Rotate certificates before expiry -- Enable HSTS headers - -### 3. Monitoring -- Track authentication success/failure rates -- Monitor token expiry patterns -- Alert on PKCE validation failures -- Log all security-related events -- Monitor checkout login success rates and identifier usage - -This implementation provides secure, scalable authentication for the Fotospiel tenant system, following OAuth2 best practices with PKCE for public clients and flexible session auth for web flows. +This Sanctum-based approach keeps login logic inside the Laravel application, avoids custom OAuth infrastructure, and works uniformly across web, PWA, and the planned React Native wrapper. diff --git a/docs/prp/marketing-checkout-payment-architecture.md b/docs/prp/marketing-checkout-payment-architecture.md index 0e787fe..e4d2e4c 100644 --- a/docs/prp/marketing-checkout-payment-architecture.md +++ b/docs/prp/marketing-checkout-payment-architecture.md @@ -13,7 +13,7 @@ - **Wizard API surface** (JSON routes under `/checkout/*`) is session-authenticated, CSRF-protected, and returns structured payloads consumed by the PWA. - **Webhooks** (Stripe, Paddle) map incoming provider events back to `CheckoutSession` rows to guarantee reconciliation and support 3DS / async capture paths. - **Feature Flag**: `config/checkout.php` exposes `CHECKOUT_WIZARD_ENABLED` and `CHECKOUT_WIZARD_FLAG` so the SPA flow can be toggled or gradual-rolled out during launch. -- **Operational**: Rotate JWT signing keys with `php artisan oauth:rotate-keys` (updates key folder per KID; remember to bump `OAUTH_JWT_KID`). +- **Operational**: Track Sanctum PAT issuance via `personal_access_tokens` and document forced logout procedures (no OAuth key rotation required anymore). ## Payment State Machine State constants live on `CheckoutSession` (`status` column, enum): diff --git a/docs/prp/tenant-app-specs/README.md b/docs/prp/tenant-app-specs/README.md index e518794..a1a0738 100644 --- a/docs/prp/tenant-app-specs/README.md +++ b/docs/prp/tenant-app-specs/README.md @@ -16,12 +16,12 @@ Die App ersetzt das frühere Filament-basierte Tenant-Panel und fokussiert auf e - **Direkter Checkout**: Paddle sind in die Paketwahl des Welcome Flows eingebettet; Fortschritt wird im Onboarding-Context persistiert. - **Filament Wizard**: Für Super-Admins existiert ein paralleler QR/Join-Token-Wizard in Filament (Token-Generierung, Layout-Downloads, Rotation). - **Join Tokens only**: Gäste erhalten ausschließlich join-token-basierte Links/QRs; slug-basierte URLs wurden deaktiviert. QR-Drucklayouts liegen unter `resources/views/pdf/join-tokens/*`. -- **OAuth Alignment**: `VITE_OAUTH_CLIENT_ID` + `/event-admin/auth/callback` werden seedingseitig synchron gehalten; siehe `docs/prp/tenant-app-specs/api-usage.md`. +- **Auth Alignment**: Sanctum-PATs über `/api/v1/tenant-auth/login` + `/tenant-auth/exchange`; siehe `docs/prp/tenant-app-specs/api-usage.md`. ## Kernziele - **Deliverables**: Voll funktionsfähige App mit CRUD-Operationen für Tenant-Ressourcen (Events, Photos, Tasks, etc.). - **UI/UX**: Framework7-Komponenten für konsistente, native Mobile-Interfaces (iOS/Android). -- **Technologie-Stack**: React/Vite (Core), Framework7 (UI), Capacitor (Native), OAuth2 + PKCE (Auth). +- **Technologie-Stack**: React/Vite (Core), Framework7 (UI), Capacitor (Native), Sanctum PATs (Auth). - **Distribution**: App Store (iOS), Google Play (Android), PWA-Install (Web). ## Struktur dieser PRP diff --git a/docs/prp/tenant-app-specs/api-usage.md b/docs/prp/tenant-app-specs/api-usage.md index 37c3319..9d5d6b2 100644 --- a/docs/prp/tenant-app-specs/api-usage.md +++ b/docs/prp/tenant-app-specs/api-usage.md @@ -1,28 +1,30 @@ # API-Nutzung der Tenant Admin App -Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit der Backend-Hauptapp kommuniziert. Alle Requests sind tenant-scoped und erfordern JWT-Authentifizierung. +Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Requests sind tenant-scoped und erfordern ein Sanctum Personal Access Token (PAT) mit der Fähigkeit `tenant-admin` bzw. `tenant:`. ## Authentifizierung -### OAuth2 Flow (PKCE) -- **Start**: `GET /oauth/authorize` (Browser-Redirect mit PKCE-Challenge) - - **Params**: `client_id`, `redirect_uri`, `scope=tenant:read tenant:write`, `state`, `code_challenge`, `code_challenge_method=S256` - - **Response**: Authorization Code +### Passwort-Login (PAT) +- **Endpoint**: `POST /api/v1/tenant-auth/login` +- **Body** (`application/json`): `{ "login": "username oder email", "password": "••••" }` +- **Response**: `{ token, token_type, abilities[], user { ... } }` +- **Fehler**: `422` bei ungültigen Daten, `429` bei Rate-Limit (`throttle:tenant-auth`). +- **Hinweis**: `login` akzeptiert E-Mail _oder_ Usernamen. Erfolgreiche Logins revoken vorhandene PATs mit Name `tenant-admin`. -- **Token Exchange**: `POST /oauth/token` - - **Body**: `grant_type=authorization_code`, `client_id`, `code`, `redirect_uri`, `code_verifier` - - **Response**: `{ access_token, refresh_token, expires_in, token_type }` - - **Headers**: `Content-Type: application/x-www-form-urlencoded` +### Session→PAT Exchange (Google/Login-Shell) +- **Endpoint**: `POST /api/v1/tenant-auth/exchange` +- **Middleware**: Sanctum stateful cookies (`SANCTUM_STATEFUL_DOMAINS`), `throttle:tenant-auth`. +- **Use-Case**: Nach Google-Login oder Marketing-Checkout Session wird das PAT ohne erneute Passwort-Eingabe ausgegeben. +- **Response**: identisch zum Passwort-Login. -- **Token Refresh**: `POST /oauth/token` - - **Body**: `grant_type=refresh_token`, `client_id`, `refresh_token` - - **Response**: Neuer Access/Refresh-Token +### Token Status & Logout +- **`GET /api/v1/tenant-auth/me`** – liefert `{ user, tenant, abilities }` und spiegelt die PAT-Fähigkeiten wider. +- **`POST /api/v1/tenant-auth/logout`** – löscht das aktuelle PAT aus `personal_access_tokens`. -- **Token Validation**: `GET /api/v1/tenant/me` - - **Redirect URI**: Standardmaessig `${origin}/event-admin/auth/callback` (per Vite-Env anpassbar) - - **Headers**: `Authorization: Bearer {access_token}` - - **Response**: `{ id, email, tenant_id, role, name }` - - **Hinweis**: `client_id` entspricht `VITE_OAUTH_CLIENT_ID`; Seeder `OAuthClientSeeder` synchronisiert Frontend und Backend. +### Legacy Payload `/tenant/me` +- **Endpoint**: `GET /api/v1/tenant/me` +- **Response**: historisches Format (`tenant_id`, `remaining_events`, `scopes`), betrieben über denselben PAT. +- **Kompatibilität**: Dient älteren Clients als Übergang; neue Features sollten `/tenant-auth/me` verwenden. ## Dashboard @@ -188,7 +190,7 @@ Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit ## Allgemeine Headers Alle API-Requests enthalten: -- **Authorization**: `Bearer {access_token}` (JWT mit tenant_id) +- **Authorization**: `Bearer {access_token}` (Sanctum PAT mit Fähigkeit `tenant:{id}`) - **Content-Type**: `application/json` (für POST/PATCH) - **If-Match**: `{etag}` (für Concurrency-Control bei Updates) - **Accept**: `application/json` @@ -230,7 +232,7 @@ Mutierende Endpunkte (PATCH/DELETE) erfordern: ## Sicherheit -- **Tenant-Isolation**: Alle Endpunkte prüfen JWT-tenant_id gegen request tenant_id +- **Tenant-Isolation**: Middleware vergleicht PAT-Fähigkeit (`tenant:{id}`) mit dem angefragten Tenant - **RBAC**: Nur tenant_admin kann mutieren, member kann nur lesen/hochladen - **Rate Limiting**: 100 Requests/Minute pro Tenant - **ETag**: Automatische Concurrency-Control @@ -259,8 +261,9 @@ curl -H "Authorization: Bearer {token}" \ ### Environment-Variablen - **VITE_API_URL**: Backend-API-URL (Pflicht) -- **VITE_OAUTH_CLIENT_ID**: OAuth-Client-ID (Pflicht, muss mit `config/services.php` übereinstimmen – der Seeder legt damit den Client in `oauth_clients` an) -- **VITE_REVENUECAT_PUBLIC_KEY**: Optional fuer In-App-Kaeufe (RevenueCat) +- **VITE_ENABLE_TENANT_SWITCHER**: Dev-Flag um Tenants im Header auszuwählen (optional, Default `false`) +- **VITE_REVENUECAT_PUBLIC_KEY**: Optional für In-App-Käufe (RevenueCat) +- **SANCTUM_STATEFUL_DOMAINS** (Backend): Enthält die Origins des Admin-PWA/TWA, damit der Session→PAT-Exchange funktioniert. - **REVENUECAT_WEBHOOK_SECRET / REVENUECAT_PRODUCT_MAPPINGS / REVENUECAT_APP_USER_PREFIX / REVENUECAT_WEBHOOK_QUEUE**: Backend-Konfiguration für RevenueCat-Webhooks, siehe `config/services.php`. ### Build & Deploy @@ -273,4 +276,3 @@ curl -H "Authorization: Bearer {token}" \ - Installierbar auf Desktop/Mobile via "Zum Startbildschirm hinzufügen". Für weitere Details siehe die spezifischen Dokumentationsdateien. - diff --git a/docs/prp/tenant-app-specs/functional-specs.md b/docs/prp/tenant-app-specs/functional-specs.md index cc5d4ed..db98d74 100644 --- a/docs/prp/tenant-app-specs/functional-specs.md +++ b/docs/prp/tenant-app-specs/functional-specs.md @@ -18,8 +18,8 @@ Die Admin-App muss folgende Kernfunktionen bereitstellen: ## Capabilities ### Authentifizierung & Autorisierung -- OAuth2 Authorization Code mit PKCE, Refresh-Tokens via Secure Storage (Web: IndexedDB, Capacitor: Preferences/Keychain). -- Tenant-Scoped Tokens; Rollen `tenant_admin` (vollständig) & `member` (read-only, Upload). +- Sanctum Personal Access Tokens (`/api/v1/tenant-auth/login|exchange`) mit Fähigkeiten `tenant-admin`, `tenant:{id}`; Speicherung in IndexedDB sowie Keychain/Keystore für Capacitor. +- Rollenbasierte Fähigkeiten: `tenant_admin` (vollständig), `member` (read-only, Upload). Token-Exchange nach Google-Login oder Session-Login via `/tenant-auth/exchange`. ### Onboarding Journey - Routen `/event-admin/welcome/*` bilden den Flow. @@ -67,7 +67,7 @@ Die App nutzt Endpunkte aus `docs/prp/03-api.md`. | Bereich | Endpunkte | | --- | --- | -| Auth | `POST /oauth/token`, `POST /oauth/token/refresh` | +| Auth | `POST /tenant-auth/login`, `POST /tenant-auth/exchange`, `POST /tenant-auth/logout` | | Onboarding | `GET /tenant/me` (Progress Flags), `GET /tenant/packages`, `POST /tenant/events` | | Events | `GET/POST/PATCH/DELETE /tenant/events`, `POST /tenant/events/{event}/toggle`, Join-Token Routen | | Medien | `GET /tenant/events/{event}/photos`, `POST /tenant/events/{event}/photos`, `PATCH /tenant/photos/{id}` | @@ -76,7 +76,7 @@ Die App nutzt Endpunkte aus `docs/prp/03-api.md`. ## Nicht-funktionale Anforderungen - **Performance**: Ladezeit < 2s; Code-Splitting der Onboarding-Screens. -- **Sicherheit**: Keine sensiblen Logs; CSRF-mitigiert via PKCE/OAuth; Token-Refresh automatisiert. +- **Sicherheit**: Keine sensiblen Logs; CSRF abgesichert via Sanctum stateful middleware; Token-Neuausstellung durch erneutes Login oder Exchange. - **Accessibility**: Tastaturbedienung, Fokus-Indikatoren, `prefers-reduced-motion`. - **Internationalisierung**: Sprachumschaltung in Einstellungen; Standard de, Fallback en. diff --git a/docs/todo/security-hardening-epic.md b/docs/todo/security-hardening-epic.md index 8a3ebd6..8ad7d87 100644 --- a/docs/todo/security-hardening-epic.md +++ b/docs/todo/security-hardening-epic.md @@ -10,7 +10,7 @@ Raise the baseline security posture across guest APIs, checkout, media storage, - Refresh-token revocation tooling (per device/IP) and anomaly alerts. - Device fingerprint/subnet allowances documented and configurable. - **Tickets** - - `SEC-IO-01` — Generate dual-key rollout playbook + automation (Week 1). *(Runbook: `docs/deployment/oauth-key-rotation.md`; commands: `oauth:list-keys`, `oauth:prune-keys`)* + - `SEC-IO-01` — Document PAT revocation/rotation playbook (Week 1). Include scripted revocation of stale tokens and guidance for forced re-login. (Replace legacy OAuth key rotation runbook). - `SEC-IO-02` — Build refresh-token management UI + audit logs (Week 2). *(Filament console + audit trail added 2025-10-23)* - `SEC-IO-03` — Implement subnet/device matching configuration & tests (Week 3). diff --git a/docs/todo/tenant-admin-onboarding-fusion.md b/docs/todo/tenant-admin-onboarding-fusion.md index 096520f..acddec8 100644 --- a/docs/todo/tenant-admin-onboarding-fusion.md +++ b/docs/todo/tenant-admin-onboarding-fusion.md @@ -42,11 +42,10 @@ Owner: Codex (handoff) ## Priority: Immediate (Tenant admin refresh 2025-10-18) - [x] Replace the `/event-admin/login` landing with a public welcome screen that explains Fotospiel for non-technical couples, keeps the login button, and updates `resources/js/admin/router.tsx`, `constants.ts`, and new `WelcomeTeaserPage`. -- [x] Align OAuth setup by reading `VITE_OAUTH_CLIENT_ID` in `OAuthClientSeeder`, updating redirect URIs to `/event-admin/auth/callback`, reseeding, and documenting the env expectation in `docs/prp/tenant-app-specs/api-usage.md` / `13-backend-authentication.md`. +- [x] Align authentication by migrating the tenant-admin flow to Sanctum PATs, dropping `VITE_OAUTH_CLIENT_ID`, updating the exchange endpoints, and documenting the env expectations in `docs/prp/tenant-app-specs/api-usage.md` / `13-backend-authentication.md`. - [x] Rebrand the Filament tenant panel away from “Admin” by adjusting `AdminPanelProvider` (brand name, home URL, navigation visibility) and registering a new onboarding home page. - [x] Build the Filament onboarding wizard (welcome → task package selection → event name → color palette → QR layout) with persisted progress on the tenant record and guards that hide legacy resource menus until completion. - [x] Expose QR invite generation in Filament via a dedicated page/component that reuses the join-token flow from `EventDetailPage.tsx`, ensuring tokens stay in sync between PWA and Filament. - [x] Update PRP/docs to cover die neue Welcome Journey, OAuth-Ausrichtung, Filament-Onboarding und QR-Tooling; Regression Notes + Tests dokumentiert. -