From 78bed0fbb1c1b3151fde34c6bc85d268a2626c38 Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Mon, 30 Mar 2026 11:49:08 -0300 Subject: [PATCH] feat: RapidAPI --- .../DockerSecretsConfigurationProvider.cs | 3 +++ Filters/ApiKeyAuthorizeAttribute.cs | 25 +++++++++++++++++-- appsettings.json | 3 +++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Configuration/DockerSecretsConfigurationProvider.cs b/Configuration/DockerSecretsConfigurationProvider.cs index 64e9b80..d14b86c 100644 --- a/Configuration/DockerSecretsConfigurationProvider.cs +++ b/Configuration/DockerSecretsConfigurationProvider.cs @@ -100,6 +100,9 @@ namespace QRRapidoApp.Configuration // OAuth - Microsoft ["microsoft_client_id"] = "Authentication:Microsoft:ClientId", ["microsoft_client_secret"] = "Authentication:Microsoft:ClientSecret", + + // RapidAPI + ["rapidapi_proxy_secret"] = "RapidApi:ProxySecret", } }; diff --git a/Filters/ApiKeyAuthorizeAttribute.cs b/Filters/ApiKeyAuthorizeAttribute.cs index ed3899b..90c4a58 100644 --- a/Filters/ApiKeyAuthorizeAttribute.cs +++ b/Filters/ApiKeyAuthorizeAttribute.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Configuration; using QRRapidoApp.Models; using QRRapidoApp.Services; @@ -8,7 +9,9 @@ namespace QRRapidoApp.Filters [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class ApiKeyAuthorizeAttribute : Attribute, IAsyncActionFilter { - private const string ApiKeyHeaderName = "X-API-Key"; + private const string ApiKeyHeaderName = "X-API-Key"; + private const string RapidApiSecretHeaderName = "X-RapidAPI-Proxy-Secret"; + private const string RapidApiUserHeaderName = "X-RapidAPI-User"; // Tracks 429 events per key for abuse logging (key: prefix, value: list of timestamps) // In-process only; acceptable for the abuse detection use case. @@ -30,10 +33,28 @@ namespace QRRapidoApp.Filters try { + // ── RapidAPI flow ──────────────────────────────────────────── + var config = context.HttpContext.RequestServices.GetRequiredService(); + var expectedSecret = config["RapidApi:ProxySecret"]; + + if (!string.IsNullOrWhiteSpace(expectedSecret) && + context.HttpContext.Request.Headers.TryGetValue(RapidApiSecretHeaderName, out var incomingSecret) && + string.Equals(incomingSecret, expectedSecret, StringComparison.Ordinal)) + { + var rapidApiUser = context.HttpContext.Request.Headers[RapidApiUserHeaderName].ToString(); + context.HttpContext.Items["AuthSource"] = "RapidAPI"; + context.HttpContext.Items["RapidApiUser"] = rapidApiUser; + context.HttpContext.Items["ApiPlanTier"] = ApiPlanTier.Pro; + logger.LogInformation("RapidAPI request authorized. RapidAPI-User: {User}", rapidApiUser); + await next(); + return; + } + + // ── Direct X-API-Key flow ──────────────────────────────────── if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeaderName, out var extractedApiKey)) { logger.LogWarning("API Key missing in request headers from {IP}", GetIp(context)); - context.Result = JsonError(401, "API Key not provided. Use the X-API-Key header."); + context.Result = JsonError(401, "Unauthorized. Provide a valid X-API-Key header or use the RapidAPI marketplace."); return; } diff --git a/appsettings.json b/appsettings.json index 0a925cb..a7cca69 100644 --- a/appsettings.json +++ b/appsettings.json @@ -188,5 +188,8 @@ "Microsoft.AspNetCore": "Warning" } }, + "RapidApi": { + "ProxySecret": "" + }, "AllowedHosts": "*" } \ No newline at end of file