diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-06 15:25:36 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-06 15:25:36 -0700 |
| commit | 1bdf4ca8c0f51718124ffe5247ac133973d4f251 (patch) | |
| tree | 2904145bdbc5d2a2164cd3cb6c95346e48afd4a5 /routers/auth.py | |
| parent | 051775337251b0c7036959901eacb58471100862 (diff) | |
Add authentication, public profile, and infinite scroll
- Implement session-based auth with argon2 password hashing
- Add login form and logout button in nav
- Create public /tyler profile page with curated stats
- Implement infinite scroll for film lists (load 20 at a time)
- Add lazy loading for poster images
- Fix profile page CSS to use dark theme variables
- Use consistent star character (✦) across all pages
- Add /films/partial endpoint for pagination
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Diffstat (limited to 'routers/auth.py')
| -rw-r--r-- | routers/auth.py | 58 |
1 files changed, 58 insertions, 0 deletions
diff --git a/routers/auth.py b/routers/auth.py new file mode 100644 index 0000000..d3fb963 --- /dev/null +++ b/routers/auth.py @@ -0,0 +1,58 @@ +import os +from fastapi import APIRouter, Request +from fastapi.responses import RedirectResponse +from fastapi.templating import Jinja2Templates +from argon2 import PasswordHasher +from argon2.exceptions import VerifyMismatchError + +templates = Jinja2Templates(directory="templates") + +router = APIRouter() + +OWNER_PASSWORD_HASH = os.getenv("OWNER_PASSWORD_HASH", "") +ph = PasswordHasher() + + +@router.get("/login") +async def login_form(request: Request): + if request.session.get("authenticated"): + return RedirectResponse("/", status_code=303) + return templates.TemplateResponse(request=request, name="login.html", context={"request": request}) + + +@router.post("/login") +async def login(request: Request): + if request.session.get("authenticated"): + return RedirectResponse("/", status_code=303) + + form = await request.form() + password = form.get("password", "") + + if not OWNER_PASSWORD_HASH: + error = "Server not configured: OWNER_PASSWORD_HASH not set" + return templates.TemplateResponse( + request=request, + name="login.html", + context={"request": request, "error": error}, + status_code=500 + ) + + try: + ph.verify(OWNER_PASSWORD_HASH, password) + request.session["authenticated"] = True + return RedirectResponse("/", status_code=303) + except VerifyMismatchError: + pass + + return templates.TemplateResponse( + request=request, + name="login.html", + context={"request": request, "error": "Invalid password"}, + status_code=401 + ) + + +@router.post("/logout") +async def logout(request: Request): + request.session.clear() + return RedirectResponse("/login", status_code=303) |
