Login funktionierte, aber jede authentifizierte Folgeanfrage (Guthaben, KI-Anfrage) meldete fälschlich eine abgelaufene Anmeldung — viele Apache/PHP-FPM-Hosting-Setups reichen den Authorization-Header standardmäßig nicht an PHP durch. .htaccess erzwingt jetzt die Weiterleitung, db.php liest zusätzlich REDIRECT_HTTP_AUTHORIZATION/ getallheaders() als Fallback. AiBackendUrl auf die echte deployte Domain gesetzt. TODO.md: KI-Unterstützung auch für Stundenplanung (4.5.11), Frage zu einem console.claude.ai-Agent für Standardkontext vs. app-internem Standard-Prompt (4.5.12/4.5.13), sowie ein Planungsdiff für KI-Vorschläge auf Feldebene statt grober Neu/Geändert-Markierung (4.5.14).
76 lines
2.6 KiB
PHP
76 lines
2.6 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/** Baut eine PDO-Verbindung aus config.php auf. */
|
|
function ai_backend_db(array $config): PDO
|
|
{
|
|
$db = $config['db'];
|
|
$dsn = "mysql:host={$db['host']};dbname={$db['name']};charset=utf8mb4";
|
|
return new PDO($dsn, $db['user'], $db['pass'], [
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
PDO::ATTR_EMULATE_PREPARES => false,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Liest den Authorization-Header — auf vielen Apache/PHP-FPM-Setups landet er NICHT in
|
|
* $_SERVER['HTTP_AUTHORIZATION'] (Header wird von PHP-FPM standardmäßig verworfen), sondern nur
|
|
* in $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] (nach der .htaccess-Weiterleitung, siehe dort) oder
|
|
* ist nur über getallheaders() erreichbar. Alle drei Quellen abklappern, statt sich auf eine zu
|
|
* verlassen — genau das war die Ursache für "Login klappt, danach sofort Session abgelaufen".
|
|
*/
|
|
function ai_backend_authorization_header(): string
|
|
{
|
|
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
|
|
return $_SERVER['HTTP_AUTHORIZATION'];
|
|
}
|
|
if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
|
|
return $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
|
|
}
|
|
if (function_exists('getallheaders')) {
|
|
foreach (getallheaders() as $name => $value) {
|
|
if (strcasecmp($name, 'Authorization') === 0) {
|
|
return $value;
|
|
}
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Bearer-Token aus dem Authorization-Header lesen, gegen tokens.token_hash prüfen
|
|
* (SHA-256, der Klartext wird nie gespeichert) und den zugehörigen aktiven User zurückgeben.
|
|
* Sendet bei fehlendem/ungültigem/abgelaufenem Token selbst eine 401-Antwort und beendet das Skript.
|
|
*/
|
|
function ai_backend_authenticate(PDO $pdo): array
|
|
{
|
|
$header = ai_backend_authorization_header();
|
|
if (!preg_match('/^Bearer\s+(.+)$/i', $header, $m)) {
|
|
ai_backend_fail(401, 'Kein gültiges Token übermittelt.');
|
|
}
|
|
$tokenHash = hash('sha256', $m[1]);
|
|
|
|
$stmt = $pdo->prepare(
|
|
'SELECT u.* FROM users u
|
|
JOIN tokens t ON t.user_id = u.id
|
|
WHERE t.token_hash = ? AND t.expires_at > NOW() AND u.is_active = 1
|
|
LIMIT 1'
|
|
);
|
|
$stmt->execute([$tokenHash]);
|
|
$user = $stmt->fetch();
|
|
if (!$user) {
|
|
ai_backend_fail(401, 'Ungültiges oder abgelaufenes Token.');
|
|
}
|
|
return $user;
|
|
}
|
|
|
|
/** Einheitliche Fehlerantwort als JSON, beendet danach das Skript. */
|
|
function ai_backend_fail(int $httpStatus, string $message): never
|
|
{
|
|
http_response_code($httpStatus);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['error' => $message]);
|
|
exit;
|
|
}
|