Admin user management page (/admin/users) #10

Closed
opened 2026-03-14 19:53:39 +00:00 by forgejo_admin · 0 comments

Lineage

plan-2026-03-08-tryout-prep → Phase 5e → Phase 5e-2 (Admin user management page)

Repo

forgejo_admin/westside-app

User Story

As an admin (Marcus)
I want to manage Keycloak user accounts from the dashboard
So that I can reset passwords, change roles, and view all users without touching the Keycloak console

Context

Phase 5e-1 bulk-created 48 Keycloak accounts for paid families. Marcus needs a way to manage these accounts — reset passwords when parents forget them, change roles (player/coach/admin), and see who's registered. The Keycloak admin console is too complex for a non-technical user. This page gives Marcus a simple, purpose-built interface.

The app uses Auth.js with Keycloak OIDC. The JWT callback extracts realm_access.roles and attaches them to the session. The realm is westside-basketball with three custom roles: admin, coach, player.

Password pattern is Westside-{FirstName}-{2digits} (e.g., Westside-Marcus-47).

File Targets

Files to create:

  • src/lib/server/keycloak-admin.js — Keycloak Admin REST API client (token acquisition, list users, reset password, role management)
  • src/routes/admin/users/+page.server.js — load function (list users with roles) + form actions (resetPassword, changeRole)
  • src/routes/admin/users/+page.svelte — user list UI with search, role badges, action buttons

Files to modify:

  • src/routes/admin/+page.svelte — add "Manage Users" navigation link
  • k8s/deployment.yaml — add KEYCLOAK_ADMIN_URL and KEYCLOAK_ADMIN_PASSWORD env vars
  • k8s/auth-secret.enc.yaml — add keycloak-admin-password key (SOPS encrypted)

Files NOT to touch:

  • src/auth.js — auth config is complete, no changes needed
  • src/hooks.server.js — no changes needed
  • src/routes/coach/ — unrelated

Acceptance Criteria

  • When I navigate to /admin/users as an admin, I see a list of all Keycloak users with their name, email, and role
  • When I navigate to /admin/users without a session, I'm redirected to /signin
  • When I navigate to /admin/users with a non-admin role, I get 403 Forbidden
  • When I type in the search bar, users are filtered by name or email
  • When I click "Reset Password" on a user, a new Westside-{Name}-{NN} password is generated, set in Keycloak, and displayed once in a prominent banner with a copy button
  • When I change a user's role via the dropdown, the role is updated in Keycloak (old role removed, new role assigned)
  • Role badges are color-coded: admin=red (#c41230), coach=yellow (#ffd700), player=green (#7ec875)
  • The /admin page has a "Manage Users" link to /admin/users
  • deployment.yaml has KEYCLOAK_ADMIN_URL and KEYCLOAK_ADMIN_PASSWORD env vars
  • Dark theme matches existing admin page (bg #0a0a0a, text #e0e0e0, borders #222/#333)

Test Expectations

  • Manual test: navigate to /admin/users, verify user list loads
  • Manual test: search for a known user, verify filtering works
  • Manual test: reset a test user's password, verify new password works for login
  • Manual test: change a user's role, verify change reflected in Keycloak
  • Manual test: access /admin/users as non-admin, verify 403
  • Run command: manual browser testing (no automated test suite for this app yet)

Constraints

  • Follow existing patterns: auth guard from src/routes/admin/+page.server.js lines 24-34, form actions with use:enhance, Svelte 5 $state() / $derived() (NOT stores)
  • Keycloak Admin API base URL: https://keycloak.tail5b443a.ts.net. Read from KEYCLOAK_ADMIN_URL env var via $env/dynamic/private
  • Keycloak admin credentials: username admin, password from KEYCLOAK_ADMIN_PASSWORD env var
  • Realm: hardcode westside-basketball
  • Keycloak Admin API endpoints:
    • Token: POST /realms/master/protocol/openid-connect/token (client_id=admin-cli, grant_type=password)
    • List users: GET /admin/realms/westside-basketball/users?search={query}&max=100
    • Reset password: PUT /admin/realms/westside-basketball/users/{id}/reset-password body: {"type":"password","value":"...","temporary":false}
    • Get realm roles: GET /admin/realms/westside-basketball/roles
    • Get user roles: GET /admin/realms/westside-basketball/users/{id}/role-mappings/realm
    • Assign role: POST /admin/realms/westside-basketball/users/{id}/role-mappings/realm body: [roleRepresentation]
    • Remove role: DELETE /admin/realms/westside-basketball/users/{id}/role-mappings/realm body: [roleRepresentation]
  • Password generation (in JS): Westside-${firstName}-${Math.floor(Math.random()*90)+10}
  • Use native fetch — available in SvelteKit server context, no external HTTP library
  • SOPS encryption: age key age15ct78fr4scv4vxzj3k6q76wshywzlu0mdc64a624e264dst7zfaq6tjzjr. The .sops.yaml config should exist in repo root. Run sops k8s/auth-secret.enc.yaml to edit.
  • SOPS secret value: get from awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/{print $2}' ~/secrets/pal-e-services/secrets.env

Checklist

  • PR opened with Closes #10
  • All new files created
  • deployment.yaml updated
  • auth-secret.enc.yaml updated with keycloak-admin-password
  • "Manage Users" link added to /admin page
  • No unrelated changes
  • Dark theme consistent
  • westside-basketball — project
  • plan-2026-03-08-tryout-prep — parent plan
  • Phase 5e-1 (PR #80) — bulk account creation script (completed)
### Lineage `plan-2026-03-08-tryout-prep` → Phase 5e → Phase 5e-2 (Admin user management page) ### Repo `forgejo_admin/westside-app` ### User Story As an admin (Marcus) I want to manage Keycloak user accounts from the dashboard So that I can reset passwords, change roles, and view all users without touching the Keycloak console ### Context Phase 5e-1 bulk-created 48 Keycloak accounts for paid families. Marcus needs a way to manage these accounts — reset passwords when parents forget them, change roles (player/coach/admin), and see who's registered. The Keycloak admin console is too complex for a non-technical user. This page gives Marcus a simple, purpose-built interface. The app uses Auth.js with Keycloak OIDC. The JWT callback extracts `realm_access.roles` and attaches them to the session. The realm is `westside-basketball` with three custom roles: `admin`, `coach`, `player`. Password pattern is `Westside-{FirstName}-{2digits}` (e.g., `Westside-Marcus-47`). ### File Targets Files to create: - `src/lib/server/keycloak-admin.js` — Keycloak Admin REST API client (token acquisition, list users, reset password, role management) - `src/routes/admin/users/+page.server.js` — load function (list users with roles) + form actions (resetPassword, changeRole) - `src/routes/admin/users/+page.svelte` — user list UI with search, role badges, action buttons Files to modify: - `src/routes/admin/+page.svelte` — add "Manage Users" navigation link - `k8s/deployment.yaml` — add `KEYCLOAK_ADMIN_URL` and `KEYCLOAK_ADMIN_PASSWORD` env vars - `k8s/auth-secret.enc.yaml` — add `keycloak-admin-password` key (SOPS encrypted) Files NOT to touch: - `src/auth.js` — auth config is complete, no changes needed - `src/hooks.server.js` — no changes needed - `src/routes/coach/` — unrelated ### Acceptance Criteria - [ ] When I navigate to `/admin/users` as an admin, I see a list of all Keycloak users with their name, email, and role - [ ] When I navigate to `/admin/users` without a session, I'm redirected to `/signin` - [ ] When I navigate to `/admin/users` with a non-admin role, I get 403 Forbidden - [ ] When I type in the search bar, users are filtered by name or email - [ ] When I click "Reset Password" on a user, a new `Westside-{Name}-{NN}` password is generated, set in Keycloak, and displayed once in a prominent banner with a copy button - [ ] When I change a user's role via the dropdown, the role is updated in Keycloak (old role removed, new role assigned) - [ ] Role badges are color-coded: admin=red (#c41230), coach=yellow (#ffd700), player=green (#7ec875) - [ ] The `/admin` page has a "Manage Users" link to `/admin/users` - [ ] `deployment.yaml` has `KEYCLOAK_ADMIN_URL` and `KEYCLOAK_ADMIN_PASSWORD` env vars - [ ] Dark theme matches existing admin page (bg #0a0a0a, text #e0e0e0, borders #222/#333) ### Test Expectations - [ ] Manual test: navigate to `/admin/users`, verify user list loads - [ ] Manual test: search for a known user, verify filtering works - [ ] Manual test: reset a test user's password, verify new password works for login - [ ] Manual test: change a user's role, verify change reflected in Keycloak - [ ] Manual test: access `/admin/users` as non-admin, verify 403 - Run command: manual browser testing (no automated test suite for this app yet) ### Constraints - **Follow existing patterns**: auth guard from `src/routes/admin/+page.server.js` lines 24-34, form actions with `use:enhance`, Svelte 5 `$state()` / `$derived()` (NOT stores) - **Keycloak Admin API base URL**: `https://keycloak.tail5b443a.ts.net`. Read from `KEYCLOAK_ADMIN_URL` env var via `$env/dynamic/private` - **Keycloak admin credentials**: username `admin`, password from `KEYCLOAK_ADMIN_PASSWORD` env var - **Realm**: hardcode `westside-basketball` - **Keycloak Admin API endpoints**: - Token: `POST /realms/master/protocol/openid-connect/token` (client_id=admin-cli, grant_type=password) - List users: `GET /admin/realms/westside-basketball/users?search={query}&max=100` - Reset password: `PUT /admin/realms/westside-basketball/users/{id}/reset-password` body: `{"type":"password","value":"...","temporary":false}` - Get realm roles: `GET /admin/realms/westside-basketball/roles` - Get user roles: `GET /admin/realms/westside-basketball/users/{id}/role-mappings/realm` - Assign role: `POST /admin/realms/westside-basketball/users/{id}/role-mappings/realm` body: `[roleRepresentation]` - Remove role: `DELETE /admin/realms/westside-basketball/users/{id}/role-mappings/realm` body: `[roleRepresentation]` - **Password generation** (in JS): `Westside-${firstName}-${Math.floor(Math.random()*90)+10}` - **Use native `fetch`** — available in SvelteKit server context, no external HTTP library - **SOPS encryption**: age key `age15ct78fr4scv4vxzj3k6q76wshywzlu0mdc64a624e264dst7zfaq6tjzjr`. The `.sops.yaml` config should exist in repo root. Run `sops k8s/auth-secret.enc.yaml` to edit. - **SOPS secret value**: get from `awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/{print $2}' ~/secrets/pal-e-services/secrets.env` ### Checklist - [ ] PR opened with `Closes #10` - [ ] All new files created - [ ] deployment.yaml updated - [ ] auth-secret.enc.yaml updated with keycloak-admin-password - [ ] "Manage Users" link added to /admin page - [ ] No unrelated changes - [ ] Dark theme consistent ### Related - `westside-basketball` — project - `plan-2026-03-08-tryout-prep` — parent plan - Phase 5e-1 (PR #80) — bulk account creation script (completed)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
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
forgejo_admin/westside-landing#10
No description provided.