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",