feat: add multi-user RBAC with admin/viewer roles and user management
Add complete multi-user support with role-based access control: Backend: - Add users CRUD endpoints (GET/POST/PUT/DELETE /api/users) with admin-only guards - Add display_name column to users table with ALTER TABLE migration - Fix auth to use session-based user identity (not hardcoded 'admin') - Add POST /api/auth/logout to revoke server-side sessions - Add require_admin() and extract_session() helpers for clean RBAC - Guard all mutating endpoints (clients, providers, models, settings, backup) Frontend: - Add Users management page with create/edit/reset-password/delete modals - Add role gating: hide edit/delete buttons for viewers on clients, providers, models - Settings page hides auth tokens and admin actions for viewers - Logout now revokes server session before clearing localStorage - Sidebar shows real display_name and formatted role (Administrator/Viewer) - Fix sidebar header: single logo with onerror fallback, renamed to 'LLM Proxy' - Add badge and btn-action CSS classes for role pills and action buttons - Bump cache-bust to v=7
This commit is contained in:
@@ -15,6 +15,7 @@ class Dashboard {
|
||||
this.setupSidebar();
|
||||
this.setupRefresh();
|
||||
this.updateTime();
|
||||
this.applyRoleGating();
|
||||
|
||||
// Load initial page from hash or default to overview
|
||||
const initialPage = window.location.hash.substring(1) || 'overview';
|
||||
@@ -23,6 +24,20 @@ class Dashboard {
|
||||
setInterval(() => this.updateTime(), 1000);
|
||||
}
|
||||
|
||||
/** Hide admin-only menu items and mutation controls for non-admin users */
|
||||
applyRoleGating() {
|
||||
const user = window.authManager && window.authManager.user;
|
||||
const isAdmin = user && user.role === 'admin';
|
||||
|
||||
// Toggle visibility of admin-only sidebar items
|
||||
document.querySelectorAll('.menu-item.admin-only').forEach(item => {
|
||||
item.style.display = isAdmin ? '' : 'none';
|
||||
});
|
||||
|
||||
// Store role for use by page scripts
|
||||
window._userRole = isAdmin ? 'admin' : 'viewer';
|
||||
}
|
||||
|
||||
setupNavigation() {
|
||||
const menuItems = document.querySelectorAll('.menu-item');
|
||||
menuItems.forEach(item => {
|
||||
@@ -103,7 +118,8 @@ class Dashboard {
|
||||
'monitoring': 'Monitoring',
|
||||
'settings': 'Settings',
|
||||
'logs': 'Logs',
|
||||
'models': 'Models'
|
||||
'models': 'Models',
|
||||
'users': 'User Management'
|
||||
};
|
||||
if (titleElement) titleElement.textContent = titles[page] || 'Dashboard';
|
||||
|
||||
@@ -145,6 +161,7 @@ class Dashboard {
|
||||
case 'settings': return '<div class="loading-placeholder">Loading settings...</div>';
|
||||
case 'analytics': return this.getAnalyticsTemplate();
|
||||
case 'costs': return this.getCostsTemplate();
|
||||
case 'users': return this.getUsersTemplate();
|
||||
default: return '<div class="empty-state"><h3>Page not found</h3></div>';
|
||||
}
|
||||
}
|
||||
@@ -219,6 +236,7 @@ class Dashboard {
|
||||
}
|
||||
|
||||
getClientsTemplate() {
|
||||
const isAdmin = window._userRole === 'admin';
|
||||
return `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
@@ -226,9 +244,9 @@ class Dashboard {
|
||||
<h3 class="card-title">API Clients</h3>
|
||||
<p class="card-subtitle">Manage tokens and access</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" id="add-client">
|
||||
${isAdmin ? `<button class="btn btn-primary" id="add-client">
|
||||
<i class="fas fa-plus"></i> Create Client
|
||||
</button>
|
||||
</button>` : ''}
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="table" id="clients-table">
|
||||
@@ -474,6 +492,30 @@ class Dashboard {
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getUsersTemplate() {
|
||||
return `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div>
|
||||
<h3 class="card-title">Dashboard Users</h3>
|
||||
<p class="card-subtitle">Manage accounts and roles</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" id="add-user">
|
||||
<i class="fas fa-user-plus"></i> Create User
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="table" id="users-table">
|
||||
<thead>
|
||||
<tr><th>Username</th><th>Display Name</th><th>Role</th><th>Created</th><th>Status</th><th>Actions</th></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
Reference in New Issue
Block a user