, * "role": "student" | "teacher", * "class_id": , // bei student * "exp": * } */ declare(strict_types=1); function v2_auth_b64url(string $bin): string { return rtrim(strtr(base64_encode($bin), '+/', '-_'), '='); } function v2_auth_b64url_decode(string $s): string { $pad = strlen($s) % 4; if ($pad) $s .= str_repeat('=', 4 - $pad); return base64_decode(strtr($s, '-_', '+/')) ?: ''; } function v2_auth_token_create(array $payload): string { $payload['iat'] = time(); $payload['exp'] = time() + V2_JWT_TTL; $header = ['alg' => 'HS256', 'typ' => 'JWT']; $h = v2_auth_b64url(json_encode($header, JSON_UNESCAPED_UNICODE)); $p = v2_auth_b64url(json_encode($payload, JSON_UNESCAPED_UNICODE)); $sig = v2_auth_b64url(hash_hmac('sha256', "$h.$p", V2_JWT_SECRET, true)); return "$h.$p.$sig"; } function v2_auth_token_verify(string $token): ?array { $parts = explode('.', $token); if (count($parts) !== 3) return null; [$h, $p, $sig] = $parts; $expected = v2_auth_b64url(hash_hmac('sha256', "$h.$p", V2_JWT_SECRET, true)); if (!hash_equals($expected, $sig)) return null; $payload = json_decode(v2_auth_b64url_decode($p), true); if (!is_array($payload)) return null; if (($payload['exp'] ?? 0) < time()) return null; return $payload; } /** * Aktuellen Token aus Authorization-Header oder ?session_token=... * holen und validieren. Gibt Payload oder null zurück. * * Hinweis: Apache reicht den Authorization-Header je nach Setup nicht * an PHP weiter. Wir probieren mehrere Quellen. */ function v2_auth_current(): ?array { // 1) Standard $auth = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; // 2) Über mod_rewrite weitergereicht (siehe .htaccess) if (!$auth) $auth = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? ''; // 3) Apache getallheaders Fallback (case-insensitive) if (!$auth && function_exists('getallheaders')) { foreach (getallheaders() as $name => $value) { if (strcasecmp($name, 'Authorization') === 0) { $auth = $value; break; } } } if (str_starts_with($auth, 'Bearer ')) { $token = substr($auth, 7); } else { $token = $_GET['session_token'] ?? $_POST['session_token'] ?? ''; } if (!$token) return null; return v2_auth_token_verify($token); } /** * Schutz für Endpoints, die Auth verlangen. * Bei fehlendem/ungültigem Token: 401 + exit. * * $role kann sein: * - "any" → jede authentifizierte Rolle * - "student" → nur Schüler*in * - "teacher" → Lehrperson ODER Admin (Admin ist immer auch Lehrperson) * - "admin" → nur Admin */ function v2_auth_require(string $role = 'student'): array { $payload = v2_auth_current(); if (!$payload) { v2_response_error(401, 'unauthorized', 'Token fehlt oder ist ungültig.'); } $actual = (string) ($payload['role'] ?? ''); $ok = match ($role) { 'any' => true, 'teacher' => in_array($actual, ['teacher', 'admin'], true), default => $actual === $role, }; if (!$ok) { v2_response_error(403, 'forbidden', "Rolle '$role' erforderlich (du bist '$actual')."); } return $payload; }