Merge pull request #92 from christianlouis/copilot/fix-login-and-profile-management

Fix profile page: use Logto Account Center prebuilt flows for password change and MFA
This commit is contained in:
Christian Krakau-Louis
2026-04-02 19:48:06 +02:00
committed by GitHub
2 changed files with 69 additions and 22 deletions
+61 -14
View File
@@ -3,12 +3,14 @@ Authentication endpoints (Logto OIDC).
Routes Routes
------ ------
GET /sign-in Initiate the Logto sign-in flow. GET /sign-in Initiate the Logto sign-in flow.
GET /callback Handle the Logto authorization-code callback. GET /callback Handle the Logto authorization-code callback.
GET /sign-out Sign the user out (clears session + redirects to Logto). GET /sign-out Sign the user out (clears session + redirects to Logto).
GET /me Return the currently authenticated user's profile. GET /me Return the currently authenticated user's profile.
GET /forgot-password Redirect to Logto's forgot-password screen. GET /forgot-password Redirect to Logto's forgot-password screen (unauthenticated).
GET /account-portal Redirect to the Logto account portal (MFA management). GET /change-password Redirect to Logto Account Center password page (authenticated).
GET /manage-mfa Redirect to Logto Account Center MFA page (authenticated).
GET /account-portal Redirect to the Logto Account Center root.
""" """
from __future__ import annotations from __future__ import annotations
@@ -199,6 +201,24 @@ async def sign_out(request: Request) -> RedirectResponse:
return response return response
@router.get("/change-password")
async def change_password(request: Request) -> RedirectResponse:
"""
Redirect an authenticated user to the Logto Account Center password page.
Uses Logto's prebuilt Account Center flow at ``{LOGTO_ENDPOINT}/account/password``
so the user can change their existing password directly. A ``redirect``
query parameter is appended so that Logto returns the user to the Profile &
Security page after a successful update.
"""
if not settings.logto_configured:
raise _logto_not_configured()
base = str(request.base_url).rstrip("/")
password_url = f"{settings.LOGTO_ENDPOINT.rstrip('/')}/account/password?redirect={base}/profile"
return RedirectResponse(url=password_url, status_code=302)
@router.get("/forgot-password") @router.get("/forgot-password")
async def forgot_password(request: Request) -> RedirectResponse: async def forgot_password(request: Request) -> RedirectResponse:
""" """
@@ -209,6 +229,9 @@ async def forgot_password(request: Request) -> RedirectResponse:
password-reset form immediately instead of the normal sign-in form. password-reset form immediately instead of the normal sign-in form.
After the user resets their password they are returned via the normal After the user resets their password they are returned via the normal
callback flow and land on the app dashboard. callback flow and land on the app dashboard.
This endpoint is kept for unauthenticated / "I forgot my password" use
cases. Authenticated users should use ``/change-password`` instead.
""" """
if not settings.logto_configured: if not settings.logto_configured:
raise _logto_not_configured() raise _logto_not_configured()
@@ -229,20 +252,44 @@ async def forgot_password(request: Request) -> RedirectResponse:
return response return response
@router.get("/account-portal") @router.get("/manage-mfa")
async def account_portal(request: Request) -> RedirectResponse: async def manage_mfa(request: Request) -> RedirectResponse:
""" """
Redirect an authenticated user to the Logto account portal. Redirect an authenticated user to the Logto Account Center MFA page.
The Logto account portal (``{LOGTO_ENDPOINT}/account``) lets users manage Uses Logto's prebuilt Account Center flow at
their profile, linked identities, and multi-factor authentication settings ``{LOGTO_ENDPOINT}/account/authenticator-app`` so the user can enable,
without leaving the Logto-hosted UI. After updating their settings, users configure, or remove TOTP authenticator-app MFA directly. A ``redirect``
can simply navigate back to the app. query parameter is appended so that Logto returns the user to the Profile &
Security page after a successful update.
""" """
if not settings.logto_configured: if not settings.logto_configured:
raise _logto_not_configured() raise _logto_not_configured()
portal_url = f"{settings.LOGTO_ENDPOINT.rstrip('/')}/account" base = str(request.base_url).rstrip("/")
mfa_url = (
f"{settings.LOGTO_ENDPOINT.rstrip('/')}/account/authenticator-app"
f"?redirect={base}/profile"
)
return RedirectResponse(url=mfa_url, status_code=302)
@router.get("/account-portal")
async def account_portal(request: Request) -> RedirectResponse:
"""
Redirect an authenticated user to the Logto Account Center.
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. A ``redirect`` query parameter is
appended so that Logto returns the user to the Profile & Security page
after a successful update.
"""
if not settings.logto_configured:
raise _logto_not_configured()
base = str(request.base_url).rstrip("/")
portal_url = f"{settings.LOGTO_ENDPOINT.rstrip('/')}/account?redirect={base}/profile"
return RedirectResponse(url=portal_url, status_code=302) return RedirectResponse(url=portal_url, status_code=302)
+8 -8
View File
@@ -76,28 +76,28 @@
{% call card_header() %} {% call card_header() %}
{% call card_title() %}Account Security{% endcall %} {% call card_title() %}Account Security{% endcall %}
{% call card_description() %} {% call card_description() %}
Manage your password and multi-factor authentication settings. Manage your password and multi-factor authentication settings
These actions redirect you to your Logto identity account. via the Logto account center.
{% endcall %} {% endcall %}
{% endcall %} {% endcall %}
{% call card_content() %} {% call card_content() %}
<div class="space-y-4"> <div class="space-y-4">
<!-- Password reset --> <!-- Password change -->
<div class="flex items-start justify-between gap-4"> <div class="flex items-start justify-between gap-4">
<div> <div>
<p class="font-medium">Password</p> <p class="font-medium">Password</p>
<p class="text-sm text-base-content/60"> <p class="text-sm text-base-content/60">
Reset your Logto account password via email verification. Change your password in the Logto account center.
</p> </p>
</div> </div>
{% call button_link(href='/api/v1/auth/forgot-password', variant='outline', size='sm') %} {% call button_link(href='/api/v1/auth/change-password', variant='outline', size='sm') %}
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-1" fill="none" <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-1" fill="none"
viewBox="0 0 24 24" stroke="currentColor"> viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/> d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
</svg> </svg>
Reset Password Change Password
{% endcall %} {% endcall %}
</div> </div>
@@ -109,10 +109,10 @@
<p class="font-medium">Multi-Factor Authentication</p> <p class="font-medium">Multi-Factor Authentication</p>
<p class="text-sm text-base-content/60"> <p class="text-sm text-base-content/60">
Enable, configure, or remove MFA methods such as TOTP Enable, configure, or remove MFA methods such as TOTP
authenticator apps and backup codes in your Logto account portal. authenticator apps and backup codes in your Logto account center.
</p> </p>
</div> </div>
{% call button_link(href='/api/v1/auth/account-portal', variant='outline', size='sm') %} {% call button_link(href='/api/v1/auth/manage-mfa', variant='outline', size='sm') %}
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-1" fill="none" <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-1" fill="none"
viewBox="0 0 24 24" stroke="currentColor"> viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"