FEATURE: basic UI to view user api keys

This commit is contained in:
Sam 2016-08-16 17:06:33 +10:00
parent b7cea24d76
commit 416e7e0d1e
12 changed files with 133 additions and 4 deletions

View File

@ -73,6 +73,46 @@ const User = RestModel.extend({
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) {
const userId = this.get('id');
const username = this.get('username_lower');

View File

@ -19,7 +19,15 @@ export default Discourse.Route.extend({
const isIndexStream = INDEX_STREAM_ROUTES.indexOf(transition.targetName) !== -1;
this.controllerFor('user').set('indexStream', isIndexStream);
return true;
}
},
undoRevokeApiKey(key) {
key.undoRevoke();
},
revokeApiKey(key) {
key.revoke();
},
},
beforeModel() {

View File

@ -319,6 +319,26 @@
</div>
{{/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"}}
<div class="control-group save-button">

View File

@ -4,7 +4,7 @@ class UserApiKeysController < ApplicationController
skip_before_filter :redirect_to_login_if_required, only: [:new]
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
require_params
@ -47,6 +47,9 @@ class UserApiKeysController < ApplicationController
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!(
application_name: params[:application_name],
client_id: params[:client_id],
@ -72,6 +75,22 @@ class UserApiKeysController < ApplicationController
redirect_to "#{params[:auth_redirect]}?payload=#{CGI.escape(payload)}"
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
[
:public_key,

View File

@ -19,6 +19,7 @@ class User < ActiveRecord::Base
has_many :topic_users, dependent: :destroy
has_many :category_users, dependent: :destroy
has_many :tag_users, dependent: :destroy
has_many :user_api_keys, dependent: :destroy
has_many :topics
has_many :user_open_ids, dependent: :destroy
has_many :user_actions, dependent: :destroy

View File

@ -102,7 +102,8 @@ class UserSerializer < BasicUserSerializer
:card_image_badge_id,
:muted_usernames,
:mailing_list_posts_per_day,
:can_change_bio
:can_change_bio,
:user_api_keys
untrusted_attributes :bio_raw,
:bio_cooked,
@ -137,6 +138,21 @@ class UserSerializer < BasicUserSerializer
!(SiteSetting.enable_sso && SiteSetting.sso_overrides_bio)
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
object.user_profile.card_image_badge
end

View File

@ -571,6 +571,13 @@ en:
muted_topics_link: "Show muted topics"
watched_topics_link: "Show watched topics"
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:
flags_given: "helpful flags"

View File

@ -664,6 +664,8 @@ Discourse::Application.routes.draw do
get "/user-api-key/new" => "user_api_keys#new"
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

View File

@ -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

View File

@ -0,0 +1,5 @@
class AddRevokedAtToUserApiKeys < ActiveRecord::Migration
def change
add_column :user_api_keys, :revoked_at, :datetime
end
end

View File

@ -177,7 +177,7 @@ class Auth::DefaultCurrentUserProvider
protected
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"
raise Discourse::InvalidAccess
end

View File

@ -127,6 +127,11 @@ TXT
uri.query = ""
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