feat(auth): cut MxGateway API keys over to ZB.MOM.WW.Auth.ApiKeys 0.1.2; keep constraint enforcement+gRPC+CLI on top (Task 1.3)
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
using ZB.MOM.WW.Auth.Abstractions.ApiKeys;
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
||||
using ZB.MOM.WW.MxGateway.Server.Galaxy;
|
||||
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
|
||||
using ZB.MOM.WW.MxGateway.Server.Sessions;
|
||||
|
||||
// The gateway carries its own constraint-bearing identity downstream; the shared library also
|
||||
// defines an ApiKeyIdentity (scopes + opaque constraints JSON), so disambiguate to the gateway type.
|
||||
using ApiKeyIdentity = ZB.MOM.WW.MxGateway.Server.Security.Authentication.ApiKeyIdentity;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Server.Security.Authorization;
|
||||
|
||||
public sealed class ConstraintEnforcer(
|
||||
@@ -126,6 +131,7 @@ public sealed class ConstraintEnforcer(
|
||||
KeyId: identity?.KeyId,
|
||||
EventType: "constraint-denied",
|
||||
RemoteAddress: null,
|
||||
CreatedUtc: DateTimeOffset.UtcNow,
|
||||
Details: $"{commandKind}: {target}: {failure.ConstraintName}: {failure.Message}"),
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
+16
-5
@@ -1,9 +1,14 @@
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.Auth.Abstractions.ApiKeys;
|
||||
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
||||
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
|
||||
|
||||
// The handler pushes the gateway's constraint-bearing identity; alias away the shared library's
|
||||
// ApiKeyIdentity so the unqualified name resolves to the gateway type.
|
||||
using ApiKeyIdentity = ZB.MOM.WW.MxGateway.Server.Security.Authentication.ApiKeyIdentity;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Server.Security.Authorization;
|
||||
|
||||
public sealed class GatewayGrpcAuthorizationInterceptor(
|
||||
@@ -57,25 +62,31 @@ public sealed class GatewayGrpcAuthorizationInterceptor(
|
||||
}
|
||||
|
||||
string? authorizationHeader = context.RequestHeaders.GetValue("authorization");
|
||||
ApiKeyVerificationResult verificationResult = await apiKeyVerifier
|
||||
.VerifyAsync(authorizationHeader, context.CancellationToken)
|
||||
|
||||
// The shared verifier owns parse + pepper + lookup + revocation + constant-time compare,
|
||||
// returning a discriminated failure rather than throwing. Every authentication failure maps
|
||||
// to Unauthenticated with an opaque message; the client never learns which stage failed.
|
||||
ApiKeyVerification verification = await apiKeyVerifier
|
||||
.VerifyAsync(authorizationHeader ?? string.Empty, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!verificationResult.Succeeded || verificationResult.Identity is null)
|
||||
if (!verification.Succeeded || verification.Identity is null)
|
||||
{
|
||||
throw new RpcException(new Status(
|
||||
StatusCode.Unauthenticated,
|
||||
"Missing or invalid API key."));
|
||||
}
|
||||
|
||||
ApiKeyIdentity identity = GatewayApiKeyIdentityMapper.ToGatewayIdentity(verification.Identity);
|
||||
|
||||
string requiredScope = scopeResolver.ResolveRequiredScope(request);
|
||||
if (!verificationResult.Identity.Scopes.Contains(requiredScope))
|
||||
if (!identity.Scopes.Contains(requiredScope))
|
||||
{
|
||||
throw new RpcException(new Status(
|
||||
StatusCode.PermissionDenied,
|
||||
$"API key is missing required scope '{requiredScope}'."));
|
||||
}
|
||||
|
||||
return verificationResult.Identity;
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user