4.5 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
/loginpage using the existing neutral shell and CSS tokens. - Username/password login using the existing
users.usernameandusers.password_digestfields. - Removal of
/signuphandlers, links, and CSRF exemptions. - Redirect anonymous protected requests to
/login?next=<wanted-path>. - Redirect successful logins to a safe local
nexttarget, otherwise/dashboard. - Redirect
/to/loginfor anonymous users and/dashboardfor logged-in users. - Keep
/healthpublic 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.
- The same checks apply after one percent-decoding pass so encoded backslashes and encoded protocol-relative paths are rejected.
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:
usernameandpassword. - 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 /loginrenders a form with username and password inputs.GET /signupreturns404.POST /signupreturns404rather than being intercepted by CSRF handling.- Anonymous protected pages redirect to
/login?next=<path>. - A successful login with a safe
nextredirects to that target. - A successful login with an unsafe
nextredirects to/dashboard. - A successful login with a backslash-containing
nextredirects to/dashboard. - A successful login with a percent-encoded backslash in
nextredirects to/dashboard. - Anonymous
/redirects to/login. - Logged-in
/redirects to/dashboard. - Non-admin users still receive
403for 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