diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index e53a5f2..8c0c7e8 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -33,94 +33,73 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot"; + var callbackUrl = callback?.ToString(); - return $"{title}

{title}

{message}

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

{(!string.IsNullOrEmpty(traceId) ? $"TraceId={traceId}" : string.Empty)}
{script}"; + var script = callback == null ? string.Empty : + $""; + + var blockInfo = "

" + (callback == null ? + "Вернитесь обратно и попробуйте снова позже.

" : + $"Если вы не будете автоматически перенаправлены, нажмите ниже.

" + + $"Перейти вручную"); + + return $"{title}

{title}

{blockInfo}

{message}

TraceId={traceId}
{script}"; } [HttpGet("OAuth2")] [BadRequestResponse] [Produces("text/html")] [MaintenanceModeIgnore] - public async Task OAuth2([FromQuery] string code, [FromQuery] string state) + 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(), + if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(state)) + return Content(GenerateHtmlResponse( + "Ошибка передачи данных!", + "Провайдер OAuth не передал нужных данных.", + null, + traceId, + true), "text/html"); + + var result = await oAuthService.LoginOAuth(HttpContext, GetCookieParams(), HttpContext.GetApiUrl(Url.Action("OAuth2")!), code, state); - } - catch (Exception e) + + string? callbackUrl = null; + + if (result.Callback != null) + callbackUrl = result.Callback + (result.Callback.Query.Length > 0 ? "&" : "?") + + $"result={Uri.EscapeDataString(result.Token)}"; + + string title, message; + + if (!result.Success) { - title = "Произошла ошибка при общении с провайдером OAuth!"; - message = e.Message; - return Content(GenerateHtmlResponse(title, message, null, true, traceId), "text/html"); + if (callbackUrl != null) + callbackUrl += $"&traceId={Uri.EscapeDataString(traceId)}"; + + title = "Ошибка авторизации!"; + message = result.ErrorMessage ?? "Произошла ошибка. Попробуйте ещё раз."; } - - var userEntity = user.Value; - - if (userId != null) + else { - 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"); + title = "Авторизация завершена!"; + message = "Вы будете перенаправлены обратно через несколько секунд."; } - 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"); + return Content(GenerateHtmlResponse( + title, + message, + callbackUrl == null ? null : new Uri(callbackUrl), + traceId, + !result.Success), "text/html"); } /// diff --git a/Security/Common/Domain/LoginOAuthResult.cs b/Security/Common/Domain/LoginOAuthResult.cs new file mode 100644 index 0000000..b3f0b1e --- /dev/null +++ b/Security/Common/Domain/LoginOAuthResult.cs @@ -0,0 +1,11 @@ +using System; + +namespace Mirea.Api.Security.Common.Domain; + +public class LoginOAuthResult +{ + public bool Success { get; set; } + public required string Token { get; set; } + public Uri? Callback { get; set; } + public string? ErrorMessage { get; set; } +} \ No newline at end of file diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index c3ab569..8ba5018 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -10,7 +10,6 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; -using System.Security; using System.Security.Cryptography; using System.Text; using System.Text.Json; @@ -19,7 +18,8 @@ using System.Threading.Tasks; namespace Mirea.Api.Security.Services; -public class OAuthService(ILogger logger, Dictionary providers, string secretKey) +public class OAuthService(ILogger logger, Dictionary providers, + ICacheService cache) { public required ReadOnlyMemory SecretKey { private get; init; } @@ -195,21 +195,26 @@ public class OAuthService(ILogger logger, Dictionary [.. providers.Select(x => (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key)))]; - public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, + public async Task LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) { - + var result = new LoginOAuthResult() + { + Token = GeneratorKey.GenerateBase64(32) + }; var parts = state.Split('_'); if (parts.Length != 2) { - throw new SecurityException("The request data is invalid or malformed."); - + result.ErrorMessage = "The request data is invalid or malformed."; + return result; } var payload = DecryptPayload(parts[0]); var checksum = parts[1]; + result.Callback = new Uri(payload.Callback); + if (!providers.TryGetValue(payload.Provider, out var providerInfo) || !ProviderData.TryGetValue(payload.Provider, out var currentProviderStruct)) { @@ -217,12 +222,15 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary logger, Dictionary