porchlight/docs/plans/2026-02-18-admin-pages-design.md
2026-04-10 11:28:51 +02:00

3.9 KiB

Admin Pages Design

Overview

Admin pages for user management in the porchlight OIDC provider. Authenticated users with the "admin" group can list, search, view, edit, activate/deactivate, and delete users, manage group memberships, view/delete credentials, create invite links, and re-invite existing users.

Routing & Auth

New router at src/porchlight/admin/routes.py, mounted at /admin/ in app.py.

Admin guard: Every admin route fetches the full user via user_repo.get_by_userid() and checks "admin" in user.groups. Unauthenticated users redirect to /login. Non-admin authenticated users get 403.

Endpoints

Method Path Purpose
GET /admin/users User list (paginated, searchable)
GET /admin/users/{userid} User detail page
POST /admin/users/{userid}/profile Update user profile
POST /admin/users/{userid}/groups Update group memberships
POST /admin/users/{userid}/activate Activate user
POST /admin/users/{userid}/deactivate Deactivate user
DELETE /admin/users/{userid}/credentials/password Delete user's password
DELETE /admin/users/{userid}/credentials/webauthn/{cred_id} Delete a WebAuthn key
POST /admin/users/{userid}/invite Generate re-invite link
DELETE /admin/users/{userid} Delete user entirely
POST /admin/invite Create invite for new username

Templates & UI

Template Structure

templates/admin/
    base.html        -- extends base.html, adds admin nav + admin label
    users.html       -- user list table
    user_detail.html -- single-user detail with sections

User List Page (/admin/users)

  • Search input at top (HTMX GET to filter, targets table body)
  • Table columns: Username, Name, Email, Groups, Status, Created
  • Each row links to detail page
  • Active/inactive toggle button per row (HTMX POST, swaps button)
  • Pagination controls (prev/next, HTMX)
  • "Create Invite" form -- enter username, generates magic link URL

User Detail Page (/admin/users/{userid})

Single page with sections, each with its own HTMX form/target:

  1. Profile -- Same editable fields as self-service (given_name, family_name, preferred_username, email, phone_number, picture, locale). Username displayed read-only. HTMX POST.

  2. Groups -- Current groups as removable tags/chips. Text input to add groups. HTMX POST replaces full group list.

  3. Credentials -- Read-only list: password (exists/doesn't), WebAuthn keys (device name, created). Delete buttons per credential (HTMX DELETE with confirmation).

  4. Actions -- Re-invite button (shows generated URL). Delete user button (with confirmation). Activate/deactivate toggle.

New CSS

  • .admin-table -- bordered table with hover rows
  • .group-tag -- removable group chips
  • .status-badge / .status-active / .status-inactive -- status indicators

Data Layer Changes

No Schema Changes

Existing tables cover all needs: users, user_groups, webauthn_credentials, password_credentials, magic_links.

Repository Additions

Add to UserRepository protocol and SQLite implementation:

  • search_users(query: str, offset: int, limit: int) -> list[User] -- SQL LIKE on username and email
  • count_users(query: str | None) -> int -- total count for pagination

Groups update: fetch user, modify user.groups, call update(user) (existing method handles group replacement).

Testing

Python Unit Tests

  • Admin guard (403 for non-admin, redirect for unauthenticated)
  • search_users() and count_users() repository methods
  • Route handlers: profile update, group update, activate/deactivate, delete, invite

E2E Playwright Tests

  • Auth guard (non-admin blocked from /admin/)
  • User list: pagination, search, inline activate/deactivate
  • User detail: edit profile, manage groups, view credentials, delete credential, re-invite, delete user
  • Create invite from admin UI