diff --git a/docs/superpowers/specs/2026-05-06-l4d2-web-auth-pages-design.md b/docs/superpowers/specs/2026-05-06-l4d2-web-auth-pages-design.md new file mode 100644 index 0000000..5278f39 --- /dev/null +++ b/docs/superpowers/specs/2026-05-06-l4d2-web-auth-pages-design.md @@ -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=`. +- 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=`. +- 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 +```