sec: return the token instead of performing actions with the user
All checks were successful
.NET Test Pipeline / build-and-test (push) Successful in 2m0s
Build and Deploy Docker Container / build-and-deploy (push) Successful in 2m53s

This commit is contained in:
2024-12-26 08:51:22 +03:00
parent dcdd43469b
commit 43edab2912
3 changed files with 104 additions and 91 deletions

View File

@ -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<OAuthService> logger, Dictionary<OAuthProvider, (string ClientId, string Secret)> providers, string secretKey)
public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider, (string ClientId, string Secret)> providers,
ICacheService cache)
{
public required ReadOnlyMemory<byte> SecretKey { private get; init; }
@ -195,21 +195,26 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
public (OAuthProvider Provider, Uri Redirect)[] GetAvailableProviders(string redirectUri) =>
[.. 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<LoginOAuthResult> 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<OAuthService> logger, Dictionary<OAuthProvider
"is not registered as a possible data recipient from state: {State}",
state);
throw new SecurityException("Invalid authorization request. Please try again later.");
result.ErrorMessage = "Invalid authorization request. Please try again later.";
return result;
}
var requestInfo = new RequestContextInfo(context, cookieOptions);
var checksumRequest = GetHmacString(requestInfo);
result.ErrorMessage = "Authorization failed. Please try again later.";
if (checksumRequest != checksum)
{
logger.LogWarning(
@ -231,10 +239,11 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
checksumRequest,
checksum
);
throw new SecurityException("Suspicious activity detected. Please try again.");
return result;
}
OAuthTokenResponse? accessToken = null;
OAuthTokenResponse? accessToken;
try
{
accessToken = await ExchangeCodeForTokensAsync(currentProviderStruct.TokenUrl, redirectUrl, code, providerInfo.ClientId,
@ -243,27 +252,41 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to exchange code for access token with provider {Provider}. State: {State}",
provider,
secretStateData);
payload.Provider,
checksum);
return result;
}
if (accessToken == null)
throw new SecurityException("Unable to complete authorization with the provider. Please try again later.");
return result;
OAuthUser? result = null;
OAuthUser? user;
try
{
result = await GetUserProfileAsync(currentProviderStruct.UserInfoUrl, currentProviderStruct.AuthHeader, accessToken.AccessToken,
provider, cancellation);
user = await GetUserProfileAsync(currentProviderStruct.UserInfoUrl, currentProviderStruct.AuthHeader, accessToken.AccessToken,
payload.Provider, cancellation);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to retrieve user information from provider {Provider}", provider);
logger.LogWarning(ex, "Failed to retrieve user information from provider {Provider}",
payload.Provider);
return result;
}
if (result == null)
throw new SecurityException("Unable to retrieve user information. Please check the details and try again.");
if (user == null)
return result;
return (provider, result);
result.ErrorMessage = null;
result.Success = true;
await cache.SetAsync(
result.Token,
JsonSerializer.SerializeToUtf8Bytes(user),
absoluteExpirationRelativeToNow: TimeSpan.FromMinutes(15),
cancellationToken: cancellation);
return result;
}
}