docs(l4d2-web): design auth pages
This commit is contained in:
parent
07495f9319
commit
08b32bb26f
1 changed files with 101 additions and 0 deletions
101
docs/superpowers/specs/2026-05-06-l4d2-web-auth-pages-design.md
Normal file
101
docs/superpowers/specs/2026-05-06-l4d2-web-auth-pages-design.md
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# l4d2 Web Auth Pages Design
|
||||||
|
|
||||||
|
Date: 2026-05-06
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Replace the current plain-text login/signup placeholders with a small, functional authentication UI for the Flask web app. Keep the current username/password schema for now, remove public signup, and redirect users back to the page they originally wanted after login when that target is safe.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
In scope:
|
||||||
|
|
||||||
|
- Real server-rendered `/login` page using the existing neutral shell and CSS tokens.
|
||||||
|
- Username/password login using the existing `users.username` and `users.password_digest` fields.
|
||||||
|
- Removal of `/signup` handlers, links, and CSRF exemptions.
|
||||||
|
- Redirect anonymous protected requests to `/login?next=<wanted-path>`.
|
||||||
|
- Redirect successful logins to a safe local `next` target, otherwise `/dashboard`.
|
||||||
|
- Redirect `/` to `/login` for anonymous users and `/dashboard` for logged-in users.
|
||||||
|
- Keep `/health` public for monitoring.
|
||||||
|
|
||||||
|
Out of scope:
|
||||||
|
|
||||||
|
- Email-based login.
|
||||||
|
- Steam login.
|
||||||
|
- Account self-registration.
|
||||||
|
- Password reset or password change flows.
|
||||||
|
- User invitation flows.
|
||||||
|
- Schema changes to split users, credentials, and external identities.
|
||||||
|
|
||||||
|
## Routing Behavior
|
||||||
|
|
||||||
|
`GET /login` renders the login page. The page accepts an optional `next` query parameter and preserves it in a hidden form field so a successful POST can return the user to the requested page.
|
||||||
|
|
||||||
|
`POST /login` validates the username and password with the existing password hash helpers. On success, it stores `session["user_id"]` and redirects to the safe local `next` value if present. If `next` is missing, empty, or unsafe, it redirects to `/dashboard`.
|
||||||
|
|
||||||
|
`GET /signup` and `POST /signup` are removed. They should return Flask's normal `404 Not Found` response.
|
||||||
|
|
||||||
|
`GET /` redirects to `/login` for anonymous users and `/dashboard` for logged-in users.
|
||||||
|
|
||||||
|
Protected page decorators keep their current authorization semantics, but anonymous redirects include the requested path in `next`. Logged-in non-admin users still receive `403 Forbidden` for admin-only pages.
|
||||||
|
|
||||||
|
## Safe Redirect Rules
|
||||||
|
|
||||||
|
A `next` target is safe only when it is a local absolute path:
|
||||||
|
|
||||||
|
- It must start with `/`.
|
||||||
|
- It must not start with `//`.
|
||||||
|
- It must not contain a URL scheme such as `https://`.
|
||||||
|
|
||||||
|
Unsafe values are ignored and replaced with `/dashboard`. This avoids open redirects while keeping the implementation simple.
|
||||||
|
|
||||||
|
## UI Behavior
|
||||||
|
|
||||||
|
The login page should be minimal and consistent with the current admin-console UI:
|
||||||
|
|
||||||
|
- Brand/title: `left4me`.
|
||||||
|
- Short explanatory text.
|
||||||
|
- Fields: `username` and `password`.
|
||||||
|
- Submit button: `log in`.
|
||||||
|
- No signup link.
|
||||||
|
|
||||||
|
The shared shell should not show main application navigation to anonymous users. Anonymous users may see the brand only. Logged-in users keep the existing navigation and account controls.
|
||||||
|
|
||||||
|
## Data Model
|
||||||
|
|
||||||
|
No schema change is planned for this iteration. `User.username` remains the local login identifier. This intentionally defers email-based and Steam-based identity modeling until those flows are designed.
|
||||||
|
|
||||||
|
Future Steam login should use a separate identity model rather than forcing Steam users into an email/password shape, but that is not part of this change.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Invalid credentials keep the current simple behavior: `401 invalid credentials`.
|
||||||
|
|
||||||
|
Missing username or password also remain simple response errors. The first UI pass does not need inline validation messages or flashed form errors.
|
||||||
|
|
||||||
|
Rate limiting on `POST /login` remains unchanged.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Tests should cover:
|
||||||
|
|
||||||
|
- `GET /login` renders a form with username and password inputs.
|
||||||
|
- `GET /signup` returns `404`.
|
||||||
|
- `POST /signup` returns `404` rather than being intercepted by CSRF handling.
|
||||||
|
- Anonymous protected pages redirect to `/login?next=<path>`.
|
||||||
|
- A successful login with a safe `next` redirects to that target.
|
||||||
|
- A successful login with an unsafe `next` redirects to `/dashboard`.
|
||||||
|
- Anonymous `/` redirects to `/login`.
|
||||||
|
- Logged-in `/` redirects to `/dashboard`.
|
||||||
|
- Non-admin users still receive `403` for admin pages.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Run these checks after implementation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest l4d2web/tests/test_auth.py -q
|
||||||
|
pytest l4d2web/tests/test_pages.py -q
|
||||||
|
pytest l4d2web/tests/test_security.py -q
|
||||||
|
pytest l4d2web/tests -q
|
||||||
|
```
|
||||||
Loading…
Reference in a new issue