first commit
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user