Files
2025-02-26 23:42:19 +01:00

391 lines
15 KiB
C#

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<ApplicationUser> userManager,
ApplicationDbContext context,
ILogger<AuthTokenRepo> logger,
ITopicProducer<LogOutMessageType> logOutTopic,
ITopicProducer<TokenValidationMessageType> tokenValidationTopic,
IConfiguration configuration
) : IAuthTokenRepo
{
public async Task<bool> 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<int> 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<int> 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<int> 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<int> 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<int> 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<string?> 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<int> 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<string?> CreateJwtToken(ApplicationUser user, DateTime? expires, List<Claim>? 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<string?> 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<bool> 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<SigningCredentials> 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);
}
}