diff --git a/app/models/api_token.rb b/app/models/api_token.rb
index f2e9e00180..46b6b64952 100644
--- a/app/models/api_token.rb
+++ b/app/models/api_token.rb
@@ -6,6 +6,7 @@
#
# id :bigint not null, primary key
# expires_in :integer
+# ip_address :inet
# refresh_token :string
# revoked_at :datetime
# scopes :string
@@ -19,6 +20,7 @@
# Indexes
#
# index_api_tokens_on_application_id (application_id)
+# index_api_tokens_on_ip_address (ip_address)
# index_api_tokens_on_token_bidx (token_bidx) UNIQUE
# index_api_tokens_on_user_id (user_id)
#
@@ -53,4 +55,19 @@ def self.generate(options = {})
def abbreviated = "#{token[..7]}...#{token[-3..]}"
+ def geocode_result
+ return nil unless ip_address.present?
+ return @geocode_result if defined?(@geocode_result)
+
+ @geocode_result = Geocoder.search(ip_address.to_s)&.first
+ end
+
+ def latitude
+ geocode_result&.latitude
+ end
+
+ def longitude
+ geocode_result&.longitude
+ end
+
end
diff --git a/app/views/users/_oauth_authorization.erb b/app/views/users/_oauth_authorization.erb
index 84963cee36..952476b805 100644
--- a/app/views/users/_oauth_authorization.erb
+++ b/app/views/users/_oauth_authorization.erb
@@ -49,6 +49,20 @@
<% end %>
+ <% latest_token_with_ip = authorization.tokens.where.not(ip_address: nil).order(created_at: :desc).first %>
+ <% if latest_token_with_ip.present? %>
+ <% if latest_token_with_ip.latitude.present? && latest_token_with_ip.longitude.present? %>
+
+ <% else %>
+
<%= latest_token_with_ip.ip_address %>
+ <%= authorization.authorization_count == 1 ? "Authorized" : "Last authorized" %> <%= local_time_ago authorization.created_at %>
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 2e5be4504d..d3fa9bceb0 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -357,7 +357,7 @@ # tokens, you can check that the requested data belongs to the specified tenant. # # Default value is an empty Array: [] - # custom_access_token_attributes [:tenant_id] + custom_access_token_attributes [:ip_address] # Hook into the strategies' request & response life-cycle in case your # application needs advanced customization or logging: @@ -366,9 +366,12 @@ # puts "BEFORE HOOK FIRED! #{request}" # end # - # after_successful_strategy_response do |request, response| - # puts "AFTER HOOK FIRED! #{request}, #{response}" - # end + after_successful_strategy_response do |request, response| + if response.respond_to?(:token) && response.token.is_a?(ApiToken) + ip = request.remote_ip + response.token.update(ip_address: ip) if ip.present? + end + end # Hook into Authorization flow in order to implement Single Sign Out # or add any other functionality. Inside the block you have an access diff --git a/db/migrate/20251117030308_add_ip_address_to_api_tokens.rb b/db/migrate/20251117030308_add_ip_address_to_api_tokens.rb new file mode 100644 index 0000000000..fedaa9b00e --- /dev/null +++ b/db/migrate/20251117030308_add_ip_address_to_api_tokens.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AddIpAddressToApiTokens < ActiveRecord::Migration[8.0] + disable_ddl_transaction! + + def change + safety_assured do + add_column :api_tokens, :ip_address, :inet + add_index :api_tokens, :ip_address, algorithm: :concurrently + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 807d8c3be9..689bd83668 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,7 +12,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_11_15_104532) do +ActiveRecord::Schema[8.0].define(version: 2025_11_17_030308) do create_schema "google_sheets" # These are extensions that must be enabled in order to support this database @@ -227,7 +227,9 @@ t.string "refresh_token" t.integer "expires_in" t.string "scopes" + t.inet "ip_address" t.index ["application_id"], name: "index_api_tokens_on_application_id" + t.index ["ip_address"], name: "index_api_tokens_on_ip_address" t.index ["token_bidx"], name: "index_api_tokens_on_token_bidx", unique: true t.index ["user_id"], name: "index_api_tokens_on_user_id" end