diff --git a/src/Paseto/Builder/PasetoBuilderExtensions.cs b/src/Paseto/Builder/PasetoBuilderExtensions.cs
index 5c4e5ef..f35573f 100644
--- a/src/Paseto/Builder/PasetoBuilderExtensions.cs
+++ b/src/Paseto/Builder/PasetoBuilderExtensions.cs
@@ -1,93 +1,145 @@
-namespace Paseto.Builder;
-
-using System;
-
-public static class PasetoBuilderExtensions
-{
- //public const string DateTimeISO8601Format = "yyyy-MM-ddTHH:mm:sszzz"; // The default format used by Json.NET is the ISO 8601 standard
-
- ///
- /// Adds an issuer claim to the Paseto.
- ///
- /// The PasetoBuilder instance.
- /// The issuer.
- /// Current builder instance
- public static PasetoBuilder Issuer(this PasetoBuilder builder, string issuer) => builder.AddClaim(RegisteredClaims.Issuer, issuer);
-
- ///
- /// Adds a subject claim to the Paseto.
- ///
- /// The PasetoBuilder instance.
- /// The subject.
- /// Current builder instance
- public static PasetoBuilder Subject(this PasetoBuilder builder, string subject) => builder.AddClaim(RegisteredClaims.Subject, subject);
-
- ///
- /// Adds an audience claim to the Paseto.
- ///
- /// The PasetoBuilder instance.
- /// The audience.
- /// Current builder instance
- public static PasetoBuilder Audience(this PasetoBuilder builder, string audience) => builder.AddClaim(RegisteredClaims.Audience, audience);
-
- ///
- /// Adds an expiration claim to the Paseto.
- /// The Utc time will be converted to Unix time.
- ///
- /// This method behaves the same as .
- ///
- /// The PasetoBuilder instance.
- /// The Utc time.
- /// Current builder instance
- public static PasetoBuilder Expiration(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.ExpirationTime, time);
-
- ///
- /// Adds a not before claim to the Paseto.
- /// The Utc time will be converted to Unix time.
- ///
- /// This method behaves the same as .
- ///
- /// The PasetoBuilder instance.
- /// The Utc time.
- /// Current builder instance
- public static PasetoBuilder NotBefore(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.NotBefore, time);
-
- ///
- /// Adds a not before claim to the Paseto.
- /// The Utc time will be converted to Unix time.
- ///
- /// This method behaves the same as .
- ///
- /// The PasetoBuilder instance.
- /// The Utc time.
- /// Current builder instance
- public static PasetoBuilder ValidFrom(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.NotBefore, time);
-
- ///
- /// Adds an expiration claim to the Paseto.
- /// The Utc time will be converted to Unix time.
- ///
- /// This method behaves the same as .
- ///
- /// The PasetoBuilder instance.
- /// The Utc time.
- /// Current builder instance
- public static PasetoBuilder ValidTo(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.ExpirationTime, time);
-
- ///
- /// Adds an issued claim to the Paseto.
- /// The Utc time will be converted to Unix time.
- ///
- /// The PasetoBuilder instance.
- /// The Utc time.
- /// Current builder instance
- public static PasetoBuilder IssuedAt(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.IssuedAt, time);
-
- ///
- /// Adds a token identifier or jti claim to the Paseto.
- ///
- /// The PasetoBuilder instance.
- /// The token identifier.
- /// Current builder instance
- public static PasetoBuilder TokenIdentifier(this PasetoBuilder builder, string jti) => builder.AddClaim(RegisteredClaims.TokenIdentifier, jti);
-}
+namespace Paseto.Builder;
+
+using System;
+
+public static class PasetoBuilderExtensions
+{
+ //public const string DateTimeISO8601Format = "yyyy-MM-ddTHH:mm:sszzz"; // The default format used by Json.NET is the ISO 8601 standard
+
+ ///
+ /// Adds an issuer claim to the Paseto.
+ ///
+ /// The PasetoBuilder instance.
+ /// The issuer.
+ /// Current builder instance
+ public static PasetoBuilder Issuer(this PasetoBuilder builder, string issuer) => builder.AddClaim(RegisteredClaims.Issuer, issuer);
+
+ ///
+ /// Adds a subject claim to the Paseto.
+ ///
+ /// The PasetoBuilder instance.
+ /// The subject.
+ /// Current builder instance
+ public static PasetoBuilder Subject(this PasetoBuilder builder, string subject) => builder.AddClaim(RegisteredClaims.Subject, subject);
+
+ ///
+ /// Adds an audience claim to the Paseto.
+ ///
+ /// The PasetoBuilder instance.
+ /// The audience.
+ /// Current builder instance
+ public static PasetoBuilder Audience(this PasetoBuilder builder, string audience) => builder.AddClaim(RegisteredClaims.Audience, audience);
+
+ ///
+ /// Adds an expiration claim to the Paseto.
+ /// The Utc time will be converted to Unix time.
+ ///
+ /// This method behaves the same as .
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc time.
+ /// Current builder instance
+ public static PasetoBuilder Expiration(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.ExpirationTime, time);
+
+ ///
+ /// Adds an expiration claim to the Paseto.
+ /// The Utc time will be converted to Unix time.
+ ///
+ /// This method behaves the same as .
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc offset.
+ /// Current builder instance
+ public static PasetoBuilder Expiration(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.ExpirationTime, offset);
+
+ ///
+ /// Adds a not before claim to the Paseto.
+ /// The Utc time will be converted to Unix time.
+ ///
+ /// This method behaves the same as .
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc time.
+ /// Current builder instance
+ public static PasetoBuilder NotBefore(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.NotBefore, time);
+
+ ///
+ /// Adds a not before claim to the Paseto.
+ /// The Utc time will be converted to Unix time.
+ ///
+ /// This method behaves the same as .
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc offset.
+ /// Current builder instance
+ public static PasetoBuilder NotBefore(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.NotBefore, offset);
+
+ ///
+ /// Adds a not before claim to the Paseto.
+ /// The Utc time will be converted to Unix time.
+ ///
+ /// This method behaves the same as .
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc time.
+ /// Current builder instance
+ public static PasetoBuilder ValidFrom(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.NotBefore, time);
+
+ ///
+ /// Adds a not before claim to the Paseto.
+ /// The Utc time will be converted to Unix time.
+ ///
+ /// This method behaves the same as .
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc offset.
+ /// Current builder instance
+ public static PasetoBuilder ValidFrom(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.NotBefore, offset);
+
+ ///
+ /// Adds an expiration claim to the Paseto.
+ /// The Utc time will be converted to Unix time.
+ ///
+ /// This method behaves the same as .
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc time.
+ /// Current builder instance
+ public static PasetoBuilder ValidTo(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.ExpirationTime, time);
+
+ ///
+ /// Adds an expiration claim to the Paseto.
+ /// The Utc time will be converted to Unix time.
+ ///
+ /// This method behaves the same as .
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc offset.
+ /// Current builder instance
+ public static PasetoBuilder ValidTo(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.ExpirationTime, offset);
+
+ ///
+ /// Adds an issued claim to the Paseto.
+ /// The Utc time will be converted to Unix time.
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc time.
+ /// Current builder instance
+ public static PasetoBuilder IssuedAt(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.IssuedAt, time);
+
+ ///
+ /// Adds an issued claim to the Paseto.
+ ///
+ /// The PasetoBuilder instance.
+ /// The Utc offset.
+ /// Current builder instance
+ public static PasetoBuilder IssuedAt(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.IssuedAt, offset);
+
+ ///
+ /// Adds a token identifier or jti claim to the Paseto.
+ ///
+ /// The PasetoBuilder instance.
+ /// The token identifier.
+ /// Current builder instance
+ public static PasetoBuilder TokenIdentifier(this PasetoBuilder builder, string jti) => builder.AddClaim(RegisteredClaims.TokenIdentifier, jti);
+}
diff --git a/src/Paseto/Validators/DateValidator.cs b/src/Paseto/Validators/DateValidator.cs
index 39bd8fd..3317dba 100644
--- a/src/Paseto/Validators/DateValidator.cs
+++ b/src/Paseto/Validators/DateValidator.cs
@@ -1,76 +1,71 @@
-namespace Paseto.Validators;
-
-using System;
-using System.Text.Json;
-using Paseto.Validators.Internal;
-
-///
-/// The Base Date Validator.
-///
-///
-public abstract class DateValidator : BaseValidator
-{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The payload.
- public DateValidator(PasetoPayload payload) : base(payload) { }
-
- ///
- /// Validates the input value against the provided optional expected value. Throws an exception if not valid.
- ///
- /// The input value to validate.
- /// The optional expected value.
- public abstract void ValidateDate(IComparable value, IComparable expected = null);
-
- ///
- /// Validates the payload against the provided optional expected value. Throws an exception if not valid.
- ///
- /// The optional expected value.
- ///
- /// Token has expired.
- ///
- public override void Validate(IComparable expected = null)
- {
- if (!Payload.TryGetValue(ClaimName, out var value))
- throw new PasetoTokenValidationException($"Claim '{ClaimName}' not found");
-
- DateTime exp;
- try
- {
- if (value is JsonElement json)
- value = GetValueFromJsonElement(json);
-
- if (value is string s)
- exp = DateTimeOffset.Parse(s).UtcDateTime;
- else
- exp = Convert.ToDateTime(value);
- }
- catch (Exception)
- {
- throw new PasetoTokenValidationException($"Claim '{ClaimName}' must be a DateTime");
- }
-
- expected ??= DateTime.UtcNow;
-
- ValidateDate(exp, expected);
- }
-
- ///
- /// Validates the payload against the provided optional expected value.
- ///
- /// The optional expected value.
- /// true if the specified value is valid; otherwise, false.
- public override bool IsValid(IComparable expected = null)
- {
- try
- {
- Validate(expected);
- return true;
- }
- catch (Exception)
- {
- return false;
- }
- }
+namespace Paseto.Validators;
+
+using System;
+using System.Text.Json;
+using Paseto.Validators.Internal;
+
+///
+/// The Base Date Validator.
+///
+///
+public abstract class DateValidator : BaseValidator
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The payload.
+ public DateValidator(PasetoPayload payload) : base(payload) { }
+
+ ///
+ /// Validates the input value against the provided optional expected value. Throws an exception if not valid.
+ ///
+ /// The input value to validate.
+ /// The optional expected value.
+ public abstract void ValidateDate(IComparable value, IComparable expected = null);
+
+ ///
+ /// Validates the payload against the provided optional expected value. Throws an exception if not valid.
+ ///
+ /// The optional expected value.
+ ///
+ /// Token has expired.
+ ///
+ public override void Validate(IComparable expected = null)
+ {
+ if (!Payload.TryGetValue(ClaimName, out var value))
+ throw new PasetoTokenValidationException($"Claim '{ClaimName}' not found");
+
+ var exp = value switch
+ {
+ JsonElement { ValueKind: JsonValueKind.String } str when DateTimeOffset.TryParse(str.GetString(), out var dto) => dto.UtcDateTime,
+ DateTimeOffset offset => offset.UtcDateTime,
+ DateTime dt => dt,
+ _ => throw new PasetoTokenValidationException($"Claim '{ClaimName}' must be a DateTime")
+ };
+
+ if (expected is DateTimeOffset o)
+ expected = o.UtcDateTime;
+ else
+ expected ??= DateTime.UtcNow;
+
+ ValidateDate(exp, expected);
+ }
+
+ ///
+ /// Validates the payload against the provided optional expected value.
+ ///
+ /// The optional expected value.
+ /// true if the specified value is valid; otherwise, false.
+ public override bool IsValid(IComparable expected = null)
+ {
+ try
+ {
+ Validate(expected);
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
}
\ No newline at end of file
diff --git a/tests/Paseto.Tests/PasetoValidatorTests.cs b/tests/Paseto.Tests/PasetoValidatorTests.cs
index 8471b56..3ba7883 100644
--- a/tests/Paseto.Tests/PasetoValidatorTests.cs
+++ b/tests/Paseto.Tests/PasetoValidatorTests.cs
@@ -1,149 +1,153 @@
-namespace Paseto.Tests;
-
-using System;
-
-using FluentAssertions;
-using Xunit;
-
-using Paseto.Builder;
-using Paseto.Extensions;
-
-public class PasetoValidatorTests
-{
- private const string HelloPaseto = "Hello Paseto!";
- private const string IssuedBy = "Paragon Initiative Enterprises";
-
- [Fact]
- public void PayloadIssuedAtNextDayValidationFails()
- {
- var iat = new Validators.IssuedAtValidator(new PasetoPayload
- {
- { RegisteredClaims.IssuedAt.GetRegisteredClaimName(), DateTime.UtcNow.AddHours(24) }
- });
-
- Action act = () => iat.Validate(DateTime.UtcNow);
- act.Should().Throw().WithMessage("Token is not yet valid");
- }
-
- [Fact]
- public void PayloadIssuedAtPreviousDayValidationSucceeds()
- {
- var iat = new Validators.IssuedAtValidator(new PasetoPayload
- {
- { RegisteredClaims.IssuedAt.GetRegisteredClaimName(), DateTime.UtcNow.AddHours(-24) }
- });
-
- Action act = () => iat.Validate(DateTime.UtcNow);
- act.Should().NotThrow();
- }
-
- [Fact]
- public void PayloadIssuedAtSameDayValidationSucceeds()
- {
- var now = DateTime.UtcNow;
-
- var iat = new Validators.IssuedAtValidator(new PasetoPayload
- {
- { RegisteredClaims.IssuedAt.GetRegisteredClaimName(), now }
- });
-
- Action act = () => iat.Validate(now);
- act.Should().NotThrow();
- }
-
- [Fact]
- public void PayloadNotBeforeNextDayValidationFails()
- {
- var nbf = new Validators.NotBeforeValidator(new PasetoPayload
- {
- { RegisteredClaims.NotBefore.GetRegisteredClaimName(), DateTime.UtcNow.AddHours(24) }
- });
-
- Action act = () => nbf.Validate(DateTime.UtcNow);
- act.Should().Throw().WithMessage("Token is not yet valid");
- }
-
- [Fact]
- public void PayloadNotBeforeDayValidationSucceeds()
- {
- var nbf = new Validators.NotBeforeValidator(new PasetoPayload
- {
- { RegisteredClaims.NotBefore.GetRegisteredClaimName(), DateTime.UtcNow.AddHours(-24) }
- });
-
- Action act = () => nbf.Validate(DateTime.UtcNow);
- act.Should().NotThrow();
- }
-
- [Fact]
- public void PayloadExpirationTimeYesterdayValidationFails()
- {
- var exp = new Validators.ExpirationTimeValidator(new PasetoPayload
- {
- { RegisteredClaims.ExpirationTime.GetRegisteredClaimName(), DateTime.UtcNow.AddHours(-24) }
- });
-
- Action act = () => exp.Validate(DateTime.UtcNow);
- act.Should().Throw().WithMessage("Token has expired");
- }
-
- [Fact]
- public void PayloadExpirationNextDayTimeValidationSucceeds()
- {
- var exp = new Validators.ExpirationTimeValidator(new PasetoPayload
- {
- { RegisteredClaims.ExpirationTime.GetRegisteredClaimName(), DateTime.UtcNow.AddHours(24) }
- });
-
- Action act = () => exp.Validate(DateTime.UtcNow);
- act.Should().NotThrow();
- }
-
- [Fact]
- public void PayloadEqualValidationNonEqualFails()
- {
- var val = new Validators.EqualValidator(new PasetoPayload
- {
- { RegisteredClaims.Issuer.GetRegisteredClaimName(), IssuedBy }
- }, RegisteredClaims.Issuer.GetRegisteredClaimName());
-
- Action act = () => val.Validate(IssuedBy + ".");
- act.Should().Throw();
- }
-
- [Fact]
- public void PayloadEqualValidationTest()
- {
- var val = new Validators.EqualValidator(new PasetoPayload
- {
- { RegisteredClaims.Issuer.GetRegisteredClaimName(), IssuedBy }
- }, RegisteredClaims.Issuer.GetRegisteredClaimName());
-
- Action act = () => val.Validate(IssuedBy);
- act.Should().NotThrow();
- }
-
- [Fact]
- public void PayloadCustomValidationNonEqualFails()
- {
- var val = new Validators.EqualValidator(new PasetoPayload
- {
- { "example", HelloPaseto }
- }, "example");
-
- Action act = () => val.Validate(HelloPaseto + "!");
- act.Should().Throw();
- }
-
- [Fact]
- public void PayloadCustomValidationTest()
- {
- var val = new Validators.EqualValidator(new PasetoPayload
- {
- { "example", HelloPaseto }
- }, "example");
-
- Action act = () => val.Validate(HelloPaseto);
- act.Should().NotThrow();
- }
-}
+namespace Paseto.Tests;
+
+using System;
+using FluentAssertions;
+using Xunit;
+using Builder;
+using Paseto.Extensions;
+
+public class PasetoValidatorTests
+{
+ private const string HelloPaseto = "Hello Paseto!";
+ private const string IssuedBy = "Paragon Initiative Enterprises";
+
+ [Theory]
+ [MemberData(nameof(FutureTimes))]
+ public void PayloadIssuedAtNextDayValidationFails(IComparable when, IComparable compareTo)
+ {
+ var iat = new Validators.IssuedAtValidator(CreateDateValidatorPayload(RegisteredClaims.IssuedAt, when));
+
+ Action act = () => iat.Validate(compareTo);
+ act.Should().Throw().WithMessage("Token is not yet valid");
+ }
+
+ [Theory]
+ [MemberData(nameof(PastTimes))]
+ public void PayloadIssuedAtPreviousDayValidationSucceeds(IComparable when, IComparable compareTo)
+ {
+ var iat = new Validators.IssuedAtValidator(CreateDateValidatorPayload(RegisteredClaims.IssuedAt, when));
+
+ Action act = () => iat.Validate(compareTo);
+ act.Should().NotThrow();
+ }
+
+ [Theory]
+ [MemberData(nameof(NowTimes))]
+ public void PayloadIssuedAtSameDayValidationSucceeds(IComparable when)
+ {
+ var iat = new Validators.IssuedAtValidator(CreateDateValidatorPayload(RegisteredClaims.IssuedAt, when));
+
+ Action act = () => iat.Validate(when);
+ act.Should().NotThrow();
+ }
+
+ [Theory]
+ [MemberData(nameof(FutureTimes))]
+ public void PayloadNotBeforeNextDayValidationFails(IComparable when, IComparable compareTo)
+ {
+ var nbf = new Validators.NotBeforeValidator(CreateDateValidatorPayload(RegisteredClaims.NotBefore, when));
+
+ Action act = () => nbf.Validate(compareTo);
+ act.Should().Throw().WithMessage("Token is not yet valid");
+ }
+
+ [Theory]
+ [MemberData(nameof(PastTimes))]
+ public void PayloadNotBeforeDayValidationSucceeds(IComparable when, IComparable compareTo)
+ {
+ var nbf = new Validators.NotBeforeValidator(CreateDateValidatorPayload(RegisteredClaims.NotBefore, when));
+
+ Action act = () => nbf.Validate(compareTo);
+ act.Should().NotThrow();
+ }
+
+ [Theory]
+ [MemberData(nameof(PastTimes))]
+ public void PayloadExpirationTimeYesterdayValidationFails(IComparable when, IComparable compareTo)
+ {
+ var exp = new Validators.ExpirationTimeValidator(CreateDateValidatorPayload(RegisteredClaims.ExpirationTime, when));
+ Action act = () => exp.Validate(compareTo);
+ act.Should().Throw().WithMessage("Token has expired");
+ }
+
+ [Theory]
+ [MemberData(nameof(FutureTimes))]
+ public void PayloadExpirationNextDayTimeValidationSucceeds(IComparable when, IComparable compareTo)
+ {
+ var exp = new Validators.ExpirationTimeValidator(CreateDateValidatorPayload(RegisteredClaims.ExpirationTime, when));
+
+ Action act = () => exp.Validate(compareTo);
+ act.Should().NotThrow();
+ }
+
+ [Fact]
+ public void PayloadEqualValidationNonEqualFails()
+ {
+ var val = new Validators.EqualValidator(new PasetoPayload
+ {
+ { RegisteredClaims.Issuer.GetRegisteredClaimName(), IssuedBy }
+ }, RegisteredClaims.Issuer.GetRegisteredClaimName());
+
+ Action act = () => val.Validate(IssuedBy + ".");
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void PayloadEqualValidationTest()
+ {
+ var val = new Validators.EqualValidator(new PasetoPayload
+ {
+ { RegisteredClaims.Issuer.GetRegisteredClaimName(), IssuedBy }
+ }, RegisteredClaims.Issuer.GetRegisteredClaimName());
+
+ Action act = () => val.Validate(IssuedBy);
+ act.Should().NotThrow();
+ }
+
+ [Fact]
+ public void PayloadCustomValidationNonEqualFails()
+ {
+ var val = new Validators.EqualValidator(new PasetoPayload
+ {
+ { "example", HelloPaseto }
+ }, "example");
+
+ Action act = () => val.Validate(HelloPaseto + "!");
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void PayloadCustomValidationTest()
+ {
+ var val = new Validators.EqualValidator(new PasetoPayload
+ {
+ { "example", HelloPaseto }
+ }, "example");
+
+ Action act = () => val.Validate(HelloPaseto);
+ act.Should().NotThrow();
+ }
+
+ public static TheoryData FutureTimes => new()
+ {
+ { DateTime.UtcNow.AddHours(24), DateTime.UtcNow },
+ { DateTimeOffset.UtcNow.AddHours(24), DateTimeOffset.UtcNow }
+ };
+
+ public static TheoryData PastTimes => new()
+ {
+ { DateTime.UtcNow.AddHours(-24), DateTime.UtcNow },
+ { DateTimeOffset.UtcNow.AddHours(-24), DateTimeOffset.UtcNow }
+ };
+
+ public static TheoryData NowTimes = new()
+ {
+ { DateTime.UtcNow },
+ { DateTimeOffset.UtcNow }
+ };
+
+ private static PasetoPayload CreateDateValidatorPayload(RegisteredClaims claim, IComparable when) => new()
+ {
+ { claim.ToDescription(), when }
+ };
+}
\ No newline at end of file