bifroest/app/identity/routes.py
2026-10-27 08:34:17 +01:00

63 lines
2.6 KiB
Python

"""Anmelden und Abmelden."""
from typing import Annotated
from fastapi import APIRouter, Form, Request
from fastapi.responses import Response
from app import web
from app.core.errors import Invalid
from app.identity import service as identity
def safe_next(target: str) -> str:
"""Nur relative Ziele innerhalb der Anwendung (kein Open Redirect)."""
return target if target.startswith("/") and not target.startswith("//") else "/workspaces"
def router() -> APIRouter:
r = APIRouter()
@r.get("/login")
def login_page(request: Request, next: str = "/workspaces") -> Response:
if web.current(request):
return web.redirect(safe_next(next))
return web.render(request, "login.html", {"next_url": safe_next(next), "username": ""})
@r.post("/login")
def login(request: Request, username: Annotated[str, Form()], password: Annotated[str, Form()],
next: Annotated[str, Form()] = "/workspaces") -> Response:
settings = web.settings(request)
with web.db(request).session() as session:
token = identity.login(session, settings, username, password)
if token is None:
return web.render(request, "login.html", {"next_url": safe_next(next), "username": username,
"error": "Nutzername oder Passwort falsch."}, 401)
response = web.redirect(safe_next(next))
response.set_cookie(web.COOKIE, token, httponly=True, samesite="lax", secure=settings.cookie_secure,
max_age=settings.session_hours * 3600)
return response
@r.get("/password")
def password_page(request: Request, principal: web.User) -> Response:
return web.render(request, "password.html", {})
@r.post("/password")
def change_password(request: Request, principal: web.Checked, current: Annotated[str, Form()],
new: Annotated[str, Form()], repeat: Annotated[str, Form()]) -> Response:
try:
with web.db(request).session() as session:
identity.change_password(session, principal, current, new, repeat)
except Invalid as e:
return web.render(request, "password.html", {"error": str(e)}, 400)
return web.redirect("/workspaces")
@r.post("/logout")
def logout(request: Request, principal: web.Checked) -> Response:
with web.db(request).session() as session:
identity.logout(session, principal)
response = web.redirect("/login")
response.delete_cookie(web.COOKIE)
return response
return r