add uncommitted plans and CLAUDE.md
This commit is contained in:
parent
6b4cbdc152
commit
fb133f9cba
10 changed files with 5241 additions and 0 deletions
95
docs/plans/2026-02-18-admin-pages-design.md
Normal file
95
docs/plans/2026-02-18-admin-pages-design.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue