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 user, AuthService auth, PasswordHashService passwordService) : BaseController { private CookieOptionsParameters GetCookieParams() => new() { Domain = HttpContext.GetCurrentDomain(), Path = UrlHelper.GetSubPathWithoutFirstApiName + "api" }; [HttpPost("Login")] [BadRequestResponse] public async Task> 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> Login([FromQuery] string code) { var tokenResult = await auth.LoginAsync(GetCookieParams(), HttpContext, code); return Ok(tokenResult ? AuthenticationStep.None : AuthenticationStep.TotpRequired); } /// /// Refreshes the authentication token using the existing refresh token. /// /// User's AuthRoles. [HttpGet("ReLogin")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> ReLogin() { await auth.RefreshTokenAsync(GetCookieParams(), HttpContext); return Ok(AuthRoles.Admin); } /// /// Logs the user out by clearing the refresh token and performing any necessary cleanup. /// /// An Ok response if the logout was successful. [HttpGet("Logout")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] public async Task Logout() { await auth.LogoutAsync(GetCookieParams(), HttpContext); return Ok(); } /// /// Retrieves the role of the authenticated user. /// /// The role of the authenticated user. [HttpGet("GetRole")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] public ActionResult GetRole() => Ok(AuthRoles.Admin); [HttpPost("RenewPassword")] [ApiExplorerSettings(IgnoreApi = true)] [Localhost] [BadRequestResponse] public ActionResult 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); } }