ソースを参照

feat(auth): requireCapability (scope rule + origin + rate-limit) + Retry-After

Fszontagh 1 ヶ月 前
コミット
f3f29fcb32
2 ファイル変更25 行追加1 行削除
  1. 23 1
      src/server.cpp
  2. 2 0
      src/server.hpp

+ 23 - 1
src/server.cpp

@@ -1,6 +1,7 @@
 #include "server.hpp"
 #include "errors.hpp"
 #include "json_http.hpp"
+#include "rate_limiter.hpp"
 #include <chrono>
 #include <fstream>
 #include <sstream>
@@ -38,10 +39,31 @@ void requireProjectAccess(const ApiKey& k, const std::string& project) {
         throw ApiError(ErrCode::Forbidden, "forbidden", "key not granted project '" + project + "'");
 }
 
+void requireCapability(ServerDeps& d, const ApiKey& k, const httplib::Request& req,
+                       const std::string& project, const std::string& collection, KeyOp op) {
+    requireProjectAccess(k, project);
+    if (!k.scope) return;                                   // legacy full-access key
+    const KeyScope& sc = *k.scope;
+    const KeyScopeRule* rule = sc.findRule(collection, op);
+    if (!rule)
+        throw ApiError(ErrCode::Forbidden, "forbidden", "key scope does not permit this operation");
+    std::string origin;
+    if (auto it = req.headers.find("Origin"); it != req.headers.end()) origin = it->second;
+    if (!sc.originAllowed(origin))
+        throw ApiError(ErrCode::Forbidden, "forbidden", "origin not allowed for this key");
+    if (!rateLimiter().allow(k.id, sc.rateLimitPerMin))
+        throw ApiError(ErrCode::TooManyRequests, "rate_limited", "rate limit exceeded");
+    // Phase 2 (CAPTCHA) inserts the rule->requireHumanToken check here.
+    (void)d;
+}
+
 void ApiServer::registerRoutes() {
     svr_.set_exception_handler([](const httplib::Request&, httplib::Response& res, std::exception_ptr ep) {
         try { std::rethrow_exception(ep); }
-        catch (const ApiError& e) { sendJson(res, httpStatus(e.code), errorBody(e.slug, e.what())); }
+        catch (const ApiError& e) {
+            if (e.code == ErrCode::TooManyRequests) res.set_header("Retry-After", "60");
+            sendJson(res, httpStatus(e.code), errorBody(e.slug, e.what()));
+        }
         catch (const std::exception& e) { sendJson(res, 500, errorBody("internal", e.what())); }
     });
 

+ 2 - 0
src/server.hpp

@@ -41,6 +41,8 @@ std::optional<ApiKey> resolveKey(ServerDeps& d, const httplib::Request& req);
 ApiKey requireKey(ServerDeps& d, const httplib::Request& req);              // throws Unauthorized
 void   requireAdmin(const ApiKey& k);                                      // throws Forbidden
 void   requireProjectAccess(const ApiKey& k, const std::string& project);  // throws Forbidden
+void   requireCapability(ServerDeps& d, const ApiKey& k, const httplib::Request& req,
+                         const std::string& project, const std::string& collection, KeyOp op);
 
 // Route registration (handlers/*.cpp).
 void registerMetaRoutes(ApiServer&);