58 lines
1.9 KiB
C#
58 lines
1.9 KiB
C#
using System.Security.Cryptography;
|
|
|
|
namespace MxGateway.Server.Security.Authentication;
|
|
|
|
public sealed class ApiKeyVerifier(
|
|
IApiKeyParser parser,
|
|
IApiKeySecretHasher hasher,
|
|
IApiKeyStore keyStore) : IApiKeyVerifier
|
|
{
|
|
public async Task<ApiKeyVerificationResult> 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));
|
|
}
|
|
}
|