{"id":18646754,"url":"https://github.com/metabase/throttle","last_synced_at":"2025-04-11T12:31:58.917Z","repository":{"id":62433636,"uuid":"52405956","full_name":"metabase/throttle","owner":"metabase","description":"Tools for throttling access to API endpoints or other code pathways :yum:","archived":false,"fork":false,"pushed_at":"2019-08-16T22:05:36.000Z","size":31,"stargazers_count":19,"open_issues_count":0,"forks_count":6,"subscribers_count":52,"default_branch":"master","last_synced_at":"2025-03-25T14:06:33.819Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/metabase.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-02-24T01:50:32.000Z","updated_at":"2024-11-04T04:17:14.000Z","dependencies_parsed_at":"2022-11-01T21:01:59.314Z","dependency_job_id":null,"html_url":"https://github.com/metabase/throttle","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metabase%2Fthrottle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metabase%2Fthrottle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metabase%2Fthrottle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metabase%2Fthrottle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/metabase","download_url":"https://codeload.github.com/metabase/throttle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247968240,"owners_count":21025797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T06:22:27.200Z","updated_at":"2025-04-11T12:31:58.650Z","avatar_url":"https://github.com/metabase.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Throttling\n\n[![Clojars Project](https://clojars.org/metabase/throttle/latest-version.svg)](http://clojars.org/metabase/throttle)\n\n[![Dependencies Status](http://jarkeeper.com/metabase/throttle/status.png)](http://jarkeeper.com/metabase/throttle)\n[![Circle CI](https://circleci.com/gh/metabase/throttle.svg?style=svg)](https://circleci.com/gh/metabase/throttle)\n[![GitHub license](https://img.shields.io/badge/license-3%E2%80%92Clause%20BSD-blue.svg)](https://raw.githubusercontent.com/metabase/throttle/master/LICENSE.txt)\n\nA `Throttler` is a simple object used for throttling API endpoints or other code pathways. It keeps track of all calls with some value over some past period of time. If the number of calls with this value exceeds some threshold,\nan exception is thrown, telling a user they must wait some period of time before trying again.\n\n### Example\n\nLet's consider a login endpoint that we want to make sure isn't vulnerable to a naughty hacker who tries to brute-force the password for a given email address :speak_no_evil:.\nThe basic concept here is to keep a list of logins attempts over the last hour. This list looks like:\n\n```clojure\n[[\"cam@metabase.com\" 1438045261132] ; Unix timestamps (milliseconds)\n [\"cam@metabase.com\" 1438045260450]\n [\"cam@metabase.com\" 1438045259037]\n [\"cam@metabase.com\" 1438045258204]]\n```\n\nEvery time there's a login attempt, push a new pair of `[email timestamp-milliseconds]` to the front of the list.\nThe list is thus automatically ordered by date, and we can drop the portion of the list with logins that are over\nan hour old as needed.\n\nOnce we've passed some number of login attempts over the past hour (e.g. 5) for a given email address, calculate some delay before\nthey're allowed to try to log in again (e.g., 15 seconds). This number will increase exponentially as the number of\nrecent failures increases (e.g., 40 seconds for 6 failed attempts, 90 for 7 failed attempts, etc).\n\nIf applicable, calculate the time since the last failed attempt, and throw an exception telling the user the number\nof seconds they must wait before trying again.\n\n### Usage\n\nDefine a new throttler with `make-throttler`, overriding default settings as needed. The first parameter is the name of the field or value being checked and is used to generate appropriate error messages.\n\n```clojure\n(require '[throttle.core :as throttle])\n(def email-throttler (throttle/make-throttler :email, :attempts-threshold 10))\n```\n\nThen call `check` at the appropriate point in your code with some value to apply throttling.\n\n```clojure\n(defn my-endpoint-fn [:as {{:keys [email]} :body}]\n  (throttle/check email-throttler email)\n  ...)\n```\n\nIf you only want to throttle failures of some operation, like login attempts, you can use `with-throttling`.\n\n```clojure\n(defn my-login-fn [username password]\n  (throttle/with-throttling [login-throttler username]\n    (login username password)))\n```\n\nIn the above case throttling will only kick in after `login` threw an exception `attempts-threshold` times.\n\n`with-throttling` accepts multiple throttler-key pairs.\n\n### Configuration\n\nThe following are options that can be passed to `make-throttler`:\n\n*  `attempt-ttl-ms`\n    Amount of time to keep an entry under consideration for throttling. (default: one hour)\n*  `attempts-threshold`\n    Number of attempts allowed with a given key before throttling is applied. (default: `10`)\n*  `initial-delay-ms`\n    Once throttling is in effect, initial delay before allowing another attempt. This grows according to `delay-exponent`. (default: 15 seconds)\n*  `delay-exponent`\n    For each subsequent failure past `attempts-threshold`, increase the delay to\n\n    ```\n    initial-delay-ms * (num-attempts-over-theshold ^ delay-exponent)\n    ```\n\n    e.g. if `initial-delay-ms` is `15` and `delay-exponent` is `2`, the first attempt past `attempts-threshold` will require the user to wait 15 seconds\n    `(15 * 1^2)`, the next attempt after that 60 seconds `(15 * 2^2)`, then 135, and so on. (default: `1.5`)\n\n### LICENSE\n\n[3-clause BSD](https://raw.githubusercontent.com/metabase/throttle/master/LICENSE.txt)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetabase%2Fthrottle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmetabase%2Fthrottle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetabase%2Fthrottle/lists"}