Files
Hermes Agent 5d87e4975c Add HttpOnly cookie auth for cross-domain SPA login
Problem: Frontend JS bundle used credentials:'same-origin' which
dropped cookies on cross-domain requests. Login worked (200) but
the subsequent /auth/me check returned 401, leaving the user stuck
on the login screen.

Fix:
- AuthController now sets a dtp_jwt HttpOnly cookie on login/refresh
- Cookie uses Domain=.fahrschultermin.de (shared between frontend
  and api subdomain), Secure, SameSite=Lax, Max-Age=8h
- JwtMiddleware reads JWT from Authorization header OR cookie
- Added AuthController::me() endpoint (was missing, caused 500)
- Logout endpoint clears the cookie
- Frontend index.php patches fetch() to use credentials:'include'
  for all /api/v1/* calls
2026-06-04 20:33:31 +02:00

101 lines
4.2 KiB
PHP
Executable File

<?php
declare(strict_types=1);
$bootstrap = require __DIR__ . '/../app/bootstrap.php';
use App\Http\Controllers\AppointmentsController;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\BootstrapController;
use App\Http\Controllers\InstructorsController;
use App\Http\Controllers\ReferenceDataController;
use App\Http\Controllers\StudentsController;
use App\Http\Controllers\TenantsController;
use App\Http\Controllers\UsersController;
$router = $bootstrap['router'];
$request = new App\Support\Request();
$path = $request->path();
if ($request->method() === 'OPTIONS') {
http_response_code(204);
exit;
}
if (str_starts_with($path, '/api/v1')) {
$auth = new AuthController();
$bootstrapController = new BootstrapController();
$students = new StudentsController();
$references = new ReferenceDataController();
$appointments = new AppointmentsController();
$users = new UsersController();
$tenants = new TenantsController();
$instructors = new InstructorsController();
$router->post('/api/v1/auth/login', [$auth, 'login']);
$router->post('/api/v1/auth/logout', [$auth, 'logout']);
$router->get('/api/v1/auth/me', [$auth, 'me']);
$router->get('/api/v1/bootstrap', $bootstrapController);
$router->get('/api/v1/students', [$students, 'index']);
$router->post('/api/v1/students', [$students, 'store']);
$router->patch('/api/v1/students/{id}/training', [$students, 'updateTraining']);
$router->patch('/api/v1/students/{id}', [$students, 'update']);
$router->delete('/api/v1/students/{id}', [$students, 'destroy']);
$router->get('/api/v1/lesson-types', [$references, 'lessonTypes']);
$router->post('/api/v1/lesson-types', [$references, 'storeLessonType']);
$router->post('/api/v1/lesson-types/reorder', [$references, 'reorderLessonTypes']);
$router->patch('/api/v1/lesson-types/{id}', [$references, 'updateLessonType']);
$router->delete('/api/v1/lesson-types/{id}', [$references, 'destroyLessonType']);
$router->post('/api/v1/lesson-types/visibility', [$references, 'updateLessonTypeMatrix']);
$router->get('/api/v1/license-class-templates', [$references, 'templates']);
$router->post('/api/v1/license-class-templates', [$references, 'storeTemplate']);
$router->patch('/api/v1/license-class-templates/{id}', [$references, 'updateTemplate']);
$router->get('/api/v1/instructors', [$instructors, 'index']);
$router->post('/api/v1/instructors', [$instructors, 'store']);
$router->patch('/api/v1/instructors/{id}', [$instructors, 'update']);
$router->get('/api/v1/users', [$users, 'index']);
$router->post('/api/v1/users', [$users, 'store']);
$router->patch('/api/v1/users/{id}', [$users, 'update']);
$router->get('/api/v1/tenants', [$tenants, 'index']);
$router->post('/api/v1/tenants', [$tenants, 'store']);
$router->patch('/api/v1/tenants/{id}', [$tenants, 'update']);
$router->patch('/api/v1/tenant/profile', [$tenants, 'updateOwn']);
$router->get('/api/v1/appointments', [$appointments, 'index']);
$router->post('/api/v1/appointments/validate', [$appointments, 'validate']);
$router->post('/api/v1/appointments', [$appointments, 'store']);
$router->patch('/api/v1/appointments/{id}', [$appointments, 'update']);
$router->delete('/api/v1/appointments/{id}', [$appointments, 'destroy']);
$router->dispatch();
exit;
}
$assetManifestPath = __DIR__ . '/assets/.vite/manifest.json';
$mainAsset = '/assets/index.js';
$styleAsset = '/assets/index.css';
if (is_file($assetManifestPath)) {
$manifest = json_decode(file_get_contents($assetManifestPath), true);
$entry = $manifest['src/main.jsx'] ?? null;
if (is_array($entry)) {
$mainAsset = '/assets/' . ltrim((string) $entry['file'], '/');
if (!empty($entry['css'][0])) {
$styleAsset = '/assets/' . ltrim((string) $entry['css'][0], '/');
}
}
}
?><!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DriveTime Planner</title>
<link rel="stylesheet" href="<?= htmlspecialchars($styleAsset, ENT_QUOTES) ?>">
</head>
<body>
<div id="root"></div>
<script type="module" src="<?= htmlspecialchars($mainAsset, ENT_QUOTES) ?>"></script>
</body>
</html>