From 43edab2912b73834a07d48e6db50fe9d13dbb970 Mon Sep 17 00:00:00 2001
From: Polianin Nikita
Date: Thu, 26 Dec 2024 08:51:22 +0300
Subject: [PATCH] sec: return the token instead of performing actions with the
user
---
Endpoint/Controllers/V1/AuthController.cs | 121 +++++++++------------
Security/Common/Domain/LoginOAuthResult.cs | 11 ++
Security/Services/OAuthService.cs | 63 +++++++----
3 files changed, 104 insertions(+), 91 deletions(-)
create mode 100644 Security/Common/Domain/LoginOAuthResult.cs
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