fix(identity): correct password-reset email link (trailing slash, tenant, URL-encoding)#1302
Merged
iammukeshm merged 1 commit intoJun 19, 2026
Conversation
Build the link with QueryHelpers: trim the trailing slash from the configured origin, URL-encode each value, and include the tenant the reset page requires. A host-only OriginUrl produced "//reset-password" (404 in the SPA router), the link lacked the tenant query param (treated as malformed), and the email was not encoded. Adds UserPasswordServiceTests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The password-reset email builds a link the dashboard SPA cannot open. With
OriginOptions.OriginUrlset to a host-only URL, the emitted link is malformed in three independent ways, so the reset page
rejects it as a broken link. This fixes the link builder in
UserPasswordService.ForgotPasswordAsync.Environment
main@ 8c216adReproduction
appsettings.Production.json(OriginOptions.OriginUrlempty):POST /api/v1/identity/forgot-passwordreturns 500 ("Origin URL is not configured.").OriginOptions__OriginUrl=https://app.example.comand request a reset for a real user.https://app.example.com//reset-password?token=...&email=user+x@example.com(double slash, no
tenant, unencoded email) — the reset page shows "this link is incomplete".Defects fixed
OriginOptions.OriginUrlis aUri;ForgotPasswordCommandHandler.cs:24usesOriginUrl.ToString(), which appends a trailing slash for a host-only URL. Combined withUserPasswordService.cs:42($"{origin}/reset-password...") this yields//reset-password. The SPAroute is
/reset-password(clients/dashboard/src/routes.tsx), so it 404s in the client-side router.tenant. The reset page requirestoken+email+tenantand shows a "malformed link"state when any is absent (
clients/dashboard/src/pages/auth/reset-password.tsx). The backend builtonly
?token=...&email=....+(sub-addressing) or other reserved characters were corrupted.
Change
Build the link with
QueryHelpers.AddQueryString, matching the sibling email-link builderUserRegistrationService.GetEmailVerificationUriAsync. This trims the trailing slash, URL-encodes eachvalue, and adds the
tenantthe reset page requires.tenantIdis already guaranteed non-empty byEnsureValidTenant()earlier in the method;tokenisalready Base64Url-encoded. No public signature changes. No
src/BuildingBlocks/changes.Tests
Adds
UserPasswordServiceTests:+/@, asserting single slash,tenantpresent and the+encoded (an unencoded+would decode to a space);dotnet test src/Tests/Identity.Tests→ 312 passing. Build clean underTreatWarningsAsErrors.Out of scope
There is a broader structural issue (single global origin;
register/confirmderive the origin fromthe API host), which affects any deployment with a front-end separate from the API. That part is
non-trivial, so per CONTRIBUTING I will raise it as a Discussion rather than fold it into this fix.