shoc-backend/Api.SeaHavenIndustries/Controllers/AuthenticationController.cs

214 lines
8.7 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(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));
}
}
[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
}
}