feat: harden auth ops and deployment baseline

This commit is contained in:
2026-06-27 10:53:53 +09:00
parent a6ca30eec8
commit 28060b71be
41 changed files with 714 additions and 208 deletions
+63 -2
View File
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using TaxBaik.Web.Services;
namespace TaxBaik.Web.Controllers;
@@ -18,14 +19,61 @@ public class AuthController : ControllerBase
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
return BadRequest(new { message = "Username and password are required" });
return BadRequest(new ProblemDetails { Title = "로그인 정보가 필요합니다.", Status = StatusCodes.Status400BadRequest });
var token = await _authService.AuthenticateAndGenerateTokenAsync(request.Username, request.Password);
if (token == null)
return Unauthorized(new { message = "Invalid username or password" });
return Unauthorized(new ProblemDetails { Title = "아이디 또는 비밀번호가 올바르지 않습니다.", Status = StatusCodes.Status401Unauthorized });
return Ok(new { token, expiresIn = 28800 });
}
[HttpPost("change-password")]
[Microsoft.AspNetCore.Authorization.Authorize]
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
{
var username = User.FindFirstValue(ClaimTypes.Name);
if (string.IsNullOrWhiteSpace(username))
return Unauthorized(new ProblemDetails { Title = "인증 정보가 올바르지 않습니다.", Status = StatusCodes.Status401Unauthorized });
try
{
var changed = await _authService.ChangePasswordAsync(username, request.CurrentPassword, request.NewPassword);
if (!changed)
return Unauthorized(new ProblemDetails { Title = "현재 비밀번호가 올바르지 않습니다.", Status = StatusCodes.Status401Unauthorized });
return Ok(new { message = "비밀번호가 변경되었습니다." });
}
catch (ArgumentException ex)
{
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
}
}
[HttpPost("reset-password")]
public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordRequest request)
{
try
{
var reset = await _authService.ResetPasswordAsync(request.Username, request.NewPassword, request.ResetToken);
if (!reset)
return Unauthorized(new ProblemDetails { Title = "재설정 토큰 또는 사용자 정보가 올바르지 않습니다.", Status = StatusCodes.Status401Unauthorized });
return Ok(new { message = "비밀번호가 재설정되었습니다." });
}
catch (InvalidOperationException)
{
return StatusCode(StatusCodes.Status503ServiceUnavailable, new ProblemDetails
{
Title = "비밀번호 재설정 토큰이 서버에 설정되어 있지 않습니다.",
Status = StatusCodes.Status503ServiceUnavailable
});
}
catch (ArgumentException ex)
{
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
}
}
}
public class LoginRequest
@@ -33,3 +81,16 @@ public class LoginRequest
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
public class ChangePasswordRequest
{
public string CurrentPassword { get; set; } = string.Empty;
public string NewPassword { get; set; } = string.Empty;
}
public class ResetPasswordRequest
{
public string Username { get; set; } = string.Empty;
public string NewPassword { get; set; } = string.Empty;
public string ResetToken { get; set; } = string.Empty;
}
+21 -10
View File
@@ -28,7 +28,7 @@ public class BlogController : ControllerBase
{
var post = await _blogService.GetBySlugAsync(slug);
if (post == null)
return NotFound(new { message = "Post not found" });
return NotFound(new ProblemDetails { Title = "포스트를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
return Ok(post);
}
@@ -44,21 +44,32 @@ public class BlogController : ControllerBase
[Authorize]
public async Task<IActionResult> Create([FromBody] CreateBlogPostDto dto)
{
if (string.IsNullOrWhiteSpace(dto.Title) || string.IsNullOrWhiteSpace(dto.Content))
return BadRequest(new { message = "Title and content are required" });
var result = await _blogService.CreateAsync(dto);
return CreatedAtAction(nameof(GetBySlug), new { slug = result.Slug }, result);
try
{
var result = await _blogService.CreateAsync(dto);
return CreatedAtAction(nameof(GetBySlug), new { slug = result.Slug }, result);
}
catch (ValidationException ex)
{
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
}
}
[HttpPut("{id}")]
[Authorize]
public async Task<IActionResult> Update(int id, [FromBody] CreateBlogPostDto dto)
{
var result = await _blogService.UpdateAsync(id, dto);
if (result == null)
return NotFound(new { message = "Post not found" });
return Ok(result);
try
{
var result = await _blogService.UpdateAsync(id, dto);
if (result == null)
return NotFound(new ProblemDetails { Title = "포스트를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
return Ok(result);
}
catch (ValidationException ex)
{
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
}
}
[HttpDelete("{id}")]
+31 -14
View File
@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TaxBaik.Application.Services;
using TaxBaik.Domain.Interfaces;
namespace TaxBaik.Web.Controllers;
@@ -10,29 +9,40 @@ namespace TaxBaik.Web.Controllers;
public class InquiryController : ControllerBase
{
private readonly InquiryService _inquiryService;
private readonly IInquiryRepository _inquiryRepository;
public InquiryController(InquiryService inquiryService, IInquiryRepository inquiryRepository)
public InquiryController(InquiryService inquiryService)
{
_inquiryService = inquiryService;
_inquiryRepository = inquiryRepository;
}
[HttpPost]
public async Task<IActionResult> Submit([FromBody] SubmitInquiryRequest request)
{
if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Phone))
return BadRequest(new { message = "Name and phone are required" });
return BadRequest(new ProblemDetails { Title = "이름과 전화번호를 입력하세요.", Status = StatusCodes.Status400BadRequest });
await _inquiryService.SubmitAsync(request.Name, request.Phone, request.ServiceType, request.Message);
return Ok(new { message = "Inquiry submitted successfully" });
try
{
await _inquiryService.SubmitAsync(
request.Name,
request.Phone,
request.ServiceType,
request.Message,
request.Email,
HttpContext.Connection.RemoteIpAddress?.ToString());
return Ok(new { message = "상담 신청이 접수되었습니다." });
}
catch (ValidationException ex)
{
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
}
}
[HttpGet]
[Authorize]
public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
{
var (inquiries, total) = await _inquiryRepository.GetPagedAsync(page, pageSize);
var (inquiries, total) = await _inquiryService.GetPagedAsync(page, pageSize);
return Ok(new { data = inquiries, total, page, pageSize });
}
@@ -40,9 +50,9 @@ public class InquiryController : ControllerBase
[Authorize]
public async Task<IActionResult> GetById(int id)
{
var inquiry = await _inquiryRepository.GetByIdAsync(id);
var inquiry = await _inquiryService.GetByIdAsync(id);
if (inquiry == null)
return NotFound(new { message = "Inquiry not found" });
return NotFound(new ProblemDetails { Title = "문의를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
return Ok(inquiry);
}
@@ -50,12 +60,19 @@ public class InquiryController : ControllerBase
[Authorize]
public async Task<IActionResult> UpdateStatus(int id, [FromBody] UpdateStatusRequest request)
{
var inquiry = await _inquiryRepository.GetByIdAsync(id);
var inquiry = await _inquiryService.GetByIdAsync(id);
if (inquiry == null)
return NotFound(new { message = "Inquiry not found" });
return NotFound(new ProblemDetails { Title = "문의를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
await _inquiryRepository.UpdateStatusAsync(id, request.Status);
return Ok(new { message = "Status updated" });
try
{
await _inquiryService.UpdateStatusAsync(id, request.Status);
return Ok(new { message = "상태가 변경되었습니다." });
}
catch (ValidationException ex)
{
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
}
}
}