Skip to content

Commit

Permalink
feat: allow DateTimeOffset to date claims
Browse files Browse the repository at this point in the history
  • Loading branch information
grosch committed Jul 12, 2024
1 parent ef33574 commit c802d3f
Show file tree
Hide file tree
Showing 3 changed files with 368 additions and 317 deletions.
238 changes: 145 additions & 93 deletions src/Paseto/Builder/PasetoBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -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

/// <summary>
/// Adds an issuer claim to the Paseto.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="issuer">The issuer.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder Issuer(this PasetoBuilder builder, string issuer) => builder.AddClaim(RegisteredClaims.Issuer, issuer);

/// <summary>
/// Adds a subject claim to the Paseto.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="subject">The subject.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder Subject(this PasetoBuilder builder, string subject) => builder.AddClaim(RegisteredClaims.Subject, subject);

/// <summary>
/// Adds an audience claim to the Paseto.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="audience">The audience.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder Audience(this PasetoBuilder builder, string audience) => builder.AddClaim(RegisteredClaims.Audience, audience);

/// <summary>
/// Adds an expiration claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="ValidTo" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder Expiration(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.ExpirationTime, time);

/// <summary>
/// Adds a not before claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="ValidFrom" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder NotBefore(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.NotBefore, time);

/// <summary>
/// Adds a not before claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="NotBefore" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder ValidFrom(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.NotBefore, time);

/// <summary>
/// Adds an expiration claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="Expiration" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder ValidTo(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.ExpirationTime, time);

/// <summary>
/// Adds an issued claim to the Paseto.
/// The Utc time will be converted to Unix time.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder IssuedAt(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.IssuedAt, time);

/// <summary>
/// Adds a token identifier or jti claim to the Paseto.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="jti">The token identifier.</param>
/// <returns>Current builder instance</returns>
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

/// <summary>
/// Adds an issuer claim to the Paseto.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="issuer">The issuer.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder Issuer(this PasetoBuilder builder, string issuer) => builder.AddClaim(RegisteredClaims.Issuer, issuer);

/// <summary>
/// Adds a subject claim to the Paseto.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="subject">The subject.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder Subject(this PasetoBuilder builder, string subject) => builder.AddClaim(RegisteredClaims.Subject, subject);

/// <summary>
/// Adds an audience claim to the Paseto.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="audience">The audience.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder Audience(this PasetoBuilder builder, string audience) => builder.AddClaim(RegisteredClaims.Audience, audience);

/// <summary>
/// Adds an expiration claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="ValidTo(PasetoBuilder, DateTime)" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder Expiration(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.ExpirationTime, time);

/// <summary>
/// Adds an expiration claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="ValidTo(PasetoBuilder, DateTimeOffset)" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="offset">The Utc offset.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder Expiration(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.ExpirationTime, offset);

/// <summary>
/// Adds a not before claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="ValidFrom(PasetoBuilder, DateTime)" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder NotBefore(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.NotBefore, time);

/// <summary>
/// Adds a not before claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="ValidFrom(PasetoBuilder, DateTimeOffset)" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="offset">The Utc offset.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder NotBefore(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.NotBefore, offset);

/// <summary>
/// Adds a not before claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="NotBefore(PasetoBuilder, DateTime)" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder ValidFrom(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.NotBefore, time);

/// <summary>
/// Adds a not before claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="NotBefore(PasetoBuilder, DateTimeOffset)" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="offset">The Utc offset.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder ValidFrom(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.NotBefore, offset);

/// <summary>
/// Adds an expiration claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="Expiration(PasetoBuilder, DateTime)" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder ValidTo(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.ExpirationTime, time);

/// <summary>
/// Adds an expiration claim to the Paseto.
/// The Utc time will be converted to Unix time.
///
/// This method behaves the same as <see cref="Expiration(PasetoBuilder, DateTimeOffset)" />.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="offset">The Utc offset.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder ValidTo(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.ExpirationTime, offset);

/// <summary>
/// Adds an issued claim to the Paseto.
/// The Utc time will be converted to Unix time.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="time">The Utc time.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder IssuedAt(this PasetoBuilder builder, DateTime time) => builder.AddClaim(RegisteredClaims.IssuedAt, time);

/// <summary>
/// Adds an issued claim to the Paseto.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="offset">The Utc offset.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder IssuedAt(this PasetoBuilder builder, DateTimeOffset offset) => builder.AddClaim(RegisteredClaims.IssuedAt, offset);

/// <summary>
/// Adds a token identifier or jti claim to the Paseto.
/// </summary>
/// <param name="builder">The PasetoBuilder instance.</param>
/// <param name="jti">The token identifier.</param>
/// <returns>Current builder instance</returns>
public static PasetoBuilder TokenIdentifier(this PasetoBuilder builder, string jti) => builder.AddClaim(RegisteredClaims.TokenIdentifier, jti);
}
145 changes: 70 additions & 75 deletions src/Paseto/Validators/DateValidator.cs
Original file line number Diff line number Diff line change
@@ -1,76 +1,71 @@
namespace Paseto.Validators;

using System;
using System.Text.Json;
using Paseto.Validators.Internal;

/// <summary>
/// The Base Date Validator.
/// </summary>
/// <seealso cref="IPasetoPayloadValidator" />
public abstract class DateValidator : BaseValidator
{
/// <summary>
/// Initializes a new instance of the <see cref="DateValidator"/> class.
/// </summary>
/// <param name="payload">The payload.</param>
public DateValidator(PasetoPayload payload) : base(payload) { }

/// <summary>
/// Validates the input value against the provided optional expected value. Throws an exception if not valid.
/// </summary>
/// <param name="value">The input value to validate.</param>
/// <param name="expected">The optional expected value.</param>
public abstract void ValidateDate(IComparable value, IComparable expected = null);

/// <summary>
/// Validates the payload against the provided optional expected value. Throws an exception if not valid.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <exception cref="PasetoTokenValidationException">
/// Token has expired.
/// </exception>
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);
}

/// <summary>
/// Validates the payload against the provided optional expected value.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <returns><c>true</c> if the specified value is valid; otherwise, <c>false</c>.</returns>
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;

/// <summary>
/// The Base Date Validator.
/// </summary>
/// <seealso cref="IPasetoPayloadValidator" />
public abstract class DateValidator : BaseValidator
{
/// <summary>
/// Initializes a new instance of the <see cref="DateValidator"/> class.
/// </summary>
/// <param name="payload">The payload.</param>
public DateValidator(PasetoPayload payload) : base(payload) { }

/// <summary>
/// Validates the input value against the provided optional expected value. Throws an exception if not valid.
/// </summary>
/// <param name="value">The input value to validate.</param>
/// <param name="expected">The optional expected value.</param>
public abstract void ValidateDate(IComparable value, IComparable expected = null);

/// <summary>
/// Validates the payload against the provided optional expected value. Throws an exception if not valid.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <exception cref="PasetoTokenValidationException">
/// Token has expired.
/// </exception>
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);
}

/// <summary>
/// Validates the payload against the provided optional expected value.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <returns><c>true</c> if the specified value is valid; otherwise, <c>false</c>.</returns>
public override bool IsValid(IComparable expected = null)
{
try
{
Validate(expected);
return true;
}
catch (Exception)
{
return false;
}
}
}
Loading

0 comments on commit c802d3f

Please sign in to comment.