diff --git a/app/routers/users.py b/app/routers/users.py index 1262d33..03de7f2 100644 --- a/app/routers/users.py +++ b/app/routers/users.py @@ -70,12 +70,31 @@ async def update_user( current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): - """Update an existing user (email, is_active, role).""" + """Update an existing user (email, is_active, role). Admin only.""" + if current_user.role != "admin": + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Only admins can update users.", + ) + user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found.") update_data = payload.model_dump(exclude_none=True) + + if "role" in update_data: + if update_data["role"] not in ("admin", "viewer"): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Role must be 'admin' or 'viewer'.", + ) + if user_id == current_user.id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="You cannot change your own role.", + ) + for field, value in update_data.items(): if hasattr(user, field): setattr(user, field, value) diff --git a/static/js/app.js b/static/js/app.js index 01fa4a6..7bcf14e 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -1368,8 +1368,8 @@ async function loadUsers() { ${u.id} ${esc(u.username)} ${esc(u.email || '-')} - ${esc(u.role || 'admin')} - ${esc(u.auth_provider || 'local')} + ${esc(u.role || 'admin')} + ${esc(u.auth_provider || 'local')} ${langDisplay} ${mfaDisplay} ${u.is_active ? `${t('common.active')}` : `${t('common.disabled')}`} @@ -1381,6 +1381,11 @@ async function loadUsers() { } ${u.auth_provider === 'local' ? `` : ''} ${u.totp_enabled ? `` : ''} + ${currentUser && currentUser.role === 'admin' && u.id !== currentUser.id + ? (u.role === 'admin' + ? `` + : ``) + : ''} @@ -1440,6 +1445,16 @@ async function toggleUserActive(id, active) { } } +async function toggleUserRole(id, currentRole) { + const newRole = currentRole === 'admin' ? 'viewer' : 'admin'; + try { + await api('PUT', `/users/${id}`, { role: newRole }); + loadUsers(); + } catch (err) { + showSettingsAlert('danger', t('errors.updateFailed', { error: err.message })); + } +} + async function resetUserPassword(id, username) { if (!confirm(t('messages.confirmResetPassword', { username }))) return; try { diff --git a/static/lang/de.json b/static/lang/de.json index fcf8785..fdebf0d 100644 --- a/static/lang/de.json +++ b/static/lang/de.json @@ -170,6 +170,8 @@ "saveBranding": "Branding speichern", "userManagement": "Benutzerverwaltung", "newUser": "Neuer Benutzer", + "makeAdmin": "Zum Admin befördern", + "makeViewer": "Zum Viewer degradieren", "thId": "ID", "thUsername": "Benutzername", "thEmail": "E-Mail", diff --git a/static/lang/en.json b/static/lang/en.json index 445d2bd..e15ba20 100644 --- a/static/lang/en.json +++ b/static/lang/en.json @@ -191,6 +191,8 @@ "saveBranding": "Save Branding", "userManagement": "User Management", "newUser": "New User", + "makeAdmin": "Promote to admin", + "makeViewer": "Demote to viewer", "thId": "ID", "thUsername": "Username", "thEmail": "Email",