fullstackhero/dotnet-starter-kit · error · NotFoundException

user not found

Error message

user not found

What it means

ResetPasswordAsync throws NotFoundException when userManager.FindByEmailAsync(email) returns null, i.e. no user with that email exists in the current tenant. The reset cannot proceed without the user record that owns the password.

Solutions

  1. Verify the email address is spelled correctly and matches the registered user
  2. Ensure the request resolves to the same tenant the user belongs to (send the tenant identifier)
  3. Check the user row exists in the AspNetUsers table for that tenant/email
  4. If the user was deleted, re-register the account before resetting the password

Example fix

var user = await userManager.FindByEmailAsync(email);
if (user is null) return Results.NotFound($"no user with email {email}");
await passwordService.ResetPasswordAsync(email, token, newPassword, ct);
Defensive patterns

Strategy: validation

Validate before calling

var normalized = email?.Trim().ToLowerInvariant();
if (string.IsNullOrWhiteSpace(normalized) || !new EmailAddressAttribute().IsValid(normalized))
    return Results.BadRequest("A valid email is required.");
var exists = await userManager.Users.AnyAsync(u => u.NormalizedEmail == userManager.NormalizeEmail(normalized));
if (!exists) return Results.NotFound("No account found for this email in the current tenant.");

Try / catch

try
{
    await passwordService.ResetPasswordAsync(email, token, newPassword, ct);
}
catch (NotFoundException)
{
    return Results.NotFound("No account found for this email in the current tenant.");
}

Prevention

When it happens

Trigger: Calling ResetPasswordAsync(email, token, password, ...) with an email that has no matching FshUser — wrong email typed, user deleted, or the user exists in a different tenant.

Common situations: Client mistyped the email; calling reset in the wrong tenant context (users are tenant-scoped); user was removed between requesting the reset token and submitting it; test data never seeded the user.

Understand the failure class

Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/722b2982533e619d. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Services/UserPasswordService.cs:69

                ["email"] = email,
                ["tenant"] = multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id,
            });
        var mailRequest = new MailRequest(
            new Collection<string> { user.Email },
            "Reset Password",
            $"Please reset your password using the following link: {resetPasswordUri}");

        jobService.Enqueue(() => mailService.SendAsync(mailRequest, CancellationToken.None));
    }

    public async Task ResetPasswordAsync(string email, string password, string token, CancellationToken cancellationToken)
    {
        EnsureValidTenant();

        var user = await userManager.FindByEmailAsync(email);
        if (user == null)
        {
            throw new NotFoundException("user not found");
        }

        token = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(token));
        var result = await userManager.ResetPasswordAsync(user, token, password);

        if (!result.Succeeded)
        {
            var errors = result.Errors.Select(e => e.Description).ToList();
            throw new CustomException("error resetting password", errors);
        }

        // Raise domain event for password reset
        var tenantId = multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id;
        user.RecordPasswordChanged(wasReset: true, tenantId);
        await db.SaveChangesAsync(cancellationToken);
    }

    public async Task ChangePasswordAsync(string password, string newPassword, string confirmNewPassword, string userId, CancellationToken cancellationToken = default)

View on GitHub (pinned to 3f2959e683)