feat: add multi-user RBAC with admin/viewer roles and user management
Some checks failed
CI / Check (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Release Build (push) Has been cancelled

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:
2026-03-02 15:58:33 -05:00
parent 5bf41be343
commit e07377adc0
17 changed files with 885 additions and 49 deletions

View File

@@ -87,7 +87,20 @@ class AuthManager {
}
}
logout() {
async logout() {
// Revoke server-side session first
try {
if (this.token) {
await fetch('/api/auth/logout', {
method: 'POST',
headers: { 'Authorization': `Bearer ${this.token}` }
});
}
} catch (e) {
// Best-effort — still clear local state even if server call fails
console.warn('Server logout failed:', e);
}
// Clear localStorage
localStorage.removeItem('dashboard_token');
localStorage.removeItem('dashboard_user');
@@ -104,14 +117,6 @@ class AuthManager {
this.showToast('Successfully logged out', 'info');
}
generateToken() {
// Generate a simple token for demo purposes
// In production, this would come from the server
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2);
return btoa(`${timestamp}:${random}`).replace(/=/g, '');
}
showLogin() {
const loginScreen = document.getElementById('login-screen');
const dashboard = document.getElementById('dashboard');
@@ -154,13 +159,14 @@ class AuthManager {
updateUserInfo() {
const userNameElement = document.querySelector('.user-name');
const userRoleElement = document.querySelector('.user-role');
if (userNameElement && this.user) {
userNameElement.textContent = this.user.name;
userNameElement.textContent = this.user.name || this.user.username || 'User';
}
if (userRoleElement && this.user) {
userRoleElement.textContent = this.user.role;
const roleLabels = { admin: 'Administrator', viewer: 'Viewer' };
userRoleElement.textContent = roleLabels[this.user.role] || this.user.role || 'User';
}
}