mirror of
https://github.com/discourse/discourse.git
synced 2025-06-04 07:56:01 +08:00
FEATURE: Automatically expire keys if not used for a configurable amount of time. (#6264)
This commit is contained in:

committed by
Régis Hanol

parent
1d913d3204
commit
860c1c3dcd
@ -18,6 +18,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p>
|
<p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p>
|
||||||
|
<p><span>{{i18n "user.api_last_used_at"}}</span> {{bound-date key.last_used_at}}</p>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
15
app/jobs/scheduled/auto_expire_user_api_keys.rb
Normal file
15
app/jobs/scheduled/auto_expire_user_api_keys.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module Jobs
|
||||||
|
|
||||||
|
class AutoExpireUserApiKeys < Jobs::Scheduled
|
||||||
|
every 1.day
|
||||||
|
|
||||||
|
def execute(args)
|
||||||
|
if SiteSetting.expire_user_api_keys_days > 0
|
||||||
|
expire_user_api_keys_days = SiteSetting.expire_user_api_keys_days.days.ago
|
||||||
|
|
||||||
|
UserApiKey.where("last_used_at < ?", expire_user_api_keys_days).update_all(revoked_at: Time.zone.now)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -74,6 +74,7 @@ end
|
|||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# revoked_at :datetime
|
# revoked_at :datetime
|
||||||
# scopes :text default([]), not null, is an Array
|
# scopes :text default([]), not null, is an Array
|
||||||
|
# last_used_at :datetime not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
@ -184,10 +184,12 @@ class UserSerializer < BasicUserSerializer
|
|||||||
id: k.id,
|
id: k.id,
|
||||||
application_name: k.application_name,
|
application_name: k.application_name,
|
||||||
scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s}") },
|
scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s}") },
|
||||||
created_at: k.created_at
|
created_at: k.created_at,
|
||||||
|
last_used_at: k.last_used_at,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
keys.sort! { |a, b| a.last_used_at.to_time > b.last_used_at.to_time }
|
||||||
keys.length > 0 ? keys : nil
|
keys.length > 0 ? keys : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -704,6 +704,7 @@ en:
|
|||||||
revoke_access: "Revoke Access"
|
revoke_access: "Revoke Access"
|
||||||
undo_revoke_access: "Undo Revoke Access"
|
undo_revoke_access: "Undo Revoke Access"
|
||||||
api_approved: "Approved:"
|
api_approved: "Approved:"
|
||||||
|
api_last_used_at: "Last used at:"
|
||||||
theme: "Theme"
|
theme: "Theme"
|
||||||
home: "Default Home Page"
|
home: "Default Home Page"
|
||||||
staged: "Staged"
|
staged: "Staged"
|
||||||
|
@ -1770,6 +1770,7 @@ en:
|
|||||||
min_trust_level_for_user_api_key: "Trust level required for generation of user API keys"
|
min_trust_level_for_user_api_key: "Trust level required for generation of user API keys"
|
||||||
allowed_user_api_auth_redirects: "Allowed URL for authentication redirect for user API keys"
|
allowed_user_api_auth_redirects: "Allowed URL for authentication redirect for user API keys"
|
||||||
allowed_user_api_push_urls: "Allowed URLs for server push to user API"
|
allowed_user_api_push_urls: "Allowed URLs for server push to user API"
|
||||||
|
expire_user_api_keys_days: "Number of days before an user API key automatically expires (0 for never)"
|
||||||
|
|
||||||
tagging_enabled: "Enable tags on topics?"
|
tagging_enabled: "Enable tags on topics?"
|
||||||
min_trust_to_create_tag: "The minimum trust level required to create a tag."
|
min_trust_to_create_tag: "The minimum trust level required to create a tag."
|
||||||
|
@ -1681,6 +1681,8 @@ user_api:
|
|||||||
allowed_user_api_auth_redirects:
|
allowed_user_api_auth_redirects:
|
||||||
default: 'https://api.discourse.org/api/auth_redirect|discourse://auth_redirect'
|
default: 'https://api.discourse.org/api/auth_redirect|discourse://auth_redirect'
|
||||||
type: list
|
type: list
|
||||||
|
expire_user_api_keys_days:
|
||||||
|
default: 180
|
||||||
|
|
||||||
tags:
|
tags:
|
||||||
tagging_enabled:
|
tagging_enabled:
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
class AddUserApiKeysLastUsedAt < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :user_api_keys, :last_used_at, :datetime, null: false, default: -> { 'CURRENT_TIMESTAMP' }
|
||||||
|
end
|
||||||
|
end
|
@ -258,6 +258,7 @@ class Auth::DefaultCurrentUserProvider
|
|||||||
raise Discourse::InvalidAccess
|
raise Discourse::InvalidAccess
|
||||||
end
|
end
|
||||||
|
|
||||||
|
api_key.update_columns(last_used_at: Time.zone.now)
|
||||||
if client_id.present? && client_id != api_key.client_id
|
if client_id.present? && client_id != api_key.client_id
|
||||||
api_key.update_columns(client_id: client_id)
|
api_key.update_columns(client_id: client_id)
|
||||||
end
|
end
|
||||||
|
16
spec/integration/user_api_keys_spec.rb
Normal file
16
spec/integration/user_api_keys_spec.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe 'user api keys integration' do
|
||||||
|
|
||||||
|
let(:user_api_key) { Fabricate(:readonly_user_api_key) }
|
||||||
|
|
||||||
|
it 'updates last used time on use' do
|
||||||
|
user_api_key.update_columns(last_used_at: 7.days.ago)
|
||||||
|
|
||||||
|
freeze_time
|
||||||
|
get '/session/current.json', headers: {
|
||||||
|
HTTP_USER_API_KEY: user_api_key.key,
|
||||||
|
}
|
||||||
|
expect(user_api_key.reload.last_used_at).to eq(Time.zone.now)
|
||||||
|
end
|
||||||
|
end
|
24
spec/jobs/auto_expire_user_api_keys.rb
Normal file
24
spec/jobs/auto_expire_user_api_keys.rb
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Jobs::AutoExpireUserApiKeys do
|
||||||
|
let(:key1) { Fabricate(:readonly_user_api_key) }
|
||||||
|
let(:key2) { Fabricate(:readonly_user_api_key) }
|
||||||
|
|
||||||
|
context 'when user api key is unused in last 1 days' do
|
||||||
|
|
||||||
|
before do
|
||||||
|
SiteSetting.expire_user_api_keys_days = true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should revoke the key' do
|
||||||
|
freeze_time
|
||||||
|
|
||||||
|
key1.update!(last_used_at: 2.days.ago)
|
||||||
|
|
||||||
|
described_class.new.execute({})
|
||||||
|
|
||||||
|
expect(key1.reload.revoked_at).to eq(Time.zone.now)
|
||||||
|
expect(key2.reload.revoked_at).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Reference in New Issue
Block a user