(feature): jwt generator update and dto doc comparison
This commit is contained in:
parent
bbe4000095
commit
e671932669
@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using MaksIT.Core.Security;
|
using MaksIT.Core.Security.JWT;
|
||||||
|
|
||||||
namespace MaksIT.Core.Tests.Security {
|
namespace MaksIT.Core.Tests.Security {
|
||||||
public class JwtGeneratorTests {
|
public class JwtGeneratorTests {
|
||||||
|
|||||||
@ -2,4 +2,20 @@
|
|||||||
|
|
||||||
public abstract class DtoDocumentBase<T> : DtoObjectBase {
|
public abstract class DtoDocumentBase<T> : DtoObjectBase {
|
||||||
public required T Id { get; set; }
|
public required T Id { get; set; }
|
||||||
|
|
||||||
|
// Override Equals to compare based on Id
|
||||||
|
public override bool Equals(object? obj) {
|
||||||
|
if (obj is DtoDocumentBase<T> other) // Compare with the same base type
|
||||||
|
return EqualityComparer<T>.Default.Equals(Id, other.Id); // Use EqualityComparer for generic type comparison
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override GetHashCode to use Id
|
||||||
|
public override int GetHashCode() {
|
||||||
|
if (Id is null)
|
||||||
|
throw new InvalidOperationException("Id cannot be null when generating hash code.");
|
||||||
|
|
||||||
|
return EqualityComparer<T>.Default.GetHashCode(Id); // Use EqualityComparer for hash code generation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<!-- NuGet package metadata -->
|
<!-- NuGet package metadata -->
|
||||||
<PackageId>MaksIT.Core</PackageId>
|
<PackageId>MaksIT.Core</PackageId>
|
||||||
<Version>1.4.0</Version>
|
<Version>1.4.1</Version>
|
||||||
<Authors>Maksym Sadovnychyy</Authors>
|
<Authors>Maksym Sadovnychyy</Authors>
|
||||||
<Company>MAKS-IT</Company>
|
<Company>MAKS-IT</Company>
|
||||||
<Product>MaksIT.Core</Product>
|
<Product>MaksIT.Core</Product>
|
||||||
|
|||||||
29
src/MaksIT.Core/Security/JWT/JWTTokenClaims.cs
Normal file
29
src/MaksIT.Core/Security/JWT/JWTTokenClaims.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
namespace MaksIT.Core.Security.JWT;
|
||||||
|
|
||||||
|
public class JWTTokenClaims {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the unique identifier for the user.
|
||||||
|
/// </summary>
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the username associated with the user.
|
||||||
|
/// </summary>
|
||||||
|
public string? Username { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of roles associated with the current entity.
|
||||||
|
/// </summary>
|
||||||
|
public List<string>? Roles { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the date and time when the token was issued.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? IssuedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the date and time when the token expires.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? ExpiresAt { get; set; }
|
||||||
|
}
|
||||||
40
src/MaksIT.Core/Security/JWT/JWTTokenGenerateRequest.cs
Normal file
40
src/MaksIT.Core/Security/JWT/JWTTokenGenerateRequest.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
namespace MaksIT.Core.Security.JWT;
|
||||||
|
|
||||||
|
public class JWTTokenGenerateRequest {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Secret key used for signing the JWT token. Must be kept secure and not shared publicly.
|
||||||
|
/// </summary>
|
||||||
|
public required string Secret { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Issuer of the JWT token, typically the application or service that generates the token.
|
||||||
|
/// </summary>
|
||||||
|
public required string Issuer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the audience for the application.
|
||||||
|
/// </summary>
|
||||||
|
public required string Audience { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expiration time in minutes.
|
||||||
|
/// </summary>
|
||||||
|
public double Expiration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the unique identifier for the user.
|
||||||
|
/// </summary>
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the username associated with the user.
|
||||||
|
/// </summary>
|
||||||
|
public string? Username { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of roles associated with the current entity.
|
||||||
|
/// </summary>
|
||||||
|
public List<string>? Roles { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,35 +1,33 @@
|
|||||||
using System.Text;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.Text;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
|
|
||||||
namespace MaksIT.Core.Security;
|
|
||||||
|
|
||||||
public class JWTTokenClaims {
|
namespace MaksIT.Core.Security.JWT;
|
||||||
public string? UserId { get; set; }
|
|
||||||
public string? Username { get; set; }
|
|
||||||
public List<string>? Roles { get; set; }
|
|
||||||
public DateTime? IssuedAt { get; set; }
|
|
||||||
public DateTime? ExpiresAt { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class JWTTokenGenerateRequest {
|
|
||||||
public required string Secret { get; set; }
|
|
||||||
public required string Issuer { get; set; }
|
|
||||||
public required string Audience { get; set; }
|
|
||||||
public double Expiration { get; set; }
|
|
||||||
public string? UserId { get; set; }
|
|
||||||
public string? Username { get; set; }
|
|
||||||
public List<string>? Roles { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class JwtGenerator {
|
public static class JwtGenerator {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to generate a JWT token using the specified request parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">
|
||||||
|
/// The <see cref="JWTTokenGenerateRequest"/> containing the secret, issuer, audience, expiration, user ID, username, and roles for the token.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="tokenData">
|
||||||
|
/// When this method returns <c>true</c>, contains a tuple with the generated JWT string and its associated claims; otherwise, <c>null</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="errorMessage">
|
||||||
|
/// When this method returns <c>false</c>, contains the error message describing the failure; otherwise, <c>null</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if the token was successfully generated; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
public static bool TryGenerateToken(JWTTokenGenerateRequest request, [NotNullWhen(true)] out (string, JWTTokenClaims)? tokenData, [NotNullWhen(false)] out string? errorMessage) {
|
public static bool TryGenerateToken(JWTTokenGenerateRequest request, [NotNullWhen(true)] out (string, JWTTokenClaims)? tokenData, [NotNullWhen(false)] out string? errorMessage) {
|
||||||
try {
|
try {
|
||||||
var secretKey = GetSymmetricSecurityKey(request.Secret);
|
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(request.Secret));
|
||||||
var credentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
|
var credentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
|
||||||
|
|
||||||
var issuedAt = DateTime.UtcNow;
|
var issuedAt = DateTime.UtcNow;
|
||||||
@ -84,6 +82,17 @@ public static class JwtGenerator {
|
|||||||
|
|
||||||
public static string GenerateSecret(int keySize = 32) => Convert.ToBase64String(GetRandomBytes(keySize));
|
public static string GenerateSecret(int keySize = 32) => Convert.ToBase64String(GetRandomBytes(keySize));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to validate a JWT token using the provided secret, issuer, audience, and token string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="secret"></param>
|
||||||
|
/// <param name="issuer"></param>
|
||||||
|
/// <param name="audience"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <param name="tokenClaims"></param>
|
||||||
|
/// <param name="errorMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="SecurityTokenException"></exception>
|
||||||
public static bool TryValidateToken(
|
public static bool TryValidateToken(
|
||||||
string secret,
|
string secret,
|
||||||
string issuer,
|
string issuer,
|
||||||
@ -136,8 +145,8 @@ public static class JwtGenerator {
|
|||||||
var issuedAtClaim = principal.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Iat)?.Value;
|
var issuedAtClaim = principal.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Iat)?.Value;
|
||||||
var expiresAtClaim = principal.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Exp)?.Value;
|
var expiresAtClaim = principal.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Exp)?.Value;
|
||||||
|
|
||||||
DateTime? issuedAt = issuedAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(issuedAtClaim)).UtcDateTime : (DateTime?)null;
|
DateTime? issuedAt = issuedAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(issuedAtClaim)).UtcDateTime : null;
|
||||||
DateTime? expiresAt = expiresAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiresAtClaim)).UtcDateTime : (DateTime?)null;
|
DateTime? expiresAt = expiresAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiresAtClaim)).UtcDateTime : null;
|
||||||
|
|
||||||
return new JWTTokenClaims {
|
return new JWTTokenClaims {
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
@ -148,9 +157,6 @@ public static class JwtGenerator {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private helper method to get a symmetric security key
|
|
||||||
private static SymmetricSecurityKey GetSymmetricSecurityKey(string secret) => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
|
|
||||||
|
|
||||||
// Private helper method for generating random bytes
|
// Private helper method for generating random bytes
|
||||||
private static byte[] GetRandomBytes(int size) {
|
private static byte[] GetRandomBytes(int size) {
|
||||||
var bytes = new byte[size];
|
var bytes = new byte[size];
|
||||||
Loading…
Reference in New Issue
Block a user