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 userId, CancellationToken cancellation = default) { var refreshToken = GenerateRefreshToken(); var (token, expireIn) = GenerateAccessToken(userId); var authTokenStruct = new AuthToken { CreatedAt = DateTime.UtcNow, Ip = request.Ip, RefreshToken = refreshToken, UserAgent = request.UserAgent, UserId = userId, AccessToken = token }; await SetAuthTokenDataToCache(request.Fingerprint, authTokenStruct, cancellation); return new AuthTokenResponse { AccessToken = token, AccessExpiresIn = expireIn, RefreshToken = authTokenStruct.RefreshToken, RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime), }; } public async Task GenerateAuthTokensWithPreAuthAsync(TokenRequest request, string preAuthToken, CancellationToken cancellation = default) => await GenerateAuthTokensAsync(request, await new PreAuthService(cache).MatchToken(request, preAuthToken, cancellation), cancellation); 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 (token, expireIn) = GenerateAccessToken(authToken.UserId); await RevokeAccessToken(authToken.AccessToken); authToken.AccessToken = token; await SetAuthTokenDataToCache(request.Fingerprint, authToken, cancellation); return new AuthTokenResponse { AccessToken = token, AccessExpiresIn = expireIn, RefreshToken = GenerateRefreshToken(), RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime) }; } public async Task LogoutAsync(string fingerprint, CancellationToken cancellation = default) { var authTokenStruct = await cache.GetAsync(GetAuthCacheKey(fingerprint), cancellation); if (authTokenStruct == null) return; await RevokeAccessToken(authTokenStruct.AccessToken); await cache.RemoveAsync(fingerprint, cancellation); } }