getBody()->getContents(), true); $email = $body['email'] ?? ''; $password = $body['password'] ?? ''; if (empty($email) || empty($password)) { return $this->json($response, ['message' => 'Email and password required'], 400); } $user = User::where('email', $email)->first(); if (!$user || !password_verify($password, $user->password_hash)) { return $this->json($response, ['message' => 'Invalid credentials'], 401); } if (!$user->is_active) { return $this->json($response, ['message' => 'Account disabled'], 403); } // Generate JWT $now = time(); $payload = [ 'iss' => 'api.fahrschultermin.de', 'sub' => $user->id, 'tenant_id' => $user->tenant_id, 'role' => $user->role, 'iat' => $now, 'exp' => $now + (8 * 60 * 60), // 8 hours ]; $jwt = JWT::encode($payload, $_ENV['JWT_SECRET'] ?? 'this-is-a-32-character-secret-key-for-testing', 'HS256'); $userData = $user->toArray(); unset($userData['password_hash']); return $this->json($response, [ 'token' => $jwt, 'user' => $userData, ]); } public function me(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { $user = $request->getAttribute('user'); if (!$user) { return $this->json($response, ['message' => 'Unauthenticated'], 401); } return $this->json($response, ['user' => $user]); } public function logout(ResponseInterface $response): ResponseInterface { // JWT is stateless, logout is client-side (discard token) return $this->json($response, ['message' => 'Logged out']); } private function json(ResponseInterface $response, array $data, int $status = 200): ResponseInterface { $response->getBody()->write(json_encode($data)); return $response ->withStatus($status) ->withHeader('Content-Type', 'application/json'); } }