Cleaned up login/logout flows.

This commit is contained in:
Leo Vasanko
2025-09-02 19:08:16 -06:00
parent 10e55f63b5
commit b324276173
8 changed files with 46 additions and 18 deletions

View File

@@ -1,8 +1,8 @@
import logging
from uuid import UUID, uuid4
from fastapi import Body, Cookie, FastAPI, HTTPException
from fastapi.responses import FileResponse, JSONResponse
from fastapi import Body, Cookie, FastAPI, HTTPException, Request
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
from ..authsession import expires
from ..globals import db
@@ -24,8 +24,14 @@ async def general_exception_handler(_request, exc: Exception):
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
@app.get("")
def adminapp_slashmissing(request: Request):
print("HERE")
return RedirectResponse(url=request.url_for("adminapp"))
@app.get("/")
async def admin_frontend(auth=Cookie(None)):
async def adminapp(auth=Cookie(None)):
try:
await authz.verify(auth, ["auth:admin", "auth:org:*"], match=permutil.has_any)
return FileResponse(frontend.file("admin/index.html"))

View File

@@ -1,7 +1,11 @@
import logging
from fastapi import HTTPException
from ..util import permutil
logger = logging.getLogger(__name__)
async def verify(auth: str | None, perm: list[str], match=permutil.has_all):
"""Validate session token and optional list of required permissions.
@@ -20,6 +24,16 @@ async def verify(auth: str | None, perm: list[str], match=permutil.has_all):
raise HTTPException(status_code=401, detail="Session not found")
if not match(ctx, perm):
# Determine which permissions are missing for clearer diagnostics
missing = sorted(set(perm) - set(ctx.role.permissions))
logger.warning(
"Permission denied: user=%s role=%s missing=%s required=%s granted=%s", # noqa: E501
getattr(ctx.user, "uuid", "?"),
getattr(ctx.role, "display_name", "?"),
missing,
perm,
ctx.role.permissions,
)
raise HTTPException(status_code=403, detail="Permission required")
return ctx

View File

@@ -46,10 +46,12 @@ async def lifespan(app: FastAPI): # pragma: no cover - startup path
app = FastAPI(lifespan=lifespan)
app.mount("/auth/admin", admin.app)
app.mount("/auth/api", api.app)
app.mount("/auth/ws", ws.app)
app.mount("/auth/assets", StaticFiles(directory=frontend.file("assets")), name="assets")
app.mount("/auth/admin/", admin.app)
app.mount("/auth/api/", api.app)
app.mount("/auth/ws/", ws.app)
app.mount(
"/auth/assets/", StaticFiles(directory=frontend.file("assets")), name="assets"
)
@app.get("/auth/")
@@ -61,6 +63,9 @@ async def frontapp():
@app.get("/auth/{reset}")
async def reset_link(request: Request, reset: str):
"""Pretty URL for reset links."""
if reset == "admin":
# Admin app missing trailing slash lands here, be friendly to user
return RedirectResponse(request.url_for("adminapp"), status_code=303)
if not passphrase.is_well_formed(reset):
raise HTTPException(status_code=404)
url = request.url_for("frontapp").include_query_params(reset=reset)