using System.IdentityModel.Tokens.Jwt; using System.Security.Cryptography; using Kafka.Lib.Auth; using MassTransit; using Microsoft.IdentityModel.Tokens; using OpenWarehouse.auth.api.Common.CustomIdentifyManager; using OpenWarehouse.auth.api.Common.Exemptions; namespace OpenWarehouse.auth.api.Common.AuthTokenRepo; public class AuthTokenRepo( UserManager userManager, ApplicationDbContext context, ILogger logger, ITopicProducer logOutTopic, ITopicProducer tokenValidationTopic, IConfiguration configuration ) : IAuthTokenRepo { public async Task IsTokenLockAsync(string? token) { if (token.Contains("Bearer")) token = token.Split(" ")[1]; if (token == null) return true; var tokenObj = ParseToken(token); logger.LogInformation("Checking if token with GUID: {guid}, and user id {id} is locked", tokenObj?.Id, tokenObj?.Subject); ApplicationUserTokenRepository? tokenM = await context.ApplicationUserTokenRepositories .Include(q => q.Creator) .Where(q => tokenObj != null && q.GuidToken != null && q.GuidToken.Equals(tokenObj.Id)).FirstOrDefaultAsync(); if (tokenM?.IsLocked ?? true) { var tokenTopic = new TokenValidationMessageType() { Id = tokenM?.Id.ToString(), Mess = "Token is lock", Token = token }; _ = tokenValidationTopic.Produce(tokenTopic); logger.LogDebug(tokenTopic.Text); logger.LogWarning("Token is locked: {Token}", token); return true; } logger.LogInformation("Token with GUID: {guid}, and user id {id} is unlocked", tokenObj?.Id, tokenObj?.Subject); return tokenM?.IsLocked ?? true; } public bool IsTokenLock(string? token) { var tokenObj = ParseToken(token); logger.LogInformation("Checking if token with GUID: {guid}, and user id {id} is locked", tokenObj?.Id, tokenObj?.Subject); ApplicationUserTokenRepository? tokenM = context.ApplicationUserTokenRepositories .Include(q=>q.Creator) .FirstOrDefault(q => q.GuidToken != null && q.GuidToken.Equals(token)); if (tokenM?.IsLocked ?? true) { var tokenTopic = new TokenValidationMessageType() { Id = tokenM?.Id.ToString(), Mess = "Token is lock", Token = token }; tokenValidationTopic.Produce(tokenTopic); logger.LogDebug(tokenTopic.Text); logger.LogWarning("Token is locked: {Token}", token); return true; } logger.LogInformation("Token with GUID: {guid}, and user id {id} is unlocked", tokenObj?.Id, tokenObj?.Subject); return false; } public async Task LockTokenAsync(string? token) { var tokenObj = ParseToken(token); logger.LogInformation("Running LockTokenAsync function for GUID: {Guid}, User ID: {UserId}", tokenObj?.Id, tokenObj?.Subject); ApplicationUserTokenRepository? tokenM = await context.ApplicationUserTokenRepositories .Where(q => tokenObj != null && q.GuidToken != null && q.GuidToken.Equals(tokenObj.Id)) .Include(q => q.Creator).FirstOrDefaultAsync(); if (tokenM != null) { if (tokenM.IsLocked) { logger.LogWarning("Token is already locked for GUID: {Guid}, User ID: {UserId}", tokenObj?.Id, tokenObj?.Subject); return 0; } tokenM.IsLocked = true; if (tokenM.Creator == null) throw new AccountExemptions(false, "Account not found!"); var topic = new LogOutMessageType() { Id = tokenM.Id.ToString(), Tokens = [token], UserName = tokenM.Creator.UserName }; _ = logOutTopic.Produce(topic); logger.LogDebug("Produced logout topic for token: {Token}", topic.Text); logger.LogDebug("Token locked and logout topic produced for GUID: {Guid}, User ID: {UserId}", tokenObj?.Id, tokenObj?.Subject); return await context.SaveChangesAsync(); } return 0; } public async Task LockAllUserTokenAsync(ApplicationUser user) { logger.LogInformation("Locking all tokens for user: {UserId}", user.Id); var topic = new LogOutMessageType() { Id = user.Id, UserName = user.UserName }; int count = 0; foreach (var tokenRepository in context.ApplicationUserTokenRepositories.Where(repository => repository.Creator == user && !repository.IsLocked)) { if (tokenRepository.GuidToken != null) topic.Tokens?.Add(tokenRepository.GuidToken); tokenRepository.IsLocked = true; count++; } logger.LogDebug("Produced logout topic for user: {UserId}, Tokens: {Tokens}", user.Id, topic.Tokens); _ = logOutTopic.Produce(topic); await context.SaveChangesAsync(); logger.LogInformation("Locked {Count} tokens for user: {UserId}", count, user.Id); return count; } public int LockToken(string token) { logger.LogInformation("Locking token: {Token}", token); ApplicationUserTokenRepository? tokenM = context.ApplicationUserTokenRepositories.FirstOrDefault(q => q.GuidToken != null && q.GuidToken.Equals(token)); if (tokenM != null) { tokenM.IsLocked = true; logger.LogInformation("Token locked successfully: {Token}", token); return context.SaveChanges(); } logger.LogWarning("Token not found for locking: {Token}", token); return 0; } public async Task AddTokenToRepAsync(string guid, string creator, DateTime exp, DateTime? created = null) { ApplicationUser? user = await userManager.FindByEmailAsync(creator) ?? await userManager.FindByNameAsync(creator); logger.LogInformation("Adding token to repository for creator: {Creator}", creator); if (user != null) { return await AddTokenToRepAsync(guid, user, exp,created); } logger.LogWarning("User not found for token creation: {Creator}", creator); return 0; } public async Task AddTokenToRepAsync(string guid, ApplicationUser? creator, DateTime? exp, DateTime? created = null) { created ??= DateTime.UtcNow; if (creator == null) return 0; ApplicationUserTokenRepository newToken = new ApplicationUserTokenRepository(guid, creator, created, exp); context.ApplicationUserTokenRepositories.Add(newToken); return await context.SaveChangesAsync(); } public async Task DeleteAllUserTokenAsync(ApplicationUser user) { logger.LogInformation("Deleting all tokens for user: {UserId}", user.Id); var tokens = await context.ApplicationUserTokenRepositories .Where(q => q.Creator == user) .Select(q => q.GuidToken) .ToListAsync(); var tokensInt = await context.ApplicationUserTokenRepositories .Where(q => q.Creator == user).ExecuteDeleteAsync(); var topic = new LogOutMessageType() { UserName = user.UserName ?? "User not found", Id = user.Id, Tokens = tokens }; await logOutTopic.Produce(topic); logger.LogInformation("Deleted {Count} tokens for user: {UserId}", tokensInt, user.Id); return tokensInt; } public async Task GetLastUserToken(ApplicationUser? user) { logger.LogInformation("Retrieving last token for user: {UserId}", user?.Id); return await context.ApplicationUserTokenRepositories.Where(repository => repository.Creator == user && repository.ExpireTime < DateTime.UtcNow).Select(repository => repository.GuidToken).FirstOrDefaultAsync(); } public async Task DeleteInactiveToken() { logger.LogInformation("Deleting inactive tokens"); var deletedToken = await context.ApplicationUserTokenRepositories.Where(repository => repository.ExpireTime < DateTime.UtcNow.AddHours(-2)).ExecuteDeleteAsync(); logger.LogInformation("Deleted {Count} inactive tokens", deletedToken); return deletedToken; } public async Task CreateJwtToken(ApplicationUser user, DateTime? expires, List? claims) { logger.LogInformation("Generating JWT token for user: {UserId}", user.Id); var tokenHandler = new JwtSecurityTokenHandler(); var rsaSecurityKey = await GetSigningCredentials(); var audience = configuration["Jwt:Audience"] ?? string.Empty; var issuer = configuration["Jwt:Issuer"] ?? string.Empty; if (user is { UserName: not null, Email: not null }) { var claimList = await userManager.GetClaimsAsync(user); var claim = claimList.FirstOrDefault(claim1 => claim1.Type == AccountTypeFunction.AccountTypeType); var guid = Guid.NewGuid().ToString(); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(JwtRegisteredClaimNames.Jti, guid), new Claim(JwtRegisteredClaimNames.Sub, user.Id), new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.Email, user.Email), claim ?? AccountType.Uknow.Claim(), }), Expires = expires, Audience = audience, Issuer = issuer, SigningCredentials = rsaSecurityKey, }; var token = tokenHandler.CreateToken(tokenDescriptor); string? tokenString = tokenHandler.WriteToken(token); await AddTokenToRepAsync(guid, user, DateTime.UtcNow, tokenDescriptor.Expires); logger.LogInformation("JWT token generated successfully for user: {UserId}", user.Id); return tokenString; } logger.LogWarning("Failed to generate JWT token due to missing user information."); return ""; } public async Task RegenerateJwtToken(string? token, DateTime? expires) { var tokenHandler = new JwtSecurityTokenHandler(); var tokenObj = tokenHandler.ReadJwtToken(token); var claims = tokenObj.Claims.ToList(); claims.RemoveAll(c => c.Type == JwtRegisteredClaimNames.Jti || c.Type == JwtRegisteredClaimNames.Iat || c.Type == JwtRegisteredClaimNames.Nbf || c.Type == "unique_name" || c.Type == "email"); var newJti = Guid.NewGuid().ToString(); claims.Add(new Claim(JwtRegisteredClaimNames.Jti, newJti)); var rsaSecurityKey = await GetSigningCredentials(); var userId = claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Sub)?.Value; if (string.IsNullOrEmpty(userId)) { logger.LogWarning("Cannot regenerate token: user ID not found in token claims."); return ""; } var user = await userManager.FindByIdAsync(userId); if (user == null) { logger.LogWarning("Cannot regenerate token: user not found."); return ""; } if (!string.IsNullOrEmpty(user.UserName) && !string.IsNullOrEmpty(user.Email)) { claims.Add(new Claim(ClaimTypes.Name, user.UserName)); claims.Add(new Claim(ClaimTypes.Email, user.Email)); } else { return null; } await LockTokenAsync(token); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = expires, SigningCredentials = rsaSecurityKey, }; var newToken = tokenHandler.CreateToken(tokenDescriptor); string? tokenString = tokenHandler.WriteToken(newToken); await AddTokenToRepAsync(newJti, user, DateTime.UtcNow, tokenDescriptor.Expires); return tokenString; } public async Task TokenIsLock(string? token = null, string? guid = null) { if (guid == null && token != null) { guid = ParseToken(token).Id; } if (guid == null) { logger.LogWarning("Token fot GUID: {guid} is invalid.", guid); return true; } var result = await context.ApplicationUserTokenRepositories .Where(repository => repository.GuidToken != null && repository.GuidToken.Equals(guid)) .Select(repository => repository.IsLocked).FirstOrDefaultAsync(); return result; } private JwtSecurityToken ParseToken(string? token) { logger.LogTrace("Parse token"); var tokenHandler = new JwtSecurityTokenHandler(); var tokenObj = tokenHandler.ReadJwtToken(token); logger.LogDebug("Parsed token with GUID: {guid} and userID: {id}", tokenObj.Id, tokenObj.Subject); return tokenObj; } private async Task GetSigningCredentials() { var contentRoot = configuration["contentRoot"] ?? throw new NullReferenceException(); var privateKeyPath = Path.Combine(contentRoot, "private_key_pkcs8.pem"); var rsa = RSA.Create(); rsa.ImportFromPem(await File.ReadAllTextAsync(privateKeyPath)); var key = new RsaSecurityKey(rsa) { KeyId = "1" }; return new SigningCredentials(key , SecurityAlgorithms.RsaSha256); } }