fix(web): restore admin nav menu items (#122)
v13 RBAC migration nulled users.role and moved admin authority onto user_group_members. Header still gated on session.user.role == 'admin', so admin menu was hidden for everyone. Inject user['is_admin'] via is_user_admin in get_current_user; header reads session.user.is_admin.
This commit is contained in:
parent
33b318e491
commit
6752c4a53e
3 changed files with 29 additions and 3 deletions
|
|
@ -14,6 +14,7 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
||||||
|
|
||||||
- Corporate memory pages (`/corporate-memory`, `/corporate-memory/admin`) now render the shared app header at full viewport width, matching the dashboard. Previously the `_app_header.html` include sat inside `.container-memory` (max-width: 1000px) and was cropped on wide viewports.
|
- Corporate memory pages (`/corporate-memory`, `/corporate-memory/admin`) now render the shared app header at full viewport width, matching the dashboard. Previously the `_app_header.html` include sat inside `.container-memory` (max-width: 1000px) and was cropped on wide viewports.
|
||||||
- `release.yml` now publishes a `:dev-<slug>` + `:dev-<prefix>-latest` image when a fresh branch is pushed off `main` with no extra commits. Pre-fix, `paths-ignore` on the `push` event diffed the new ref against the default branch — a same-SHA branch had zero diff, every file matched paths-ignore, and the workflow was skipped, so a developer creating a personal branch off main to deploy main's exact state to their dev VM (which pins to `:dev-<user>-latest`) had to either commit something or trigger the workflow manually. The `build-and-push` job's `if` was also tightened to `main || workflow_dispatch` only, which prevented branch-push images regardless. Both fixed: added `create:` trigger (filtered to branch refs at the job level so tag creates don't double-build with `keboola-deploy.yml`), and broadened `build-and-push.if` to also publish on non-main branch pushes / branch creates.
|
- `release.yml` now publishes a `:dev-<slug>` + `:dev-<prefix>-latest` image when a fresh branch is pushed off `main` with no extra commits. Pre-fix, `paths-ignore` on the `push` event diffed the new ref against the default branch — a same-SHA branch had zero diff, every file matched paths-ignore, and the workflow was skipped, so a developer creating a personal branch off main to deploy main's exact state to their dev VM (which pins to `:dev-<user>-latest`) had to either commit something or trigger the workflow manually. The `build-and-push` job's `if` was also tightened to `main || workflow_dispatch` only, which prevented branch-push images regardless. Both fixed: added `create:` trigger (filtered to branch refs at the job level so tag creates don't double-build with `keboola-deploy.yml`), and broadened `build-and-push.if` to also publish on non-main branch pushes / branch creates.
|
||||||
|
- Web header admin nav (All tokens, Marketplaces, Admin → Users / Groups / Resource access / Server config) is now visible to admin users again. Pre-fix, `_app_header.html` gated the admin block on `session.user.role == 'admin'`, but the v13 RBAC migration nulled `users.role` and moved admin authority onto `user_group_members` (Admin system group) — so the gate evaluated to false for everyone, including actual admins. `get_current_user` now injects `user["is_admin"]` (computed via `app.auth.access.is_user_admin`, the same call all server-side admin gates use), and the header reads `session.user.is_admin`. The role badge in the user-menu dropdown now reads "Admin" or hides — `users.role` is no longer surfaced in the UI.
|
||||||
|
|
||||||
## [0.15.0] — 2026-04-29
|
## [0.15.0] — 2026-04-29
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,7 @@ async def get_current_user(
|
||||||
if is_local_dev_mode():
|
if is_local_dev_mode():
|
||||||
user = _get_local_dev_user(conn)
|
user = _get_local_dev_user(conn)
|
||||||
if user:
|
if user:
|
||||||
|
_attach_admin_flag(user, conn)
|
||||||
return user
|
return user
|
||||||
# Fall through to normal auth if seed missing — surfaces the bug
|
# Fall through to normal auth if seed missing — surfaces the bug
|
||||||
# instead of hiding it.
|
# instead of hiding it.
|
||||||
|
|
@ -181,6 +182,7 @@ async def get_current_user(
|
||||||
from app.auth.pat_resolver import resolve_token_to_user
|
from app.auth.pat_resolver import resolve_token_to_user
|
||||||
user, reason = resolve_token_to_user(conn, token, request)
|
user, reason = resolve_token_to_user(conn, token, request)
|
||||||
if user:
|
if user:
|
||||||
|
_attach_admin_flag(user, conn)
|
||||||
return user
|
return user
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
|
@ -188,6 +190,29 @@ async def get_current_user(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _attach_admin_flag(user: dict, conn: duckdb.DuckDBPyConnection) -> None:
|
||||||
|
"""Inject ``user["is_admin"]`` so templates and route handlers can gate
|
||||||
|
admin-only UI without touching the legacy ``users.role`` column.
|
||||||
|
|
||||||
|
v13 nulled out ``users.role`` and moved admin authority onto
|
||||||
|
``user_group_members`` (Admin system group). The web header used to
|
||||||
|
gate its admin nav on ``session.user.role == 'admin'``, which silently
|
||||||
|
became false for every user — so no admin saw any admin menu items
|
||||||
|
after the v13 migration. Computing the flag once per request here
|
||||||
|
keeps every consumer in sync with ``app.auth.access.is_user_admin``
|
||||||
|
(the same call all server-side admin gates use).
|
||||||
|
"""
|
||||||
|
from app.auth.access import is_user_admin
|
||||||
|
user_id = user.get("id")
|
||||||
|
if user_id:
|
||||||
|
try:
|
||||||
|
user["is_admin"] = is_user_admin(user_id, conn)
|
||||||
|
except Exception:
|
||||||
|
user["is_admin"] = False
|
||||||
|
else:
|
||||||
|
user["is_admin"] = False
|
||||||
|
|
||||||
|
|
||||||
async def get_optional_user(
|
async def get_optional_user(
|
||||||
request: Request = None,
|
request: Request = None,
|
||||||
authorization: Optional[str] = Header(None),
|
authorization: Optional[str] = Header(None),
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
{% set _path = request.url.path %}
|
{% set _path = request.url.path %}
|
||||||
<a class="app-nav-link {% if _path == '/dashboard' or _path == '/' %}is-active{% endif %}" href="/dashboard">Dashboard</a>
|
<a class="app-nav-link {% if _path == '/dashboard' or _path == '/' %}is-active{% endif %}" href="/dashboard">Dashboard</a>
|
||||||
<a class="app-nav-link {% if _path.startswith('/install') %}is-active{% endif %}" href="/install">Install CLI</a>
|
<a class="app-nav-link {% if _path.startswith('/install') %}is-active{% endif %}" href="/install">Install CLI</a>
|
||||||
{% if session.user.role == 'admin' %}
|
{% if session.user.is_admin %}
|
||||||
<a class="app-nav-link {% if _path.startswith('/admin/tokens') %}is-active{% endif %}" href="/admin/tokens">All tokens</a>
|
<a class="app-nav-link {% if _path.startswith('/admin/tokens') %}is-active{% endif %}" href="/admin/tokens">All tokens</a>
|
||||||
<a class="app-nav-link {% if _path.startswith('/admin/marketplaces') %}is-active{% endif %}" href="/admin/marketplaces">Marketplaces</a>
|
<a class="app-nav-link {% if _path.startswith('/admin/marketplaces') %}is-active{% endif %}" href="/admin/marketplaces">Marketplaces</a>
|
||||||
{% set _admin_active = _path.startswith('/admin/users') or _path.startswith('/admin/groups') or _path.startswith('/admin/access') or _path.startswith('/admin/server-config') %}
|
{% set _admin_active = _path.startswith('/admin/users') or _path.startswith('/admin/groups') or _path.startswith('/admin/access') or _path.startswith('/admin/server-config') %}
|
||||||
|
|
@ -50,8 +50,8 @@
|
||||||
<div class="app-user-menu-panel" id="userMenuPanel" role="menu" hidden>
|
<div class="app-user-menu-panel" id="userMenuPanel" role="menu" hidden>
|
||||||
<div class="app-user-menu-header">
|
<div class="app-user-menu-header">
|
||||||
<div class="app-user-menu-email">{{ session.user.email }}</div>
|
<div class="app-user-menu-email">{{ session.user.email }}</div>
|
||||||
{% if session.user.role %}
|
{% if session.user.is_admin %}
|
||||||
<div class="app-user-menu-role">{{ session.user.role | capitalize }}</div>
|
<div class="app-user-menu-role">Admin</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<a class="app-user-menu-item {% if _path.startswith('/profile') %}is-active{% endif %}" role="menuitem" href="/profile">Profile</a>
|
<a class="app-user-menu-item {% if _path.startswith('/profile') %}is-active{% endif %}" role="menuitem" href="/profile">Profile</a>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue