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 GenerateAuthTokensAsync(TokenRequest request, string preAuthToken, CancellationToken cancellation = default) { string userId = await new PreAuthService(cache).MatchToken(request, preAuthToken, cancellation); 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 RefreshTokenAsync(TokenRequest request, string refreshToken, CancellationToken cancellation = default) { var authToken = await cache.GetAsync(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() }; } }