diff --git a/src/MaksIT.Core.Tests/Security/JwtGeneratorTests.cs b/src/MaksIT.Core.Tests/Security/JwtGeneratorTests.cs index 13f0b26..4d2b258 100644 --- a/src/MaksIT.Core.Tests/Security/JwtGeneratorTests.cs +++ b/src/MaksIT.Core.Tests/Security/JwtGeneratorTests.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using Xunit; -using MaksIT.Core.Security; +using MaksIT.Core.Security.JWT; namespace MaksIT.Core.Tests.Security { public class JwtGeneratorTests { diff --git a/src/MaksIT.Core/Abstractions/Dto/DtoDocumentBase.cs b/src/MaksIT.Core/Abstractions/Dto/DtoDocumentBase.cs index ba7c42a..5a21c47 100644 --- a/src/MaksIT.Core/Abstractions/Dto/DtoDocumentBase.cs +++ b/src/MaksIT.Core/Abstractions/Dto/DtoDocumentBase.cs @@ -2,4 +2,20 @@ public abstract class DtoDocumentBase : DtoObjectBase { public required T Id { get; set; } + + // Override Equals to compare based on Id + public override bool Equals(object? obj) { + if (obj is DtoDocumentBase other) // Compare with the same base type + return EqualityComparer.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.Default.GetHashCode(Id); // Use EqualityComparer for hash code generation + } } diff --git a/src/MaksIT.Core/MaksIT.Core.csproj b/src/MaksIT.Core/MaksIT.Core.csproj index 3ead881..3282493 100644 --- a/src/MaksIT.Core/MaksIT.Core.csproj +++ b/src/MaksIT.Core/MaksIT.Core.csproj @@ -8,7 +8,7 @@ MaksIT.Core - 1.4.0 + 1.4.1 Maksym Sadovnychyy MAKS-IT MaksIT.Core diff --git a/src/MaksIT.Core/Security/JWT/JWTTokenClaims.cs b/src/MaksIT.Core/Security/JWT/JWTTokenClaims.cs new file mode 100644 index 0000000..46c0554 --- /dev/null +++ b/src/MaksIT.Core/Security/JWT/JWTTokenClaims.cs @@ -0,0 +1,29 @@ +namespace MaksIT.Core.Security.JWT; + +public class JWTTokenClaims { + + /// + /// Gets or sets the unique identifier for the user. + /// + public string? UserId { get; set; } + + /// + /// Gets or sets the username associated with the user. + /// + public string? Username { get; set; } + + /// + /// Gets or sets the list of roles associated with the current entity. + /// + public List? Roles { get; set; } + + /// + /// Gets or sets the date and time when the token was issued. + /// + public DateTime? IssuedAt { get; set; } + + /// + /// Gets or sets the date and time when the token expires. + /// + public DateTime? ExpiresAt { get; set; } +} \ No newline at end of file diff --git a/src/MaksIT.Core/Security/JWT/JWTTokenGenerateRequest.cs b/src/MaksIT.Core/Security/JWT/JWTTokenGenerateRequest.cs new file mode 100644 index 0000000..dc41a09 --- /dev/null +++ b/src/MaksIT.Core/Security/JWT/JWTTokenGenerateRequest.cs @@ -0,0 +1,40 @@ +namespace MaksIT.Core.Security.JWT; + +public class JWTTokenGenerateRequest { + + /// + /// Secret key used for signing the JWT token. Must be kept secure and not shared publicly. + /// + public required string Secret { get; set; } + + /// + /// Issuer of the JWT token, typically the application or service that generates the token. + /// + public required string Issuer { get; set; } + + /// + /// Gets or sets the audience for the application. + /// + public required string Audience { get; set; } + + /// + /// Expiration time in minutes. + /// + public double Expiration { get; set; } + + /// + /// Gets or sets the unique identifier for the user. + /// + public string? UserId { get; set; } + + /// + /// Gets or sets the username associated with the user. + /// + public string? Username { get; set; } + + /// + /// Gets or sets the list of roles associated with the current entity. + /// + public List? Roles { get; set; } + +} \ No newline at end of file diff --git a/src/MaksIT.Core/Security/JwtGenerator.cs b/src/MaksIT.Core/Security/JWT/JwtGenerator.cs similarity index 75% rename from src/MaksIT.Core/Security/JwtGenerator.cs rename to src/MaksIT.Core/Security/JWT/JwtGenerator.cs index 8364393..380b8f4 100644 --- a/src/MaksIT.Core/Security/JwtGenerator.cs +++ b/src/MaksIT.Core/Security/JWT/JwtGenerator.cs @@ -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.Cryptography; -using System.IdentityModel.Tokens.Jwt; -using System.Diagnostics.CodeAnalysis; -using Microsoft.IdentityModel.Tokens; +using System.Text; -namespace MaksIT.Core.Security; -public class JWTTokenClaims { - public string? UserId { get; set; } - public string? Username { get; set; } - public List? 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? Roles { get; set; } - -} +namespace MaksIT.Core.Security.JWT; public static class JwtGenerator { + + /// + /// Attempts to generate a JWT token using the specified request parameters. + /// + /// + /// The containing the secret, issuer, audience, expiration, user ID, username, and roles for the token. + /// + /// + /// When this method returns true, contains a tuple with the generated JWT string and its associated claims; otherwise, null. + /// + /// + /// When this method returns false, contains the error message describing the failure; otherwise, null. + /// + /// + /// true if the token was successfully generated; otherwise, false. + /// public static bool TryGenerateToken(JWTTokenGenerateRequest request, [NotNullWhen(true)] out (string, JWTTokenClaims)? tokenData, [NotNullWhen(false)] out string? errorMessage) { try { - var secretKey = GetSymmetricSecurityKey(request.Secret); + var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(request.Secret)); var credentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); var issuedAt = DateTime.UtcNow; @@ -84,6 +82,17 @@ public static class JwtGenerator { public static string GenerateSecret(int keySize = 32) => Convert.ToBase64String(GetRandomBytes(keySize)); + /// + /// Attempts to validate a JWT token using the provided secret, issuer, audience, and token string. + /// + /// + /// + /// + /// + /// + /// + /// + /// public static bool TryValidateToken( string secret, string issuer, @@ -136,8 +145,8 @@ public static class JwtGenerator { var issuedAtClaim = principal.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Iat)?.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? expiresAt = expiresAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiresAtClaim)).UtcDateTime : (DateTime?)null; + DateTime? issuedAt = issuedAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(issuedAtClaim)).UtcDateTime : null; + DateTime? expiresAt = expiresAtClaim != null ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiresAtClaim)).UtcDateTime : null; return new JWTTokenClaims { 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 static byte[] GetRandomBytes(int size) { var bytes = new byte[size];