FEATURE: track date api key was last used

FEATURE: track date api key was last used

Start tracking the date an api key was last used. This has already been the case for user_api_keys.

This information can provide us with the ability to automatically expire unused api keys after N days.

diff --git a/app/models/api_key.rb b/app/models/api_key.rb
index f6a4819..6062025 100644
--- a/app/models/api_key.rb
+++ b/app/models/api_key.rb
@@ -35,6 +35,7 @@ end
 #  updated_at    :datetime         not null
 #  allowed_ips   :inet             is an Array
 #  hidden        :boolean          default(FALSE), not null
+#  last_used_at  :datetime
 #
 # Indexes
 #
diff --git a/db/migrate/20190903073730_add_last_used_at_to_api_key.rb b/db/migrate/20190903073730_add_last_used_at_to_api_key.rb
new file mode 100644
index 0000000..4688980
--- /dev/null
+++ b/db/migrate/20190903073730_add_last_used_at_to_api_key.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+class AddLastUsedAtToApiKey < ActiveRecord::Migration[5.2]
+  def change
+    add_column :api_keys, :last_used_at, :datetime
+  end
+end
diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb
index 27fb47a..9e8d88f 100644
--- a/lib/auth/default_current_user_provider.rb
+++ b/lib/auth/default_current_user_provider.rb
@@ -292,15 +292,22 @@ class Auth::DefaultCurrentUserProvider
         return nil
       end
 
-      if api_key.user
-        api_key.user if !api_username || (api_key.user.username_lower == api_username.downcase)
-      elsif api_username
-        User.find_by(username_lower: api_username.downcase)
-      elsif user_id = header_api_key? ? @env[HEADER_API_USER_ID] : request["api_user_id"]
-        User.find_by(id: user_id.to_i)
-      elsif external_id = header_api_key? ? @env[HEADER_API_USER_EXTERNAL_ID] : request["api_user_external_id"]
-        SingleSignOnRecord.find_by(external_id: external_id.to_s).try(:user)
+      user =
+        if api_key.user
+          api_key.user if !api_username || (api_key.user.username_lower == api_username.downcase)
+        elsif api_username
+          User.find_by(username_lower: api_username.downcase)
+        elsif user_id = header_api_key? ? @env[HEADER_API_USER_ID] : request["api_user_id"]
+          User.find_by(id: user_id.to_i)
+        elsif external_id = header_api_key? ? @env[HEADER_API_USER_EXTERNAL_ID] : request["api_user_external_id"]
+          SingleSignOnRecord.find_by(external_id: external_id.to_s).try(:user)
+        end
+
+      if user
+        api_key.update_columns(last_used_at: Time.zone.now)
       end
+
+      user
     end
   end
 
diff --git a/spec/components/auth/default_current_user_provider_spec.rb b/spec/components/auth/default_current_user_provider_spec.rb
index d6320a9..62761fa 100644
--- a/spec/components/auth/default_current_user_provider_spec.rb
+++ b/spec/components/auth/default_current_user_provider_spec.rb
@@ -56,11 +56,14 @@ describe Auth::DefaultCurrentUserProvider do
     it "raises for a user pretending" do
       user = Fabricate(:user)
       user2 = Fabricate(:user)
-      ApiKey.create!(key: "hello", user_id: user.id, created_by_id: -1)
+      key = ApiKey.create!(key: "hello", user_id: user.id, created_by_id: -1)
 
       expect {
         provider("/?api_key=hello&api_username=#{user2.username.downcase}").current_user
       }.to raise_error(Discourse::InvalidAccess)
+
+      key.reload
+      expect(key.last_used_at).to eq(nil)
     end
 
     it "raises for a user with a mismatching ip" do
@@ -74,8 +77,10 @@ describe Auth::DefaultCurrentUserProvider do
     end
 
     it "allows a user with a matching ip" do
+      freeze_time
+
       user = Fabricate(:user)
-      ApiKey.create!(key: "hello", user_id: user.id, created_by_id: -1, allowed_ips: ['100.0.0.0/24'])
+      key = ApiKey.create!(key: "hello", user_id: user.id, created_by_id: -1, allowed_ips: ['100.0.0.0/24'])
 
       found_user = provider("/?api_key=hello&api_username=#{user.username.downcase}",
                             "REMOTE_ADDR" => "100.0.0.22").current_user
@@ -86,6 +91,8 @@ describe Auth::DefaultCurrentUserProvider do
                             "HTTP_X_FORWARDED_FOR" => "10.1.1.1, 100.0.0.22").current_user
       expect(found_user.id).to eq(user.id)
 
+      key.reload
+      expect(key.last_used_at).to eq_time(Time.zone.now)
     end
 
     it "finds a user for a correct system api key" do

GitHub sha: dc9110cc

2 Likes