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 PreAuthService(ICacheService cache) { public TimeSpan Lifetime { private get; init; } private static string GeneratePreAuthToken() => Guid.NewGuid().ToString().Replace("-", "") + GeneratorKey.GenerateString(16); private static string GetPreAuthCacheKey(string fingerprint) => $"{fingerprint}_pre_auth_token"; public async Task GeneratePreAuthTokenAsync(TokenRequest request, string userId, CancellationToken cancellation = default) { var preAuthToken = GeneratePreAuthToken(); var preAuthTokenStruct = new PreAuthToken { Fingerprint = request.Fingerprint, UserId = userId, UserAgent = request.UserAgent, Token = preAuthToken, Ip = request.Ip }; await cache.SetAsync( GetPreAuthCacheKey(request.Fingerprint), JsonSerializer.SerializeToUtf8Bytes(preAuthTokenStruct), absoluteExpirationRelativeToNow: Lifetime, cancellationToken: cancellation); return new PreAuthTokenResponse { Token = preAuthToken, ExpiresIn = DateTime.UtcNow.Add(Lifetime) }; } public async Task MatchToken(TokenRequest request, string preAuthToken, CancellationToken cancellation = default) { var preAuthTokenStruct = await cache.GetAsync(GetPreAuthCacheKey(request.Fingerprint), cancellation) ?? throw new SecurityException($"The token was not found using fingerprint \"{request.Fingerprint}\""); if (preAuthTokenStruct == null || preAuthTokenStruct.Token != preAuthToken || (preAuthTokenStruct.UserAgent != request.UserAgent && preAuthTokenStruct.Ip != request.Ip)) { throw new SecurityException("It was not possible to verify the authenticity of the token"); } await cache.RemoveAsync(GetPreAuthCacheKey(request.Fingerprint), cancellation); return preAuthTokenStruct.UserId; } }