mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: basic UI to view user api keys
This commit is contained in:
parent
b7cea24d76
commit
416e7e0d1e
@ -73,6 +73,46 @@ const User = RestModel.extend({
|
|||||||
return Discourse.getURL(`/users/${this.get('username_lower')}`);
|
return Discourse.getURL(`/users/${this.get('username_lower')}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@computed()
|
||||||
|
userApiKeys() {
|
||||||
|
const keys = this.get('user_api_keys');
|
||||||
|
if (keys) {
|
||||||
|
return keys.map((raw)=>{
|
||||||
|
let obj = Em.Object.create(
|
||||||
|
raw
|
||||||
|
);
|
||||||
|
|
||||||
|
obj.revoke = () => {
|
||||||
|
this.revokeApiKey(obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
obj.undoRevoke = () => {
|
||||||
|
this.undoRevokeApiKey(obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
revokeApiKey(key) {
|
||||||
|
return ajax("/user-api-key/revoke", {
|
||||||
|
type: 'POST',
|
||||||
|
data: { id: key.get('id') }
|
||||||
|
}).then(()=>{
|
||||||
|
key.set('revoked', true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
undoRevokeApiKey(key){
|
||||||
|
return ajax("/user-api-key/undo-revoke", {
|
||||||
|
type: 'POST',
|
||||||
|
data: { id: key.get('id') }
|
||||||
|
}).then(()=>{
|
||||||
|
key.set('revoked', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
pmPath(topic) {
|
pmPath(topic) {
|
||||||
const userId = this.get('id');
|
const userId = this.get('id');
|
||||||
const username = this.get('username_lower');
|
const username = this.get('username_lower');
|
||||||
|
@ -19,7 +19,15 @@ export default Discourse.Route.extend({
|
|||||||
const isIndexStream = INDEX_STREAM_ROUTES.indexOf(transition.targetName) !== -1;
|
const isIndexStream = INDEX_STREAM_ROUTES.indexOf(transition.targetName) !== -1;
|
||||||
this.controllerFor('user').set('indexStream', isIndexStream);
|
this.controllerFor('user').set('indexStream', isIndexStream);
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
undoRevokeApiKey(key) {
|
||||||
|
key.undoRevoke();
|
||||||
|
},
|
||||||
|
|
||||||
|
revokeApiKey(key) {
|
||||||
|
key.revoke();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeModel() {
|
beforeModel() {
|
||||||
|
@ -319,6 +319,26 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if model.userApiKeys}}
|
||||||
|
<div class="control-group apps">
|
||||||
|
<label class="control-label">{{i18n 'user.apps'}}</label>
|
||||||
|
<div class="controls">
|
||||||
|
{{#each model.userApiKeys as |key|}}
|
||||||
|
<div>
|
||||||
|
<span>{{key.application_name}}</span>
|
||||||
|
{{#if key.revoked}}
|
||||||
|
{{d-button action="undoRevokeApiKey" actionParam=key class="btn" label="user.undo_revoke_access"}}
|
||||||
|
{{else}}
|
||||||
|
{{d-button action="revokeApiKey" actionParam=key class="btn" label="user.revoke_access"}}
|
||||||
|
{{/if}}
|
||||||
|
<p><span>{{i18n "user.api_permissions"}}</span> {{#if key.write}}{{i18n "user.api_read_write"}}{{else}}{{i18n "user.api_read"}}{{/if}}</p>
|
||||||
|
<p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{plugin-outlet "user-custom-controls"}}
|
{{plugin-outlet "user-custom-controls"}}
|
||||||
|
|
||||||
<div class="control-group save-button">
|
<div class="control-group save-button">
|
||||||
|
@ -4,7 +4,7 @@ class UserApiKeysController < ApplicationController
|
|||||||
|
|
||||||
skip_before_filter :redirect_to_login_if_required, only: [:new]
|
skip_before_filter :redirect_to_login_if_required, only: [:new]
|
||||||
skip_before_filter :check_xhr, :preload_json
|
skip_before_filter :check_xhr, :preload_json
|
||||||
before_filter :ensure_logged_in, only: [:create]
|
before_filter :ensure_logged_in, only: [:create, :revoke, :undo_revoke]
|
||||||
|
|
||||||
def new
|
def new
|
||||||
require_params
|
require_params
|
||||||
@ -47,6 +47,9 @@ class UserApiKeysController < ApplicationController
|
|||||||
|
|
||||||
validate_params
|
validate_params
|
||||||
|
|
||||||
|
# destroy any old keys we had
|
||||||
|
UserApiKey.where(user_id: current_user.id, client_id: params[:client_id]).destroy_all
|
||||||
|
|
||||||
key = UserApiKey.create!(
|
key = UserApiKey.create!(
|
||||||
application_name: params[:application_name],
|
application_name: params[:application_name],
|
||||||
client_id: params[:client_id],
|
client_id: params[:client_id],
|
||||||
@ -72,6 +75,22 @@ class UserApiKeysController < ApplicationController
|
|||||||
redirect_to "#{params[:auth_redirect]}?payload=#{CGI.escape(payload)}"
|
redirect_to "#{params[:auth_redirect]}?payload=#{CGI.escape(payload)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def revoke
|
||||||
|
find_key.update_columns(revoked_at: Time.zone.now)
|
||||||
|
render json: success_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def undo_revoke
|
||||||
|
find_key.update_columns(revoked_at: nil)
|
||||||
|
render json: success_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_key
|
||||||
|
key = UserApiKey.find(params[:id])
|
||||||
|
raise Discourse::InvalidAccess unless current_user.admin || key.user_id = current_user.id
|
||||||
|
key
|
||||||
|
end
|
||||||
|
|
||||||
def require_params
|
def require_params
|
||||||
[
|
[
|
||||||
:public_key,
|
:public_key,
|
||||||
|
@ -19,6 +19,7 @@ class User < ActiveRecord::Base
|
|||||||
has_many :topic_users, dependent: :destroy
|
has_many :topic_users, dependent: :destroy
|
||||||
has_many :category_users, dependent: :destroy
|
has_many :category_users, dependent: :destroy
|
||||||
has_many :tag_users, dependent: :destroy
|
has_many :tag_users, dependent: :destroy
|
||||||
|
has_many :user_api_keys, dependent: :destroy
|
||||||
has_many :topics
|
has_many :topics
|
||||||
has_many :user_open_ids, dependent: :destroy
|
has_many :user_open_ids, dependent: :destroy
|
||||||
has_many :user_actions, dependent: :destroy
|
has_many :user_actions, dependent: :destroy
|
||||||
|
@ -102,7 +102,8 @@ class UserSerializer < BasicUserSerializer
|
|||||||
:card_image_badge_id,
|
:card_image_badge_id,
|
||||||
:muted_usernames,
|
:muted_usernames,
|
||||||
:mailing_list_posts_per_day,
|
:mailing_list_posts_per_day,
|
||||||
:can_change_bio
|
:can_change_bio,
|
||||||
|
:user_api_keys
|
||||||
|
|
||||||
untrusted_attributes :bio_raw,
|
untrusted_attributes :bio_raw,
|
||||||
:bio_cooked,
|
:bio_cooked,
|
||||||
@ -137,6 +138,21 @@ class UserSerializer < BasicUserSerializer
|
|||||||
!(SiteSetting.enable_sso && SiteSetting.sso_overrides_bio)
|
!(SiteSetting.enable_sso && SiteSetting.sso_overrides_bio)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def user_api_keys
|
||||||
|
keys = object.user_api_keys.where(revoked_at: nil).map do |k|
|
||||||
|
{
|
||||||
|
id: k.id,
|
||||||
|
application_name: k.application_name,
|
||||||
|
read: k.read,
|
||||||
|
write: k.write,
|
||||||
|
created_at: k.created_at
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
keys.length > 0 ? keys : nil
|
||||||
|
end
|
||||||
|
|
||||||
def card_badge
|
def card_badge
|
||||||
object.user_profile.card_image_badge
|
object.user_profile.card_image_badge
|
||||||
end
|
end
|
||||||
|
@ -571,6 +571,13 @@ en:
|
|||||||
muted_topics_link: "Show muted topics"
|
muted_topics_link: "Show muted topics"
|
||||||
watched_topics_link: "Show watched topics"
|
watched_topics_link: "Show watched topics"
|
||||||
automatically_unpin_topics: "Automatically unpin topics when I reach the bottom."
|
automatically_unpin_topics: "Automatically unpin topics when I reach the bottom."
|
||||||
|
apps: "Apps"
|
||||||
|
revoke_access: "Revoke Access"
|
||||||
|
undo_revoke_access: "Undo Revoke Access"
|
||||||
|
api_permissions: "Permissions:"
|
||||||
|
api_approved: "Approved:"
|
||||||
|
api_read: "read"
|
||||||
|
api_read_write: "read and write"
|
||||||
|
|
||||||
staff_counters:
|
staff_counters:
|
||||||
flags_given: "helpful flags"
|
flags_given: "helpful flags"
|
||||||
|
@ -664,6 +664,8 @@ Discourse::Application.routes.draw do
|
|||||||
|
|
||||||
get "/user-api-key/new" => "user_api_keys#new"
|
get "/user-api-key/new" => "user_api_keys#new"
|
||||||
post "/user-api-key" => "user_api_keys#create"
|
post "/user-api-key" => "user_api_keys#create"
|
||||||
|
post "/user-api-key/revoke" => "user_api_keys#revoke"
|
||||||
|
post "/user-api-key/undo-revoke" => "user_api_keys#undo_revoke"
|
||||||
|
|
||||||
get "*url", to: 'permalinks#show', constraints: PermalinkConstraint.new
|
get "*url", to: 'permalinks#show', constraints: PermalinkConstraint.new
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
class UserApiClientIdIsUnique < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
remove_index :user_api_keys, [:client_id]
|
||||||
|
add_index :user_api_keys, [:client_id], unique: true
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,5 @@
|
|||||||
|
class AddRevokedAtToUserApiKeys < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :user_api_keys, :revoked_at, :datetime
|
||||||
|
end
|
||||||
|
end
|
@ -177,7 +177,7 @@ class Auth::DefaultCurrentUserProvider
|
|||||||
protected
|
protected
|
||||||
|
|
||||||
def lookup_user_api_user(user_api_key)
|
def lookup_user_api_user(user_api_key)
|
||||||
if api_key = UserApiKey.where(key: user_api_key).includes(:user).first
|
if api_key = UserApiKey.where(key: user_api_key, revoked_at: nil).includes(:user).first
|
||||||
if !api_key.write && @env["REQUEST_METHOD"] != "GET"
|
if !api_key.write && @env["REQUEST_METHOD"] != "GET"
|
||||||
raise Discourse::InvalidAccess
|
raise Discourse::InvalidAccess
|
||||||
end
|
end
|
||||||
|
@ -127,6 +127,11 @@ TXT
|
|||||||
uri.query = ""
|
uri.query = ""
|
||||||
expect(uri.to_s).to eq(args[:auth_redirect] + "?")
|
expect(uri.to_s).to eq(args[:auth_redirect] + "?")
|
||||||
|
|
||||||
|
# should overwrite if needed
|
||||||
|
args["access"] = "pr"
|
||||||
|
post :create, args
|
||||||
|
|
||||||
|
expect(response.code).to eq("302")
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user