├── .gitignore ├── composer.json ├── LICENSE ├── composer.lock ├── src └── limitrr.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /src/test.php 3 | /src/autoload-test.php 4 | test.php -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eddiejibson/limitrr-php", 3 | "description": "Better PHP rate limiting using redis.", 4 | "version": "1.0.0", 5 | "type": "library", 6 | "authors": [{ 7 | "name": "Edward Jibson", 8 | "email": "jibson@tuta.io" 9 | }], 10 | "keywords": ["security", "brute-force", "rate-limit", "middleware", "slim-php", "slimphp", "redis", "security-tools", "rate-limiter", "ratelimit", "brute-force-attacks"], 11 | "autoload": { 12 | "psr-4": { 13 | "eddiejibson\\limitrr\\": "src/" 14 | } 15 | }, 16 | "require": { 17 | "predis/predis": "^1.1", 18 | "php": "^7.0" 19 | }, 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Edward Jibson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "79f7d21b3444f924d9de727d8749ff8c", 8 | "packages": [ 9 | { 10 | "name": "predis/predis", 11 | "version": "v1.1.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/nrk/predis.git", 15 | "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", 20 | "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.3.9" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "~4.8" 28 | }, 29 | "suggest": { 30 | "ext-curl": "Allows access to Webdis when paired with phpiredis", 31 | "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" 32 | }, 33 | "type": "library", 34 | "autoload": { 35 | "psr-4": { 36 | "Predis\\": "src/" 37 | } 38 | }, 39 | "notification-url": "https://packagist.org/downloads/", 40 | "license": [ 41 | "MIT" 42 | ], 43 | "authors": [ 44 | { 45 | "name": "Daniele Alessandri", 46 | "email": "suppakilla@gmail.com", 47 | "homepage": "http://clorophilla.net" 48 | } 49 | ], 50 | "description": "Flexible and feature-complete Redis client for PHP and HHVM", 51 | "homepage": "http://github.com/nrk/predis", 52 | "keywords": [ 53 | "nosql", 54 | "predis", 55 | "redis" 56 | ], 57 | "time": "2016-06-16T16:22:20+00:00" 58 | } 59 | ], 60 | "packages-dev": [], 61 | "aliases": [], 62 | "minimum-stability": "stable", 63 | "stability-flags": [], 64 | "prefer-stable": false, 65 | "prefer-lowest": false, 66 | "platform": [], 67 | "platform-dev": [] 68 | } 69 | -------------------------------------------------------------------------------- /src/limitrr.php: -------------------------------------------------------------------------------- 1 | options = [ 20 | "keyName" => $conf["options"]["keyName"] ?? "limitrr", 21 | ]; 22 | } else { 23 | $conf["routes"]["default"] = [ 24 | "requestsPerExpiry" => $conf["routes"]["default"]["requestsPerExpiry"] ?? 100, 25 | "completedActionsPerExpiry" => $conf["routes"]["default"]["completedActionsPerExpiry"] ?? 5, 26 | "expiry" => $conf["routes"]["default"]["expiry"] ?? 900, 27 | "completedExpiry" => $conf["routes"]["default"]["completedExpiry"] ?? 900, 28 | "errorMsgs" => [ 29 | "requests" => $conf["routes"]["default"]["errorMsgs"]["requests"] ?? "As you have made too many requests, you have been rate limited.", 30 | "actions" => $conf["routes"]["default"]["errorMsgs"]["completed"] ?? "As you performed too many successful actions, you are being rate limited.", 31 | ], 32 | ]; 33 | $this->options = [ 34 | "keyName" => $conf["options"]["keyName"] ?? "limitrr", 35 | "errorStatusCode" => $conf["options"]["errorStatusCode"] ?? 429, 36 | ]; 37 | $this->routes = $this->setDefaultToUndefined($conf["routes"], $conf["routes"]["default"]); 38 | } 39 | $this->db = $this->connect($conf["redis"]); 40 | } 41 | 42 | public static function getIp() { 43 | return $_SERVER["HTTP_CF_CONNECTING_IP"] ?? $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? null; 44 | } 45 | 46 | public function __destruct() 47 | { 48 | $this->db = null; 49 | } 50 | 51 | private function connect(array $conf = []) 52 | { 53 | try { 54 | if (!isset($conf["uri"])) { 55 | $conf["host"] = $conf["host"] ?? "127.0.0.1"; 56 | $conf["port"] = $conf["port"] ?? 6379; 57 | } else { 58 | $conf = (string)$conf["uri"]; 59 | } 60 | $conf["database"] = $conf["database"] ?? 0; 61 | $client = new \Predis\Client($conf); 62 | $client->connect(); 63 | return $client; 64 | } catch (\Predis\Connection\ConnectionException $e) { 65 | $msg = $e->getMessage(); 66 | throw new \Exception("Limitrr: Could not connect to the Redis keystore. ${msg}", 1); 67 | } 68 | } 69 | 70 | public function limit(array $opts = [], $req, $res, $next) 71 | { 72 | $route = $opts["route"] ?? "default"; 73 | $ip = self::getIp(); 74 | $keyName = $this->options["keyName"]; 75 | $key = "limitrr:${keyName}:${ip}:${route}"; 76 | $result = $this->db->pipeline() 77 | ->get("${key}:requests") 78 | ->get("${key}:completed") 79 | ->ttl("${key}:requests") 80 | ->ttl("${key}:completed") 81 | ->execute(); 82 | if ($result) { 83 | if (!$result[0]) { 84 | $result[0] = -2; 85 | } 86 | if (!$result[1]) { 87 | $result[1] = -2; 88 | } 89 | if (0 > $result[2] || !$result[2]) { 90 | $result[2] = 0; 91 | } 92 | if (0 > $result[3] || !$result[3]) { 93 | $result[3] = 0; 94 | } 95 | if ($result[0] >= $this->routes[$route]["requestsPerExpiry"]) { 96 | return $res->withJson([ 97 | "error" => $this->routes[$route]["errorMsgs"]["requests"], 98 | ], $this->options["errorStatusCode"]); 99 | } elseif ($result[1] >= $this->routes[$route]["completedActionsPerExpiry"]) { 100 | return $res->withJson([ 101 | "error" => $this->routes[$route]["errorMsgs"]["completed"], 102 | ], $this->options["errorStatusCode"]); 103 | } elseif ($result[0] > -2) { 104 | try { 105 | $result = $this->db->incr("${key}:requests"); 106 | return $next($req, $res); 107 | } catch (\Exception $e) { 108 | $this->handleError($e); 109 | } 110 | } else { 111 | try { 112 | $result = $this->db->pipeline() 113 | ->incr("${key}:requests") 114 | ->expire("${key}:requests", $this->routes[$route]["requestsPerExpiry"]) 115 | ->execute(); 116 | return $next($req, $res); 117 | } catch (\Exception $e) { 118 | $this->handleError($e); 119 | } 120 | } 121 | } 122 | } 123 | 124 | public function complete(array $opts) : int 125 | { 126 | $keyName = $this->options["keyName"]; 127 | $discriminator = $opts["discriminator"]; 128 | $route = $opts["route"] ?? "default"; 129 | $key = "limitrr:${keyName}:${discriminator}:${route}"; 130 | $currentResult = $this->db->get("${key}:completed"); 131 | if ($currentResult) { 132 | try { 133 | $result = $this->db->incr("${key}:completed"); 134 | } catch (\Exception $e) { 135 | $this->handleError($e); 136 | } 137 | if ($result > $currentResult) { 138 | return $result; 139 | } else { 140 | throw new \Exception("Limitrr: Could not complete values", 1); 141 | } 142 | } else { 143 | try { 144 | $result = $this->db->pipeline() 145 | ->incr("${key}:completed") 146 | ->expire("${key}:requests", $this->routes[$route]["completedActionsPerExpiry"]) 147 | ->execute(); 148 | } catch (\Exception $e) { 149 | $this->handleError($e); 150 | } 151 | if ($result[0] > 0 && isset($result[1])) { 152 | return $result[0]; 153 | } else { 154 | throw new \Exception("Limitrr: Could not complete values", 1); 155 | } 156 | } 157 | } 158 | 159 | public function get(array $opts) : array 160 | { 161 | $keyName = $this->options["keyName"]; 162 | $discriminator = $opts["discriminator"]; 163 | $route = $opts["route"] ?? "default"; 164 | $key = "limitrr:${keyName}:${discriminator}:${route}"; 165 | if (!isset($opts["type"])) { 166 | try { 167 | $result = $this->db->pipeline() 168 | ->get("${key}:requests") 169 | ->get("${key}:completed") 170 | ->execute(); 171 | } catch (\Exception $e) { 172 | $this->handleError($e); 173 | } 174 | return [ 175 | "requests" => (int)($result[0] ?? 0), 176 | "completed" => (int)($result[1] ?? 0) 177 | ]; 178 | } else { 179 | $type = $opts["type"]; 180 | try { 181 | $result = $this->db->get("${key}:${type}"); 182 | } catch (\Exception $e) { 183 | $this->handleError($e); 184 | } 185 | return [ 186 | (string)$type => (int)($result ?? 0) 187 | ]; 188 | } 189 | } 190 | 191 | public function reset(array $opts) : bool 192 | { 193 | $keyName = $this->options["keyName"]; 194 | $discriminator = $opts["discriminator"]; 195 | $route = $opts["route"] ?? "default"; 196 | $key = "limitrr:${keyName}:${discriminator}:${route}"; 197 | if (!isset($opts["type"])) { 198 | try { 199 | $result = $this->db->pipeline() 200 | ->del("${key}:requests") 201 | ->del("${key}:completed") 202 | ->execute(); 203 | } catch (\Exception $e) { 204 | $this->handleError($e); 205 | } 206 | if ($result) { 207 | return true; 208 | } else { 209 | throw new \Exception("Limitrr: Could not reset values", 1); 210 | } 211 | } else { 212 | $type = $opts["type"]; 213 | try { 214 | $result = $this->db->del("${key}:${type}"); 215 | } catch (\Exception $e) { 216 | $this->handleError($e); 217 | } 218 | if ($result) { 219 | return true; 220 | } else { 221 | throw new \Exception("Limitrr: Could not reset values", 1); 222 | } 223 | } 224 | } 225 | 226 | private function handleError(\Exception $e) 227 | { 228 | $msg = $e->getMessage(); 229 | throw new \Exception("Limitrr : An error was encountered . ${msg} ", 1); 230 | } 231 | 232 | private function setDefaultToUndefined(array $routes, array $default) : array 233 | { 234 | foreach ($routes as $key => $value) { 235 | if ($key != "default") { 236 | foreach ($default as $defaultKey => $defaultValue) { 237 | if (!isset($routes[$key][$defaultKey])) { 238 | $routes[$key][$defaultKey] = $defaultValue; 239 | } 240 | } 241 | } 242 | } 243 | $routes["default"] = $default; 244 | return $routes; 245 | } 246 | 247 | } 248 | 249 | class RateLimitMiddleware 250 | { 251 | public function __construct(Limitrr $limitrr, array $opts = []) 252 | { 253 | $this->limitrr = $limitrr; 254 | $this->opts = $opts; 255 | return true; 256 | } 257 | // RequestInterface $req, ResponseInterface $res, callable $next 258 | public function __invoke($req, $res, $next) 259 | { 260 | return $this->limitrr->limit($this->opts, $req, $res, $next); 261 | } 262 | } 263 | 264 | class GetIpMiddleware 265 | { 266 | public function __invoke($req, $res, $next) 267 | { 268 | if ($req->hasHeader("CF-Connecting-IP")) { 269 | $ip = $req->getHeader("CF-Connecting-IP"); 270 | } elseif ($req->hasHeader("X-Forwarded-for")) { 271 | $ip = $req->getHeader("X-Forwarded-for"); 272 | } else { 273 | $ip = $req->getServerParam("REMOTE_ADDR"); 274 | } 275 | $req = $req->withAttribute("realip ", $ip); 276 | return $next($req, $res); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | chae 3 |
4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | Light rate limting within PHP using Redis. 12 |
13 | 14 | Limitrr PHP is very heavily inspired by my other library, Limitrr which was created for NodeJS. Check it out [here](http://github.com/eddiejibson/chae-limitrr) 15 | 16 | Limitrr PHP allows users to easily integrate rate limiting within their application. Unlike other similar packages, this utility allows the user to limit not only by the number of requests but also the number of completed actions (e.g allowing a certain amount of accounts to be successfully created within a timespan) and have such restricted with custom options. As well as this, custom discriminators are possible - you no longer have to limit by just the user's IP. 17 | 18 | This library also provides a middleware function for easily ratelimting whatever various routes you may have within a SlimPHP project. 19 | 20 | If you appreciate this project, please 🌟 it on GitHub. 21 | 22 | **Pull Requests are welcomed** 23 | 24 | ## Installation 25 | 26 | You can install the limitrr-php libary via executing the following commandline in your terminal (assuming you have composer [installed](https://getcomposer.org/download/)) 27 | 28 | ```bash 29 | composer require eddiejibson/limitrr-php "^1.0" 30 | ``` 31 | 32 | ## Quick Guide 33 | 34 | ### Basic Usage 35 | 36 | ```php 37 | require "/vendor/autoload.php"; //Require composer's autoload 38 | 39 | $options = [ 40 | //Redis keystore information 41 | "redis" => [ 42 | "host" => "666.chae.sh", 43 | "port" => 6379, 44 | "password" => "supersecret", 45 | ], 46 | "routes" => [ 47 | "default" => [ 48 | "requestsPerExpiry" => 5, 49 | ], 50 | ], 51 | 52 | ]; 53 | 54 | //Initialize the Limitrr class and pass the options defined above into it 55 | //Note that the options are not required. 56 | $limitrr = new \eddiejibson\limitrr\Limitrr($options); 57 | 58 | //Various examples like this can be found further into the documentation, 59 | //for each function. 60 | $result = $limitrr->get(["discriminator" => $ip]); 61 | echo $result["requests"] + " Requests"; 62 | echo $result["completed"] + " Completed"; 63 | //Note that this library is no means just for SlimPHP, it just happens to 64 | //provide a middleware function for those who may need it. 65 | 66 | //Usage within SlimPHP 67 | $app = new Slim\App(); 68 | 69 | //Use the Limitrr SlimPHP middleware function, if you wish: 70 | $app->add(new \eddiejibson\limitrr\RateLimitMiddleware($limitrr)); //Make sure to pass in the main Limitrr 71 | //instance we defined above into the middleware function. This is mandatory. 72 | 73 | //You can also add the get IP middleware function, it will append the user's real IP 74 | //(behind Cloudflare or not) to the request. 75 | $app->add(new \eddiejibson\limitrr\getIpMiddleware()); 76 | 77 | //Example usage within a route 78 | $app->get("/hello/{name}", function ($request, $response, $args) { 79 | $name = $args["name"]; 80 | $ip = $request->getAttribute('realip'); //Get the IP that was defined within Limitrr's get IP middleware function 81 | return $response->getBody()->write("Hello, ${name}. Your IP is ${ip}."); 82 | }); 83 | 84 | //You do not have to app the middleware function to every single route, globally. 85 | //You can do it indivually, too - along with passing options into such. Like so: 86 | $app->get("/createUser/{name}", function ($request, $response, $args) { 87 | //Non intensive actions like simple verification will have a different limit to intensive ones. 88 | //and will only be measured in terms of each request via the middleware. 89 | //No further action is required. 90 | if (strlen($args["name"]) < 5) { 91 | //Dummy function creating user 92 | $res = $someRandomClass->registerUser(); 93 | if ($res) { 94 | //Intensive actions like actually registering a user should have a 95 | //different limit to normal requests, hence the completedActionsPerExpiry option. 96 | //and should only be added to once this task has been completed fully 97 | //In this example, we will be limiting the amount of completed actions a certain IP can make. 98 | //Anything can be passed in here, however. For example, a email address or user ID. 99 | //$request->getAttribute('realip') was determined by calling the middleware earlier - getIpMiddleware() 100 | $limitrr->complete(["discriminator"] => $ip); 101 | } 102 | } 103 | })->add(new \eddiejibson\limitrr\RateLimitMiddleware($limitrr, ["route"=>"createUser"])); 104 | //You can also pass the route name within the limitrr middleware function 105 | 106 | $app->run(); 107 | ``` 108 | 109 | ### Get value of certain key 110 | 111 | #### limitrr->get() 112 | 113 | **Returns:** Array 114 | 115 | ```php 116 | $limitrr->get([ 117 | "discriminator" => $discriminator, //Required 118 | "route" => $route, //Not required, default is assumed 119 | "type" => $type //Not required 120 | ]); 121 | ``` 122 | 123 | ##### ->get() Parameters 124 | 125 | ***Must be passed into the function via an array*** 126 | 127 | - **discriminator:** **Required** Where discriminator is the thing being limited (e.g x amount of completed actions per discriminator) 128 | - **route**: *String* What route should the values be retrieved from? If this is not set, it will get the counts from the `default` route 129 | - **type**: *String* Instead of retrieving both values, you can specify either `requests` or `completed` in this key and only that will be returned as an integer. 130 | 131 | ##### ->get() Examples 132 | 133 | ```php 134 | $limitrr->get([ 135 | "discriminator" => $discriminator, 136 | "type" => $type, 137 | "route" => $route 138 | ]); //Besides discriminator, all parameters are optional. 139 | //If type is not passed into the function, it will 140 | //return both the amount of requests and completed actions 141 | 142 | //Where discriminator is the thing being limited 143 | //e.g x amount of completed actions/requests per discriminator 144 | $limitrr->get(["discriminator" => $discriminator]); 145 | 146 | //This tends to be the user's IP. 147 | $limitrr->get(["discriminator" => $ip]); 148 | //This will return both the amount of requests and completed actions stored under the 149 | //discriminator provided in an object. You can handle like this: 150 | $result = $limitrr->get(["discriminator" => $ip]); 151 | echo $result["requests"] + " Requests"; 152 | echo $result["completed"] + " Completed"; 153 | 154 | //The above example would get the request and completed task count from the default 155 | //route. If you would like to retrieve values from a different route, you can specify 156 | //this as well. It can be done like this: 157 | $result = $limitrr->get(["discriminator" => $ip, "route" => "exampleRouteName"]); 158 | echo $result["requests"] . " Requests made through the route exampleRouteName"; 159 | echo $result["completed"] . " Completed Tasks made through the route exampleRouteName"; 160 | 161 | //You may also only fetch only one type of value - instead of both requests and completed. 162 | $result = $limitrr->get(["discriminator" => $ip, "route" => "exampleRouteName", "type" => "completed"]); 163 | echo $result["completed"] . " Completed tasks made through the route exampleRouteName"; 164 | ``` 165 | 166 | ### Complete action/task count 167 | 168 | #### limitrr->complete() 169 | 170 | **Returns:** Integer 171 | 172 | ```php 173 | $limitrr->get([ 174 | "discriminator" => $discriminator, //Required 175 | "route" => $route, //Not required, default is assumed 176 | ]); 177 | ``` 178 | 179 | ##### ->complete() Parameters 180 | 181 | ***Must be passed into the function via an array*** 182 | 183 | - **discriminator:** **Required** Where discriminator is the thing being limited (e.g x amount of completed actions per discriminator) 184 | - **route**: *String* What route should the values be inserted into? If this is not set, it will get the counts from the `default` route 185 | 186 | ### Removal of values from certain request/completed keys 187 | 188 | #### limitrr->reset() 189 | 190 | **Returns:** Boolean 191 | 192 | ```php 193 | $limitrr->reset([ 194 | "discriminator" => $discriminator, //Required 195 | "route" => $route, //Not required, default is assumed, 196 | "type" => $type //Not required 197 | ]); 198 | ``` 199 | 200 | ##### ->reset() Parameters 201 | 202 | ***Must be passed into the function via an array*** 203 | 204 | - **discriminator:** **Required** Where discriminator is the thing being limited (e.g x amount of completed actions per discriminator) 205 | - **route**: *String* What route should the values be reset from? If this is not set, it will reset the counts from the `default` route 206 | - **type**: *String* Which count do you wish to be reset? `requests` or `completed`? If this is not set, both will be removed. 207 | 208 | ```php 209 | //Where discriminator is the thing being limited 210 | //e.g x amount of completed actions/requests per discriminator 211 | //This will remove both the amount of requests and completed action count 212 | $limitrr->reset(["discriminator" => $discriminator]); 213 | 214 | //This tends to be the user's IP. 215 | $limitrr->reset(["discriminator" => $ip]); 216 | 217 | //If you wish to reset counts from a particular route, this can be done as well. 218 | //As the type is not specified, it will remove both the request and completed count 219 | $result = $limitrr->reset([ 220 | "discriminator" => $ip, 221 | "route" => "exampleRouteName" 222 | ]); 223 | if ($result) { 224 | echo "Requests removed from the route exampleRouteName"; 225 | } else { 226 | //Do something else 227 | } 228 | 229 | //If you want to remove either one of the amount of requests or completed actions. 230 | //but not the other, this can be done as well. 231 | //The value passed in can either be "requests" or "completed". 232 | //In this example, we will be removing the request count for a certain IP 233 | $result = $limitrr->reset([ 234 | "discriminator" => $ip, 235 | "type" => "requests" 236 | ]); 237 | if ($result) { 238 | echo "Request count for the specified IP were removed" 239 | } else { 240 | //do something else 241 | } 242 | ``` 243 | 244 | ## Configuration 245 | 246 | ### redis 247 | 248 | **Required:** false 249 | 250 | **Type:** Array OR String 251 | 252 | **Description:** Redis connection information. 253 | 254 | ***Either pass in a string containing the URI of the redis instance or an object containing the connection information:*** 255 | 256 | - **port**: *Integer* Redis port. Defaults to: `6379` 257 | - **host**: *String* Redis hostname. Defaults to: `"127.0.0.1"` 258 | - **password**: *String* Redis password. Defaults to: `""` 259 | - **database**: *Integer* Redis DB. Defaults to: `0` 260 | 261 | #### Example of the redis array/string that could be passed into Limitrr 262 | 263 | ```php 264 | //Pass in a string containing a redis URI. 265 | "redis" => "redis://127.0.0.1:6379/0" 266 | //Alternatively, use an array with the connection information. 267 | "redis" => [ 268 | "port" => 6379, //Redis Port. Required: false. Defaults to 6379 269 | "host" => "127.0.0.1", //Redis hostname. required: False. Defaults to "127.0.0.1". 270 | "password" => "mysecretpassword1234", //Redis password. Required: false. Defaults to null. 271 | "database" => 0 //Redis database. Required: false. Defaults to 0. 272 | ] 273 | ``` 274 | 275 | ### options 276 | 277 | **Required:** false 278 | 279 | **Type:** Array 280 | 281 | **Description:** Various options to do with Limitrr. 282 | 283 | - **keyName:** String The keyname all of the requests will be stored under. This is mainly for aesthetic purposes and does not affect much. However, this should be changed on each initialization of the main class to prevent conflict. Defaults to: `"limitrr"` 284 | - **errorStatusCode:** Integer Status code to return when the user is being rate limited. Defaults to `429` (Too Many Requests) 285 | 286 | #### Example of the options object that could be passed into Limitrr 287 | 288 | ```php 289 | "options" => [ 290 | "keyName" => "myApp", //The keyname all of the requests will be stored under. Required: false. Defaults to "limitrr" 291 | "errorStatusCode" => 429 //Should important errors such as failure to connect to the Redis keystore be caught and displayed? 292 | ] 293 | ``` 294 | 295 | ### routes 296 | 297 | **Required**: false 298 | 299 | **Type**: Array 300 | 301 | **Description**: Define route restrictions. 302 | 303 | Inside the routes object, you can define many separate routes and set custom rules within them. The custom rules you can set are: 304 | 305 | - **requestsPerExpiry**: *Integer* How many requests can be accepted until user is rate limited? Defaults to: `100` 306 | - **completedActionsPerExpiry**: *Integer* How many completed actions can be accepted until the user is rate limited? This is useful for certain actions such as registering a user - they can have a certain amount of requests but a different (obviously smaller) amount of "completed actions". So if users have recently been successfully registered multiple times under the same IP (or other discriminator), they can be rate limited. They may be allowed 100 requests per certain expiry for general validation and the like, but only a small fraction of that for intensive procedures. Defaults to the value in `requestsPerExpiry` or `5` if not set. 307 | - **expiry**: *Integer* How long should the requests be stored (in seconds) before they are set back to 0? If set to -1, values will never expire and will stay that way indefinitely or must be manually removed. Defaults to: `900` (15 minutes) 308 | - **completedExpiry**: *Integer* How long should the "completed actions" (such as the amount of users registered from a particular IP or other discriminator) be stored for (in seconds) before it is set back to 0? If set to -1, such values will never expire and will stay that way indefinitely or must be manually removed. Defaults to the value in `expiry` or `900` (15 minutes) if not set. 309 | - **errorMsgs**: *Object* Seperate error messages for too many requests and too many completed actions. They have been given the respective key names "requests" and "actions". This will be returned to the user when they are being rate limited. If no string was set in `requests`, it will default to `"As you have made too many requests, you are being rate limited."`. Furthermore, if a value has not been set in `completed`, it will resolve to the string found in `requests`. Or, if that wasn't set either, `"As you performed too many successful actions, you have been rate limited."` will be it's value. 310 | 311 | #### Example of the routes array 312 | 313 | ```php 314 | "routes" => [ 315 | //Overwrite default route rules - not all of the keys must be set, 316 | //only the ones you wish to overwrite 317 | "default" => [ 318 | "expiry": 1000 319 | ], 320 | "exampleRoute" => [ 321 | "requestsPerExpiry" => 100, 322 | "completedActionsPerExpiry" => 5, 323 | "expiry" => 900, 324 | "completedExpiry" => 900, 325 | "errorMsgs" => [ 326 | "requests" => "As you have made too many requests, you are being rate limited.", 327 | "completed" => "As you performed too many successful actions, you have been rate limited." 328 | ] 329 | ], 330 | //If not all keys are set, they will revert to 331 | //the default values 332 | "exampleRoute2" => [ 333 | "requestsPerExpiry" => 500 334 | ] 335 | ] 336 | ``` 337 | --------------------------------------------------------------------------------