using LiteCqrs; using TeleWave.Api.Common; using TeleWave.Application.Auth; using TeleWave.Application.Auth.ChangePassword; using TeleWave.Application.Auth.ChangeUserName; using TeleWave.Application.Auth.DeleteMyAccount; using TeleWave.Application.Auth.Login; using TeleWave.Application.Auth.Logout; using TeleWave.Application.Auth.Me; using TeleWave.Application.Auth.Refresh; using TeleWave.Application.Auth.Register; namespace TeleWave.Api.Endpoints; public static class AuthEndpoints { private const string RefreshCookieName = "tw_refresh_token"; public static IEndpointRouteBuilder MapAuthEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api/auth") .WithTags("Auth") .RequireRateLimiting(RateLimiting.AuthPolicy); group.MapPost("/register", Register).Produces(); group.MapPost("/login", Login).Produces(); group.MapPost("/refresh", Refresh).Produces(); group .MapPost("/logout", Logout) .RequireAuthorization() .Produces(StatusCodes.Status204NoContent); group .MapPost("/change-password", ChangePassword) .RequireAuthorization() .Produces(StatusCodes.Status204NoContent); group .MapPost("/change-username", ChangeUserName) .RequireAuthorization() .Produces(StatusCodes.Status204NoContent); group.MapGet("/me", Me).RequireAuthorization().Produces(); group .MapDelete("/me", DeleteMe) .RequireAuthorization() .Produces(StatusCodes.Status204NoContent); return app; } private static async Task Register( RegisterCommand command, ISender sender, HttpRequest request, HttpResponse response, CancellationToken cancellationToken ) { var result = await sender.Send(command, cancellationToken); if (!result.IsSuccess) return result.ToHttpResult(); SetRefreshCookie( request, response, result.Value.RefreshToken, result.Value.RefreshTokenExpiresAt ); return Results.Ok(ToLoginResponse(result.Value)); } private static async Task Login( LoginCommand command, ISender sender, HttpRequest request, HttpResponse response, CancellationToken cancellationToken ) { var result = await sender.Send(command, cancellationToken); if (!result.IsSuccess) return result.ToHttpResult(); SetRefreshCookie( request, response, result.Value.RefreshToken, result.Value.RefreshTokenExpiresAt ); return Results.Ok(ToLoginResponse(result.Value)); } private static async Task Refresh( HttpRequest request, HttpResponse response, ISender sender, CancellationToken cancellationToken ) { if ( !request.Cookies.TryGetValue(RefreshCookieName, out var rawToken) || string.IsNullOrEmpty(rawToken) ) return Results.Unauthorized(); var result = await sender.Send(new RefreshCommand(rawToken), cancellationToken); if (!result.IsSuccess) { response.Cookies.Delete(RefreshCookieName, BuildCookieOptions(request)); return result.ToHttpResult(); } SetRefreshCookie( request, response, result.Value.RefreshToken, result.Value.RefreshTokenExpiresAt ); return Results.Ok(ToLoginResponse(result.Value)); } internal static AuthResponseDto ToLoginResponse(AuthResult auth) => new(auth.AccessToken, auth.AccessTokenExpiresAt, auth.User); private static async Task Logout( HttpRequest request, HttpResponse response, ISender sender, CancellationToken cancellationToken ) { if ( request.Cookies.TryGetValue(RefreshCookieName, out var rawToken) && !string.IsNullOrEmpty(rawToken) ) await sender.Send(new LogoutCommand(rawToken), cancellationToken); response.Cookies.Delete(RefreshCookieName, BuildCookieOptions(request)); return Results.NoContent(); } private static async Task ChangePassword( ChangePasswordCommand command, ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send(command, cancellationToken); return result.ToHttpResult(); } private static async Task ChangeUserName( ChangeUserNameCommand command, ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send(command, cancellationToken); return result.ToHttpResult(); } private static async Task Me(ISender sender, CancellationToken cancellationToken) { var result = await sender.Send(new GetCurrentUserQuery(), cancellationToken); return result.ToHttpResult(); } private static async Task DeleteMe( HttpRequest request, HttpResponse response, ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send(new DeleteMyAccountCommand(), cancellationToken); response.Cookies.Delete(RefreshCookieName, BuildCookieOptions(request)); return result.ToHttpResult(); } private static void SetRefreshCookie( HttpRequest request, HttpResponse response, string rawToken, DateTimeOffset expiresAt ) { var options = BuildCookieOptions(request); options.Expires = expiresAt; response.Cookies.Append(RefreshCookieName, rawToken, options); } private static CookieOptions BuildCookieOptions(HttpRequest request) => new() { HttpOnly = true, Secure = request.IsHttps, SameSite = SameSiteMode.Strict, Path = "/api/auth", }; } public sealed record AuthResponseDto( string AccessToken, DateTimeOffset ExpiresAt, CurrentUserDto User );