90 lines
2.8 KiB
C#
90 lines
2.8 KiB
C#
using static StellaOps.Localization.T;
|
|
|
|
namespace StellaOps.Audit.ReplayToken;
|
|
|
|
/// <summary>
|
|
/// A deterministic, content-addressable replay token with optional expiration.
|
|
/// </summary>
|
|
public sealed partial class ReplayToken : IEquatable<ReplayToken>
|
|
{
|
|
public const string Scheme = "replay";
|
|
public const string DefaultAlgorithm = "SHA-256";
|
|
public const string DefaultVersion = "1.0";
|
|
|
|
/// <summary>
|
|
/// Version 2.0 includes expiration support.
|
|
/// </summary>
|
|
public const string VersionWithExpiration = "2.0";
|
|
|
|
/// <summary>
|
|
/// Default expiration duration (1 hour).
|
|
/// </summary>
|
|
public static readonly TimeSpan DefaultExpiration = TimeSpan.FromHours(1);
|
|
|
|
/// <summary>
|
|
/// The token value (SHA-256 hash in hex).
|
|
/// </summary>
|
|
public string Value { get; }
|
|
|
|
/// <summary>
|
|
/// Algorithm used for hashing.
|
|
/// </summary>
|
|
public string Algorithm { get; }
|
|
|
|
/// <summary>
|
|
/// Version of the token generation algorithm.
|
|
/// </summary>
|
|
public string Version { get; }
|
|
|
|
/// <summary>
|
|
/// Timestamp when token was generated.
|
|
/// </summary>
|
|
public DateTimeOffset GeneratedAt { get; }
|
|
|
|
/// <summary>
|
|
/// Timestamp when token expires. Null means no expiration (v1.0 behavior).
|
|
/// </summary>
|
|
public DateTimeOffset? ExpiresAt { get; }
|
|
|
|
/// <summary>
|
|
/// Canonical representation for storage.
|
|
/// For v2.0+, includes expiration timestamp.
|
|
/// </summary>
|
|
public string Canonical => ExpiresAt.HasValue
|
|
? $"{Scheme}:v{Version}:{Algorithm}:{Value}:{ExpiresAt.Value.ToUnixTimeSeconds()}"
|
|
: $"{Scheme}:v{Version}:{Algorithm}:{Value}";
|
|
|
|
/// <summary>
|
|
/// Creates a replay token without expiration (v1.0 compatibility).
|
|
/// </summary>
|
|
public ReplayToken(string value, DateTimeOffset generatedAt, string? algorithm = null, string? version = null)
|
|
: this(value, generatedAt, null, algorithm, version)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a replay token with optional expiration.
|
|
/// </summary>
|
|
public ReplayToken(string value, DateTimeOffset generatedAt, DateTimeOffset? expiresAt, string? algorithm = null, string? version = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
throw new ArgumentException(_t("common.audit.replay_token_value_empty"), nameof(value));
|
|
}
|
|
|
|
Value = value.Trim();
|
|
GeneratedAt = generatedAt;
|
|
ExpiresAt = expiresAt;
|
|
Algorithm = string.IsNullOrWhiteSpace(algorithm) ? DefaultAlgorithm : algorithm.Trim();
|
|
|
|
if (expiresAt.HasValue && string.IsNullOrWhiteSpace(version))
|
|
{
|
|
Version = VersionWithExpiration;
|
|
}
|
|
else
|
|
{
|
|
Version = string.IsNullOrWhiteSpace(version) ? DefaultVersion : version.Trim();
|
|
}
|
|
}
|
|
}
|