diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6
index 932454aa07c..ff0ae124de1 100644
--- a/app/assets/javascripts/discourse/models/user.js.es6
+++ b/app/assets/javascripts/discourse/models/user.js.es6
@@ -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');
diff --git a/app/assets/javascripts/discourse/routes/user.js.es6 b/app/assets/javascripts/discourse/routes/user.js.es6
index fdfb99854da..03bfbb401b0 100644
--- a/app/assets/javascripts/discourse/routes/user.js.es6
+++ b/app/assets/javascripts/discourse/routes/user.js.es6
@@ -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() {
diff --git a/app/assets/javascripts/discourse/templates/user/preferences.hbs b/app/assets/javascripts/discourse/templates/user/preferences.hbs
index 660f74c501a..928fbf900ca 100644
--- a/app/assets/javascripts/discourse/templates/user/preferences.hbs
+++ b/app/assets/javascripts/discourse/templates/user/preferences.hbs
@@ -319,6 +319,26 @@
{{/if}}
+ {{#if model.userApiKeys}}
+
+
+
+ {{#each model.userApiKeys as |key|}}
+
+
{{key.application_name}}
+ {{#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}}
+
{{i18n "user.api_permissions"}} {{#if key.write}}{{i18n "user.api_read_write"}}{{else}}{{i18n "user.api_read"}}{{/if}}
+
{{i18n "user.api_approved"}} {{bound-date key.created_at}}
+
+ {{/each}}
+
+
+ {{/if}}
+
{{plugin-outlet "user-custom-controls"}}
diff --git a/app/controllers/user_api_keys_controller.rb b/app/controllers/user_api_keys_controller.rb
index cfa80de4651..59140a3ced7 100644
--- a/app/controllers/user_api_keys_controller.rb
+++ b/app/controllers/user_api_keys_controller.rb
@@ -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,
diff --git a/app/models/user.rb b/app/models/user.rb
index 34b178cbcd3..ee5f4e3fa15 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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
diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb
index 673266f5818..48ddfa89ccf 100644
--- a/app/serializers/user_serializer.rb
+++ b/app/serializers/user_serializer.rb
@@ -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
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 7a947239b0b..012a54c5d8f 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -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"
diff --git a/config/routes.rb b/config/routes.rb
index dd3b1e4a02c..30d47a8fad7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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
diff --git a/db/migrate/20160816052836_user_api_client_id_is_unique.rb b/db/migrate/20160816052836_user_api_client_id_is_unique.rb
new file mode 100644
index 00000000000..59b3dc4b354
--- /dev/null
+++ b/db/migrate/20160816052836_user_api_client_id_is_unique.rb
@@ -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
diff --git a/db/migrate/20160816063534_add_revoked_at_to_user_api_keys.rb b/db/migrate/20160816063534_add_revoked_at_to_user_api_keys.rb
new file mode 100644
index 00000000000..30c252a6e86
--- /dev/null
+++ b/db/migrate/20160816063534_add_revoked_at_to_user_api_keys.rb
@@ -0,0 +1,5 @@
+class AddRevokedAtToUserApiKeys < ActiveRecord::Migration
+ def change
+ add_column :user_api_keys, :revoked_at, :datetime
+ end
+end
diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb
index dc74a30005e..e0d96aea08e 100644
--- a/lib/auth/default_current_user_provider.rb
+++ b/lib/auth/default_current_user_provider.rb
@@ -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
diff --git a/spec/controllers/user_api_keys_controller_spec.rb b/spec/controllers/user_api_keys_controller_spec.rb
index b4c86b10f0a..60eb2910624 100644
--- a/spec/controllers/user_api_keys_controller_spec.rb
+++ b/spec/controllers/user_api_keys_controller_spec.rb
@@ -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