search and ai stabilization work, localization stablized.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Auth.Security.Dpop;
|
||||
|
||||
@@ -60,7 +61,7 @@ internal static class DpopNonceUtilities
|
||||
{
|
||||
if (value.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Value must not be empty after trimming.");
|
||||
throw new ArgumentException(_t("common.validation.empty_after_trim", "Value"));
|
||||
}
|
||||
|
||||
value.AsSpan().CopyTo(span[index..]);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text.Json;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Auth.Security.Dpop;
|
||||
|
||||
@@ -19,7 +20,7 @@ public sealed partial class DpopProofValidator
|
||||
_logger?.LogWarning("DPoP header decode failure: {Error}", headerError);
|
||||
failure = DpopValidationResult.Failure(
|
||||
"invalid_header",
|
||||
headerError ?? "Unable to decode header.");
|
||||
headerError ?? _t("auth.dpop.header_decode_failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,27 +28,27 @@ public sealed partial class DpopProofValidator
|
||||
typElement.ValueKind != JsonValueKind.String ||
|
||||
!string.Equals(typElement.GetString(), ProofType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_header", "DPoP proof missing typ=dpop+jwt header.");
|
||||
failure = DpopValidationResult.Failure("invalid_header", _t("auth.dpop.header_missing_typ"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!headerElement.TryGetProperty("alg", out var algElement) ||
|
||||
algElement.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_header", "DPoP proof missing alg header.");
|
||||
failure = DpopValidationResult.Failure("invalid_header", _t("auth.dpop.header_missing_alg"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var algorithm = algElement.GetString()?.Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrEmpty(algorithm) || !_options.NormalizedAlgorithms.Contains(algorithm))
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_header", "Unsupported DPoP algorithm.");
|
||||
failure = DpopValidationResult.Failure("invalid_header", _t("auth.dpop.header_unsupported_alg"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!headerElement.TryGetProperty("jwk", out var jwkElement))
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_header", "DPoP proof missing jwk header.");
|
||||
failure = DpopValidationResult.Failure("invalid_header", _t("auth.dpop.header_missing_jwk"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ public sealed partial class DpopProofValidator
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Failed to parse DPoP jwk header.");
|
||||
failure = DpopValidationResult.Failure("invalid_header", "DPoP proof jwk header is invalid.");
|
||||
failure = DpopValidationResult.Failure("invalid_header", _t("auth.dpop.header_invalid_jwk"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text.Json;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Auth.Security.Dpop;
|
||||
|
||||
@@ -27,13 +28,13 @@ public sealed partial class DpopProofValidator
|
||||
var segments = token.Split('.');
|
||||
if (segments.Length != 3)
|
||||
{
|
||||
error = "Token must contain three segments.";
|
||||
error = _t("auth.dpop.token_three_segments");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (segmentIndex < 0 || segmentIndex > 2)
|
||||
{
|
||||
error = "Segment index out of range.";
|
||||
error = _t("auth.dpop.segment_out_of_range");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Auth.Security.Dpop;
|
||||
|
||||
@@ -18,14 +19,14 @@ public sealed partial class DpopProofValidator
|
||||
if (!payloadElement.TryGetProperty("nonce", out var nonceElement) ||
|
||||
nonceElement.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_token", "DPoP proof missing nonce claim.");
|
||||
failure = DpopValidationResult.Failure("invalid_token", _t("auth.dpop.nonce_missing"));
|
||||
return false;
|
||||
}
|
||||
|
||||
actualNonce = nonceElement.GetString();
|
||||
if (!string.Equals(actualNonce, expectedNonce, StringComparison.Ordinal))
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_token", "DPoP nonce mismatch.");
|
||||
failure = DpopValidationResult.Failure("invalid_token", _t("auth.dpop.nonce_mismatch"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Auth.Security.Dpop;
|
||||
|
||||
@@ -21,49 +22,49 @@ public sealed partial class DpopProofValidator
|
||||
_logger?.LogWarning("DPoP payload decode failure: {Error}", payloadError);
|
||||
failure = DpopValidationResult.Failure(
|
||||
"invalid_payload",
|
||||
payloadError ?? "Unable to decode payload.");
|
||||
payloadError ?? _t("auth.dpop.payload_decode_failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!payloadElement.TryGetProperty("htm", out var htmElement) ||
|
||||
htmElement.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_payload", "DPoP proof missing htm claim.");
|
||||
failure = DpopValidationResult.Failure("invalid_payload", _t("auth.dpop.payload_missing_htm"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var method = httpMethod.Trim().ToUpperInvariant();
|
||||
if (!string.Equals(htmElement.GetString(), method, StringComparison.Ordinal))
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_payload", "DPoP htm does not match request method.");
|
||||
failure = DpopValidationResult.Failure("invalid_payload", _t("auth.dpop.payload_htm_mismatch"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!payloadElement.TryGetProperty("htu", out var htuElement) ||
|
||||
htuElement.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_payload", "DPoP proof missing htu claim.");
|
||||
failure = DpopValidationResult.Failure("invalid_payload", _t("auth.dpop.payload_missing_htu"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var normalizedHtu = NormalizeHtu(httpUri);
|
||||
if (!string.Equals(htuElement.GetString(), normalizedHtu, StringComparison.Ordinal))
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_payload", "DPoP htu does not match request URI.");
|
||||
failure = DpopValidationResult.Failure("invalid_payload", _t("auth.dpop.payload_htu_mismatch"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!payloadElement.TryGetProperty("iat", out var iatElement) ||
|
||||
iatElement.ValueKind is not JsonValueKind.Number)
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_payload", "DPoP proof missing iat claim.");
|
||||
failure = DpopValidationResult.Failure("invalid_payload", _t("auth.dpop.payload_missing_iat"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!payloadElement.TryGetProperty("jti", out var jtiElement) ||
|
||||
jtiElement.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_payload", "DPoP proof missing jti claim.");
|
||||
failure = DpopValidationResult.Failure("invalid_payload", _t("auth.dpop.payload_missing_jti"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -74,7 +75,7 @@ public sealed partial class DpopProofValidator
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
failure = DpopValidationResult.Failure("invalid_payload", "DPoP proof iat claim is not a valid number.");
|
||||
failure = DpopValidationResult.Failure("invalid_payload", _t("auth.dpop.payload_iat_invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Auth.Security.Dpop;
|
||||
|
||||
public sealed partial class DpopProofValidator
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Auth.Security.Dpop;
|
||||
|
||||
@@ -24,7 +25,7 @@ public sealed partial class DpopProofValidator : IDpopProofValidator
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var snapshot = options.Value ?? throw new InvalidOperationException("DPoP options must be provided.");
|
||||
var snapshot = options.Value ?? throw new InvalidOperationException(_t("auth.dpop.options_required"));
|
||||
_options = snapshot.Snapshot();
|
||||
_replayCache = replayCache ?? NullReplayCache.Instance;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Auth.Security.Dpop;
|
||||
|
||||
@@ -66,22 +67,22 @@ public sealed class DpopValidationOptions
|
||||
{
|
||||
if (ProofLifetime <= TimeSpan.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("DPoP proof lifetime must be greater than zero.");
|
||||
throw new InvalidOperationException(_t("auth.dpop.proof_lifetime_invalid"));
|
||||
}
|
||||
|
||||
if (AllowedClockSkew < TimeSpan.Zero || AllowedClockSkew > TimeSpan.FromMinutes(5))
|
||||
{
|
||||
throw new InvalidOperationException("DPoP allowed clock skew must be between 0 seconds and 5 minutes.");
|
||||
throw new InvalidOperationException(_t("auth.dpop.clock_skew_invalid"));
|
||||
}
|
||||
|
||||
if (ReplayWindow < TimeSpan.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("DPoP replay window must be greater than or equal to zero.");
|
||||
throw new InvalidOperationException(_t("auth.dpop.replay_window_invalid"));
|
||||
}
|
||||
|
||||
if (_allowedAlgorithms.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("At least one allowed DPoP algorithm must be configured.");
|
||||
throw new InvalidOperationException(_t("auth.dpop.algorithm_required"));
|
||||
}
|
||||
|
||||
NormalizedAlgorithms = _allowedAlgorithms
|
||||
@@ -91,7 +92,7 @@ public sealed class DpopValidationOptions
|
||||
|
||||
if (NormalizedAlgorithms.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Allowed DPoP algorithms cannot be empty after normalization.");
|
||||
throw new InvalidOperationException(_t("auth.dpop.algorithm_empty_after_normalization"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,5 +42,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Router\__Libraries\StellaOps.Messaging\StellaOps.Messaging.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Localization\StellaOps.Localization.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user