diff --git a/src/porchlight/admin/routes.py b/src/porchlight/admin/routes.py index 57809f5..634cd17 100644 --- a/src/porchlight/admin/routes.py +++ b/src/porchlight/admin/routes.py @@ -60,6 +60,38 @@ async def users_list(request: Request) -> Response: return templates.TemplateResponse(request, "admin/users.html", context) +@router.get("/users/{userid}", response_class=HTMLResponse) +async def user_detail(request: Request, userid: str) -> Response: + session_user = get_session_user(request) + if session_user is None: + return RedirectResponse("/login", status_code=303) + + admin = await _get_admin_user(request) + if admin is None: + return HTMLResponse("Forbidden", status_code=403) + + user_repo = request.app.state.user_repo + cred_repo = request.app.state.credential_repo + target_user = await user_repo.get_by_userid(userid) + if target_user is None: + return HTMLResponse("User not found", status_code=404) + + webauthn_credentials = await cred_repo.get_webauthn_by_user(userid) + password_credential = await cred_repo.get_password_by_user(userid) + + templates = request.app.state.templates + return templates.TemplateResponse( + request, + "admin/user_detail.html", + { + "target_user": target_user, + "webauthn_credentials": webauthn_credentials, + "has_password": password_credential is not None, + "active_page": "users", + }, + ) + + @router.post("/invite", response_class=HTMLResponse) async def create_invite( request: Request, diff --git a/src/porchlight/app.py b/src/porchlight/app.py index a27fc1c..d5ad11c 100644 --- a/src/porchlight/app.py +++ b/src/porchlight/app.py @@ -1,4 +1,5 @@ import secrets +from base64 import urlsafe_b64encode from collections.abc import AsyncIterator from contextlib import asynccontextmanager from pathlib import Path @@ -110,6 +111,7 @@ def create_app(settings: Settings | None = None) -> FastAPI: # Templates app.state.templates = Jinja2Templates(directory=str(PACKAGE_DIR / "templates")) + app.state.templates.env.filters["b64encode"] = lambda v: urlsafe_b64encode(v).decode().rstrip("=") # Static files app.mount("/static", StaticFiles(directory=str(PACKAGE_DIR / "static")), name="static") diff --git a/src/porchlight/templates/admin/user_detail.html b/src/porchlight/templates/admin/user_detail.html new file mode 100644 index 0000000..147adc7 --- /dev/null +++ b/src/porchlight/templates/admin/user_detail.html @@ -0,0 +1,124 @@ +{% extends "admin/base.html" %} + +{% block title %}{{ target_user.username }} — Admin — Porchlight{% endblock %} + +{% block admin_content %} +
ID: {{ target_user.userid }} · Created {{ target_user.created_at.strftime('%Y-%m-%d %H:%M') }}
Password is set. + +
+ {% else %} +No password set.
+ {% endif %} + +No security keys registered.
+ {% endif %} +