oatpp-authkit/test/test_rate_limiter.cpp
Uwe Schuster fafee1278f #16 (audit M-1..M-12): fix the medium-severity findings
M-1  TokenExtract: exact-name cookie parse (new pure cookieValue helper) —
     a substring find("session=") could be shadowed by a sibling xsession=,
     defeating __Host-/__Secure- prefix guarantees.
M-2  AuthInterceptor: gate setup-mode pseudo-admin on a loopback bind and log
     the grant; document that IAuthBackend::hasActiveUsers() must fail closed.
M-3  ws/Hub: empty propertyIds now means NO access for non-admins (was "all") —
     a non-admin whose scope set failed to populate no longer gets every
     property's notifications. Admins still get all via role.
M-4  new util/OriginCheck.hpp (originHostname/sameOrigin/originAllowed) +
     Hub doc: WSController must validate Origin at the handshake (CSWSH).
M-6  RedactedFieldRepository: ctor throws on an unknown redaction field name
     (a typo would silently redact nothing, leaving credentials in history).
M-7  RateLimiter: ctor validates capacity (finite >=1) / refillRate (finite >0),
     throws std::invalid_argument — zero/negative/NaN silently disabled it.
M-8  TokenExtract: document that clientIpTrusted's "unknown"/"invalid" sentinels
     collapse to one shared rate-limit bucket off-proxy.
M-9  new util/SessionCookie.hpp: safe-by-default Set-Cookie builder
     (HttpOnly+Secure+SameSite=Strict+Path=/), rejects control chars / ';'.
M-10 AuthInterceptor: Origin/Referer-vs-Host check on session mutations
     (defence in depth atop X-Requested-With); cert path documented as
     non-browser / not CSRF-gated.
M-11 AuthInterceptor: optional injected RateLimiter throttles invalid-token
     attempts per client IP → 429.
M-12 AuthInterceptor: sanitize request method/path (strip control chars, cap
     length) before logging — closes log-line forging (CWE-117).

(M-5 — temporal non-atomic save — was already resolved by the H-4 fix.)

Tests: new test_token_extract / test_rate_limiter / test_origin_check /
test_session_cookie; extended test_redacted_field_repository. All 19 ctest
targets pass. README + header docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:53:22 +02:00

61 lines
2.2 KiB
C++

// Tests for oatpp-authkit/util/RateLimiter.hpp — constructor validation
// (authkit#16 M-7) and basic token-bucket behaviour.
#include "oatpp-authkit/util/RateLimiter.hpp"
#include <cstdio>
#include <stdexcept>
#include <string>
namespace {
int g_failures = 0;
#define REQUIRE(expr) do { \
if (!(expr)) { \
std::fprintf(stderr, "FAIL %s:%d %s\n", __FILE__, __LINE__, #expr); \
++g_failures; \
} \
} while (0)
using namespace oatpp_authkit;
template <class F>
bool throwsInvalidArg(F&& f) {
try { f(); } catch (const std::invalid_argument&) { return true; } catch (...) { return false; }
return false;
}
void test_ctor_validation() {
REQUIRE(throwsInvalidArg([]{ RateLimiter r(0.0, 1.0); })); // capacity < 1
REQUIRE(throwsInvalidArg([]{ RateLimiter r(-5.0, 1.0); })); // negative capacity
REQUIRE(throwsInvalidArg([]{ RateLimiter r(10.0, 0.0); })); // refill 0 → silent disable
REQUIRE(throwsInvalidArg([]{ RateLimiter r(10.0, -1.0); })); // negative refill
REQUIRE(throwsInvalidArg([]{ RateLimiter r(std::nan(""), 1.0); })); // NaN capacity
REQUIRE(throwsInvalidArg([]{ RateLimiter r(10.0, std::nan("")); })); // NaN refill
REQUIRE(throwsInvalidArg([]{ RateLimiter r(1.0/0.0, 1.0); })); // inf capacity
// Valid construction does not throw.
bool ok = true;
try { RateLimiter r(3.0, 0.5); (void)r; } catch (...) { ok = false; }
REQUIRE(ok);
}
void test_burst_then_deny_and_key_isolation() {
RateLimiter rl(3.0, 0.001); // 3 burst, negligible refill within the test
REQUIRE(rl.allow("ip-a"));
REQUIRE(rl.allow("ip-a"));
REQUIRE(rl.allow("ip-a"));
REQUIRE(!rl.allow("ip-a")); // 4th denied
// Different key has its own independent bucket.
REQUIRE(rl.allow("ip-b"));
}
} // namespace
int main() {
test_ctor_validation();
test_burst_then_deny_and_key_isolation();
std::printf("%s (%d failures)\n", g_failures ? "FAIL" : "OK", g_failures);
return g_failures ? 1 : 0;
}