using System.Security.Cryptography; namespace ZB.MOM.WW.MxGateway.Server.Security.Authentication; public sealed class ApiKeyVerifier( IApiKeyParser parser, IApiKeySecretHasher hasher, IApiKeyStore keyStore) : IApiKeyVerifier { /// /// Verifies an API key from an authorization header asynchronously. /// /// Authorization header value. /// Cancellation token. /// Verification result. public async Task VerifyAsync( string? authorizationHeader, CancellationToken cancellationToken) { if (!parser.TryParseAuthorizationHeader(authorizationHeader, out ParsedApiKey? parsedKey) || parsedKey is null) { return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.MissingOrMalformedCredentials); } ApiKeyRecord? storedKey = await keyStore.FindByKeyIdAsync(parsedKey.KeyId, cancellationToken) .ConfigureAwait(false); if (storedKey is null) { return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.KeyNotFound); } if (storedKey.RevokedUtc is not null) { return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.KeyRevoked); } byte[] presentedHash; try { presentedHash = hasher.HashSecret(parsedKey.Secret); } catch (ApiKeyPepperUnavailableException) { return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.PepperUnavailable); } if (!CryptographicOperations.FixedTimeEquals(presentedHash, storedKey.SecretHash)) { return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.SecretMismatch); } await keyStore.MarkKeyUsedAsync(storedKey.KeyId, DateTimeOffset.UtcNow, cancellationToken) .ConfigureAwait(false); return ApiKeyVerificationResult.Success(new ApiKeyIdentity( KeyId: storedKey.KeyId, KeyPrefix: storedKey.KeyPrefix, DisplayName: storedKey.DisplayName, Scopes: storedKey.Scopes, Constraints: storedKey.Constraints)); } }