feat: migrate AuthController to FastEndpoints Endpoints (Phase 1)
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m31s
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m31s
IMPLEMENTATION:
- Create 4 FastEndpoints Endpoint classes:
- LoginEndpoint: POST /api/auth/login
- RefreshTokenEndpoint: POST /api/auth/refresh
- ChangePasswordEndpoint: POST /api/auth/change-password
- ResetPasswordEndpoint: POST /api/auth/reset-password
- Backup AuthController.cs (no longer active)
- Add FastEndpoints.Endpoint<TRequest, TResponse> pattern
- Implement proper DI with AuthService injection
- Use Policies("Bearer") for authorization
- Proper error handling with ThrowError()
ARCHITECTURE:
- Start of Phase 1: Core Auth APIs
- Endpoints follow FastEndpoints conventions
- DTOs: LoginRequest, RefreshTokenRequest, ChangePasswordRequest, ResetPasswordRequest, TokenPairResponse, MessageResponse
- AllowAnonymous for login/refresh/reset
- Bearer policy for change-password
VERIFICATION:
✅ dotnet build: 0 errors, 0 warnings
✅ dotnet test: 26/26 passed
✅ FastEndpoints auto-discovery working (no endpoint errors)
✅ JWT validation passes
Next Phase: BlogController and remaining APIs
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
using System.Security.Claims;
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Web.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.Auth;
|
||||
|
||||
public class ChangePasswordRequest
|
||||
{
|
||||
public string CurrentPassword { get; set; } = string.Empty;
|
||||
public string NewPassword { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class MessageResponse
|
||||
{
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ChangePasswordEndpoint : Endpoint<ChangePasswordRequest, MessageResponse>
|
||||
{
|
||||
private readonly AuthService _authService;
|
||||
|
||||
public ChangePasswordEndpoint(AuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/auth/change-password");
|
||||
Policies("Bearer");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(ChangePasswordRequest request, CancellationToken ct)
|
||||
{
|
||||
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
ThrowError("인증 정보가 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var changed = await _authService.ChangePasswordAsync(username!, request.CurrentPassword, request.NewPassword);
|
||||
if (!changed)
|
||||
{
|
||||
ThrowError("현재 비밀번호가 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
await SendAsync(new MessageResponse { Message = "비밀번호가 변경되었습니다." }, 200, cancellation: ct);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
ThrowError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Web.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.Auth;
|
||||
|
||||
public class LoginRequest
|
||||
{
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class TokenPairResponse
|
||||
{
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public string AccessToken { get; set; } = string.Empty;
|
||||
public string RefreshToken { get; set; } = string.Empty;
|
||||
public int ExpiresIn { get; set; }
|
||||
}
|
||||
|
||||
public class LoginEndpoint : Endpoint<LoginRequest, TokenPairResponse>
|
||||
{
|
||||
private readonly AuthService _authService;
|
||||
|
||||
public LoginEndpoint(AuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/auth/login");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(LoginRequest request, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
|
||||
{
|
||||
ThrowError("로그인 정보가 필요합니다.");
|
||||
}
|
||||
|
||||
var tokenPair = await _authService.AuthenticateAndGenerateTokenPairAsync(request.Username, request.Password);
|
||||
if (tokenPair == null)
|
||||
{
|
||||
ThrowError("아이디 또는 비밀번호가 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
await SendAsync(new TokenPairResponse
|
||||
{
|
||||
Token = tokenPair!.AccessToken,
|
||||
AccessToken = tokenPair.AccessToken,
|
||||
RefreshToken = tokenPair.RefreshToken,
|
||||
ExpiresIn = tokenPair.ExpiresIn
|
||||
}, 200, cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Web.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.Auth;
|
||||
|
||||
public class RefreshTokenRequest
|
||||
{
|
||||
public string RefreshToken { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class RefreshTokenEndpoint : Endpoint<RefreshTokenRequest, TokenPairResponse>
|
||||
{
|
||||
private readonly AuthService _authService;
|
||||
|
||||
public RefreshTokenEndpoint(AuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/auth/refresh");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(RefreshTokenRequest request, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.RefreshToken))
|
||||
{
|
||||
ThrowError("Refresh token이 필요합니다.");
|
||||
}
|
||||
|
||||
var tokenPair = await _authService.RefreshAccessTokenAsync(request.RefreshToken);
|
||||
if (tokenPair == null)
|
||||
{
|
||||
ThrowError("Refresh token이 유효하지 않습니다.");
|
||||
}
|
||||
|
||||
await SendAsync(new TokenPairResponse
|
||||
{
|
||||
Token = tokenPair!.AccessToken,
|
||||
AccessToken = tokenPair.AccessToken,
|
||||
RefreshToken = tokenPair.RefreshToken,
|
||||
ExpiresIn = tokenPair.ExpiresIn
|
||||
}, 200, cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Web.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.Auth;
|
||||
|
||||
public class ResetPasswordRequest
|
||||
{
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string NewPassword { get; set; } = string.Empty;
|
||||
public string ResetToken { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ResetPasswordEndpoint : Endpoint<ResetPasswordRequest, MessageResponse>
|
||||
{
|
||||
private readonly AuthService _authService;
|
||||
|
||||
public ResetPasswordEndpoint(AuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/auth/reset-password");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(ResetPasswordRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reset = await _authService.ResetPasswordAsync(request.Username, request.NewPassword, request.ResetToken);
|
||||
if (!reset)
|
||||
{
|
||||
ThrowError("재설정 토큰 또는 사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
await SendAsync(new MessageResponse { Message = "비밀번호가 재설정되었습니다." }, 200, cancellation: ct);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
ThrowError("비밀번호 재설정 토큰이 서버에 설정되어 있지 않습니다.", statusCode: 503);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
ThrowError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user