63 lines
2.6 KiB
Python
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
|