The hash uses the start of a fixed window as an HMAC key. Therefore the
windows stored in the DB also use this so that there can only be one
active window for each IP.

Signed-off-by: Florian Pritz <bluew...@xinu.at>
---
 schema/aur-schema.sql     |  2 +-
 upgrading/4.7.0.txt       |  2 +-
 web/lib/aurjson.class.php | 12 +++++++-----
 3 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/schema/aur-schema.sql b/schema/aur-schema.sql
index 79de3f2..45f8993 100644
--- a/schema/aur-schema.sql
+++ b/schema/aur-schema.sql
@@ -403,7 +403,7 @@ CREATE TABLE AcceptedTerms (
 -- Rate limits for API
 --
 CREATE TABLE `ApiRateLimit` (
-  IP VARCHAR(45) NOT NULL,
+  IP VARCHAR(255) NOT NULL,
   Requests INT(11) NOT NULL,
   WindowStart BIGINT(20) NOT NULL,
   PRIMARY KEY (`ip`)
diff --git a/upgrading/4.7.0.txt b/upgrading/4.7.0.txt
index 820e454..fcdbb4d 100644
--- a/upgrading/4.7.0.txt
+++ b/upgrading/4.7.0.txt
@@ -2,7 +2,7 @@
 
 ---
 CREATE TABLE `ApiRateLimit` (
-  IP VARCHAR(45) NOT NULL,
+  IP VARCHAR(255) NOT NULL,
   Requests INT(11) NOT NULL,
   WindowStart BIGINT(20) NOT NULL,
   PRIMARY KEY (`ip`)
diff --git a/web/lib/aurjson.class.php b/web/lib/aurjson.class.php
index c51e9c2..168c6b7 100644
--- a/web/lib/aurjson.class.php
+++ b/web/lib/aurjson.class.php
@@ -153,6 +153,9 @@ private function check_ratelimit($ip) {
                }
 
                $window_length = config_get("ratelimit", "window_length");
+               $hmac_key = floor(time() / ($window_length));
+               $ip = hash_hmac("sha256", $ip, $hmac_key);
+
                $this->update_ratelimit($ip);
                $stmt = $this->dbh->prepare("
                        SELECT Requests FROM ApiRateLimit
@@ -181,14 +184,13 @@ private function check_ratelimit($ip) {
        private function update_ratelimit($ip) {
                $window_length = config_get("ratelimit", "window_length");
                $db_backend = config_get("database", "backend");
-               $time = time();
+               $window_start = floor(time() / ($window_length));
 
                // Clean up old windows
-               $deletion_time = $time - $window_length;
                $stmt = $this->dbh->prepare("
                        DELETE FROM ApiRateLimit
                        WHERE WindowStart < :time");
-               $stmt->bindParam(":time", $deletion_time);
+               $stmt->bindParam(":time", $window_start);
                $stmt->execute();
 
                if ($db_backend == "mysql") {
@@ -198,7 +200,7 @@ private function update_ratelimit($ip) {
                                VALUES (:ip, 1, :window_start)
                                ON DUPLICATE KEY UPDATE Requests=Requests+1");
                        $stmt->bindParam(":ip", $ip);
-                       $stmt->bindParam(":window_start", $time);
+                       $stmt->bindParam(":window_start", $window_start);
                        $stmt->execute();
                } elseif ($db_backend == "sqlite") {
                        $stmt = $this->dbh->prepare("
@@ -206,7 +208,7 @@ private function update_ratelimit($ip) {
                                (IP, Requests, WindowStart)
                                VALUES (:ip, 0, :window_start);");
                        $stmt->bindParam(":ip", $ip);
-                       $stmt->bindParam(":window_start", $time);
+                       $stmt->bindParam(":window_start", $window_start);
                        $stmt->execute();
 
                        $stmt = $this->dbh->prepare("
-- 
2.17.0

Reply via email to