2024-06-21 21:52:21 +03:00
|
|
|
|
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;
|
2024-08-24 04:30:31 +03:00
|
|
|
|
using Mirea.Api.Endpoint.Common.Attributes;
|
2024-07-05 01:35:19 +03:00
|
|
|
|
using Mirea.Api.Endpoint.Common.Services;
|
2024-07-04 23:54:17 +03:00
|
|
|
|
using Mirea.Api.Endpoint.Common.Settings;
|
2024-06-21 21:52:21 +03:00
|
|
|
|
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<Admin> 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,
|
2024-07-05 01:59:36 +03:00
|
|
|
|
Path = UrlHelper.GetSubPathWithoutFirstApiName + "api",
|
2024-08-10 23:11:43 +03:00
|
|
|
|
Domain = HttpContext.GetCurrentDomain(),
|
2024-08-24 02:25:29 +03:00
|
|
|
|
HttpOnly = true,
|
2024-08-10 23:11:43 +03:00
|
|
|
|
#if !DEBUG
|
2024-08-24 02:25:29 +03:00
|
|
|
|
Secure = true
|
2024-08-10 23:11:43 +03:00
|
|
|
|
#endif
|
2024-06-21 21:52:21 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Response.Cookies.Append(name, value, cookieOptions);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-24 02:26:11 +03:00
|
|
|
|
private void SetRefreshToken(string value, DateTimeOffset? expires = null)
|
|
|
|
|
{
|
2024-06-21 21:52:21 +03:00
|
|
|
|
SetCookie("refresh_token", value, expires);
|
2024-08-24 02:26:11 +03:00
|
|
|
|
SetCookie("user_key", Fingerprint, expires);
|
|
|
|
|
}
|
2024-06-21 21:52:21 +03:00
|
|
|
|
|
2024-08-24 02:26:11 +03:00
|
|
|
|
private void SetFirstToken(string value, DateTimeOffset? expires = null)
|
|
|
|
|
{
|
2024-06-21 21:52:21 +03:00
|
|
|
|
SetCookie("authentication_token", value, expires);
|
2024-08-24 02:26:11 +03:00
|
|
|
|
SetCookie("user_key", Fingerprint, expires);
|
|
|
|
|
}
|
2024-06-21 21:52:21 +03:00
|
|
|
|
|
|
|
|
|
[ApiExplorerSettings(IgnoreApi = true)]
|
|
|
|
|
public void OnActionExecuting(ActionExecutingContext context)
|
|
|
|
|
{
|
2024-08-24 02:25:29 +03:00
|
|
|
|
Ip = HttpContext.Connection.RemoteIpAddress?.ToString()!;
|
|
|
|
|
UserAgent = Request.Headers.UserAgent.ToString();
|
|
|
|
|
Fingerprint = Request.Cookies["user_key"] ?? string.Empty;
|
2024-06-21 21:52:21 +03:00
|
|
|
|
RefreshToken = Request.Cookies["refresh_token"] ?? string.Empty;
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(Fingerprint)) return;
|
|
|
|
|
|
|
|
|
|
Fingerprint = Guid.NewGuid().ToString().Replace("-", "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[ApiExplorerSettings(IgnoreApi = true)]
|
|
|
|
|
public void OnActionExecuted(ActionExecutedContext context) { }
|
|
|
|
|
|
2024-06-28 22:55:18 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handles user authentication by verifying the username/email and password,
|
|
|
|
|
/// then generating and returning an authentication token if successful.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="request">The login request containing the username/email and password.</param>
|
|
|
|
|
/// <returns>A TokenResponse containing the access token and its expiry if successful, otherwise an Unauthorized response.</returns>
|
2024-06-21 21:52:21 +03:00
|
|
|
|
[HttpPost("Login")]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
|
|
|
public async Task<ActionResult<TokenResponse>> 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
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 22:55:18 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Refreshes the authentication token using the existing refresh token.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>A TokenResponse containing the new access token and its expiry if successful, otherwise an Unauthorized response.</returns>
|
2024-06-21 21:52:21 +03:00
|
|
|
|
[HttpGet("ReLogin")]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
|
|
|
public async Task<ActionResult<TokenResponse>> 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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 22:55:18 +03:00
|
|
|
|
/// <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>
|
2024-06-21 21:52:21 +03:00
|
|
|
|
[HttpGet("Logout")]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
|
|
|
[Authorize]
|
|
|
|
|
public async Task<ActionResult> Logout()
|
|
|
|
|
{
|
|
|
|
|
SetRefreshToken("", DateTimeOffset.MinValue);
|
|
|
|
|
SetFirstToken("", DateTimeOffset.MinValue);
|
|
|
|
|
|
|
|
|
|
await auth.LogoutAsync(Fingerprint);
|
|
|
|
|
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 22:55:18 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves the role of the authenticated user.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>The role of the authenticated user.</returns>
|
2024-06-21 21:52:21 +03:00
|
|
|
|
[HttpGet("GetRole")]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
|
|
|
[Authorize]
|
2024-08-24 04:30:31 +03:00
|
|
|
|
[CacheMaxAge(0, 0, 1)]
|
2024-06-21 21:52:21 +03:00
|
|
|
|
public ActionResult<AuthRoles> GetRole() => Ok(AuthRoles.Admin);
|
|
|
|
|
}
|