using Socialize.Infrastructure.Security; using Socialize.Modules.Identity.Data; using Socialize.Modules.Identity.Configuration; using Socialize.Modules.Identity.Services; using Microsoft.Extensions.Options; namespace Socialize.Modules.Identity.Handlers; [PublicAPI] public record LoginRequest( string Email, string Password); [PublicAPI] public record LoginResponse( string AccessToken, string RefreshToken); [PublicAPI] public class LoginHandler( UserManager userManager, IOptionsSnapshot jwtOptions, AccessTokenFactory accessTokenFactory) : Endpoint { public override void Configure() { AllowAnonymous(); Post("/api/users/login"); Options(o => o.WithTags("Users")); } public override async Task HandleAsync( LoginRequest request, CancellationToken ct) { // Find the user by email User? user = await userManager.FindByEmailAsync(request.Email); user ??= await userManager.FindByNameAsync(request.Email); if (user is null) { await SendStringAsync( "Invalid email or password", 401, cancellation: ct); return; } // Verify password bool isPasswordValid = await userManager.CheckPasswordAsync(user, request.Password); if (!isPasswordValid) { await SendStringAsync( "Invalid email or password", 401, cancellation: ct); return; } // Check if the email is confirmed if (!user.EmailConfirmed) { await SendStringAsync( "Email not verified. Please check your email for verification instructions.", 401, cancellation: ct); return; } // Generate a new refresh token user.RefreshToken = RefreshTokenGenerator.Next(); user.RefreshTokenExpiryTime = DateTime.UtcNow.Add(jwtOptions.Value.RefreshTokenLifetime); await userManager.UpdateAsync(user); // Generate JWT token string accessToken = await accessTokenFactory.CreateAsync(user); await SendOkAsync( new LoginResponse(accessToken, user.RefreshToken), ct); } }