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:
-
Profile -- Same editable fields as self-service (given_name, family_name, preferred_username, email, phone_number, picture, locale). Username displayed read-only. HTMX POST.
-
Groups -- Current groups as removable tags/chips. Text input to add groups. HTMX POST replaces full group list.
-
Credentials -- Read-only list: password (exists/doesn't), WebAuthn keys (device name, created). Delete buttons per credential (HTMX DELETE with confirmation).
-
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 emailcount_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()andcount_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