mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
88e98b4727
#26 Mobile responsive: - Hamburger button (SVG) in topbar for all three panels (admin/user/reseller) - Sidebar overlay div for click-outside-to-close on mobile - nova.js: DOMContentLoaded toggle handler with overlay and auto-close on nav click - nova.css: sidebar-overlay, page-header, panel/panel-header, table, btn-success/warning/danger/secondary/xs, badge-muted; mobile media query shows toggle, fixes stats-grid/modal/panel-header layout #27 Custom error pages: - /errors/404.php and /errors/500.php with NovaCPX dark theme matching panel design - Apache ErrorDocument 400/401/403/404/500/503 for ports 8880/8881/8882 with Alias /errors #28 API rate limiting: - api_rate_limits table (migration 004) with per-IP per-bucket counters - api/index.php: 10 req/min for auth endpoint, 120 req/min for all others - Returns X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers - Returns 429 Too Many Requests when exceeded; rate limit failure is non-fatal #29 Session Manager: - sessions.php endpoint: list/revoke/revoke-user/revoke-all - Admin panel Sessions page: table of active sessions with user, role, IP, browser, timestamps - Revoke single session, revoke all for user, revoke all sessions (self-evicts)
58 lines
2.2 KiB
PHP
58 lines
2.2 KiB
PHP
<?php
|
|
/**
|
|
* Sessions endpoint — admin session management
|
|
* GET sessions/list — all active sessions with user info
|
|
* DELETE sessions/revoke — {session_id} revoke one session
|
|
* DELETE sessions/revoke-user — {user_id} revoke all sessions for a user
|
|
* DELETE sessions/revoke-all — revoke all sessions except current
|
|
*/
|
|
|
|
Auth::getInstance()->require('admin');
|
|
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
$db = DB::getInstance();
|
|
$me = Auth::getInstance()->user();
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
|
|
match (true) {
|
|
|
|
$action === 'list' && $method === 'GET' => (function() use ($db) {
|
|
$rows = $db->fetchAll(
|
|
"SELECT s.id, s.user_id, s.ip_address, s.user_agent, s.created_at, s.expires_at,
|
|
u.username, u.email, u.role
|
|
FROM sessions s
|
|
JOIN users u ON u.id = s.user_id
|
|
WHERE s.expires_at > NOW()
|
|
ORDER BY s.created_at DESC
|
|
LIMIT 200"
|
|
) ?: [];
|
|
Response::json(['success' => true, 'data' => $rows]);
|
|
})(),
|
|
|
|
$action === 'revoke' && $method === 'DELETE' => (function() use ($db, $body) {
|
|
$sid = trim($body['session_id'] ?? '');
|
|
if (!$sid) Response::error('session_id required', 400);
|
|
$db->execute("DELETE FROM sessions WHERE id = ?", [$sid]);
|
|
Response::json(['success' => true]);
|
|
})(),
|
|
|
|
$action === 'revoke-user' && $method === 'DELETE' => (function() use ($db, $body) {
|
|
$uid = (int)($body['user_id'] ?? 0);
|
|
if (!$uid) Response::error('user_id required', 400);
|
|
$count = $db->execute("DELETE FROM sessions WHERE user_id = ?", [$uid]);
|
|
Response::json(['success' => true, 'data' => ['revoked' => $count]]);
|
|
})(),
|
|
|
|
$action === 'revoke-all' && $method === 'DELETE' => (function() use ($db, $me, $body) {
|
|
// Keep current session if provided
|
|
$keepId = $body['keep_session'] ?? null;
|
|
if ($keepId) {
|
|
$db->execute("DELETE FROM sessions WHERE id != ?", [hash('sha256', $keepId)]);
|
|
} else {
|
|
$db->execute("DELETE FROM sessions");
|
|
}
|
|
Response::json(['success' => true]);
|
|
})(),
|
|
|
|
default => Response::error('Not found', 404),
|
|
};
|