feat(web): add Hub-gated admin UI with allowlist and settings #14

Merged
David merged 1 commit from feat/admin-ui-hub-gated into main 2026-06-14 23:13:15 +02:00
Owner

Add a minimal, server-rendered admin web UI (step 5 of the multi-user remote MCP server epic), gated on the signed-in user's is_admin flag from the step-3 Hub login session. It lets a global admin manage who can use an internet-facing deployment and tune operational settings, with no JavaScript.

New src/web/admin.rs hosts the admin surface and the browser login it depends on. require_admin resolves the signed-in user from the existing yt_mcp_session cookie and refuses any non-admin caller (unauthenticated, unknown, disabled, or merely non-admin) with a 403, so the admin routes are reachable only by an admin. GET /admin renders the dashboard (allowlist, users with their active-session counts, settings); POST /admin/allowlist/add and /admin/allowlist/remove manage allowlist entries; POST /admin/users/disable disables a user and revokes all their access and refresh tokens in one transaction; POST /admin/settings inserts or updates a setting. GET /login renders the sign-in page and GET /login/start begins the upstream Hub round-trip, stashing /admin as the post-login destination so the shared hub_callback resumes there.

New src/web/templates.rs plus src/web/templates/ (askama 0.12, configured via askama.toml to load from src/web/templates/) provide the login, consent, admin, and error pages over a shared base layout. Every interpolated value is HTML-escaped by default. render() turns any template into a text/html response and error_page() renders the standard error page at a given status.

src/web/oauth_as.rs gains a consent interstitial: an authenticated /authorize now renders the consent page (showing the requesting client and scope) until the user grants or denies, instead of issuing a code unconditionally; granting re-submits with consent=granted and issues the code, denying redirects back with error=access_denied. The post-login return_to is generalized to a full same-origin path (guarded by is_safe_local_path against open redirects) so both the OAuth flow (/authorize?...) and the admin login (/admin) resume through one hub_callback. The session-cookie name and the cookie / redirect helpers are made pub(crate) for reuse by the admin module.

src/db/mod.rs adds list_users, list_settings (plus a Setting row type), count_active_access_tokens, and disable_user_and_revoke_tokens (status -> disabled and all tokens revoked in a single transaction). The three oci-build Dockerfiles copy askama.toml alongside the sources so the compile-time template loader resolves in the release builds.

#YTMCP-7

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Add a minimal, server-rendered admin web UI (step 5 of the multi-user remote MCP server epic), gated on the signed-in user's is_admin flag from the step-3 Hub login session. It lets a global admin manage who can use an internet-facing deployment and tune operational settings, with no JavaScript. New src/web/admin.rs hosts the admin surface and the browser login it depends on. require_admin resolves the signed-in user from the existing yt_mcp_session cookie and refuses any non-admin caller (unauthenticated, unknown, disabled, or merely non-admin) with a 403, so the admin routes are reachable only by an admin. GET /admin renders the dashboard (allowlist, users with their active-session counts, settings); POST /admin/allowlist/add and /admin/allowlist/remove manage allowlist entries; POST /admin/users/disable disables a user and revokes all their access and refresh tokens in one transaction; POST /admin/settings inserts or updates a setting. GET /login renders the sign-in page and GET /login/start begins the upstream Hub round-trip, stashing /admin as the post-login destination so the shared hub_callback resumes there. New src/web/templates.rs plus src/web/templates/ (askama 0.12, configured via askama.toml to load from src/web/templates/) provide the login, consent, admin, and error pages over a shared base layout. Every interpolated value is HTML-escaped by default. render() turns any template into a text/html response and error_page() renders the standard error page at a given status. src/web/oauth_as.rs gains a consent interstitial: an authenticated /authorize now renders the consent page (showing the requesting client and scope) until the user grants or denies, instead of issuing a code unconditionally; granting re-submits with consent=granted and issues the code, denying redirects back with error=access_denied. The post-login return_to is generalized to a full same-origin path (guarded by is_safe_local_path against open redirects) so both the OAuth flow (/authorize?...) and the admin login (/admin) resume through one hub_callback. The session-cookie name and the cookie / redirect helpers are made pub(crate) for reuse by the admin module. src/db/mod.rs adds list_users, list_settings (plus a Setting row type), count_active_access_tokens, and disable_user_and_revoke_tokens (status -> disabled and all tokens revoked in a single transaction). The three oci-build Dockerfiles copy askama.toml alongside the sources so the compile-time template loader resolves in the release builds. #YTMCP-7 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(web): add Hub-gated admin UI with allowlist and settings
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 28s
Create release / Create release from merged PR (pull_request) Has been skipped
cadc174b0a
Add a minimal, server-rendered admin web UI (step 5 of the multi-user remote MCP server epic), gated on the signed-in user's is_admin flag from the step-3 Hub login session. It lets a global admin manage who can use an internet-facing deployment and tune operational settings, with no JavaScript.

New src/web/admin.rs hosts the admin surface and the browser login it depends on. require_admin resolves the signed-in user from the existing yt_mcp_session cookie and refuses any non-admin caller (unauthenticated, unknown, disabled, or merely non-admin) with a 403, so the admin routes are reachable only by an admin. GET /admin renders the dashboard (allowlist, users with their active-session counts, settings); POST /admin/allowlist/add and /admin/allowlist/remove manage allowlist entries; POST /admin/users/disable disables a user and revokes all their access and refresh tokens in one transaction; POST /admin/settings inserts or updates a setting. GET /login renders the sign-in page and GET /login/start begins the upstream Hub round-trip, stashing /admin as the post-login destination so the shared hub_callback resumes there.

New src/web/templates.rs plus src/web/templates/ (askama 0.12, configured via askama.toml to load from src/web/templates/) provide the login, consent, admin, and error pages over a shared base layout. Every interpolated value is HTML-escaped by default. render() turns any template into a text/html response and error_page() renders the standard error page at a given status.

src/web/oauth_as.rs gains a consent interstitial: an authenticated /authorize now renders the consent page (showing the requesting client and scope) until the user grants or denies, instead of issuing a code unconditionally; granting re-submits with consent=granted and issues the code, denying redirects back with error=access_denied. The post-login return_to is generalized to a full same-origin path (guarded by is_safe_local_path against open redirects) so both the OAuth flow (/authorize?...) and the admin login (/admin) resume through one hub_callback. The session-cookie name and the cookie / redirect helpers are made pub(crate) for reuse by the admin module.

src/db/mod.rs adds list_users, list_settings (plus a Setting row type), count_active_access_tokens, and disable_user_and_revoke_tokens (status -> disabled and all tokens revoked in a single transaction). The three oci-build Dockerfiles copy askama.toml alongside the sources so the compile-time template loader resolves in the release builds.

#YTMCP-7

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
David merged commit 7195acd8d2 into main 2026-06-14 23:13:15 +02:00
David deleted branch feat/admin-ui-hub-gated 2026-06-14 23:13:15 +02:00
Commenting is not possible because the repository is archived.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandoras-box/youtrack-mcp!14
No description provided.