using ScadaLink.Commons.Entities.InboundApi;
using ScadaLink.Commons.Interfaces.Repositories;
namespace ScadaLink.InboundAPI;
///
/// WP-1: Validates API keys from X-API-Key header.
/// Checks that the key exists, is enabled, and is approved for the requested method.
///
public class ApiKeyValidator
{
private readonly IInboundApiRepository _repository;
public ApiKeyValidator(IInboundApiRepository repository)
{
_repository = repository;
}
///
/// Validates an API key for a given method.
/// Returns (isValid, apiKey, statusCode, errorMessage).
///
public async Task ValidateAsync(
string? apiKeyValue,
string methodName,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(apiKeyValue))
{
return ApiKeyValidationResult.Unauthorized("Missing X-API-Key header");
}
var apiKey = await _repository.GetApiKeyByValueAsync(apiKeyValue, cancellationToken);
if (apiKey == null || !apiKey.IsEnabled)
{
return ApiKeyValidationResult.Unauthorized("Invalid or disabled API key");
}
var method = await _repository.GetMethodByNameAsync(methodName, cancellationToken);
if (method == null)
{
return ApiKeyValidationResult.NotFound($"Method '{methodName}' not found");
}
// Check if this key is approved for the method
var approvedKeys = await _repository.GetApprovedKeysForMethodAsync(method.Id, cancellationToken);
var isApproved = approvedKeys.Any(k => k.Id == apiKey.Id);
if (!isApproved)
{
return ApiKeyValidationResult.Forbidden("API key not approved for this method");
}
return ApiKeyValidationResult.Valid(apiKey, method);
}
}
///
/// Result of API key validation.
///
public class ApiKeyValidationResult
{
public bool IsValid { get; private init; }
public int StatusCode { get; private init; }
public string? ErrorMessage { get; private init; }
public ApiKey? ApiKey { get; private init; }
public ApiMethod? Method { get; private init; }
public static ApiKeyValidationResult Valid(ApiKey apiKey, ApiMethod method) =>
new() { IsValid = true, StatusCode = 200, ApiKey = apiKey, Method = method };
public static ApiKeyValidationResult Unauthorized(string message) =>
new() { IsValid = false, StatusCode = 401, ErrorMessage = message };
public static ApiKeyValidationResult Forbidden(string message) =>
new() { IsValid = false, StatusCode = 403, ErrorMessage = message };
public static ApiKeyValidationResult NotFound(string message) =>
new() { IsValid = false, StatusCode = 400, ErrorMessage = message };
}