updated the docs, removed oauth and introduced sanctum pat
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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*
|
||||||
|
|||||||
@@ -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.
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user