updated the docs, removed oauth and introduced sanctum pat

This commit is contained in:
Codex Agent
2025-11-07 07:47:25 +01:00
parent 67affd3317
commit 9cc9950b0c
16 changed files with 153 additions and 503 deletions

View File

@@ -98,12 +98,9 @@ PADDLE_PUBLIC_KEY=
PADDLE_BASE_URL= PADDLE_BASE_URL=
PADDLE_CONSOLE_URL= PADDLE_CONSOLE_URL=
OAUTH_JWT_KID=fotospiel-jwt # Sanctum / SPA auth
OAUTH_KEY_STORE= SANCTUM_STATEFUL_DOMAINS=localhost,localhost:3000
OAUTH_REFRESH_ENFORCE_IP=true SANCTUM_TOKEN_PREFIX=
OAUTH_REFRESH_ALLOW_SUBNET=false
OAUTH_REFRESH_MAX_ACTIVE=5
OAUTH_REFRESH_AUDIT_RETENTION_DAYS=90
JOIN_TOKEN_FAILURE_LIMIT=10 JOIN_TOKEN_FAILURE_LIMIT=10
JOIN_TOKEN_FAILURE_DECAY=5 JOIN_TOKEN_FAILURE_DECAY=5
JOIN_TOKEN_ACCESS_LIMIT=120 JOIN_TOKEN_ACCESS_LIMIT=120

View File

@@ -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.

View File

@@ -97,8 +97,8 @@ Basierend auf aktueller Code-Analyse und Implementierung:
### Verbleibende Tasks ### Verbleibende Tasks
1. **Security Implementation (1 Tag)** 1. **Security Implementation (1 Tag)**
- Rate Limiting: 100/min tenant, 10/min oauth *(aktiv)* - Rate Limiting: 100/min tenant API, 20/min tenant-auth login/exchange *(aktiv)*
- Token-Rotation in OAuthController *(KID-basierte Schlüssel & `oauth:rotate-keys`)* - Token issuance handled by Sanctum PAT endpoints; rotation via PAT revocation *(aktiv)*
- IP-Binding für Refresh Tokens *(konfigurierbar, Subnetzrelax optional)* - IP-Binding für Refresh Tokens *(konfigurierbar, Subnetzrelax optional)*
### Milestones ### Milestones

View File

@@ -626,7 +626,7 @@ class SuperAdminMiddleware
1. **Model & Migration:** PurchaseHistory, OAuthClient Tabellen 1. **Model & Migration:** PurchaseHistory, OAuthClient Tabellen
2. **TenantResource:** Erweiterung mit neuen Feldern, Relations, Actions 2. **TenantResource:** Erweiterung mit neuen Feldern, Relations, Actions
3. **PurchaseHistoryResource:** Vollständige CRUD mit Filtern und Export 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 5. **Widgets:** Dashboard-Übersicht mit Charts und Stats
6. **Policies & Middleware:** Security für SuperAdmin-Funktionen 6. **Policies & Middleware:** Security für SuperAdmin-Funktionen
7. **Tests:** Feature-Tests für Credit-Management, Permissions 7. **Tests:** Feature-Tests für Credit-Management, Permissions

View File

@@ -29,7 +29,7 @@ This addendum supersedes tenant-facing Filament guidance in `fotospiel_prp.md`.
## API Surface (Tenant) ## 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. - Entities: events, galleries, members, uploads, settings, purchases.
- Conventions: pagination, filtering, 429 rate limits, trace IDs in errors. - Conventions: pagination, filtering, 429 rate limits, trace IDs in errors.

View File

@@ -9,7 +9,7 @@
- DB: Single database, row-level multi-tenancy using `tenant_id` and policies. - DB: Single database, row-level multi-tenancy using `tenant_id` and policies.
Components 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. - Media pipeline: upload -> scan -> transform (EXIF/orientation/sizes) -> store -> CDN.
- Notifications: Web Push (VAPID); Capacitor push for iOS wrapper when needed. - Notifications: Web Push (VAPID); Capacitor push for iOS wrapper when needed.
- Observability: request ID, structured logs, audit trails for admin actions. - Observability: request ID, structured logs, audit trails for admin actions.

View File

@@ -2,7 +2,7 @@
- Base URL: `/api/v1` - Base URL: `/api/v1`
- Auth - 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). - Super Admin: session-authenticated Filament (web only).
- Common - Common
- Pagination: `page`, `per_page` (max 100). - 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. - Rate limits: per-tenant and per-device for tenant apps; 429 with `x-rate-limit-*` headers.
Key Endpoints (abridged) 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. - Tenants (Super Admin only): list/read; no create via API for MVP.
- Events (tenant): CRUD, publish, archive; unique by `tenant_id + slug`. - Events (tenant): CRUD, publish, archive; unique by `tenant_id + slug`.
- Photos (tenant): signed upload URL, create, list, moderate, feature. - Photos (tenant): signed upload URL, create, list, moderate, feature.

View File

@@ -6,7 +6,7 @@ Packaging
- Installable PWA (A2HS) with offline and background sync. - Installable PWA (A2HS) with offline and background sync.
Auth & Tenancy 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. - Tokens carry `tenant_id` and roles; backend enforces scoping.
Capabilities Capabilities

View File

@@ -9,8 +9,8 @@
## 2025 Hardening Priorities ## 2025 Hardening Priorities
- **Identity & OAuth***Owner: Backend Platform* - **Identity & Token Management***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. 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* - **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. 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* - **Public API Resilience***Owner: Core API*

View File

@@ -2,418 +2,128 @@
## Overview ## 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` - **Endpoint**: `POST /checkout/login`
- **Method**: POST - **Body**: `{ identifier, password, locale? }`
- **Content-Type**: `application/json` - **Remember behaviour**: the checkbox is hidden on the new Tenant Admin login screen but applied automatically sessions persist via `Auth::login($user, true)`.
- **Parameters**: - 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`.
- `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')
**Authentication Logic**: ### Tenant Admin PWA Login Screen
- Validate input using Laravel Validator. - 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.
- Search for user by email or username using Eloquent query: `User::where('email', $identifier)->orWhere('username', $identifier)->first()`. - After a successful PAT response, the token is persisted to both `localStorage` and `sessionStorage`; abilities are cached client-side to power feature flags.
- 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.
**Response** (JSON, 200 OK): ### Google Sign-In
```json - 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.
"user": { - The shell immediately calls `/api/v1/tenant-auth/exchange` to obtain a PAT, keeping the flow consistent with password logins.
"id": 1, - Failures redirect back to the login page with query-string error codes that the PWA surfaces via i18n strings.
"email": "user@example.com",
"name": "John Doe",
"pending_purchase": true
},
"message": "Login erfolgreich"
}
```
**Error Response** (JSON, 422 Unprocessable Entity): ## Middleware & Guards
```json
{
"errors": {
"identifier": ["Ungültige Anmeldedaten."]
}
}
```
**Security**: | Middleware | Purpose |
- CSRF protection via `web` middleware. | --- | --- |
- Rate limiting recommended (add `throttle:6,1` middleware). | `auth:sanctum` | Validates PATs issued by Sanctum. |
- Password hashing with Laravel's `Hash` facade. | `tenant.admin` | Ensures the authenticated user is a tenant admin or super admin. |
- Session regeneration after login to prevent fixation attacks. | `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 ## Security Considerations
- Leverages `AuthenticatedSessionController` for core logic where possible, but custom handling for identifier flexibility and checkout context.
- Compatible with Inertia.js for SPA responses.
## 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 ## Database & Infrastructure
- **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`
**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 ## Configuration & Environment
- **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)
**Response** (JSON): Set the following environment variables to support the hybrid flow:
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "def50200...",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "tenant:read tenant:write"
}
```
### 3. Token Refresh - `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.
- **Endpoint**: `POST /api/v1/oauth/token` - `SANCTUM_TOKEN_PREFIX` optional prefix to enable secret-scanning detection.
- **Method**: POST - Remove the deprecated OAuth variables:
- **Content-Type**: `application/x-www-form-urlencoded` - `VITE_OAUTH_CLIENT_ID`
- **Parameters**: - `OAUTH_JWT_KID`
- `grant_type`: `refresh_token` - `OAUTH_KEY_STORE`
- `client_id`: `tenant-admin-app` - `OAUTH_REFRESH_*`
- `refresh_token`: Current refresh token
**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 ## Testing
- **Endpoint**: `GET /api/v1/tenant/me`
- **Method**: GET
- **Authorization**: `Bearer {access_token}`
- **Purpose**: Validate token and return tenant information
**Response** (JSON): - Feature tests cover `/api/v1/tenant-auth/login`, `/exchange`, and `/logout` (`tests/Feature/Auth/TenantProfileApiTest.php`, `tests/Feature/TenantCreditsTest.php`).
```json - 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.
"tenant_id": "tenant-uuid",
"tenant_name": "Event Photo Company",
"credits": 150,
"role": "admin",
"email": "admin@eventphoto.com"
}
```
## Security Requirements 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.
### 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.

View File

@@ -13,7 +13,7 @@
- **Wizard API surface** (JSON routes under `/checkout/*`) is session-authenticated, CSRF-protected, and returns structured payloads consumed by the PWA. - **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. - **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. - **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 ## Payment State Machine
State constants live on `CheckoutSession` (`status` column, enum): State constants live on `CheckoutSession` (`status` column, enum):

View File

@@ -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. - **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). - **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/*`. - **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 ## Kernziele
- **Deliverables**: Voll funktionsfähige App mit CRUD-Operationen für Tenant-Ressourcen (Events, Photos, Tasks, etc.). - **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). - **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). - **Distribution**: App Store (iOS), Google Play (Android), PWA-Install (Web).
## Struktur dieser PRP ## Struktur dieser PRP

View File

@@ -1,28 +1,30 @@
# API-Nutzung der Tenant Admin App # 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:<id>`.
## Authentifizierung ## Authentifizierung
### OAuth2 Flow (PKCE) ### Passwort-Login (PAT)
- **Start**: `GET /oauth/authorize` (Browser-Redirect mit PKCE-Challenge) - **Endpoint**: `POST /api/v1/tenant-auth/login`
- **Params**: `client_id`, `redirect_uri`, `scope=tenant:read tenant:write`, `state`, `code_challenge`, `code_challenge_method=S256` - **Body** (`application/json`): `{ "login": "username oder email", "password": "••••" }`
- **Response**: Authorization Code - **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` ### Session→PAT Exchange (Google/Login-Shell)
- **Body**: `grant_type=authorization_code`, `client_id`, `code`, `redirect_uri`, `code_verifier` - **Endpoint**: `POST /api/v1/tenant-auth/exchange`
- **Response**: `{ access_token, refresh_token, expires_in, token_type }` - **Middleware**: Sanctum stateful cookies (`SANCTUM_STATEFUL_DOMAINS`), `throttle:tenant-auth`.
- **Headers**: `Content-Type: application/x-www-form-urlencoded` - **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` ### Token Status & Logout
- **Body**: `grant_type=refresh_token`, `client_id`, `refresh_token` - **`GET /api/v1/tenant-auth/me`** liefert `{ user, tenant, abilities }` und spiegelt die PAT-Fähigkeiten wider.
- **Response**: Neuer Access/Refresh-Token - **`POST /api/v1/tenant-auth/logout`** löscht das aktuelle PAT aus `personal_access_tokens`.
- **Token Validation**: `GET /api/v1/tenant/me` ### Legacy Payload `/tenant/me`
- **Redirect URI**: Standardmaessig `${origin}/event-admin/auth/callback` (per Vite-Env anpassbar) - **Endpoint**: `GET /api/v1/tenant/me`
- **Headers**: `Authorization: Bearer {access_token}` - **Response**: historisches Format (`tenant_id`, `remaining_events`, `scopes`), betrieben über denselben PAT.
- **Response**: `{ id, email, tenant_id, role, name }` - **Kompatibilität**: Dient älteren Clients als Übergang; neue Features sollten `/tenant-auth/me` verwenden.
- **Hinweis**: `client_id` entspricht `VITE_OAUTH_CLIENT_ID`; Seeder `OAuthClientSeeder` synchronisiert Frontend und Backend.
## Dashboard ## Dashboard
@@ -188,7 +190,7 @@ Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit
## Allgemeine Headers ## Allgemeine Headers
Alle API-Requests enthalten: 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) - **Content-Type**: `application/json` (für POST/PATCH)
- **If-Match**: `{etag}` (für Concurrency-Control bei Updates) - **If-Match**: `{etag}` (für Concurrency-Control bei Updates)
- **Accept**: `application/json` - **Accept**: `application/json`
@@ -230,7 +232,7 @@ Mutierende Endpunkte (PATCH/DELETE) erfordern:
## Sicherheit ## 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 - **RBAC**: Nur tenant_admin kann mutieren, member kann nur lesen/hochladen
- **Rate Limiting**: 100 Requests/Minute pro Tenant - **Rate Limiting**: 100 Requests/Minute pro Tenant
- **ETag**: Automatische Concurrency-Control - **ETag**: Automatische Concurrency-Control
@@ -259,8 +261,9 @@ curl -H "Authorization: Bearer {token}" \
### Environment-Variablen ### Environment-Variablen
- **VITE_API_URL**: Backend-API-URL (Pflicht) - **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_ENABLE_TENANT_SWITCHER**: Dev-Flag um Tenants im Header auszuwählen (optional, Default `false`)
- **VITE_REVENUECAT_PUBLIC_KEY**: Optional fuer In-App-Kaeufe (RevenueCat) - **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`. - **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 ### Build & Deploy
@@ -273,4 +276,3 @@ curl -H "Authorization: Bearer {token}" \
- Installierbar auf Desktop/Mobile via "Zum Startbildschirm hinzufügen". - Installierbar auf Desktop/Mobile via "Zum Startbildschirm hinzufügen".
Für weitere Details siehe die spezifischen Dokumentationsdateien. Für weitere Details siehe die spezifischen Dokumentationsdateien.

View File

@@ -18,8 +18,8 @@ Die Admin-App muss folgende Kernfunktionen bereitstellen:
## Capabilities ## Capabilities
### Authentifizierung & Autorisierung ### Authentifizierung & Autorisierung
- OAuth2 Authorization Code mit PKCE, Refresh-Tokens via Secure Storage (Web: IndexedDB, Capacitor: Preferences/Keychain). - 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.
- Tenant-Scoped Tokens; Rollen `tenant_admin` (vollständig) & `member` (read-only, Upload). - 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 ### Onboarding Journey
- Routen `/event-admin/welcome/*` bilden den Flow. - Routen `/event-admin/welcome/*` bilden den Flow.
@@ -67,7 +67,7 @@ Die App nutzt Endpunkte aus `docs/prp/03-api.md`.
| Bereich | Endpunkte | | 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` | | 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 | | 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}` | | 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 ## Nicht-funktionale Anforderungen
- **Performance**: Ladezeit < 2s; Code-Splitting der Onboarding-Screens. - **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`. - **Accessibility**: Tastaturbedienung, Fokus-Indikatoren, `prefers-reduced-motion`.
- **Internationalisierung**: Sprachumschaltung in Einstellungen; Standard de, Fallback en. - **Internationalisierung**: Sprachumschaltung in Einstellungen; Standard de, Fallback en.

View File

@@ -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. - Refresh-token revocation tooling (per device/IP) and anomaly alerts.
- Device fingerprint/subnet allowances documented and configurable. - Device fingerprint/subnet allowances documented and configurable.
- **Tickets** - **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-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). - `SEC-IO-03` — Implement subnet/device matching configuration & tests (Week 3).

View File

@@ -42,11 +42,10 @@ Owner: Codex (handoff)
## Priority: Immediate (Tenant admin refresh 2025-10-18) ## 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] 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] 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] 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] 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. - [x] Update PRP/docs to cover die neue Welcome Journey, OAuth-Ausrichtung, Filament-Onboarding und QR-Tooling; Regression Notes + Tests dokumentiert.