diff --git a/backend/app/api/api_v1/endpoints/auth.py b/backend/app/api/api_v1/endpoints/auth.py index 91a1c0c..802a342 100644 --- a/backend/app/api/api_v1/endpoints/auth.py +++ b/backend/app/api/api_v1/endpoints/auth.py @@ -7,6 +7,8 @@ GET /sign-in – Initiate the Logto sign-in flow. GET /callback – Handle the Logto authorization-code callback. GET /sign-out – Sign the user out (clears session + redirects to Logto). GET /me – Return the currently authenticated user's profile. +GET /forgot-password – Redirect to Logto's forgot-password screen. +GET /account-portal – Redirect to the Logto account portal (MFA management). """ from __future__ import annotations @@ -197,6 +199,53 @@ async def sign_out(request: Request) -> RedirectResponse: return response +@router.get("/forgot-password") +async def forgot_password(request: Request) -> RedirectResponse: + """ + Redirect the user to Logto's forgot-password screen. + + Builds a standard Logto authorization URL and appends the + ``first_screen=forgot_password`` parameter so that Logto shows the + password-reset form immediately instead of the normal sign-in form. + After the user resets their password they are returned via the normal + callback flow and land on the app dashboard. + """ + if not settings.logto_configured: + raise _logto_not_configured() + + storage = CookieStorage(request) + client = make_logto_client(storage) + + sign_in_url: str = await client.signIn(redirectUri=_get_redirect_uri(request)) + + # Append the Logto-specific first_screen parameter so the password-reset + # form is shown directly. The sign-in URL normally already contains a "?" + # but we defensively detect the right separator in case the structure varies. + separator = "&" if "?" in sign_in_url else "?" + forgot_url = f"{sign_in_url}{separator}first_screen=forgot_password" + + response = RedirectResponse(url=forgot_url, status_code=302) + storage.apply_to_response(response) + return response + + +@router.get("/account-portal") +async def account_portal(request: Request) -> RedirectResponse: + """ + Redirect an authenticated user to the Logto account portal. + + The Logto account portal (``{LOGTO_ENDPOINT}/account``) lets users manage + their profile, linked identities, and multi-factor authentication settings + without leaving the Logto-hosted UI. After updating their settings, users + can simply navigate back to the app. + """ + if not settings.logto_configured: + raise _logto_not_configured() + + portal_url = f"{settings.LOGTO_ENDPOINT.rstrip('/')}/account" + return RedirectResponse(url=portal_url, status_code=302) + + @router.get("/me", response_model=None) async def get_current_user( request: Request, diff --git a/backend/app/main.py b/backend/app/main.py index 5ff61c1..ac3f06a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -461,6 +461,19 @@ async def settings_page(request: Request): return templates.TemplateResponse(request, "settings.html") +@app.get("/profile", response_class=HTMLResponse) +async def profile_page(request: Request): + return templates.TemplateResponse( + request, + "profile.html", + { + "app_name": settings.PROJECT_NAME, + "logto_configured": settings.logto_configured, + "auth_disabled": settings.AUTH_DISABLED, + }, + ) + + @app.get("/mail-sources", response_class=HTMLResponse) async def mail_sources_page(request: Request): return templates.TemplateResponse(request, "mail_sources.html") diff --git a/backend/app/templates/layouts/base.html b/backend/app/templates/layouts/base.html index 7fdb424..4928bab 100644 --- a/backend/app/templates/layouts/base.html +++ b/backend/app/templates/layouts/base.html @@ -61,6 +61,7 @@ +
  • Profile & Security
  • Settings
  • diff --git a/backend/app/templates/login.html b/backend/app/templates/login.html index 363bb2d..1c7b372 100644 --- a/backend/app/templates/login.html +++ b/backend/app/templates/login.html @@ -88,6 +88,13 @@ Sign in with Logto +
    + + Forgot your password? + +
    +

    Logto securely handles authentication. Your credentials are never sent to {{ app_name }}. diff --git a/backend/app/templates/profile.html b/backend/app/templates/profile.html new file mode 100644 index 0000000..63e218b --- /dev/null +++ b/backend/app/templates/profile.html @@ -0,0 +1,182 @@ +{% extends "layouts/base.html" %} +{% from "components/ui/card.html" import card, card_header, card_title, card_description, card_content %} +{% from "components/ui/button.html" import button_link %} + +{% block title %}My Profile – {{ app_name }}{% endblock %} + +{% block content %} +

    + + + {% call card() %} + {% call card_header() %} + {% call card_title() %}My Profile{% endcall %} + {% call card_description() %}Your account information from your current session.{% endcall %} + {% endcall %} + {% call card_content() %} +
    + + + +
    +

    +

    +
    +
    + +
    +
    + Username +

    +
    +
    + Role +

    + + +

    +
    +
    + Logto ID +

    +
    +
    + Auth mode +

    + + +

    +
    +
    + {% endcall %} + {% endcall %} + + + {% if logto_configured and not auth_disabled %} + {% call card() %} + {% call card_header() %} + {% call card_title() %}Account Security{% endcall %} + {% call card_description() %} + Manage your password and multi-factor authentication settings. + These actions redirect you to your Logto identity account. + {% endcall %} + {% endcall %} + {% call card_content() %} +
    + + +
    +
    +

    Password

    +

    + Reset your Logto account password via email verification. +

    +
    + {% call button_link(href='/api/v1/auth/forgot-password', variant='outline', size='sm') %} + + + + Reset Password + {% endcall %} +
    + +
    + + +
    +
    +

    Multi-Factor Authentication

    +

    + Enable, configure, or remove MFA methods such as TOTP + authenticator apps and backup codes in your Logto account portal. +

    +
    + {% call button_link(href='/api/v1/auth/account-portal', variant='outline', size='sm') %} + + + + Manage MFA + {% endcall %} +
    + +
    + {% endcall %} + {% endcall %} + {% elif auth_disabled %} + + {% else %} + + {% endif %} + +
    + + +{% endblock %}