Support for per-user API keys

This commit is contained in:
Robin Ward
2013-10-22 15:53:08 -04:00
parent 5e2d8dcf37
commit 348e2e3ef2
45 changed files with 670 additions and 87 deletions
@@ -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>&nbsp;</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'>
&mdash;
</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>