shoc-backend/Api.SeaHavenIndustries/Controllers/AuthenticationController.cs
Alexandre Brandizzi 7d245eb717
refactor: enforce backend boundaries and optimize dispatch (#30)
* refactor(api): enforce service and data-service boundaries

* refactor(api): complete feature service boundaries

* refactor(identity): enforce service and data boundaries

* refactor(vendors): enforce service and data boundaries

* refactor(workorders): enforce service and data boundaries

* refactor(backend): enforce architecture and optimize dispatch

* style(backend): format changed architecture files

* fix(architecture): address backend review follow-ups

* fix(backend): sanitize exception disclosure in changed API endpoints

Replace raw exception-message disclosure (ex.Message) returned to API
callers with a stable sanitized public message plus correlated structured
internal logging, across the endpoints changed in this PR.

- Add SanitizedErrors helper: logs the original exception at Error with a
  generated correlation id and returns a stable public message referencing
  it so support can trace without exposing internals.
- Inject ILogger<T> into the 14 changed controllers and route every
  ex.Message/dbex.Message disclosure through the helper, preserving status
  codes, response shapes, and business data (e.g. OpenWorkOrders).
- Leave FluentValidation (vex.Errors) and existing fixed-message catches
  untouched; out-of-scope controllers (Account/Contact/Employee/Asset/
  PMSchedule) are unchanged.
- Add focused tests proving internal exception text is not returned and
  that Error logging carrying the original exception is invoked.

* fix(architecture): abstract job run state access

* style: format board update service

* test: use collection assertion idiom
2026-07-24 17:35:34 -03:00

172 lines
6.1 KiB
C#

using Api.SeaHavenIndustries.DTOs;
using Api.SeaHavenIndustries.Helper;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
using System.Security.Claims;
namespace Api.SeaHavenIndustries.Controllers
{
[ApiController]
[Route("api/Authentication")]
public class AuthenticationController : Controller
{
private readonly IAuthenticationService _authenticationService;
private readonly ILogger<AuthenticationController> _logger;
public AuthenticationController(IAuthenticationService authenticationService, ILogger<AuthenticationController> logger)
{
_authenticationService = authenticationService;
_logger = logger;
}
[AllowAnonymous]
[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel model, CancellationToken cancellationToken)
{
try
{
var result = await _authenticationService.LoginAsync(model.Username, model.Password, cancellationToken);
if (result != null)
{
return Ok(new
{
token = result.Token,
expiration = result.Expiration,
email = result.Email,
userRoles = result.UserRole,
phoneNumber = result.PhoneNumber,
fullname = result.Fullname,
id = result.Id
});
}
return Unauthorized();
}
catch (Exception ex)
{
return StatusCode(500, _logger.Sanitize(ex));
}
}
[Route("ChangePassword")]
[HttpPost]
public async Task<IActionResult> ChangePassword(ChangePasswords usermodel, CancellationToken cancellationToken)
{
var userid = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "";
var succeeded = await _authenticationService.ChangePasswordAsync(userid, usermodel.Currentpassword, usermodel.Confirmpassword, cancellationToken);
if (succeeded)
{
return Ok(new Response { Status = "Success ", Message = "Password successfully changed" });
}
else
return BadRequest(new Response { Status = "Old Password is incorrect" });
}
[HttpPost]
[Route("UpdateProfile")]
public async Task<IActionResult> UserProfileUpdate([FromForm] User_DTO model, CancellationToken cancellationToken)
{
try
{
var userid = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "";
var dto = new UpdateProfileRequestDTO
{
Name = model.Name,
Email = model.Email,
Contact = model.Contact
};
var data = await _authenticationService.UpdateProfileAsync(userid, dto, cancellationToken);
return Ok(new DataResponse
{
Message = "Introduction Updated Successfully",
Status = "200",
Data = data == null ? null : new
{
data.FirstName,
data.Email,
data.Contact
}
});
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
#region Forget Password Area
[AllowAnonymous]
[HttpPost()]
[Route("ForgetPassword")]
public async Task<IActionResult> ForgetPassword(string Email, CancellationToken cancellationToken)
{
var found = await _authenticationService.ForgetPasswordAsync(Email, cancellationToken);
if (found)
{
return Ok(new Response { Status = "Success ", Message = "Please check your email for code" });
}
else
{
return BadRequest(new Response { Status = "Error", Message = "No such email is registered" });
}
}
//need email and code
[AllowAnonymous]
[HttpPost()]
[Route("VerificationCode")]
public async Task<IActionResult> VerificationCode(string code, CancellationToken cancellationToken)
{
try
{
if (await _authenticationService.VerifyCodeAsync(code, cancellationToken))
{
return Ok(new Response { Status = "Success ", Message = "Code Matched" });
}
else
{
return BadRequest(new Response { Status = "Error", Message = "Code Not Matched" });
}
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
// need email, password and code
[AllowAnonymous]
[HttpPost()]
[Route("ResetPassword")]
public async Task<IActionResult> ResetPassword(ForgetPassword_Dto fpdto, CancellationToken cancellationToken)
{
try
{
if (await _authenticationService.ResetPasswordAsync(fpdto.Email, fpdto.Code, fpdto.Password, cancellationToken))
{
return Ok(new Response { Status = "Success ", Message = "password changed" });
}
else
{
return BadRequest(new Response { Status = "Error", Message = "Your email or code not found please check" });
}
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
#endregion
}
}