Bug fixing

This commit is contained in:
Your Name
2025-05-25 15:05:01 +12:00
parent 3cffb6cd94
commit 27acc9f9f0
7 changed files with 125 additions and 9 deletions

View File

@@ -84,17 +84,17 @@ The server can be configured by creating a JSON configuration file at `~/.config
"port": 8080,
"storage_path": "/path/to/storage",
"write_tokens": ["your-secret-token"],
}
```
Optionally, you can modify the CORS configuration. Defaults:
```json
"cors": {
"allowed_origins": ["*"],
"allowed_methods": ["GET", "PUT", "POST", "DELETE", "OPTIONS"],
"allowed_headers": ["Authorization", "Content-Type"],
"allow_credentials": false
},
"rate_limiting": {
"auth_rate_limit": 5, // Maximum number of auth attempts
"auth_window_seconds": 10 // Time window in seconds (10 seconds)
}
}
```
## API Endpoints

View File

@@ -59,6 +59,17 @@ bool load_config(const std::string& config_path, ServerConfig& config) {
}
}
// Parse rate limiting configuration
if (j.contains("rate_limiting")) {
const auto& rate_limit = j["rate_limiting"];
if (rate_limit.contains("auth_rate_limit")) {
config.auth_rate_limit = rate_limit["auth_rate_limit"].get<int>();
}
if (rate_limit.contains("auth_window_seconds")) {
config.auth_window_seconds = rate_limit["auth_window_seconds"].get<int>();
}
}
return true;
} catch (const std::exception& e) {
std::cerr << "Error parsing config file: " << e.what() << std::endl;

View File

@@ -17,6 +17,9 @@ struct ServerConfig {
std::vector<std::string> allowed_methods = {"GET", "PUT", "POST", "DELETE", "OPTIONS"};
std::vector<std::string> allowed_headers = {"Authorization", "Content-Type"};
bool allow_credentials = false;
// Rate limiting configuration
int auth_rate_limit = 5; // Maximum number of auth attempts
int auth_window_seconds = 10; // Time window in seconds (10 seconds)
};
bool load_config(const std::string& config_path, ServerConfig& config);

62
src/rate_limiter.hpp Normal file
View File

@@ -0,0 +1,62 @@
#ifndef RATE_LIMITER_HPP
#define RATE_LIMITER_HPP
#include <string>
#include <unordered_map>
#include <chrono>
#include <mutex>
#include <queue>
namespace simple_object_storage {
class RateLimiter {
public:
RateLimiter(int max_requests, std::chrono::seconds window)
: max_requests_(max_requests), window_(window) {}
bool is_allowed(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto now = std::chrono::steady_clock::now();
auto& bucket = buckets_[key];
// Clean up old requests
while (!bucket.empty() && (now - bucket.front()) > window_) {
bucket.pop();
}
// Check if we're under the limit
if (bucket.size() < max_requests_) {
bucket.push(now);
return true;
}
return false;
}
// New method: check if over the limit WITHOUT incrementing
bool is_over_limit(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto now = std::chrono::steady_clock::now();
auto& bucket = buckets_[key];
while (!bucket.empty() && (now - bucket.front()) > window_) {
bucket.pop();
}
return bucket.size() >= max_requests_;
}
void reset(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
buckets_.erase(key);
}
private:
int max_requests_;
std::chrono::seconds window_;
std::unordered_map<std::string, std::queue<std::chrono::steady_clock::time_point>> buckets_;
std::mutex mutex_;
};
} // namespace simple_object_storage
#endif

View File

@@ -32,38 +32,50 @@ bool Server::init_db() {
bool Server::validate_write_request(const httplib::Request &req, httplib::Response &res, const std::vector<std::string> &required_params, std::map<std::string, std::string> &params)
{
std::string client_ip = req.remote_addr;
// Check if the client is already over the limit (do NOT increment)
if (auth_rate_limiter_->is_over_limit(client_ip)) {
res.status = 429;
nlohmann::json response = {{"result", "error"}, {"error", "Too many authentication attempts. Please try again later."}};
res.set_content(response.dump(), "application/json");
return false;
}
// Get token from Authorization header
std::string token;
if (req.has_header("Authorization")) {
const auto& auth_header = req.get_header_value("Authorization");
// Check if it's a Bearer token
if (auth_header.substr(0, 7) == "Bearer ") {
token = auth_header.substr(7);
}
}
if (token.empty()) {
// Only count failed attempt (increment the limiter)
auth_rate_limiter_->is_allowed(client_ip); // This will increment the count
res.status = 401;
nlohmann::json response = {{"result", "error"}, {"error", "Missing or invalid Authorization header"}};
res.set_content(response.dump(), "application/json");
return false;
}
// Check if token is valid
bool write_token_valid = std::find(config_.write_tokens.begin(), config_.write_tokens.end(), token) != config_.write_tokens.end();
if (!write_token_valid) {
// Only count failed attempt (increment the limiter)
auth_rate_limiter_->is_allowed(client_ip); // This will increment the count
res.status = 403;
nlohmann::json response = {{"result", "error"}, {"error", "Invalid write token"}};
res.set_content(response.dump(), "application/json");
return false;
}
// Get other parameters from query params
// If authentication is successful, do not increment rate limiter
for (const auto& param : req.params) {
params[param.first] = param.second;
}
// Check for required parameters
for (const auto& param : required_params) {
if (!req.has_param(param)) {
res.status = 400;
@@ -92,6 +104,12 @@ Server::Server(const ServerConfig& config)
// Initialize the put handler
put_handler_ = std::make_unique<PutHandler>(*this);
// Initialize rate limiter
auth_rate_limiter_ = std::make_unique<RateLimiter>(
config_.auth_rate_limit,
std::chrono::seconds(config_.auth_window_seconds)
);
}
Server::~Server() {

View File

@@ -7,6 +7,7 @@
#include "json.hpp"
#include "database.hpp"
#include "config.hpp"
#include "rate_limiter.hpp"
namespace simple_object_storage {
@@ -43,6 +44,7 @@ private:
httplib::Server server_;
bool running_;
std::unique_ptr<PutHandler> put_handler_;
std::unique_ptr<RateLimiter> auth_rate_limiter_;
};
} // namespace simple_object_storage

20
test.sh
View File

@@ -267,4 +267,24 @@ fi
curl -s -H "Authorization: Bearer ${WRITE_TOKEN}" "${BASE_URL}/deleteobject?hash=${FIRST_HASH}" > /dev/null
curl -s -H "Authorization: Bearer ${WRITE_TOKEN}" "${BASE_URL}/deleteobject?hash=${SECOND_HASH}" > /dev/null
# Test 3: Verify rate limiting behavior
title "Testing rate limiting behavior"
# Use a known invalid token
INVALID_TOKEN="invalid_token"
# Make 5 requests with an invalid token
for i in {1..5}; do
echo "Attempt $i with invalid token"
RESPONSE=$(curl -s -X PUT -H "Authorization: Bearer ${INVALID_TOKEN}" -F "file=@${SCRIPT_DIR}/${SCRIPT_NAME}" -F "metadata={\"labeltags\":[\"test:latest\"]}" "${BASE_URL}/upload")
echo "Response: ${RESPONSE}"
done
# Now try a request with a valid token - should be rate limited
echo "Attempting request with valid token (should be rate limited)"
RESPONSE=$(curl -s -X PUT -H "Authorization: Bearer ${WRITE_TOKEN}" -F "file=@${SCRIPT_DIR}/${SCRIPT_NAME}" -F "metadata={\"labeltags\":[\"test:latest\"]}" "${BASE_URL}/upload")
if ! echo "${RESPONSE}" | jq -r '.error' | grep -q "Too many authentication attempts"; then
die "Expected rate limit error, got: ${RESPONSE}"
fi
title "ALL TESTS PASSED"