api keys: add API key usage tracking.

Track when an API key was last used, which IP address last used it, and
how many times it's been used overall.

This is so you can tell when an API key was last used, so you know if
the key is safe to delete, and so you can tell if an unrecognized IP has
used your key.
This commit is contained in:
evazion
2021-02-14 20:54:45 -06:00
parent 25fda1ecc2
commit d99985160a
4 changed files with 39 additions and 3 deletions

View File

@@ -3,6 +3,8 @@
### API Changes
* You can now have multiple API keys.
* You can now see when your API keys were last used, how many times they've
been used, and which IP address last used them.
* API keys can be restricted to only work with certain IPs or certain API
endpoints.
* If you're an app or script developer, and you have an app that requests API

View File

@@ -90,6 +90,7 @@ class SessionLoader
def authenticate_api_key(name, key)
user, api_key = User.find_by_name(name)&.authenticate_api_key(key)
raise AuthenticationFailure if user.blank?
update_api_key(api_key)
raise User::PrivilegeError if !api_key.has_permission?(request.remote_ip, request.params[:controller], request.params[:action])
CurrentUser.user = user
end
@@ -117,6 +118,11 @@ class SessionLoader
CurrentUser.user.update_attribute(:last_ip_addr, @request.remote_ip)
end
def update_api_key(api_key)
api_key.increment!(:uses, touch: :last_used_at)
api_key.update!(last_ip_address: request.remote_ip)
end
def set_time_zone
Time.zone = CurrentUser.user.time_zone
end

View File

@@ -51,14 +51,22 @@
<%= safe_join(api_key.permitted_ip_addresses, "<br>".html_safe).presence || "All" %>
<% end %>
<% if !params[:user_id].present? %>
<% t.column "User" do |api_key| %>
<%= link_to_user api_key.user %>
<% t.column :uses %>
<% t.column "Last Used" do |api_key| %>
<%= time_ago_in_words_tagged api_key.last_used_at %>
<% if api_key.last_ip_address.present? %>
<br>by <%= api_key.last_ip_address %>
<% end %>
<% end %>
<% t.column "Created" do |api_key| %>
<%= time_ago_in_words_tagged api_key.created_at %>
<% if !params[:user_id].present? %>
<br> by <%= link_to_user api_key.user %>
<% end %>
<% end %>
<% t.column column: "control" do |api_key| %>

View File

@@ -52,14 +52,20 @@ class ApplicationControllerTest < ActionDispatch::IntegrationTest
should "succeed for api key matches" do
basic_auth_string = "Basic #{::Base64.encode64("#{@user.name}:#{@api_key.key}")}"
get edit_user_path(@user), headers: { HTTP_AUTHORIZATION: basic_auth_string }
assert_response :success
assert_equal(1, @api_key.reload.uses)
assert_not_nil(@api_key.reload.last_used_at)
end
should "succeed when the user has multiple api keys" do
@api_key2 = create(:api_key, user: @user)
basic_auth_string = "Basic #{::Base64.encode64("#{@user.name}:#{@api_key2.key}")}"
get edit_user_path(@user), headers: { HTTP_AUTHORIZATION: basic_auth_string }
assert_response :success
assert_equal(1, @api_key2.reload.uses)
assert_not_nil(@api_key2.reload.last_used_at)
end
should "fail for api key mismatches" do
@@ -80,13 +86,19 @@ class ApplicationControllerTest < ActionDispatch::IntegrationTest
context "using the api_key parameter" do
should "succeed for api key matches" do
get edit_user_path(@user), params: { login: @user.name, api_key: @api_key.key }
assert_response :success
assert_equal(1, @api_key.reload.uses)
assert_not_nil(@api_key.reload.last_used_at)
end
should "succeed when the user has multiple api keys" do
@api_key2 = create(:api_key, user: @user)
get edit_user_path(@user), params: { login: @user.name, api_key: @api_key2.key }
assert_response :success
assert_equal(1, @api_key2.reload.uses)
assert_not_nil(@api_key2.reload.last_used_at)
end
should "fail for api key mismatches" do
@@ -135,6 +147,10 @@ class ApplicationControllerTest < ActionDispatch::IntegrationTest
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("2600:dead:beef::1")
get posts_path, params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response 403
assert_equal(6, @api_key.reload.uses)
assert_equal("2600:dead:beef::1", @api_key.reload.last_ip_address.to_s)
assert_not_nil(@api_key.reload.last_used_at)
end
should "restrict requests to the permitted endpoints" do
@@ -152,6 +168,10 @@ class ApplicationControllerTest < ActionDispatch::IntegrationTest
put post_path(@post), params: { login: @api_key.user.name, api_key: @api_key.key, post: { rating: "s" }}
assert_response 403
assert_equal(4, @api_key.reload.uses)
assert_equal("127.0.0.1", @api_key.reload.last_ip_address.to_s)
assert_not_nil(@api_key.reload.last_used_at)
end
end