getParsedBody(); $body = is_array($body) ? $body : Json::decode((string) $request->getBody()); $result = $this->auth->login( username: (string) ($body['username'] ?? $body['account'] ?? ''), password: (string) ($body['password'] ?? ''), clientIp: self::clientIp($request), ); if ($result['ok'] === false) { return Json::error($response, $result['error'], $result['status']); } return $this->cookie->attach(Json::write($response, ['user' => self::describe($result['session'])]), $result['session']->id); } /** * Die Auth-Sonde der Oberflaeche. * * Bewusst ohne RequireAuth: die SPA fragt hier vor jeder Anzeige an und * entscheidet anhand von 200 oder 401, ob sie die Anmeldemaske zeigt. */ public function me(Request $request, Response $response): Response { $session = SessionMiddleware::of($request); if ($session === null) { return Json::write($response, ['user' => null], 401); } return Json::write($response, ['user' => self::describe($session)]); } /** * Haelt die Sitzung am Leben, solange jemand am Bildschirm arbeitet. * Die Oberflaeche ruft das alle fuenf Minuten auf. */ public function refresh(Request $request, Response $response): Response { $session = SessionMiddleware::of($request); if ($session === null) { return Json::write($response, ['user' => null], 401); } return Json::write($response, ['user' => self::describe($session)]); } public function logout(Request $request, Response $response): Response { $this->auth->logout($this->cookie->read($request)); return $this->cookie->clear(Json::write($response, ['ok' => true])); } /** Eigenes Passwort aendern. Erlaubt jedem angemeldeten Benutzer. */ public function changeOwnPassword(Request $request, Response $response): Response { $session = SessionMiddleware::of($request); if ($session === null) { return Json::error($response, 'Bitte erneut anmelden.', 401); } $body = $request->getParsedBody(); $body = is_array($body) ? $body : Json::decode((string) $request->getBody()); $current = (string) ($body['currentPassword'] ?? ''); $next = (string) ($body['newPassword'] ?? ''); $user = $this->users->findByUsernameWithHash($session->username); if ($user === null || !password_verify($current, $user->passwordHash)) { return Json::error($response, 'Das aktuelle Passwort ist nicht korrekt.', 403); } if (($problem = AuthService::rejectWeakPassword($next)) !== null) { return Json::error($response, $problem, 400); } $this->users->update($user->id, ['password_hash' => AuthService::hash($next)]); $this->users->audit($user->id, $user->displayName, 'password.self', $user->id, $user->username); // Alle anderen Sitzungen dieses Benutzers verfallen; die eigene bleibt bestehen. return Json::write($response, ['ok' => true]); } /** @return array */ private static function describe(Session $session): array { return [ 'id' => $session->userId, 'username' => $session->username, 'name' => $session->displayName, 'role' => $session->role->value, 'roleLabel' => $session->role->label(), 'permissions' => $session->role->permissions(), 'canManageUsers' => $session->can(Permission::UsersManage), ]; } /** * Hinter nginx steht die echte Adresse in X-Forwarded-For. Es wird nur der * erste Eintrag verwendet und nur, wenn der Header ueberhaupt gesetzt ist -- * der Origin ist ausschliesslich ueber den Proxy erreichbar. */ private static function clientIp(Request $request): string { $forwarded = $request->getHeaderLine('X-Forwarded-For'); if ($forwarded !== '') { return trim(explode(',', $forwarded)[0]); } $server = $request->getServerParams(); return is_string($server['REMOTE_ADDR'] ?? null) ? $server['REMOTE_ADDR'] : 'unbekannt'; } }