diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs new file mode 100644 index 0000000..86b6fd7 --- /dev/null +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -0,0 +1,140 @@ +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); +}