using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Model; using Mirea.Api.Security.Common.Dto.Requests; using Mirea.Api.Security.Services; using System; using System.Security; using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] public class AuthController(IOptionsSnapshot user, AuthService auth, PasswordHashService passwordService) : BaseController, IActionFilter { private string Fingerprint { get; set; } = string.Empty; private string Ip { get; set; } = string.Empty; private string UserAgent { get; set; } = string.Empty; private string RefreshToken { get; set; } = string.Empty; private void SetCookie(string name, string value, DateTimeOffset? expires = null) { var cookieOptions = new CookieOptions { Expires = expires, Path = "/api", Domain = Request.Headers["X-Forwarded-Host"], Secure = true, HttpOnly = true }; Response.Cookies.Append(name, value, cookieOptions); } private void SetRefreshToken(string value, DateTimeOffset? expires = null) => SetCookie("refresh_token", value, expires); private void SetFirstToken(string value, DateTimeOffset? expires = null) => SetCookie("authentication_token", value, expires); [ApiExplorerSettings(IgnoreApi = true)] public void OnActionExecuting(ActionExecutingContext context) { Ip = context.HttpContext.Connection.RemoteIpAddress?.ToString()!; UserAgent = context.HttpContext.Request.Headers.UserAgent.ToString(); Fingerprint = context.HttpContext.Request.Cookies["user_key"] ?? string.Empty; RefreshToken = Request.Cookies["refresh_token"] ?? string.Empty; if (!string.IsNullOrWhiteSpace(Fingerprint)) return; Fingerprint = Guid.NewGuid().ToString().Replace("-", ""); SetCookie("user_key", Fingerprint); } [ApiExplorerSettings(IgnoreApi = true)] public void OnActionExecuted(ActionExecutedContext context) { } [HttpPost("Login")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] 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) || !passwordService.VerifyPassword(request.Password, userEntity.Salt, userEntity.PasswordHash)) return Unauthorized("Invalid username/email or password"); var token = await auth.GenerateAuthTokensAsync(new TokenRequest { Fingerprint = Fingerprint, Ip = Ip, UserAgent = UserAgent }, "1"); SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); return Ok(new TokenResponse { AccessToken = token.AccessToken, ExpiresIn = token.AccessExpiresIn }); } [HttpGet("ReLogin")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task> ReLogin() { if (string.IsNullOrEmpty(RefreshToken)) return Unauthorized(); try { var token = await auth.RefreshTokenAsync( new TokenRequest { Ip = Ip, UserAgent = UserAgent, Fingerprint = Fingerprint }, RefreshToken ); SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); return Ok(new TokenResponse { AccessToken = token.AccessToken, ExpiresIn = token.AccessExpiresIn }); } catch (SecurityException) { return Unauthorized(); } } [HttpGet("Logout")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] public async Task Logout() { SetRefreshToken("", DateTimeOffset.MinValue); SetFirstToken("", DateTimeOffset.MinValue); await auth.LogoutAsync(Fingerprint); return Ok(); } [HttpGet("GetRole")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] public ActionResult GetRole() => Ok(AuthRoles.Admin); }