Support for per-user API keys
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
This controller supports the interface for dealing with API keys
|
||||
|
||||
@class AdminApiController
|
||||
@extends Ember.ArrayController
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AdminApiController = Ember.ArrayController.extend({
|
||||
|
||||
actions: {
|
||||
/**
|
||||
Generates a master api key
|
||||
|
||||
@method generateMasterKey
|
||||
@param {Discourse.ApiKey} the key to regenerate
|
||||
**/
|
||||
generateMasterKey: function(key) {
|
||||
var self = this;
|
||||
Discourse.ApiKey.generateMasterKey().then(function (key) {
|
||||
self.get('model').pushObject(key);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Creates an API key instance with internal user object
|
||||
|
||||
@method regenerateKey
|
||||
@param {Discourse.ApiKey} the key to regenerate
|
||||
**/
|
||||
regenerateKey: function(key) {
|
||||
bootbox.confirm(I18n.t("admin.api.confirm_regen"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
if (result) {
|
||||
key.regenerate();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Revokes an API key
|
||||
|
||||
@method revokeKey
|
||||
@param {Discourse.ApiKey} the key to revoke
|
||||
**/
|
||||
revokeKey: function(key) {
|
||||
var self = this;
|
||||
bootbox.confirm(I18n.t("admin.api.confirm_revoke"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
if (result) {
|
||||
key.revoke().then(function() {
|
||||
self.get('model').removeObject(key);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Has a master key already been generated?
|
||||
|
||||
@property hasMasterKey
|
||||
@type {Boolean}
|
||||
**/
|
||||
hasMasterKey: function() {
|
||||
return !!this.get('model').findBy('user', null);
|
||||
}.property('model.@each')
|
||||
|
||||
});
|
||||
@@ -27,6 +27,28 @@ Discourse.AdminUserController = Discourse.ObjectController.extend({
|
||||
});
|
||||
|
||||
this.send('toggleTitleEdit');
|
||||
},
|
||||
|
||||
generateApiKey: function() {
|
||||
this.get('model').generateApiKey();
|
||||
},
|
||||
|
||||
regenerateApiKey: function() {
|
||||
var self = this;
|
||||
bootbox.confirm(I18n.t("admin.api.confirm_regen"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
if (result) {
|
||||
self.get('model').generateApiKey();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
revokeApiKey: function() {
|
||||
var self = this;
|
||||
bootbox.confirm(I18n.t("admin.api.confirm_revoke"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
if (result) {
|
||||
self.get('model').revokeApiKey();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
Discourse.AdminApi = Discourse.Model.extend({
|
||||
VALID_KEY_LENGTH: 64,
|
||||
|
||||
keyExists: function(){
|
||||
var key = this.get('key') || '';
|
||||
return key && key.length === this.VALID_KEY_LENGTH;
|
||||
}.property('key'),
|
||||
|
||||
generateKey: function(){
|
||||
var adminApi = this;
|
||||
Discourse.ajax('/admin/api/generate_key', {type: 'POST'}).then(function (result) {
|
||||
adminApi.set('key', result.key);
|
||||
});
|
||||
},
|
||||
|
||||
regenerateKey: function(){
|
||||
alert(I18n.t('not_implemented'));
|
||||
}
|
||||
});
|
||||
|
||||
Discourse.AdminApi.reopenClass({
|
||||
find: function() {
|
||||
var model = Discourse.AdminApi.create();
|
||||
Discourse.ajax("/admin/api").then(function(data) {
|
||||
model.setProperties(data);
|
||||
});
|
||||
return model;
|
||||
}
|
||||
});
|
||||
@@ -8,6 +8,34 @@
|
||||
**/
|
||||
Discourse.AdminUser = Discourse.User.extend({
|
||||
|
||||
/**
|
||||
Generates an API key for the user. Will regenerate if they already have one.
|
||||
|
||||
@method generateApiKey
|
||||
@returns {Promise} a promise that resolves to the newly generated API key
|
||||
**/
|
||||
generateApiKey: function() {
|
||||
var self = this;
|
||||
return Discourse.ajax("/admin/users/" + this.get('id') + "/generate_api_key", {type: 'POST'}).then(function (result) {
|
||||
var apiKey = Discourse.ApiKey.create(result.api_key);
|
||||
self.set('api_key', apiKey);
|
||||
return apiKey;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Revokes a user's current API key
|
||||
|
||||
@method revokeApiKey
|
||||
@returns {Promise} a promise that resolves when the API key has been deleted
|
||||
**/
|
||||
revokeApiKey: function() {
|
||||
var self = this;
|
||||
return Discourse.ajax("/admin/users/" + this.get('id') + "/revoke_api_key", {type: 'DELETE'}).then(function (result) {
|
||||
self.set('api_key', null);
|
||||
});
|
||||
},
|
||||
|
||||
deleteAllPosts: function() {
|
||||
this.set('can_delete_all_posts', false);
|
||||
var user = this;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
Our data model for representing an API key in the system
|
||||
|
||||
@class ApiKey
|
||||
@extends Discourse.Model
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.ApiKey = Discourse.Model.extend({
|
||||
|
||||
/**
|
||||
Regenerates the api key
|
||||
|
||||
@method regenerate
|
||||
@returns {Promise} a promise that resolves to the key
|
||||
**/
|
||||
regenerate: function() {
|
||||
var self = this;
|
||||
return Discourse.ajax('/admin/api/key', {type: 'PUT', data: {id: this.get('id')}}).then(function (result) {
|
||||
self.set('key', result.api_key.key);
|
||||
return self;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Revokes the current key
|
||||
|
||||
@method revoke
|
||||
@returns {Promise} a promise that resolves when the key has been revoked
|
||||
**/
|
||||
revoke: function() {
|
||||
var self = this;
|
||||
return Discourse.ajax('/admin/api/key', {type: 'DELETE', data: {id: this.get('id')}});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Discourse.ApiKey.reopenClass({
|
||||
|
||||
/**
|
||||
Creates an API key instance with internal user object
|
||||
|
||||
@method create
|
||||
@param {Object} the properties to create
|
||||
@returns {Discourse.ApiKey} the ApiKey instance
|
||||
**/
|
||||
create: function(apiKey) {
|
||||
var result = this._super(apiKey);
|
||||
if (result.user) {
|
||||
result.user = Discourse.AdminUser.create(result.user);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
Finds a list of API keys
|
||||
|
||||
@method find
|
||||
@returns {Promise} a promise that resolves to the array of `Discourse.ApiKey` instances
|
||||
**/
|
||||
find: function() {
|
||||
return Discourse.ajax("/admin/api").then(function(keys) {
|
||||
return keys.map(function (key) {
|
||||
return Discourse.ApiKey.create(key);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Generates a master api key and returns it.
|
||||
|
||||
@method generateMasterKey
|
||||
@returns {Promise} a promise that resolves to a master `Discourse.ApiKey`
|
||||
**/
|
||||
generateMasterKey: function() {
|
||||
return Discourse.ajax("/admin/api/key", {type: 'POST'}).then(function (result) {
|
||||
return Discourse.ApiKey.create(result.api_key);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -9,7 +9,7 @@
|
||||
Discourse.AdminApiRoute = Discourse.Route.extend({
|
||||
|
||||
model: function() {
|
||||
return Discourse.AdminApi.find();
|
||||
return Discourse.ApiKey.find();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -1,13 +1,33 @@
|
||||
<h3>{{i18n admin.api.long_title}}</h3>
|
||||
{{#if keyExists}}
|
||||
<strong>{{i18n admin.api.key}}:</strong> {{key}}
|
||||
<button class='btn' {{action regenerateKey target="model"}}>
|
||||
{{i18n admin.api.regenerate}}
|
||||
</button>
|
||||
<p>{{{i18n admin.api.note_html}}}</p>
|
||||
{{#if model}}
|
||||
<table class='api-keys'>
|
||||
<tr>
|
||||
<th>{{i18n admin.api.key}}</th>
|
||||
<th>{{i18n admin.api.user}}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{{#each model}}
|
||||
<tr>
|
||||
<td class='key'>{{key}}</td>
|
||||
<td>
|
||||
{{#if user}}
|
||||
{{#link-to 'adminUser' user}}
|
||||
{{avatar user imageSize="small"}}
|
||||
{{/link-to}}
|
||||
{{else}}
|
||||
{{i18n admin.api.all_users}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
<button class='btn' {{action regenerateKey this}}>{{i18n admin.api.regenerate}}</button>
|
||||
<button class='btn' {{action revokeKey this}}>{{i18n admin.api.revoke}}</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
{{else}}
|
||||
<p>{{{i18n admin.api.info_html}}}</p>
|
||||
<button class='btn' {{action generateKey target="model"}}>
|
||||
{{i18n admin.api.generate}}
|
||||
</button>
|
||||
<p>{{i18n admin.api.none}}</p>
|
||||
{{/if}}
|
||||
|
||||
{{#unless hasMasterKey}}
|
||||
<button class='btn' {{action generateMasterKey}}>{{i18n admin.api.generate_master}}</button>
|
||||
{{/unless }}
|
||||
|
||||
@@ -125,6 +125,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n admin.api.key}}</div>
|
||||
|
||||
{{#if api_key}}
|
||||
<div class='long-value'>
|
||||
{{api_key.key}}
|
||||
<button class='btn' {{action regenerateApiKey}}>{{i18n admin.api.regenerate}}</button>
|
||||
<button {{action revokeApiKey}} class="btn">{{i18n admin.api.revoke}}</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class='value'>
|
||||
—
|
||||
</div>
|
||||
<div class='controls'>
|
||||
<button {{action generateApiKey}} class="btn">{{i18n admin.api.generate}}</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n admin.user.admin}}</div>
|
||||
<div class='value'>{{admin}}</div>
|
||||
|
||||
Reference in New Issue
Block a user