mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 03:43:11 +00:00
205 lines
8.3 KiB
C#
205 lines
8.3 KiB
C#
using Api.SeaHavenIndustries.DTOs;
|
|
using Api.SeaHavenIndustries.Helper;
|
|
using Api.SeaHavenIndustries.Infrastructure;
|
|
using Data.SeaHavenIndustries;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using Microsoft.Extensions.Logging;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Interfaces;
|
|
using System.Runtime.CompilerServices;
|
|
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(LoginPayload.From(result));
|
|
}
|
|
|
|
return Unauthorized();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
return StatusCode(500, _logger.Sanitize(ex));
|
|
}
|
|
|
|
}
|
|
|
|
[Authorize]
|
|
[Route("ChangePassword")]
|
|
[HttpPost]
|
|
public async Task<IActionResult> ChangePassword(ChangePasswords usermodel, CancellationToken cancellationToken)
|
|
{
|
|
// [Compare] already rejects this during model validation; the check here keeps the
|
|
// unconfirmed password from ever being set if that validation is bypassed.
|
|
if (!string.Equals(usermodel.Newpassword, usermodel.Confirmpassword, StringComparison.Ordinal))
|
|
return BadRequest(new Response { Status = "Password confirmation does not match", Message = "Passwords don't match" });
|
|
|
|
var userid = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "";
|
|
var result = await _authenticationService.ChangePasswordAsync(userid, usermodel.Currentpassword, usermodel.Newpassword, cancellationToken);
|
|
return result.Status switch
|
|
{
|
|
ChangePasswordStatus.Succeeded =>
|
|
Ok(new Response { Status = "Success ", Message = "Password successfully changed" }),
|
|
ChangePasswordStatus.PasswordRejected =>
|
|
BadRequest(new Response { Status = "Password does not meet requirements", Message = PasswordRequirementsMessage }),
|
|
ChangePasswordStatus.Failed =>
|
|
BadRequest(new Response { Status = "Password not changed", Message = "Your password could not be changed. Try again." }),
|
|
_ => BadRequest(new Response { Status = "Old Password is incorrect", Message = "Current password is incorrect" })
|
|
};
|
|
}
|
|
|
|
private const string PasswordRequirementsMessage =
|
|
"Password must be at least 6 characters and include one uppercase letter, one number, and one special character.";
|
|
|
|
[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
|
|
|
|
public const string ForgetPasswordMessage =
|
|
"If that email belongs to an account, a reset code has been sent to it.";
|
|
|
|
// Email and code are read only from the JSON body, so they never appear in URLs
|
|
// or in proxy and load balancer access logs.
|
|
[AllowAnonymous]
|
|
[HttpPost()]
|
|
[Route("ForgetPassword")]
|
|
[EnableRateLimiting(PasswordResetRateLimiting.ForgetPasswordPolicy)]
|
|
public async Task<IActionResult> ForgetPassword(
|
|
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] ForgetPasswordRequest_Dto? body,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
await _authenticationService.ForgetPasswordAsync(body?.Email, cancellationToken);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Same answer as success: a failure only a registered address can hit
|
|
// must not reveal that the address is registered.
|
|
LogResetFailure(ex);
|
|
}
|
|
|
|
return Ok(new Response { Status = "Success ", Message = ForgetPasswordMessage });
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[HttpPost()]
|
|
[Route("VerificationCode")]
|
|
[EnableRateLimiting(PasswordResetRateLimiting.VerificationCodePolicy)]
|
|
public async Task<IActionResult> VerificationCode(
|
|
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] VerificationCode_Dto? body,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
if (await _authenticationService.VerifyCodeAsync(body?.Email, body?.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)
|
|
{
|
|
LogResetFailure(ex);
|
|
return BadRequest(new Response { Status = "Error", Message = "Code Not Matched" });
|
|
}
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[HttpPost()]
|
|
[Route("ResetPassword")]
|
|
[EnableRateLimiting(PasswordResetRateLimiting.ResetPasswordPolicy)]
|
|
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)
|
|
{
|
|
LogResetFailure(ex);
|
|
return BadRequest(new Response { Status = "Error", Message = "Your email or code not found please check" });
|
|
}
|
|
}
|
|
|
|
// These endpoints answer failures exactly like a wrong code or an unknown email, so
|
|
// an error only a registered account can trigger reveals nothing. Exception
|
|
// messages here can echo the email or code, so only the type is logged.
|
|
private void LogResetFailure(Exception exception, [CallerMemberName] string operation = "")
|
|
{
|
|
_logger.LogError(
|
|
"Password reset {Operation} failed with {ExceptionType}.",
|
|
operation,
|
|
exception.GetType().FullName);
|
|
}
|
|
#endregion
|
|
}
|
|
}
|