From b9a92fc4821d36b8937a6603eb90cf67794ee013 Mon Sep 17 00:00:00 2001 From: Archibold Date: Sat, 7 Nov 2026 14:42:56 +1100 Subject: [PATCH] Handle encrypted/HS256 id_tokens and let an admin claim promote on login --- README.md | 7 +++++-- src/uwudash_mk3/users.py | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cb32854..43a1911 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,11 @@ own `/docs` / `/openapi.json` requires a session: unauthenticated requests get | `admin` | everything a viewer can, plus create/update/delete services and list or change user roles | | `viewer` | read the service list, open services through the proxy, see their own profile | -The role is decided at **first** login and never changed by logging in again - -promotions and demotions go through `PATCH /api/users/{id}` (or the Settings page): +The role is recomputed on **every** login, but a login only ever promotes: a +matching `UWUDASH_OIDC_ADMIN_CLAIM` turns an existing `viewer` into `admin`, +while an existing `admin` is never demoted by a login - demotions go through +`PATCH /api/users/{id}` (or the Settings page), so a misconfigured claim +cannot lock the dashboard out: 1. if `UWUDASH_OIDC_ADMIN_CLAIM` is set and the token's claim (scalar or list) contains one of `UWUDASH_OIDC_ADMIN_VALUES` → `admin`; diff --git a/src/uwudash_mk3/users.py b/src/uwudash_mk3/users.py index ce5851f..563af8b 100644 --- a/src/uwudash_mk3/users.py +++ b/src/uwudash_mk3/users.py @@ -78,8 +78,10 @@ class UserStore: ) -> User: """Create a user, or refresh an existing one on every login. - ``role`` only applies to brand-new users: an existing admin is never - demoted by a login, promotion/demotion goes through ``set_role``. + ``role`` applies to brand-new users, and an ``admin`` role promotes an + existing viewer (that is what a configured admin claim does on login). + A login NEVER demotes: an existing admin stays admin, demotion goes + through ``set_role`` so one bad claim cannot lock the dashboard out. """ with self._lock: users = self._read() @@ -89,6 +91,8 @@ class UserStore: user.email = email or user.email user.name = name or user.name user.last_login_at = now + if role == "admin" and user.role != "admin": + user.role = "admin" self._write(users) return user