78 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			78 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System.Collections.Immutable;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| 
 | |
| namespace StellaOps.Auth.Security.Dpop;
 | |
| 
 | |
| /// <summary>
 | |
| /// Configures acceptable algorithms and replay windows for DPoP proof validation.
 | |
| /// </summary>
 | |
| public sealed class DpopValidationOptions
 | |
| {
 | |
|     private readonly HashSet<string> allowedAlgorithms = new(StringComparer.Ordinal);
 | |
| 
 | |
|     public DpopValidationOptions()
 | |
|     {
 | |
|         allowedAlgorithms.Add("ES256");
 | |
|         allowedAlgorithms.Add("ES384");
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Maximum age a proof is considered valid relative to <see cref="IssuedAt"/>.
 | |
|     /// </summary>
 | |
|     public TimeSpan ProofLifetime { get; set; } = TimeSpan.FromMinutes(2);
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Allowed clock skew when evaluating <c>iat</c>.
 | |
|     /// </summary>
 | |
|     public TimeSpan AllowedClockSkew { get; set; } = TimeSpan.FromSeconds(30);
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Duration a successfully validated proof is tracked to prevent replay.
 | |
|     /// </summary>
 | |
|     public TimeSpan ReplayWindow { get; set; } = TimeSpan.FromMinutes(5);
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Algorithms (JWA) permitted for DPoP proofs.
 | |
|     /// </summary>
 | |
|     public ISet<string> AllowedAlgorithms => allowedAlgorithms;
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Normalised, upper-case representation of allowed algorithms.
 | |
|     /// </summary>
 | |
|     public IReadOnlySet<string> NormalizedAlgorithms { get; private set; } = ImmutableHashSet<string>.Empty;
 | |
| 
 | |
|     public void Validate()
 | |
|     {
 | |
|         if (ProofLifetime <= TimeSpan.Zero)
 | |
|         {
 | |
|             throw new InvalidOperationException("DPoP proof lifetime must be greater than zero.");
 | |
|         }
 | |
| 
 | |
|         if (AllowedClockSkew < TimeSpan.Zero || AllowedClockSkew > TimeSpan.FromMinutes(5))
 | |
|         {
 | |
|             throw new InvalidOperationException("DPoP allowed clock skew must be between 0 seconds and 5 minutes.");
 | |
|         }
 | |
| 
 | |
|         if (ReplayWindow < TimeSpan.Zero)
 | |
|         {
 | |
|             throw new InvalidOperationException("DPoP replay window must be greater than or equal to zero.");
 | |
|         }
 | |
| 
 | |
|         if (allowedAlgorithms.Count == 0)
 | |
|         {
 | |
|             throw new InvalidOperationException("At least one allowed DPoP algorithm must be configured.");
 | |
|         }
 | |
| 
 | |
|         NormalizedAlgorithms = allowedAlgorithms
 | |
|             .Select(static algorithm => algorithm.Trim().ToUpperInvariant())
 | |
|             .Where(static algorithm => algorithm.Length > 0)
 | |
|             .ToImmutableHashSet(StringComparer.Ordinal);
 | |
| 
 | |
|         if (NormalizedAlgorithms.Count == 0)
 | |
|         {
 | |
|             throw new InvalidOperationException("Allowed DPoP algorithms cannot be empty after normalization.");
 | |
|         }
 | |
|     }
 | |
| }
 |