left4me/docs/superpowers/specs/2026-05-06-l4d2-web-auth-pages-design.md
2026-05-06 13:01:48 +02:00

4.3 KiB

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://.
  • It must not contain backslashes, which browsers may normalize into path separators.

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.
  • A successful login with a backslash-containing 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:

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