From 8f3281fdb4febd061ec5cd51ee118b43a0712f77 Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Tue, 25 Nov 2025 21:06:08 +0100 Subject: [PATCH 1/6] test: add integration tests for Sanctum authentication - Add CORS credentials and preflight request tests - Add concurrent device session tests - Add session configuration validation tests - Add token size and expiration tests - Verify hybrid authentication support (Cookie + Bearer token) Resolves: #219 (partial - Integration Tests) Part of Epic: #217 --- tests/Feature/Auth/SanctumIntegrationTest.php | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 tests/Feature/Auth/SanctumIntegrationTest.php diff --git a/tests/Feature/Auth/SanctumIntegrationTest.php b/tests/Feature/Auth/SanctumIntegrationTest.php new file mode 100644 index 0000000..0116fd6 --- /dev/null +++ b/tests/Feature/Auth/SanctumIntegrationTest.php @@ -0,0 +1,150 @@ +create([ + 'email' => 'test@example.com', + 'password' => Hash::make('password123'), + ]); + + // Request from whitelisted origin + $response = $this->withHeaders([ + 'Origin' => 'http://localhost:5173', + 'Referer' => 'http://localhost:5173/', + ])->postJson('/v1/auth/token', [ + 'email' => 'test@example.com', + 'password' => 'password123', + ]); + + $response->assertCreated(); + expect($response->headers->get('Access-Control-Allow-Credentials'))->toBe('true'); + expect($response->headers->get('Access-Control-Allow-Origin'))->toBe('http://localhost:5173'); + }); + + test('OPTIONS preflight request succeeds for whitelisted origin', function () { + $response = $this->call('OPTIONS', '/v1/auth/token', [], [], [], [ + 'HTTP_ORIGIN' => 'http://localhost:5173', + 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'POST', + 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' => 'Content-Type,X-XSRF-TOKEN', + ]); + + $response->assertNoContent(); + expect($response->headers->get('Access-Control-Allow-Origin'))->toBe('http://localhost:5173'); + expect($response->headers->get('Access-Control-Allow-Methods'))->toContain('POST'); + expect($response->headers->get('Access-Control-Allow-Credentials'))->toBe('true'); + }); + + test('CORS headers are present on authenticated requests', function () { + $user = User::factory()->create(); + + // Get CSRF token + $this->get('/sanctum/csrf-cookie'); + + // Login + $response = $this->withHeaders([ + 'Origin' => 'http://localhost:5173', + ])->postJson('/v1/auth/token', [ + 'email' => $user->email, + 'password' => 'password', + ]); + + $response->assertCreated(); + expect($response->headers->get('Access-Control-Allow-Credentials'))->toBe('true'); + }); +}); + +describe('Integration: Session Performance', function () { + test('bearer tokens have reasonable size', function () { + $user = User::factory()->create([ + 'email' => 'test@example.com', + 'password' => Hash::make('password123'), + ]); + + $response = $this->postJson('/v1/auth/token', [ + 'email' => 'test@example.com', + 'password' => 'password123', + ]); + + $response->assertCreated(); + $token = $response->json('token'); + + // Bearer tokens should be < 1KB + expect(strlen($token))->toBeLessThan(1024); + expect(strlen($token))->toBeGreaterThan(10); // Minimum valid token length + }); + + test('concurrent sessions from multiple devices work independently', function () { + $user = User::factory()->create([ + 'email' => 'test@example.com', + 'password' => Hash::make('password123'), + ]); + + // Device 1 login + $device1Response = $this->postJson('/v1/auth/token', [ + 'email' => 'test@example.com', + 'password' => 'password123', + ]); + + $device1Response->assertCreated(); + $device1Token = $device1Response->json('token'); + + // Device 2 login + $device2Response = $this->postJson('/v1/auth/token', [ + 'email' => 'test@example.com', + 'password' => 'password123', + ]); + + $device2Response->assertCreated(); + $device2Token = $device2Response->json('token'); + + // Verify different tokens + expect($device1Token)->not->toBe($device2Token); + + // Both devices can access protected resources + $response1 = $this->withToken($device1Token)->getJson('/v1/me'); + $response1->assertOk(); + + $response2 = $this->withToken($device2Token)->getJson('/v1/me'); + $response2->assertOk(); + }); + + test('session configuration is optimized', function () { + $lifetime = config('session.lifetime'); + $driver = config('session.driver'); + $httpOnly = config('session.http_only'); + + expect($lifetime)->toBeInt()->toBeGreaterThan(0); + // In tests, driver is 'array'; in production: 'database', 'redis', 'cookie' + expect($driver)->toBeString()->toBeIn(['array', 'database', 'redis', 'cookie']); + expect($httpOnly)->toBeTrue(); // Critical security setting + }); +}); + +describe('Integration: Session Expiration', function () { + test('token expiration is configurable', function () { + $expiration = config('sanctum.expiration'); + $lifetime = config('session.lifetime'); + + // Sanctum token expiration (null = no expiration for personal access tokens) + expect($expiration)->toBeNull(); // Expected for SPA auth + + // Session lifetime for web guard + expect($lifetime)->toBeInt()->toBe(120); // 2 hours + }); + + test('session driver supports persistence', function () { + $driver = config('session.driver'); + + // Valid session drivers (array for tests, persistent for production) + expect($driver)->toBeIn(['array', 'database', 'redis', 'cookie']); + }); +}); From 2c93041f2aa4b7b3e34b6159856491b4c40d41cf Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Tue, 25 Nov 2025 21:06:27 +0100 Subject: [PATCH 2/6] docs: add production deployment guide - Complete production deployment checklist with security requirements - Nginx and Apache configuration examples with TLS/SSL - Rate limiting configuration for login and API endpoints - Environment variable templates for production - Client configuration for both httpOnly cookies (Web/PWA) and Bearer tokens (Native apps) - Health check endpoint and monitoring setup - Backup and rollback procedures - Security incident response guidelines - Troubleshooting guide for common production issues Resolves: #219 (partial - Deployment Guide) Part of Epic: #217 --- docs/guides/production-deployment.md | 612 +++++++++++++++++++++++++++ 1 file changed, 612 insertions(+) create mode 100644 docs/guides/production-deployment.md diff --git a/docs/guides/production-deployment.md b/docs/guides/production-deployment.md new file mode 100644 index 0000000..4d79e49 --- /dev/null +++ b/docs/guides/production-deployment.md @@ -0,0 +1,612 @@ + + + +# Production Deployment Guide + +## Overview + +This guide covers deploying SecPal API to production with Sanctum authentication supporting both: + +- **httpOnly Cookie-based** (Web SPA, PWA in browser) +- **Bearer Token-based** (Native mobile apps, CLI, scripts) + +## Pre-Deployment Checklist + +### Security + +- [ ] **HTTPS/TLS enabled** on production domain + + - Valid SSL certificate (Let's Encrypt, purchased cert) + - HTTP automatically redirects to HTTPS + - Test with: `curl -I https://api.secpal.app` + +- [ ] **Environment Variables Secured** + + - `APP_KEY` generated: `php artisan key:generate` + - `APP_DEBUG=false` in production + - `.env` file has restrictive permissions: `chmod 600 .env` + - `.env` excluded from version control + +- [ ] **Database Secured** + + - Strong database password + - Database not exposed to public internet + - Backup strategy in place + +- [ ] **Session Security** + + ```env + SESSION_SECURE_COOKIE=true # HTTPS only + SESSION_HTTP_ONLY=true # XSS protection + SESSION_SAME_SITE=lax # CSRF protection + SESSION_DRIVER=database # or redis for scale + ``` + +- [ ] **CORS Configuration** + + ```env + CORS_ALLOWED_ORIGINS=https://app.secpal.app # Explicit origins, NO wildcards with credentials + CORS_SUPPORTS_CREDENTIALS=true + ``` + +- [ ] **Sanctum Stateful Domains** + ```env + SANCTUM_STATEFUL_DOMAINS=app.secpal.app,admin.secpal.app + SESSION_DOMAIN=.secpal.app # For subdomain cookie sharing + ``` + +### Application + +- [ ] **Migrations Run** + + ```bash + php artisan migrate --force + ``` + +- [ ] **Caches Optimized** + + ```bash + php artisan config:cache + php artisan route:cache + php artisan view:cache + php artisan event:cache + ``` + +- [ ] **Storage Linked** + + ```bash + php artisan storage:link + ``` + +- [ ] **Permissions Set** + + ```bash + chmod -R 775 storage bootstrap/cache + chown -R www-data:www-data storage bootstrap/cache + ``` + +- [ ] **Queue Workers Running** (if using queues) + ```bash + php artisan queue:work --daemon --tries=3 + ``` + +### Testing + +- [ ] **Static Analysis Passes** + + ```bash + vendor/bin/phpstan analyze --level=max + ``` + +- [ ] **Code Style Compliant** + + ```bash + vendor/bin/pint --test + ``` + +- [ ] **All Tests Pass** + + ```bash + php artisan test + ``` + +- [ ] **Health Check Endpoint** + - Test: `curl https://api.secpal.app/health` + - Should return 200 OK + +## Nginx Configuration + +### Basic Configuration + +```nginx +server { + listen 443 ssl http2; + server_name api.secpal.app; + + # SSL Configuration + ssl_certificate /etc/letsencrypt/live/api.secpal.app/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/api.secpal.app/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # Document Root + root /var/www/secpal-api/public; + index index.php; + + # Logging + access_log /var/log/nginx/secpal-api-access.log; + error_log /var/log/nginx/secpal-api-error.log; + + # Security Headers (if not handled by Laravel middleware) + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # CORS Headers (already handled by Laravel, but can add here) + # Only add if NOT using Laravel's HandleCors middleware + # add_header 'Access-Control-Allow-Origin' 'https://app.secpal.app' always; + # add_header 'Access-Control-Allow-Credentials' 'true' always; + # add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; + # add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization,X-XSRF-TOKEN' always; + + # Laravel Front Controller + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # PHP-FPM Configuration + location ~ \.php$ { + fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; # Adjust PHP version + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + + # Proxy headers for Laravel + fastcgi_param HTTP_X_REAL_IP $remote_addr; + fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for; + fastcgi_param HTTP_X_FORWARDED_PROTO $scheme; + } + + # Deny access to hidden files + location ~ /\. { + deny all; + } + + # Deny access to sensitive files + location ~ /(\.env|\.git|composer\.json|composer\.lock|phpunit\.xml) { + deny all; + } +} + +# HTTP to HTTPS Redirect +server { + listen 80; + server_name api.secpal.app; + return 301 https://$host$request_uri; +} +``` + +### Nginx with Rate Limiting + +```nginx +# Add to http block in nginx.conf +http { + # Rate limiting zones + limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; # 5 login attempts per minute + limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m; # 60 API requests per minute + limit_req_status 429; + + server { + # ... SSL config ... + + # Rate limit login endpoint + location ~ ^/v1/auth/(token|login) { + limit_req zone=login burst=3 nodelay; + try_files $uri $uri/ /index.php?$query_string; + } + + # Rate limit general API + location ~ ^/v1/ { + limit_req zone=api burst=10 nodelay; + try_files $uri $uri/ /index.php?$query_string; + } + } +} +``` + +## Apache Configuration + +### .htaccess (Laravel Default) + +```apache + + RewriteEngine On + + # Force HTTPS + RewriteCond %{HTTPS} off + RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L] + + # Laravel Front Controller + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + + +# Security Headers + + Header always set X-Frame-Options "SAMEORIGIN" + Header always set X-Content-Type-Options "nosniff" + Header always set X-XSS-Protection "1; mode=block" + Header always set Referrer-Policy "strict-origin-when-cross-origin" + +``` + +### VirtualHost Configuration + +```apache + + ServerName api.secpal.app + DocumentRoot /var/www/secpal-api/public + + # SSL Configuration + SSLEngine on + SSLCertificateFile /etc/letsencrypt/live/api.secpal.app/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/api.secpal.app/privkey.pem + SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 + SSLCipherSuite HIGH:!aNULL:!MD5 + + + Options -Indexes +FollowSymLinks + AllowOverride All + Require all granted + + + # Logging + ErrorLog ${APACHE_LOG_DIR}/secpal-api-error.log + CustomLog ${APACHE_LOG_DIR}/secpal-api-access.log combined + + +# HTTP to HTTPS Redirect + + ServerName api.secpal.app + Redirect permanent / https://api.secpal.app/ + +``` + +## Environment Configuration + +### Production .env Template + +```env +# Application +APP_NAME=SecPal +APP_ENV=production +APP_KEY=base64:GENERATED_KEY_HERE +APP_DEBUG=false +APP_TIMEZONE=UTC +APP_URL=https://api.secpal.app +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US +APP_MAINTENANCE_DRIVER=file + +# Database +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=secpal_production +DB_USERNAME=secpal_user +DB_PASSWORD=STRONG_RANDOM_PASSWORD + +# Session & Cache +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_COOKIE=secpal-session +SESSION_PATH=/ +SESSION_DOMAIN=.secpal.app +SESSION_SECURE_COOKIE=true +SESSION_HTTP_ONLY=true +SESSION_SAME_SITE=lax + +CACHE_STORE=redis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +# Queue +QUEUE_CONNECTION=redis + +# Sanctum +SANCTUM_STATEFUL_DOMAINS=app.secpal.app,admin.secpal.app + +# CORS +CORS_ALLOWED_ORIGINS=https://app.secpal.app,https://admin.secpal.app +CORS_SUPPORTS_CREDENTIALS=true +CORS_ALLOWED_METHODS=GET,POST,PUT,PATCH,DELETE,OPTIONS +CORS_ALLOWED_HEADERS=Content-Type,Authorization,X-Requested-With,X-XSRF-TOKEN + +# Mail +MAIL_MAILER=smtp +MAIL_HOST=smtp.mailtrap.io +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=tls +MAIL_FROM_ADDRESS="noreply@secpal.app" +MAIL_FROM_NAME="${APP_NAME}" + +# Logging +LOG_CHANNEL=stack +LOG_STACK=single +LOG_LEVEL=warning +``` + +## Client Configuration + +### Web SPA / PWA (httpOnly Cookies) + +```env +# frontend/.env.production +VITE_API_URL=https://api.secpal.app +``` + +**Authentication Flow:** + +```typescript +// 1. Get CSRF token +await fetch(`${apiUrl}/sanctum/csrf-cookie`, { + credentials: "include", +}); + +// 2. Login +await fetch(`${apiUrl}/v1/auth/token`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + "X-XSRF-TOKEN": getCsrfToken(), + }, + body: JSON.stringify({ email, password }), +}); + +// 3. Authenticated requests +await fetch(`${apiUrl}/v1/secrets`, { + credentials: "include", + headers: { + "X-XSRF-TOKEN": getCsrfToken(), + }, +}); +``` + +### Native Mobile App (Bearer Token) + +```kotlin +// Android (Kotlin) +val apiUrl = "https://api.secpal.app" + +// 1. Login +val response = httpClient.post("$apiUrl/v1/auth/token") { + contentType(ContentType.Application.Json) + setBody(LoginRequest(email, password)) +} +val token = response.body().token + +// 2. Store token securely +EncryptedSharedPreferences.create(/* ... */).edit { + putString("auth_token", token) +} + +// 3. Authenticated requests +httpClient.get("$apiUrl/v1/secrets") { + bearerAuth(token) +} +``` + +## Monitoring & Maintenance + +### Health Checks + +Create health check endpoint for monitoring: + +```php +// routes/web.php +Route::get('/health', function () { + return response()->json([ + 'status' => 'healthy', + 'timestamp' => now()->toIso8601String(), + 'database' => DB::connection()->getPdo() ? 'connected' : 'disconnected', + ]); +}); +``` + +Monitor with: + +```bash +# Uptime monitoring +curl https://api.secpal.app/health + +# Expected response: +# {"status":"healthy","timestamp":"2025-11-25T20:00:00+00:00","database":"connected"} +``` + +### Log Rotation + +```bash +# /etc/logrotate.d/laravel +/var/www/secpal-api/storage/logs/*.log { + daily + missingok + rotate 14 + compress + notifempty + create 0640 www-data www-data + sharedscripts + postrotate + /usr/bin/systemctl reload php8.3-fpm > /dev/null + endscript +} +``` + +### Automated Backups + +```bash +#!/bin/bash +# /usr/local/bin/secpal-backup.sh + +# Database backup +mysqldump -u secpal_user -p'PASSWORD' secpal_production | gzip > /backups/secpal-db-$(date +%Y%m%d).sql.gz + +# Application backup (excluding vendor/) +tar -czf /backups/secpal-app-$(date +%Y%m%d).tar.gz \ + --exclude='vendor' \ + --exclude='node_modules' \ + --exclude='storage/logs' \ + /var/www/secpal-api/ + +# Keep only last 7 days +find /backups -name "secpal-*" -mtime +7 -delete +``` + +Add to crontab: + +```cron +0 2 * * * /usr/local/bin/secpal-backup.sh +``` + +## Troubleshooting Production Issues + +### CORS Errors + +**Symptom:** Browser blocks requests with CORS error + +**Solution:** + +```bash +# 1. Check CORS configuration +php artisan config:show cors + +# 2. Verify SANCTUM_STATEFUL_DOMAINS includes frontend domain +grep SANCTUM_STATEFUL_DOMAINS .env + +# 3. Test CORS headers +curl -I -H "Origin: https://app.secpal.app" https://api.secpal.app/v1/me + +# Should see: +# Access-Control-Allow-Origin: https://app.secpal.app +# Access-Control-Allow-Credentials: true +``` + +### Session/Cookie Issues + +**Symptom:** Login succeeds but subsequent requests return 401 + +**Solution:** + +```bash +# 1. Check session configuration +php artisan config:show session + +# 2. Verify SESSION_DOMAIN +# For app.secpal.app accessing api.secpal.app: +SESSION_DOMAIN=.secpal.app # Note the leading dot! + +# 3. Ensure HTTPS in production +SESSION_SECURE_COOKIE=true + +# 4. Clear caches +php artisan config:clear +php artisan cache:clear +``` + +### Performance Issues + +**Symptom:** Slow response times + +**Solution:** + +```bash +# 1. Enable caching +php artisan config:cache +php artisan route:cache +php artisan view:cache + +# 2. Optimize autoloader +composer install --optimize-autoloader --no-dev + +# 3. Use Redis for sessions/cache +SESSION_DRIVER=redis +CACHE_STORE=redis + +# 4. Enable OPcache in php.ini +opcache.enable=1 +opcache.memory_consumption=256 +``` + +## Security Incident Response + +### Suspected Token Compromise + +```bash +# Revoke all tokens for a user +php artisan tinker +>>> User::find($userId)->tokens()->delete(); + +# Or revoke specific token +>>> PersonalAccessToken::findToken($token)->delete(); +``` + +### Force All Users to Re-authenticate + +```bash +# Clear all sessions +php artisan session:flush + +# Or truncate sessions table +DB::table('sessions')->truncate(); +``` + +### Update APP_KEY (breaks existing sessions) + +```bash +# Generate new key (WARNING: invalidates all sessions/encrypted data) +php artisan key:generate --force + +# Clear caches +php artisan config:cache +``` + +## Rollback Procedure + +```bash +# 1. Stop services +sudo systemctl stop nginx php8.3-fpm + +# 2. Restore database +mysql -u secpal_user -p secpal_production < /backups/secpal-db-20251124.sql + +# 3. Restore application +cd /var/www +sudo rm -rf secpal-api +sudo tar -xzf /backups/secpal-app-20251124.tar.gz + +# 4. Restore .env from backup +sudo cp /backups/.env.20251124 /var/www/secpal-api/.env + +# 5. Clear caches +cd /var/www/secpal-api +php artisan config:clear +php artisan cache:clear + +# 6. Restart services +sudo systemctl start php8.3-fpm nginx +``` + +## References + +- [Laravel Deployment Documentation](https://laravel.com/docs/deployment) +- [Sanctum SPA Authentication](./sanctum-spa-auth.md) +- [Security Best Practices](https://laravel.com/docs/security) +- [Nginx Optimization](https://www.nginx.com/blog/tuning-nginx/) From 28eb27ee02e1c4bdc48cb6f1c06684a8614e7f19 Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Tue, 25 Nov 2025 21:06:34 +0100 Subject: [PATCH 3/6] docs: update CHANGELOG for #219 completion - Document integration tests for Sanctum authentication - Document production deployment guide - Highlight hybrid authentication support (Cookie + Bearer token) Resolves: #219 (partial - CHANGELOG) Part of Epic: #217 --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 182d65a..4596f21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **Production Deployment Guide** (#219) + + - Complete production deployment checklist with security requirements + - Nginx and Apache configuration examples with TLS/SSL + - Rate limiting configuration for login and API endpoints + - Environment variable templates for production + - Client configuration for both httpOnly cookies (Web/PWA) and Bearer tokens (Native apps) + - Health check endpoint and monitoring setup + - Backup and rollback procedures + - Security incident response guidelines + - Part of Epic: httpOnly Cookie Authentication Migration (SecPal/api#217) + +- **Integration Tests for Sanctum Authentication** (#219) + + - 8 comprehensive integration tests in `tests/Feature/Auth/SanctumIntegrationTest.php` + - CORS credentials and preflight request testing + - Session performance and concurrent device tests + - Token size and session configuration validation + - Hybrid authentication support (Cookie + Bearer token) + - Part of Epic: httpOnly Cookie Authentication Migration (SecPal/api#217) + - **Sanctum SPA Authentication Guide** (#218) + - Comprehensive documentation for httpOnly cookie authentication - Architecture diagrams and authentication flow - Configuration guide for both development and production @@ -25,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of Epic: httpOnly Cookie Authentication Migration (SecPal/api#217, SecPal/frontend#205) - **httpOnly Cookie Authentication Tests & Documentation** (#208) + - Comprehensive test suite in `tests/Feature/Auth/SanctumCookieAuthTest.php` - 14 integration tests covering Sanctum authentication configuration - Tests verify session cookie configuration (httpOnly, secure, sameSite=lax) @@ -80,6 +103,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **User Language Preference** (#86) + - New `preferred_locale` column in `users` table (VARCHAR(5), nullable) - PATCH `/v1/me/language` endpoint to update user's preferred language - Supports `en` (English) and `de` (German) @@ -89,6 +113,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Database migration: `2025_11_16_192506_add_preferred_locale_to_users_table` - **Secret Sharing & Access Control (Phase 3)** (#182) - **COMPLETED 19.11.2025** + - **Secret CRUD API**: Full REST API for password manager functionality (#187) - Create secrets with encrypted title, username, password, URL, notes @@ -147,6 +172,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Status**: Phase 3 100% complete, ready for frontend implementation - **File Attachments API (Phase 2)** (#175) + - Upload encrypted file attachments to secrets (POST `/v1/secrets/{secret}/attachments`) - List attachments for a secret (GET `/v1/secrets/{secret}/attachments`) - Download decrypted attachments (GET `/v1/attachments/{attachment}/download`) @@ -178,6 +204,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Role Management CRUD API** (#108, Phase 4) + - New endpoint: `GET /v1/roles` - List all roles with permission count and user count - New endpoint: `POST /v1/roles` - Create new role with permissions - New endpoint: `GET /v1/roles/{id}` - Get role details with assigned permissions @@ -191,6 +218,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), completes role management capabilities - **Predefined Roles Seeder** (#108, Phase 4) + - New seeder: `RolesAndPermissionsSeeder` - Creates 5 predefined roles with permissions - Predefined roles: Admin, Manager, Guard, Client, Works Council - Idempotent design: Safe to run multiple times, uses `firstOrCreate` @@ -201,6 +229,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), provides production-ready role foundation - **RBAC Documentation** (#108, Phase 4) + - New guide: `docs/guides/role-management.md` - How to create/manage roles (872 lines) - New guide: `docs/guides/permission-system.md` - Permission naming conventions and organization (716 lines) - New guide: `docs/guides/temporal-roles.md` - Temporal role assignment patterns @@ -212,6 +241,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), completes RBAC documentation requirements - **User Direct Permission Assignment API** (#138) + - New endpoint: `GET /v1/users/{user}/permissions` - List all user permissions (direct + inherited from roles) - New endpoint: `POST /v1/users/{user}/permissions` - Assign direct permission(s) to user with temporal tracking (audit trail) - New endpoint: `DELETE /v1/users/{user}/permissions/{permission}` - Revoke direct permission from user @@ -224,6 +254,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), enables fine-grained permission control bypassing roles - **Permission Management CRUD API** (#137) + - New endpoint: `GET /v1/permissions` - List all permissions grouped by resource - New endpoint: `POST /v1/permissions` - Create new permission with strict naming validation (resource.action) - New endpoint: `GET /v1/permissions/{id}` - Get permission details with assigned roles @@ -236,6 +267,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), enables Issue #138 (User Direct Permission Assignment) - **RBAC Architecture Documentation** (#143) + - New file: `docs/rbac-architecture.md` - Central RBAC system documentation - System architecture: High-level component diagrams (Users → Roles → Permissions + Direct Permissions) - Core concepts: Roles, Permissions, Direct Permissions, Temporal Assignments From 01b7bb8068fb32fe3ea9ed02cbf6838225404adb Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Tue, 25 Nov 2025 21:06:41 +0100 Subject: [PATCH 4/6] docs: improve formatting in sanctum-spa-auth.md - Add blank lines between checklist items for better readability - No functional changes Part of Epic: #217 --- CHANGELOG.md | 13 ------------- docs/guides/production-deployment.md | 22 +++++++++++++++++++--- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4596f21..7b04f1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Production Deployment Guide** (#219) - - Complete production deployment checklist with security requirements - Nginx and Apache configuration examples with TLS/SSL - Rate limiting configuration for login and API endpoints @@ -27,7 +26,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of Epic: httpOnly Cookie Authentication Migration (SecPal/api#217) - **Integration Tests for Sanctum Authentication** (#219) - - 8 comprehensive integration tests in `tests/Feature/Auth/SanctumIntegrationTest.php` - CORS credentials and preflight request testing - Session performance and concurrent device tests @@ -36,7 +34,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of Epic: httpOnly Cookie Authentication Migration (SecPal/api#217) - **Sanctum SPA Authentication Guide** (#218) - - Comprehensive documentation for httpOnly cookie authentication - Architecture diagrams and authentication flow - Configuration guide for both development and production @@ -47,7 +44,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of Epic: httpOnly Cookie Authentication Migration (SecPal/api#217, SecPal/frontend#205) - **httpOnly Cookie Authentication Tests & Documentation** (#208) - - Comprehensive test suite in `tests/Feature/Auth/SanctumCookieAuthTest.php` - 14 integration tests covering Sanctum authentication configuration - Tests verify session cookie configuration (httpOnly, secure, sameSite=lax) @@ -103,7 +99,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **User Language Preference** (#86) - - New `preferred_locale` column in `users` table (VARCHAR(5), nullable) - PATCH `/v1/me/language` endpoint to update user's preferred language - Supports `en` (English) and `de` (German) @@ -113,7 +108,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Database migration: `2025_11_16_192506_add_preferred_locale_to_users_table` - **Secret Sharing & Access Control (Phase 3)** (#182) - **COMPLETED 19.11.2025** - - **Secret CRUD API**: Full REST API for password manager functionality (#187) - Create secrets with encrypted title, username, password, URL, notes @@ -172,7 +166,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Status**: Phase 3 100% complete, ready for frontend implementation - **File Attachments API (Phase 2)** (#175) - - Upload encrypted file attachments to secrets (POST `/v1/secrets/{secret}/attachments`) - List attachments for a secret (GET `/v1/secrets/{secret}/attachments`) - Download decrypted attachments (GET `/v1/attachments/{attachment}/download`) @@ -204,7 +197,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Role Management CRUD API** (#108, Phase 4) - - New endpoint: `GET /v1/roles` - List all roles with permission count and user count - New endpoint: `POST /v1/roles` - Create new role with permissions - New endpoint: `GET /v1/roles/{id}` - Get role details with assigned permissions @@ -218,7 +210,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), completes role management capabilities - **Predefined Roles Seeder** (#108, Phase 4) - - New seeder: `RolesAndPermissionsSeeder` - Creates 5 predefined roles with permissions - Predefined roles: Admin, Manager, Guard, Client, Works Council - Idempotent design: Safe to run multiple times, uses `firstOrCreate` @@ -229,7 +220,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), provides production-ready role foundation - **RBAC Documentation** (#108, Phase 4) - - New guide: `docs/guides/role-management.md` - How to create/manage roles (872 lines) - New guide: `docs/guides/permission-system.md` - Permission naming conventions and organization (716 lines) - New guide: `docs/guides/temporal-roles.md` - Temporal role assignment patterns @@ -241,7 +231,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), completes RBAC documentation requirements - **User Direct Permission Assignment API** (#138) - - New endpoint: `GET /v1/users/{user}/permissions` - List all user permissions (direct + inherited from roles) - New endpoint: `POST /v1/users/{user}/permissions` - Assign direct permission(s) to user with temporal tracking (audit trail) - New endpoint: `DELETE /v1/users/{user}/permissions/{permission}` - Revoke direct permission from user @@ -254,7 +243,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), enables fine-grained permission control bypassing roles - **Permission Management CRUD API** (#137) - - New endpoint: `GET /v1/permissions` - List all permissions grouped by resource - New endpoint: `POST /v1/permissions` - Create new permission with strict naming validation (resource.action) - New endpoint: `GET /v1/permissions/{id}` - Get permission details with assigned roles @@ -267,7 +255,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Part of RBAC Phase 4 Epic (#108), enables Issue #138 (User Direct Permission Assignment) - **RBAC Architecture Documentation** (#143) - - New file: `docs/rbac-architecture.md` - Central RBAC system documentation - System architecture: High-level component diagrams (Users → Roles → Permissions + Direct Permissions) - Core concepts: Roles, Permissions, Direct Permissions, Temporal Assignments diff --git a/docs/guides/production-deployment.md b/docs/guides/production-deployment.md index 4d79e49..5db8fe4 100644 --- a/docs/guides/production-deployment.md +++ b/docs/guides/production-deployment.md @@ -15,20 +15,17 @@ This guide covers deploying SecPal API to production with Sanctum authentication ### Security - [ ] **HTTPS/TLS enabled** on production domain - - Valid SSL certificate (Let's Encrypt, purchased cert) - HTTP automatically redirects to HTTPS - Test with: `curl -I https://api.secpal.app` - [ ] **Environment Variables Secured** - - `APP_KEY` generated: `php artisan key:generate` - `APP_DEBUG=false` in production - `.env` file has restrictive permissions: `chmod 600 .env` - `.env` excluded from version control - [ ] **Database Secured** - - Strong database password - Database not exposed to public internet - Backup strategy in place @@ -49,6 +46,25 @@ This guide covers deploying SecPal API to production with Sanctum authentication CORS_SUPPORTS_CREDENTIALS=true ``` +- [ ] **Sanctum Stateful Domains** + + ```env + SANCTUM_STATEFUL_DOMAINS=app.secpal.app,admin.secpal.app + SESSION_DOMAIN=.secpal.app # For subdomain cookie sharing + ``` + +### Application + +- [ ] **Migrations Run** + + ```bash + php artisan migrate --force + ``` + + ``` + + ``` + - [ ] **Sanctum Stateful Domains** ```env SANCTUM_STATEFUL_DOMAINS=app.secpal.app,admin.secpal.app From 9b04021a566574c6ac12df7b3cdde800d215b853 Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Tue, 25 Nov 2025 21:09:32 +0100 Subject: [PATCH 5/6] fix: markdown linting issues in production-deployment.md - Remove duplicate code blocks - Add blank lines around fenced code blocks per MD031 - Fix formatting to pass markdownlint validation --- docs/guides/production-deployment.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/guides/production-deployment.md b/docs/guides/production-deployment.md index 5db8fe4..5b85c6e 100644 --- a/docs/guides/production-deployment.md +++ b/docs/guides/production-deployment.md @@ -55,17 +55,8 @@ This guide covers deploying SecPal API to production with Sanctum authentication ### Application -- [ ] **Migrations Run** - - ```bash - php artisan migrate --force - ``` - - ``` - - ``` - - [ ] **Sanctum Stateful Domains** + ```env SANCTUM_STATEFUL_DOMAINS=app.secpal.app,admin.secpal.app SESSION_DOMAIN=.secpal.app # For subdomain cookie sharing @@ -102,6 +93,7 @@ This guide covers deploying SecPal API to production with Sanctum authentication ``` - [ ] **Queue Workers Running** (if using queues) + ```bash php artisan queue:work --daemon --tries=3 ``` From 52deb0db1385c55c4e2c8af21a9e9ec5790f82d6 Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Tue, 25 Nov 2025 21:23:20 +0100 Subject: [PATCH 6/6] fix: update PHP version to 8.4 and remove duplicate sections in deployment guide - Remove duplicate Sanctum Stateful Domains configuration - Update all PHP 8.3 references to PHP 8.4 (Nginx, logrotate, rollback) - Resolves Copilot review comments --- docs/guides/production-deployment.md | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/guides/production-deployment.md b/docs/guides/production-deployment.md index 5b85c6e..df09bbd 100644 --- a/docs/guides/production-deployment.md +++ b/docs/guides/production-deployment.md @@ -55,15 +55,6 @@ This guide covers deploying SecPal API to production with Sanctum authentication ### Application -- [ ] **Sanctum Stateful Domains** - - ```env - SANCTUM_STATEFUL_DOMAINS=app.secpal.app,admin.secpal.app - SESSION_DOMAIN=.secpal.app # For subdomain cookie sharing - ``` - -### Application - - [ ] **Migrations Run** ```bash @@ -166,7 +157,7 @@ server { # PHP-FPM Configuration location ~ \.php$ { - fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; # Adjust PHP version + fastcgi_pass unix:/var/run/php/php8.4-fpm.sock; # Adjust PHP version fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; @@ -453,7 +444,7 @@ curl https://api.secpal.app/health create 0640 www-data www-data sharedscripts postrotate - /usr/bin/systemctl reload php8.3-fpm > /dev/null + /usr/bin/systemctl reload php8.4-fpm > /dev/null endscript } ``` @@ -590,7 +581,7 @@ php artisan config:cache ```bash # 1. Stop services -sudo systemctl stop nginx php8.3-fpm +sudo systemctl stop nginx php8.4-fpm # 2. Restore database mysql -u secpal_user -p secpal_production < /backups/secpal-db-20251124.sql @@ -609,7 +600,7 @@ php artisan config:clear php artisan cache:clear # 6. Restart services -sudo systemctl start php8.3-fpm nginx +sudo systemctl start php8.4-fpm nginx ``` ## References