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 }; }