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.MapperDto; 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.Collections.Generic; using System.Diagnostics; using System.Security.Claims; using System.Threading.Tasks; using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] public class AuthController(IOptionsSnapshot user, IOptionsSnapshot generalConfig, AuthService auth, PasswordHashService passwordService, OAuthService oAuthService) : BaseController { private CookieOptionsParameters GetCookieParams() => new() { Domain = HttpContext.GetCurrentDomain(), Path = UrlHelper.GetSubPathWithoutFirstApiName + "api" }; private static string GenerateHtmlResponse(string title, string message, OAuthProvider? provider, bool isError = false, string? traceId = null) { var messageColor = isError ? "red" : "white"; var script = ""; return $"{title}

{title}

{message}

Это информационная страница. Вы можете закрыть её.

{(!string.IsNullOrEmpty(traceId) ? $"TraceId={traceId}" : string.Empty)}
{script}"; } [HttpGet("OAuth2")] [BadRequestResponse] [Produces("text/html")] [MaintenanceModeIgnore] public async Task OAuth2([FromQuery] string code, [FromQuery] string state) { var userId = HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); string title; string message; OAuthProvider provider; OAuthUser oAuthUser; var traceId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; try { (provider, oAuthUser) = await oAuthService.LoginOAuth(HttpContext, GetCookieParams(), HttpContext.GetApiUrl(Url.Action("OAuth2")!), code, state); } catch (Exception e) { title = "Произошла ошибка при общении с провайдером OAuth!"; message = e.Message; return Content(GenerateHtmlResponse(title, message, null, true, traceId), "text/html"); } var userEntity = user.Value; if (userId != null) { userEntity.OAuthProviders ??= []; if (!userEntity.OAuthProviders.TryAdd(provider, oAuthUser)) { title = "Ошибка связи аккаунта!"; message = "Этот OAuth провайдер уже связан с вашей учетной записью. " + "Пожалуйста, используйте другого провайдера или удалите связь с аккаунтом."; return Content(GenerateHtmlResponse(title, message, provider, true, traceId), "text/html"); } userEntity.SaveSetting(); title = "Учетная запись успешно связана."; message = "Вы успешно связали свою учетную запись с провайдером OAuth. Вы можете продолжить использовать приложение."; return Content(GenerateHtmlResponse(title, message, provider), "text/html"); } if (userEntity.OAuthProviders != null && userEntity.OAuthProviders.TryGetValue(provider, out var userOAuth) && userOAuth.Id == oAuthUser.Id) { await auth.LoginOAuthAsync(GetCookieParams(), HttpContext, new User { Id = 1.ToString(), Username = userEntity.Username, Email = userEntity.Email, PasswordHash = userEntity.PasswordHash, Salt = userEntity.Salt, TwoFactorAuthenticator = userEntity.TwoFactorAuthenticator, SecondFactorToken = userEntity.Secret, OAuthProviders = userEntity.OAuthProviders }); title = "Успешный вход в аккаунт."; message = "Вы успешно вошли в свою учетную запись. Добро пожаловать!"; return Content(GenerateHtmlResponse(title, message, provider), "text/html"); } title = "Вы успешно зарегистрированы."; message = "Процесс завершен. Вы можете закрыть эту страницу."; userEntity.Email = string.IsNullOrEmpty(oAuthUser.Email) ? string.Empty : oAuthUser.Email; userEntity.Username = string.IsNullOrEmpty(oAuthUser.Username) ? string.Empty : oAuthUser.Username; userEntity.OAuthProviders ??= []; userEntity.OAuthProviders.Add(provider, oAuthUser); userEntity.SaveSetting(); return Content(GenerateHtmlResponse(title, message, provider), "text/html"); } /// /// Initiates the OAuth2 authorization process for the selected provider. /// /// /// This method generates a redirect URL for the selected provider and redirects the user to it. /// /// The identifier of the OAuth provider to authorize with. /// A redirect to the OAuth provider's authorization URL. /// Thrown if the specified provider is not valid. [HttpGet("AuthorizeOAuth2")] [MaintenanceModeIgnore] public ActionResult AuthorizeOAuth2([FromQuery] int provider) { if (!Enum.IsDefined(typeof(OAuthProvider), provider)) throw new ControllerArgumentException("There is no selected provider"); return Redirect(oAuthService.GetProviderRedirect(HttpContext, GetCookieParams(), HttpContext.GetApiUrl(Url.Action("OAuth2")!), (OAuthProvider)provider).AbsoluteUri); } /// /// Retrieves a list of available OAuth providers with their corresponding authorization URLs. /// /// /// This allows the client to fetch all possible OAuth options and the URLs required to initiate authorization. /// /// A list of available providers and their redirect URLs. [HttpGet("AvailableProviders")] [MaintenanceModeIgnore] public ActionResult> AvailableProviders() => Ok(oAuthService .GetAvailableProviders(HttpContext.GetApiUrl(Url.Action("AuthorizeOAuth2")!)) .ConvertToDto()); /// /// Logs in a user using their username or email and password. /// /// The login request containing username/email and password. /// A TwoFactorAuthentication token if the login is successful; otherwise, a BadRequest response. [HttpPost("Login")] [BadRequestResponse] public async Task> Login([FromBody] LoginRequest request) { var userEntity = user.Value; var tokenResult = await auth.LoginAsync( GetCookieParams(), new User { Id = 1.ToString(), Username = userEntity.Username, Email = userEntity.Email, PasswordHash = userEntity.PasswordHash, Salt = userEntity.Salt, TwoFactorAuthenticator = userEntity.TwoFactorAuthenticator, SecondFactorToken = userEntity.Secret, OAuthProviders = userEntity.OAuthProviders }, HttpContext, request.Password, request.Username); return Ok(tokenResult.ConvertToDto()); } /// /// Performs two-factor authentication for the user. /// /// The request containing the method and code for two-factor authentication. /// A boolean indicating whether the two-factor authentication was successful. [HttpPost("2FA")] [BadRequestResponse] public async Task> TwoFactorAuth([FromBody] TwoFactorAuthRequest request) { var tokenResult = await auth.LoginAsync(GetCookieParams(), HttpContext, request.Method.ConvertFromDto(), request.Code); return Ok(tokenResult); } /// /// 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")] 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) { var passwordPolicy = generalConfig.Value.PasswordPolicy; var passwordPolicyService = new PasswordPolicyService(passwordPolicy); if (string.IsNullOrEmpty(password)) password = string.Empty; else passwordPolicyService.ValidatePasswordOrThrow(password); while (!passwordPolicyService.TryValidatePassword(password)) password = GeneratorKey.GenerateAlphaNumeric(passwordPolicy.MinimumLength + 2, includes: "!@#%^"); var (salt, hash) = passwordService.HashPassword(password); var admin = user.Value; admin.Salt = salt; admin.PasswordHash = hash; admin.SaveSetting(); return Ok(password); } }