using Hutopy.Infrastructure.Security; using Hutopy.Modules.Identity.Configuration; using Hutopy.Modules.Identity.Data; using Microsoft.Extensions.Options; namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record RegisterRequest( string Email, string Password, string Name); [PublicAPI] public record RegisterResponse( string AccessToken, string RefreshToken); [PublicAPI] public class RegisterHandler( UserManager userManager, IOptionsSnapshot jwtOptions) : Endpoint { public override void Configure() { AllowAnonymous(); Post("/api/users/register"); Options(o => o.WithTags("Users")); } public override async Task HandleAsync( RegisterRequest request, CancellationToken ct) { // Check if the user already exists var existingUser = await userManager.FindByEmailAsync(request.Email); if (existingUser is not null) { await SendStringAsync( "A user with this email already exists", 400, cancellation: ct); return; } // Create a refresh token var refreshToken = RefreshTokenGenerator.Next(); // Split the name into firstname and lastname (if provided) var nameParts = request.Name.Split(' ', 2); var firstname = nameParts[0]; var lastname = nameParts.Length > 1 ? nameParts[1] : string.Empty; // Create a new user var user = new User { UserName = request.Email, Email = request.Email, Firstname = firstname, Lastname = lastname, Alias = request.Name, RefreshToken = refreshToken, RefreshTokenExpiryTime = DateTime.UtcNow.Add(jwtOptions.Value.RefreshTokenLifetime) }; var result = await userManager.CreateAsync( user, request.Password); if (!result.Succeeded) { await SendStringAsync( result.Errors.First().Description, 400, cancellation: ct); return; } // Generate JWT token var accessToken = JwtTokenHelper.GenerateJwtToken( expiresIn: jwtOptions.Value.Lifetime, issuer: jwtOptions.Value.Issuer, audience: jwtOptions.Value.Audience, key: jwtOptions.Value.Key, userId: user.Id.ToString(), email: user.Email ?? string.Empty, alias: user.Alias, firstname: user.Firstname ?? string.Empty, lastname: user.Lastname ?? string.Empty, portraitUrl: user.PortraitUrl); await SendOkAsync( new RegisterResponse(accessToken, user.RefreshToken), cancellation: ct); } }