{"action":"created","comment":{"url":"https://api.github.com/repos/hexpm/hex_web/pulls/comments/88559242","pull_request_review_id":9130650,"id":88559242,"diff_hunk":"@@ -0,0 +1,70 @@\n+defmodule HexWeb.PlugAttack do\n+  use PlugAttack\n+  import HexWeb.ControllerHelpers\n+  import Plug.Conn\n+\n+  @addresses_ets :blocked_addresses\n+\n+  rule \"allow local\", conn do\n+    allow conn.remote_ip == {127, 0, 0, 1}\n+  end\n+\n+  rule \"block addresses\", conn do\n+    try_reload_addresses()\n+\n+    check_blocked(ip_str(conn.remote_ip))\n+  end\n+\n+  rule \"ip throttle\", conn do\n+    throttle(conn.remote_ip, [storage: {PlugAttack.Storage.Ets, HexWeb.PlugAttack},\n+                              limit: 100, period: 60_000])\n+  end\n+\n+  def allow_action(conn, {:throttle, data}, _opts) do\n+    add_throttling_headers(conn, data)\n+  end\n+\n+  def allow_action(conn, _data, _opts) do\n+    conn\n+  end\n+\n+  def block_action(conn, {:throttle, data}, _opts) do\n+    conn\n+    |> add_throttling_headers(data)\n+    |> render_error(429, message: \"API rate limit exceeded for #{ip_str(conn.remote_ip)}\")\n+  end\n+\n+  def block_action(conn, _data, _opts) do\n+    render_error(conn, 403, message: \"Blocked\")\n+  end\n+\n+  defp add_throttling_headers(conn, data) do\n+    # The expires_at value is a unix time in milliseconds, we want to return one\n+    # in seconds\n+    reset = div(data[:expires_at], 1_000)\n+    conn\n+    |> put_resp_header(\"x-ratelimit-limit\", Integer.to_string(data[:limit]))\n+    |> put_resp_header(\"x-ratelimit-remaining\", Integer.to_string(data[:remaining]))\n+    |> put_resp_header(\"x-ratelimit-reset\", Integer.to_string(reset))\n+  end\n+\n+  defp ip_str({a, b, c, d}) do\n+    \"#{a}.#{b}.#{c}.#{d}\"\n+  end\n+\n+  defp check_blocked(ip) do\n+    case :ets.lookup(@addresses_ets, ip) do","path":"web/plugs/plug_attack.ex","position":56,"original_position":56,"commit_id":"8582285a107ee91b12f1adb283817f2948e35a71","original_commit_id":"8582285a107ee91b12f1adb283817f2948e35a71","user":{"login":"michalmuskala","id":477062,"avatar_url":"https://avatars.githubusercontent.com/u/477062?v=3","gravatar_id":"","url":"https://api.github.com/users/michalmuskala","html_url":"https://github.com/michalmuskala","followers_url":"https://api.github.com/users/michalmuskala/followers","following_url":"https://api.github.com/users/michalmuskala/following{/other_user}","gists_url":"https://api.github.com/users/michalmuskala/gists{/gist_id}","starred_url":"https://api.github.com/users/michalmuskala/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/michalmuskala/subscriptions","organizations_url":"https://api.github.com/users/michalmuskala/orgs","repos_url":"https://api.github.com/users/michalmuskala/repos","events_url":"https://api.github.com/users/michalmuskala/events{/privacy}","received_events_url":"https://api.github.com/users/michalmuskala/received_events","type":"User","site_admin":false},"body":"That is 100% right.","created_at":"2016-11-17T22:14:07Z","updated_at":"2016-11-17T22:14:07Z","html_url":"https://github.com/hexpm/hex_web/pull/441#discussion_r88559242","pull_request_url":"https://api.github.com/repos/hexpm/hex_web/pulls/441","_links":{"self":{"href":"https://api.github.com/repos/hexpm/hex_web/pulls/comments/88559242"},"html":{"href":"https://github.com/hexpm/hex_web/pull/441#discussion_r88559242"},"pull_request":{"href":"https://api.github.com/repos/hexpm/hex_web/pulls/441"}}},"pull_request":{"url":"https://api.github.com/repos/hexpm/hex_web/pulls/441","id":94143381,"html_url":"https://github.com/hexpm/hex_web/pull/441","diff_url":"https://github.com/hexpm/hex_web/pull/441.diff","patch_url":"https://github.com/hexpm/hex_web/pull/441.patch","issue_url":"https://api.github.com/repos/hexpm/hex_web/issues/441","number":441,"state":"open","locked":false,"title":"Use PlugAttack","user":{"login":"michalmuskala","id":477062,"avatar_url":"https://avatars.githubusercontent.com/u/477062?v=3","gravatar_id":"","url":"https://api.github.com/users/michalmuskala","html_url":"https://github.com/michalmuskala","followers_url":"https://api.github.com/users/michalmuskala/followers","following_url":"https://api.github.com/users/michalmuskala/following{/other_user}","gists_url":"https://api.github.com/users/michalmuskala/gists{/gist_id}","starred_url":"https://api.github.com/users/michalmuskala/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/michalmuskala/subscriptions","organizations_url":"https://api.github.com/users/michalmuskala/orgs","repos_url":"https://api.github.com/users/michalmuskala/repos","events_url":"https://api.github.com/users/michalmuskala/events{/privacy}","received_events_url":"https://api.github.com/users/michalmuskala/received_events","type":"User","site_admin":false},"body":"This is a PoC for replacing custom components for rate limiting and ip blocking with `plug_attack` package, I recently released.\r\n\r\nIt has couple advantages:\r\n* increasing rate limit for authenticated users is trivial - just change the `limit` option based on some connection params,\r\n* skipping rate limiting of conditional requests should be easier to do with a plug-like interface (although we know about that only after the controller processes the request, so it might require some additional work)\r\n* there is a clean storage interface, so implementing something with redis should be easier\r\n* I'm considering adding a loadable blocklist - this should simplify the `BlockAddress` thing even more.","created_at":"2016-11-17T10:49:03Z","updated_at":"2016-11-17T22:14:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"344a9adeef576130c450c6b5a593546927d9282d","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/hexpm/hex_web/pulls/441/commits","review_comments_url":"https://api.github.com/repos/hexpm/hex_web/pulls/441/comments","review_comment_url":"https://api.github.com/repos/hexpm/hex_web/pulls/comments{/number}","comments_url":"https://api.github.com/repos/hexpm/hex_web/issues/441/comments","statuses_url":"https://api.github.com/repos/hexpm/hex_web/statuses/8582285a107ee91b12f1adb283817f2948e35a71","head":{"label":"michalmuskala:plug-attack","ref":"plug-attack","sha":"8582285a107ee91b12f1adb283817f2948e35a71","user":{"login":"michalmuskala","id":477062,"avatar_url":"https://avatars.githubusercontent.com/u/477062?v=3","gravatar_id":"","url":"https://api.github.com/users/michalmuskala","html_url":"https://github.com/michalmuskala","followers_url":"https://api.github.com/users/michalmuskala/followers","following_url":"https://api.github.com/users/michalmuskala/following{/other_user}","gists_url":"https://api.github.com/users/michalmuskala/gists{/gist_id}","starred_url":"https://api.github.com/users/michalmuskala/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/michalmuskala/subscriptions","organizations_url":"https://api.github.com/users/michalmuskala/orgs","repos_url":"https://api.github.com/users/michalmuskala/repos","events_url":"https://api.github.com/users/michalmuskala/events{/privacy}","received_events_url":"https://api.github.com/users/michalmuskala/received_events","type":"User","site_admin":false},"repo":{"id":30710073,"name":"hex_web","full_name":"michalmuskala/hex_web","owner":{"login":"michalmuskala","id":477062,"avatar_url":"https://avatars.githubusercontent.com/u/477062?v=3","gravatar_id":"","url":"https://api.github.com/users/michalmuskala","html_url":"https://github.com/michalmuskala","followers_url":"https://api.github.com/users/michalmuskala/followers","following_url":"https://api.github.com/users/michalmuskala/following{/other_user}","gists_url":"https://api.github.com/users/michalmuskala/gists{/gist_id}","starred_url":"https://api.github.com/users/michalmuskala/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/michalmuskala/subscriptions","organizations_url":"https://api.github.com/users/michalmuskala/orgs","repos_url":"https://api.github.com/users/michalmuskala/repos","events_url":"https://api.github.com/users/michalmuskala/events{/privacy}","received_events_url":"https://api.github.com/users/michalmuskala/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/michalmuskala/hex_web","description":"API server and website for Hex","fork":true,"url":"https://api.github.com/repos/michalmuskala/hex_web","forks_url":"https://api.github.com/repos/michalmuskala/hex_web/forks","keys_url":"https://api.github.com/repos/michalmuskala/hex_web/keys{/key_id}","collaborators_url":"https://api.github.com/repos/michalmuskala/hex_web/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/michalmuskala/hex_web/teams","hooks_url":"https://api.github.com/repos/michalmuskala/hex_web/hooks","issue_events_url":"https://api.github.com/repos/michalmuskala/hex_web/issues/events{/number}","events_url":"https://api.github.com/repos/michalmuskala/hex_web/events","assignees_url":"https://api.github.com/repos/michalmuskala/hex_web/assignees{/user}","branches_url":"https://api.github.com/repos/michalmuskala/hex_web/branches{/branch}","tags_url":"https://api.github.com/repos/michalmuskala/hex_web/tags","blobs_url":"https://api.github.com/repos/michalmuskala/hex_web/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/michalmuskala/hex_web/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/michalmuskala/hex_web/git/refs{/sha}","trees_url":"https://api.github.com/repos/michalmuskala/hex_web/git/trees{/sha}","statuses_url":"https://api.github.com/repos/michalmuskala/hex_web/statuses/{sha}","languages_url":"https://api.github.com/repos/michalmuskala/hex_web/languages","stargazers_url":"https://api.github.com/repos/michalmuskala/hex_web/stargazers","contributors_url":"https://api.github.com/repos/michalmuskala/hex_web/contributors","subscribers_url":"https://api.github.com/repos/michalmuskala/hex_web/subscribers","subscription_url":"https://api.github.com/repos/michalmuskala/hex_web/subscription","commits_url":"https://api.github.com/repos/michalmuskala/hex_web/commits{/sha}","git_commits_url":"https://api.github.com/repos/michalmuskala/hex_web/git/commits{/sha}","comments_url":"https://api.github.com/repos/michalmuskala/hex_web/comments{/number}","issue_comment_url":"https://api.github.com/repos/michalmuskala/hex_web/issues/comments{/number}","contents_url":"https://api.github.com/repos/michalmuskala/hex_web/contents/{+path}","compare_url":"https://api.github.com/repos/michalmuskala/hex_web/compare/{base}...{head}","merges_url":"https://api.github.com/repos/michalmuskala/hex_web/merges","archive_url":"https://api.github.com/repos/michalmuskala/hex_web/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/michalmuskala/hex_web/downloads","issues_url":"https://api.github.com/repos/michalmuskala/hex_web/issues{/number}","pulls_url":"https://api.github.com/repos/michalmuskala/hex_web/pulls{/number}","milestones_url":"https://api.github.com/repos/michalmuskala/hex_web/milestones{/number}","notifications_url":"https://api.github.com/repos/michalmuskala/hex_web/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/michalmuskala/hex_web/labels{/name}","releases_url":"https://api.github.com/repos/michalmuskala/hex_web/releases{/id}","deployments_url":"https://api.github.com/repos/michalmuskala/hex_web/deployments","created_at":"2015-02-12T15:57:10Z","updated_at":"2016-07-15T06:40:16Z","pushed_at":"2016-11-17T10:37:48Z","git_url":"git://github.com/michalmuskala/hex_web.git","ssh_url":"git@github.com:michalmuskala/hex_web.git","clone_url":"https://github.com/michalmuskala/hex_web.git","svn_url":"https://github.com/michalmuskala/hex_web","homepage":"https://hex.pm","size":5674,"stargazers_count":0,"watchers_count":0,"language":"Elixir","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"hexpm:master","ref":"master","sha":"95f1714aa9365365df3efced67023ed5e7da5c2e","user":{"login":"hexpm","id":6621265,"avatar_url":"https://avatars.githubusercontent.com/u/6621265?v=3","gravatar_id":"","url":"https://api.github.com/users/hexpm","html_url":"https://github.com/hexpm","followers_url":"https://api.github.com/users/hexpm/followers","following_url":"https://api.github.com/users/hexpm/following{/other_user}","gists_url":"https://api.github.com/users/hexpm/gists{/gist_id}","starred_url":"https://api.github.com/users/hexpm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hexpm/subscriptions","organizations_url":"https://api.github.com/users/hexpm/orgs","repos_url":"https://api.github.com/users/hexpm/repos","events_url":"https://api.github.com/users/hexpm/events{/privacy}","received_events_url":"https://api.github.com/users/hexpm/received_events","type":"Organization","site_admin":false},"repo":{"id":16328245,"name":"hex_web","full_name":"hexpm/hex_web","owner":{"login":"hexpm","id":6621265,"avatar_url":"https://avatars.githubusercontent.com/u/6621265?v=3","gravatar_id":"","url":"https://api.github.com/users/hexpm","html_url":"https://github.com/hexpm","followers_url":"https://api.github.com/users/hexpm/followers","following_url":"https://api.github.com/users/hexpm/following{/other_user}","gists_url":"https://api.github.com/users/hexpm/gists{/gist_id}","starred_url":"https://api.github.com/users/hexpm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hexpm/subscriptions","organizations_url":"https://api.github.com/users/hexpm/orgs","repos_url":"https://api.github.com/users/hexpm/repos","events_url":"https://api.github.com/users/hexpm/events{/privacy}","received_events_url":"https://api.github.com/users/hexpm/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hexpm/hex_web","description":"API server and website for Hex","fork":false,"url":"https://api.github.com/repos/hexpm/hex_web","forks_url":"https://api.github.com/repos/hexpm/hex_web/forks","keys_url":"https://api.github.com/repos/hexpm/hex_web/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hexpm/hex_web/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hexpm/hex_web/teams","hooks_url":"https://api.github.com/repos/hexpm/hex_web/hooks","issue_events_url":"https://api.github.com/repos/hexpm/hex_web/issues/events{/number}","events_url":"https://api.github.com/repos/hexpm/hex_web/events","assignees_url":"https://api.github.com/repos/hexpm/hex_web/assignees{/user}","branches_url":"https://api.github.com/repos/hexpm/hex_web/branches{/branch}","tags_url":"https://api.github.com/repos/hexpm/hex_web/tags","blobs_url":"https://api.github.com/repos/hexpm/hex_web/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hexpm/hex_web/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hexpm/hex_web/git/refs{/sha}","trees_url":"https://api.github.com/repos/hexpm/hex_web/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hexpm/hex_web/statuses/{sha}","languages_url":"https://api.github.com/repos/hexpm/hex_web/languages","stargazers_url":"https://api.github.com/repos/hexpm/hex_web/stargazers","contributors_url":"https://api.github.com/repos/hexpm/hex_web/contributors","subscribers_url":"https://api.github.com/repos/hexpm/hex_web/subscribers","subscription_url":"https://api.github.com/repos/hexpm/hex_web/subscription","commits_url":"https://api.github.com/repos/hexpm/hex_web/commits{/sha}","git_commits_url":"https://api.github.com/repos/hexpm/hex_web/git/commits{/sha}","comments_url":"https://api.github.com/repos/hexpm/hex_web/comments{/number}","issue_comment_url":"https://api.github.com/repos/hexpm/hex_web/issues/comments{/number}","contents_url":"https://api.github.com/repos/hexpm/hex_web/contents/{+path}","compare_url":"https://api.github.com/repos/hexpm/hex_web/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hexpm/hex_web/merges","archive_url":"https://api.github.com/repos/hexpm/hex_web/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hexpm/hex_web/downloads","issues_url":"https://api.github.com/repos/hexpm/hex_web/issues{/number}","pulls_url":"https://api.github.com/repos/hexpm/hex_web/pulls{/number}","milestones_url":"https://api.github.com/repos/hexpm/hex_web/milestones{/number}","notifications_url":"https://api.github.com/repos/hexpm/hex_web/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hexpm/hex_web/labels{/name}","releases_url":"https://api.github.com/repos/hexpm/hex_web/releases{/id}","deployments_url":"https://api.github.com/repos/hexpm/hex_web/deployments","created_at":"2014-01-28T22:29:21Z","updated_at":"2016-11-17T14:18:34Z","pushed_at":"2016-11-17T10:49:03Z","git_url":"git://github.com/hexpm/hex_web.git","ssh_url":"git@github.com:hexpm/hex_web.git","clone_url":"https://github.com/hexpm/hex_web.git","svn_url":"https://github.com/hexpm/hex_web","homepage":"https://hex.pm","size":5959,"stargazers_count":212,"watchers_count":212,"language":"Elixir","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":98,"mirror_url":null,"open_issues_count":22,"forks":98,"open_issues":22,"watchers":212,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/hexpm/hex_web/pulls/441"},"html":{"href":"https://github.com/hexpm/hex_web/pull/441"},"issue":{"href":"https://api.github.com/repos/hexpm/hex_web/issues/441"},"comments":{"href":"https://api.github.com/repos/hexpm/hex_web/issues/441/comments"},"review_comments":{"href":"https://api.github.com/repos/hexpm/hex_web/pulls/441/comments"},"review_comment":{"href":"https://api.github.com/repos/hexpm/hex_web/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/hexpm/hex_web/pulls/441/commits"},"statuses":{"href":"https://api.github.com/repos/hexpm/hex_web/statuses/8582285a107ee91b12f1adb283817f2948e35a71"}}}}
{"actor":{"display_login":"michalmuskala"}}