Files
git.stella-ops.org/src/__Libraries/StellaOps.Audit.ReplayToken/ReplayToken.cs

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();
}
}
}