Compare commits
No commits in common. "25b6c7d69148c04d9a37abc4250fdceb6a2bc31a" and "470031af39fb36c4068a36d87b2f46822243e27a" have entirely different histories.
25b6c7d691
...
470031af39
@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Security.Common.Domain;
|
|
||||||
|
|
||||||
public class AuthToken
|
|
||||||
{
|
|
||||||
public required string RefreshToken { get; set; }
|
|
||||||
public required string UserAgent { get; set; }
|
|
||||||
public required string Ip { get; set; }
|
|
||||||
public required string UserId { get; set; }
|
|
||||||
public required string AccessToken { get; set; }
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Security.Common.Dto.Responses;
|
|
||||||
|
|
||||||
public class AuthTokenResponse
|
|
||||||
{
|
|
||||||
public required string AccessToken { get; set; }
|
|
||||||
public required string RefreshToken { get; set; }
|
|
||||||
public DateTime ExpiresIn { get; set; }
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Security.Common.Interfaces;
|
|
||||||
|
|
||||||
public interface IAccessToken
|
|
||||||
{
|
|
||||||
(string Token, DateTime ExpireIn) GenerateToken(string userId);
|
|
||||||
DateTimeOffset GetExpireDateTime(string token);
|
|
||||||
}
|
|
@ -6,11 +6,7 @@ namespace Mirea.Api.Security.Common.Interfaces;
|
|||||||
|
|
||||||
public interface ICacheService
|
public interface ICacheService
|
||||||
{
|
{
|
||||||
Task SetAsync<T>(string key, T value,
|
Task SetAsync<T>(string key, T value, TimeSpan? absoluteExpirationRelativeToNow = null, CancellationToken cancellationToken = default);
|
||||||
TimeSpan? absoluteExpirationRelativeToNow = null,
|
|
||||||
TimeSpan? slidingExpiration = null,
|
|
||||||
CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
|
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
|
||||||
Task RemoveAsync(string key, CancellationToken cancellationToken = default);
|
Task RemoveAsync(string key, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Security.Common.Interfaces;
|
|
||||||
|
|
||||||
public interface IRevokedToken
|
|
||||||
{
|
|
||||||
Task AddTokenToRevokedAsync(string token, DateTimeOffset expiresIn);
|
|
||||||
Task<bool> IsTokenRevokedAsync(string token);
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
using Mirea.Api.Security.Common.Domain;
|
|
||||||
using Mirea.Api.Security.Common.Dto.Requests;
|
|
||||||
using Mirea.Api.Security.Common.Dto.Responses;
|
|
||||||
using Mirea.Api.Security.Common.Interfaces;
|
|
||||||
using System;
|
|
||||||
using System.Security;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Security.Services;
|
|
||||||
|
|
||||||
public class AuthService(ICacheService cache, IAccessToken accessTokenService, IRevokedToken revokedToken)
|
|
||||||
{
|
|
||||||
public TimeSpan Lifetime { private get; init; }
|
|
||||||
|
|
||||||
private static string GenerateRefreshToken() => Guid.NewGuid().ToString().Replace("-", "") +
|
|
||||||
GeneratorKey.GenerateString(32);
|
|
||||||
private (string Token, DateTime ExpireIn) GenerateAccessToken(string userId) =>
|
|
||||||
accessTokenService.GenerateToken(userId);
|
|
||||||
|
|
||||||
private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token";
|
|
||||||
|
|
||||||
private Task SetAuthTokenDataToCache(string fingerprint, AuthToken data, CancellationToken cancellation) =>
|
|
||||||
cache.SetAsync(
|
|
||||||
GetAuthCacheKey(fingerprint),
|
|
||||||
JsonSerializer.SerializeToUtf8Bytes(data),
|
|
||||||
slidingExpiration: Lifetime,
|
|
||||||
cancellationToken: cancellation);
|
|
||||||
|
|
||||||
private Task RevokeAccessToken(string token) =>
|
|
||||||
revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token));
|
|
||||||
|
|
||||||
public async Task<AuthTokenResponse> GenerateAuthTokensAsync(TokenRequest request, string userId, CancellationToken cancellation = default)
|
|
||||||
{
|
|
||||||
var refreshToken = GenerateRefreshToken();
|
|
||||||
var accessToken = GenerateAccessToken(userId);
|
|
||||||
|
|
||||||
var authTokenStruct = new AuthToken
|
|
||||||
{
|
|
||||||
CreatedAt = DateTime.UtcNow,
|
|
||||||
Ip = request.Ip,
|
|
||||||
RefreshToken = refreshToken,
|
|
||||||
UserAgent = request.UserAgent,
|
|
||||||
UserId = userId,
|
|
||||||
AccessToken = accessToken.Token
|
|
||||||
};
|
|
||||||
|
|
||||||
await SetAuthTokenDataToCache(request.Fingerprint, authTokenStruct, cancellation);
|
|
||||||
|
|
||||||
return new AuthTokenResponse
|
|
||||||
{
|
|
||||||
AccessToken = accessToken.Token,
|
|
||||||
ExpiresIn = accessToken.ExpireIn,
|
|
||||||
RefreshToken = authTokenStruct.RefreshToken
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<AuthTokenResponse> GenerateAuthTokensWithPreAuthAsync(TokenRequest request, string preAuthToken,
|
|
||||||
CancellationToken cancellation = default) =>
|
|
||||||
await GenerateAuthTokensAsync(request,
|
|
||||||
await new PreAuthService(cache).MatchToken(request, preAuthToken, cancellation),
|
|
||||||
cancellation);
|
|
||||||
|
|
||||||
public async Task<AuthTokenResponse> RefreshTokenAsync(TokenRequest request, string refreshToken, CancellationToken cancellation = default)
|
|
||||||
{
|
|
||||||
var authToken = await cache.GetAsync<AuthToken>(GetAuthCacheKey(request.Fingerprint), cancellation)
|
|
||||||
?? throw new SecurityException(request.Fingerprint);
|
|
||||||
|
|
||||||
if (authToken.RefreshToken != refreshToken ||
|
|
||||||
authToken.UserAgent != request.UserAgent &&
|
|
||||||
authToken.Ip != request.Ip)
|
|
||||||
{
|
|
||||||
await cache.RemoveAsync(request.Fingerprint, cancellation);
|
|
||||||
await RevokeAccessToken(authToken.AccessToken);
|
|
||||||
|
|
||||||
throw new SecurityException(request.Fingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessToken = GenerateAccessToken(authToken.UserId);
|
|
||||||
await RevokeAccessToken(authToken.AccessToken);
|
|
||||||
|
|
||||||
authToken.AccessToken = accessToken.Token;
|
|
||||||
await SetAuthTokenDataToCache(request.Fingerprint, authToken, cancellation);
|
|
||||||
|
|
||||||
return new AuthTokenResponse
|
|
||||||
{
|
|
||||||
AccessToken = accessToken.Token,
|
|
||||||
ExpiresIn = accessToken.ExpireIn,
|
|
||||||
RefreshToken = GenerateRefreshToken()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task LogoutAsync(string fingerprint, CancellationToken cancellation = default)
|
|
||||||
{
|
|
||||||
var authTokenStruct = await cache.GetAsync<AuthToken>(GetAuthCacheKey(fingerprint), cancellation);
|
|
||||||
if (authTokenStruct == null) return;
|
|
||||||
|
|
||||||
await RevokeAccessToken(authTokenStruct.AccessToken);
|
|
||||||
|
|
||||||
await cache.RemoveAsync(fingerprint, cancellation);
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,8 +35,8 @@ public class PreAuthService(ICacheService cache)
|
|||||||
await cache.SetAsync(
|
await cache.SetAsync(
|
||||||
GetPreAuthCacheKey(request.Fingerprint),
|
GetPreAuthCacheKey(request.Fingerprint),
|
||||||
JsonSerializer.SerializeToUtf8Bytes(preAuthTokenStruct),
|
JsonSerializer.SerializeToUtf8Bytes(preAuthTokenStruct),
|
||||||
absoluteExpirationRelativeToNow: Lifetime,
|
Lifetime,
|
||||||
cancellationToken: cancellation);
|
cancellation);
|
||||||
|
|
||||||
return new PreAuthTokenResponse
|
return new PreAuthTokenResponse
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user