Polianin Nikita
cd6f25deba
All logic related to token manipulation has been transferred to the AuthService. Also added TOTP 2FA and rethought the logic of logging into the application
126 lines
4.4 KiB
C#
126 lines
4.4 KiB
C#
using Asp.Versioning;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Options;
|
|
using Mirea.Api.Dto.Common;
|
|
using Mirea.Api.Dto.Requests;
|
|
using Mirea.Api.Dto.Responses;
|
|
using Mirea.Api.Endpoint.Common.Attributes;
|
|
using Mirea.Api.Endpoint.Common.Exceptions;
|
|
using Mirea.Api.Endpoint.Common.Services;
|
|
using Mirea.Api.Endpoint.Configuration.Model;
|
|
using Mirea.Api.Security.Common.Domain;
|
|
using Mirea.Api.Security.Services;
|
|
using System;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Mirea.Api.Endpoint.Controllers.V1;
|
|
|
|
[ApiVersion("1.0")]
|
|
public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, PasswordHashService passwordService) : BaseController
|
|
{
|
|
private CookieOptionsParameters GetCookieParams() =>
|
|
new()
|
|
{
|
|
Domain = HttpContext.GetCurrentDomain(),
|
|
Path = UrlHelper.GetSubPathWithoutFirstApiName + "api"
|
|
};
|
|
|
|
[HttpPost("Login")]
|
|
[BadRequestResponse]
|
|
public async Task<ActionResult<AuthenticationStep>> Login([FromBody] LoginRequest request)
|
|
{
|
|
var userEntity = user.Value;
|
|
|
|
if (!userEntity.Username.Equals(request.Username, StringComparison.OrdinalIgnoreCase) &&
|
|
!userEntity.Email.Equals(request.Username, StringComparison.OrdinalIgnoreCase))
|
|
return BadRequest("Invalid username/email or password");
|
|
|
|
var tokenResult = await auth.LoginAsync(
|
|
GetCookieParams(),
|
|
new User
|
|
{
|
|
Id = 1,
|
|
Username = userEntity.Username,
|
|
Email = userEntity.Email,
|
|
PasswordHash = userEntity.PasswordHash,
|
|
Salt = userEntity.Salt,
|
|
SecondFactor = userEntity.SecondFactor,
|
|
SecondFactorToken = userEntity.Secret
|
|
},
|
|
HttpContext, request.Password);
|
|
|
|
return Ok(tokenResult ? AuthenticationStep.None : AuthenticationStep.TotpRequired);
|
|
}
|
|
|
|
[HttpGet("Login")]
|
|
[BadRequestResponse]
|
|
public async Task<ActionResult<AuthenticationStep>> Login([FromQuery] string code)
|
|
{
|
|
var tokenResult = await auth.LoginAsync(GetCookieParams(), HttpContext, code);
|
|
return Ok(tokenResult ? AuthenticationStep.None : AuthenticationStep.TotpRequired);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refreshes the authentication token using the existing refresh token.
|
|
/// </summary>
|
|
/// <returns>User's AuthRoles.</returns>
|
|
[HttpGet("ReLogin")]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
|
public async Task<ActionResult<AuthRoles>> ReLogin()
|
|
{
|
|
await auth.RefreshTokenAsync(GetCookieParams(), HttpContext);
|
|
return Ok(AuthRoles.Admin);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs the user out by clearing the refresh token and performing any necessary cleanup.
|
|
/// </summary>
|
|
/// <returns>An Ok response if the logout was successful.</returns>
|
|
[HttpGet("Logout")]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[Authorize]
|
|
public async Task<ActionResult> Logout()
|
|
{
|
|
await auth.LogoutAsync(GetCookieParams(), HttpContext);
|
|
|
|
return Ok();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the role of the authenticated user.
|
|
/// </summary>
|
|
/// <returns>The role of the authenticated user.</returns>
|
|
[HttpGet("GetRole")]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[Authorize]
|
|
public ActionResult<AuthRoles> GetRole() => Ok(AuthRoles.Admin);
|
|
|
|
[HttpPost("RenewPassword")]
|
|
[ApiExplorerSettings(IgnoreApi = true)]
|
|
[Localhost]
|
|
[BadRequestResponse]
|
|
public ActionResult<string> RenewPassword([FromBody] string? password = null)
|
|
{
|
|
if (string.IsNullOrEmpty(password))
|
|
password = string.Empty;
|
|
else if (!PasswordHashService.HasPasswordInPolicySecurity(password))
|
|
throw new ControllerArgumentException("The password must be at least 8 characters long and contain at least one uppercase letter and one special character.");
|
|
|
|
while (!PasswordHashService.HasPasswordInPolicySecurity(password))
|
|
password = GeneratorKey.GenerateAlphaNumeric(16, includes: "!@#%^");
|
|
|
|
var (salt, hash) = passwordService.HashPassword(password);
|
|
|
|
var admin = user.Value;
|
|
|
|
admin.Salt = salt;
|
|
admin.PasswordHash = hash;
|
|
admin.SaveSetting();
|
|
|
|
return Ok(password);
|
|
}
|
|
}
|