using System.Web; using Hutopy.Infrastructure.Configuration; using Hutopy.Infrastructure.Emailer.Contracts; using Hutopy.Modules.Identity.Data; using Microsoft.Extensions.Options; namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ForgotPasswordRequest( string Email); [PublicAPI] public class ForgotPasswordHandler( UserManager userManager, IEmailSender emailSender, IOptionsSnapshot options) : Endpoint { public override void Configure() { AllowAnonymous(); Post("/api/users/forgot-password"); Options(o => o.WithTags("Users")); } public override async Task HandleAsync( ForgotPasswordRequest request, CancellationToken ct) { // Find user by email User? user = await userManager.FindByEmailAsync(request.Email); // Always return OK even if user not found to prevent email enumeration if (user is null) { await SendOkAsync(ct); return; } // Generate password reset token string token = await userManager.GeneratePasswordResetTokenAsync(user); // URL encode the token as it may contain characters that are not URL safe string encodedToken = HttpUtility.UrlEncode(token); // Build reset link string resetLink = $"{options.Value.FrontendBaseUrl}/reset-password?email={HttpUtility.UrlEncode(request.Email)}&token={encodedToken}"; // Create a styled email message string subject = "Reset your Hutopy password"; string message = $"""

Reset Your Hutopy Password

Please click the button below to reset your password:

Reset Password

If you did not request a password reset, please ignore this email.

If the button doesn't work, you can copy and paste this link into your browser:
{resetLink}

"""; // Send email await emailSender.SendEmailAsync(request.Email, subject, message); await SendOkAsync(ct); } }