Move token management to separate view for admins only

This commit is contained in:
Johannes Zellner
2016-06-07 22:53:36 +02:00
parent 3231fe7874
commit 3540f2c197
6 changed files with 303 additions and 283 deletions

View File

@@ -99,88 +99,6 @@
</div>
</div>
<!-- Modal add client -->
<div class="modal fade" id="clientAddModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Add API Client</h4>
</div>
<div class="modal-body">
<form name="clientAddForm" role="form" novalidate ng-submit="clientAdd.submit()" autocomplete="off">
<div class="form-group" ng-class="{ 'has-error': (clientAddForm.name.$dirty && clientAddForm.name.$invalid) || (!clientAddForm.name.$dirty && clientAdd.error.name) }">
<label class="control-label">Name</label>
<div class="control-label" ng-show="(!clientAddForm.name.$dirty && clientAdd.error.name) || (clientAddForm.name.$dirty && clientAddForm.name.$invalid)">
<small ng-show="clientAddForm.name.$error.required">A name is required</small>
<small ng-show="!clientAddForm.name.$dirty && clientAdd.error.name">{{ clientAdd.error.name }}</small>
</div>
<input type="text" class="form-control" ng-model="clientAdd.name" name="name" required autofocus>
</div>
<div class="form-group" ng-class="{ 'has-error': (clientAddForm.scope.$dirty && clientAddForm.scope.$invalid) || (!clientAddForm.scope.$dirty && clientAdd.error.scope) }">
<label class="control-label">Scope</label>
<div class="control-label" ng-show="(!clientAddForm.scope.$dirty && clientAdd.error.scope) || (clientAddForm.scope.$dirty && clientAddForm.scope.$invalid)">
<small ng-show="clientAddForm.scope.$error.required">A scope is required</small>
<small ng-show="!clientAddForm.scope.$dirty && clientAdd.error.scope">{{ clientAdd.error.scope }}</small>
</div>
<input type="text" class="form-control" ng-model="clientAdd.scope" name="scope" id="clientAddScope" placeholder="Specify any number of scope separated by a comma ','" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (clientAddForm.redirectURI.$dirty && clientAddForm.redirectURI.$invalid) || (!clientAddForm.redirectURI.$dirty && clientAdd.error.redirectURI) }">
<label class="control-label">Redirect URI</label>
<div class="control-label" ng-show="!clientAddForm.redirectURI.$dirty && clientAdd.error.redirectURI">
<small ng-show="!clientAddForm.redirectURI.$dirty && clientAdd.error.redirectURI">{{ clientAdd.error.redirectURI }}</small>
</div>
<input type="text" class="form-control" ng-model="clientAdd.redirectURI" name="redirectURI" id="clientAddRedirectURI" placeholder="Required for OAuth logins">
</div>
<input class="hide" type="submit" ng-disabled="clientAddForm.$invalid || clientAdd.busy"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" ng-click="clientAdd.submit()" ng-disabled="clientAddForm.$invalid || clientAdd.busy"><i class="fa fa-spinner fa-pulse" ng-show="clientAdd.busy"></i> Add API Client</button>
</div>
</div>
</div>
</div>
<!-- Modal remove client -->
<div class="modal fade" id="clientRemoveModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Remove API Client</h4>
</div>
<div class="modal-body">
<p>
Removing client <b>{{ clientRemove.client.appId }}</b> will also remove all access from scripts and apps using those credentials.
You may want to consult the other Cloudron admins first.
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" ng-click="clientRemove.submit()" ng-disabled="clientRemove.busy"><i class="fa fa-spinner fa-pulse" ng-show="clientRemove.busy"></i> Remove API Client</button>
</div>
</div>
</div>
</div>
<!-- Modal add token -->
<div class="modal fade" id="tokenAddModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">New token created</h4>
</div>
<div class="modal-body">
<p><b ng-click-select>{{ tokenAdd.token.accessToken }}</b></p>
<p>This token will not expire. It can only be revoked.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Done</button>
</div>
</div>
</div>
</div>
<br/>
<div class="section-header">
@@ -222,57 +140,5 @@
</div>
</div>
<br/>
<div class="section-header">
<div class="text-left">
<h3>Application and API Access <button class="btn btn-primary btn-outline pull-right" ng-click="clientAdd.show()"><i class="fa fa-plus"></i> New API Client</button></h3>
</div>
</div>
<br/>
<!-- we will always at least have the webadmin token here, so activeClients always will have one entry with at least one token -->
<div class="card" ng-repeat="client in activeClients | activeOAuthClients:user">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12">
<h4 class="text-muted">
{{client.name}} <span ng-show="client.location !== 'external'">on {{client.location}}{{ config.isCustomDomain ? '.' : '-' }}{{config.fqdn}}</span>
</h4>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12">
<b>{{ client.activeTokens.length }}</b> tokens are active for this application.
<button class="btn btn-xs btn-default pull-right" ng-click="removeAccessTokens(client)" ng-disabled="!client.activeTokens.length || client.busy"><i class="fa fa-spinner fa-pulse" ng-show="client.busy"></i> Revoke access</button>
<br/>
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse{{client.id}}" ng-show="user.admin">Advanced</a>
<div id="collapse{{client.id}}" class="panel-collapse collapse">
<div class="panel-body">
<hr/>
<h4 class="text-muted">Credentials <button class="btn btn-xs btn-danger pull-right" ng-click="clientRemove.show(client)" title="Remove API Client" ng-show="client.location === 'external'">Remove API Client</button></h4>
<p>Scope: <b ng-click-select>{{ client.scope }}</b></p>
<p>RedirectURI: <b ng-click-select>{{ client.redirectURI }}</b></p>
<p>Client ID: <b ng-click-select>{{ client.id }}</b></p>
<p ng-show="client.clientSecret">Client Secret: <b ng-click-select>{{ client.clientSecret }}</b></p>
<hr/>
<h4 class="text-muted">Your Tokens <button class="btn btn-xs btn-primary btn-outline pull-right" ng-click="tokenAdd.show(client)"><i class="fa fa-plus"></i> New Token</button></h4>
<p ng-repeat="token in client.activeTokens">
<b ng-click-select>{{ token.accessToken }}</b> <button class="btn btn-xs btn-danger pull-right" ng-click="removeToken(client, token)" title="Revoke Token"><i class="fa fa-trash-o"></i></button>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Offset the footer -->
<br/><br/>

View File

@@ -1,12 +1,9 @@
'use strict';
angular.module('Application').controller('AccountController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
angular.module('Application').controller('AccountController', ['$scope', 'Client', function ($scope, Client) {
$scope.user = Client.getUserInfo();
$scope.config = Client.getConfig();
$scope.activeClients = [];
$scope.tokenInUse = null;
$scope.passwordchange = {
busy: false,
error: {},
@@ -156,128 +153,6 @@ angular.module('Application').controller('AccountController', ['$scope', '$locat
}
};
$scope.clientAdd = {
busy: false,
error: {},
name: '',
scope: '',
redirectURI: '',
show: function () {
$scope.clientAdd.busy = false;
$scope.clientAdd.error = {};
$scope.clientAdd.name = '';
$scope.clientAdd.scope = '*';
$scope.clientAdd.redirectURI = '';
$scope.clientAddForm.$setUntouched();
$scope.clientAddForm.$setPristine();
$('#clientAddModal').modal('show');
},
submit: function () {
$scope.clientAdd.busy = true;
$scope.clientAdd.error = {};
var CLIENT_REDIRECT_URI_FALLBACK = Client.apiOrigin;
Client.createOAuthClient($scope.clientAdd.name, $scope.clientAdd.scope, $scope.clientAdd.redirectURI || CLIENT_REDIRECT_URI_FALLBACK, function (error) {
$scope.clientAdd.busy = false;
if (error && error.statusCode === 400) {
if (error.message.indexOf('redirectURI must be a valid uri') === 0) {
$scope.clientAdd.error.redirectURI = error.message;
$scope.clientAddForm.redirectURI.$setPristine();
$('#clientAddRedirectURI').focus();
} else if (error.message.indexOf('Invalid scope') === 0) {
$scope.clientAdd.error.scope = error.message;
$scope.clientAddForm.scope.$setPristine();
$('#clientAddScope').focus();
} else {
console.error(error);
}
return;
}
if (error) return console.error('Unable to create API client.', error.statusCode, error.message);
refresh();
$('#clientAddModal').modal('hide');
});
}
};
$scope.clientRemove = {
busy: false,
client: {},
show: function (client) {
$scope.clientRemove.busy = false;
$scope.clientRemove.client = client;
$('#clientRemoveModal').modal('show');
},
submit: function () {
$scope.clientRemove.busy = true;
Client.delOAuthClient($scope.clientRemove.client.id, function (error) {
if (error) console.error(error);
$scope.clientRemove.busy = false;
$scope.clientRemove.client = {};
refresh();
$('#clientRemoveModal').modal('hide');
});
}
};
$scope.tokenAdd = {
busy: false,
token: {},
show: function (client) {
$scope.tokenAdd.busy = true;
$scope.tokenAdd.token = {};
var expiresAt = Date.now() + 100 * 365 * 24 * 60 * 60 * 1000; // ~100 years from now
Client.createTokenByClientId(client.id, expiresAt, function (error, result) {
if (error) console.error(error);
$scope.tokenAdd.busy = false;
$scope.tokenAdd.token = result;
$('#tokenAddModal').modal('show');
refreshClientTokens(client);
});
}
};
$scope.removeToken = function (client, token) {
Client.delToken(client.id, token.accessToken, function (error) {
if (error) console.error(error);
refreshClientTokens(client);
});
};
$scope.removeAccessTokens = function (client) {
client.busy = true;
Client.delTokensByClientId(client.id, function (error) {
if (error) console.error(error);
client.busy = false;
refreshClientTokens(client);
});
};
$scope.showTutorial = function () {
Client.setShowTutorial(true, function (error) {
if (error) return console.error(error);
@@ -285,30 +160,8 @@ angular.module('Application').controller('AccountController', ['$scope', '$locat
});
};
function refreshClientTokens(client) {
Client.getTokensByClientId(client.id, function (error, result) {
if (error) console.error(error);
client.activeTokens = result || [];
});
}
function refresh() {
$scope.tokenInUse = Client._token;
Client.getOAuthClients(function (error, activeClients) {
if (error) return console.error(error);
$scope.activeClients = activeClients;
$scope.activeClients.forEach(refreshClientTokens);
});
}
Client.onReady(refresh);
// setup all the dialog focus handling
['passwordChangeModal', 'emailChangeModal', 'displayNameChangeModal', 'clientAddModal'].forEach(function (id) {
['passwordChangeModal', 'emailChangeModal', 'displayNameChangeModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find("[autofocus]:first").focus();
});

View File

@@ -0,0 +1,137 @@
<!-- Modal add client -->
<div class="modal fade" id="clientAddModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Add API Client</h4>
</div>
<div class="modal-body">
<form name="clientAddForm" role="form" novalidate ng-submit="clientAdd.submit()" autocomplete="off">
<div class="form-group" ng-class="{ 'has-error': (clientAddForm.name.$dirty && clientAddForm.name.$invalid) || (!clientAddForm.name.$dirty && clientAdd.error.name) }">
<label class="control-label">Name</label>
<div class="control-label" ng-show="(!clientAddForm.name.$dirty && clientAdd.error.name) || (clientAddForm.name.$dirty && clientAddForm.name.$invalid)">
<small ng-show="clientAddForm.name.$error.required">A name is required</small>
<small ng-show="!clientAddForm.name.$dirty && clientAdd.error.name">{{ clientAdd.error.name }}</small>
</div>
<input type="text" class="form-control" ng-model="clientAdd.name" name="name" required autofocus>
</div>
<div class="form-group" ng-class="{ 'has-error': (clientAddForm.scope.$dirty && clientAddForm.scope.$invalid) || (!clientAddForm.scope.$dirty && clientAdd.error.scope) }">
<label class="control-label">Scope</label>
<div class="control-label" ng-show="(!clientAddForm.scope.$dirty && clientAdd.error.scope) || (clientAddForm.scope.$dirty && clientAddForm.scope.$invalid)">
<small ng-show="clientAddForm.scope.$error.required">A scope is required</small>
<small ng-show="!clientAddForm.scope.$dirty && clientAdd.error.scope">{{ clientAdd.error.scope }}</small>
</div>
<input type="text" class="form-control" ng-model="clientAdd.scope" name="scope" id="clientAddScope" placeholder="Specify any number of scope separated by a comma ','" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (clientAddForm.redirectURI.$dirty && clientAddForm.redirectURI.$invalid) || (!clientAddForm.redirectURI.$dirty && clientAdd.error.redirectURI) }">
<label class="control-label">Redirect URI</label>
<div class="control-label" ng-show="!clientAddForm.redirectURI.$dirty && clientAdd.error.redirectURI">
<small ng-show="!clientAddForm.redirectURI.$dirty && clientAdd.error.redirectURI">{{ clientAdd.error.redirectURI }}</small>
</div>
<input type="text" class="form-control" ng-model="clientAdd.redirectURI" name="redirectURI" id="clientAddRedirectURI" placeholder="Only required if OAuth logins are used">
</div>
<input class="hide" type="submit" ng-disabled="clientAddForm.$invalid || clientAdd.busy"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" ng-click="clientAdd.submit()" ng-disabled="clientAddForm.$invalid || clientAdd.busy"><i class="fa fa-spinner fa-pulse" ng-show="clientAdd.busy"></i> Add API Client</button>
</div>
</div>
</div>
</div>
<!-- Modal remove client -->
<div class="modal fade" id="clientRemoveModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Remove API Client</h4>
</div>
<div class="modal-body">
<p>
Removing client <b>{{ clientRemove.client.appId }}</b> will also remove all access from scripts and apps using those credentials.
You may want to consult the other Cloudron admins first.
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" ng-click="clientRemove.submit()" ng-disabled="clientRemove.busy"><i class="fa fa-spinner fa-pulse" ng-show="clientRemove.busy"></i> Remove API Client</button>
</div>
</div>
</div>
</div>
<!-- Modal add token -->
<div class="modal fade" id="tokenAddModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">New token created</h4>
</div>
<div class="modal-body">
<p><b ng-click-select>{{ tokenAdd.token.accessToken }}</b></p>
<p>This token will not expire. It can only be revoked.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Done</button>
</div>
</div>
</div>
</div>
<br/>
<div class="section-header">
<div class="text-left">
<h3>Application and API Access <button class="btn btn-primary btn-outline pull-right" ng-click="clientAdd.show()"><i class="fa fa-plus"></i> New API Client</button></h3>
</div>
</div>
<br/>
<!-- we will always at least have the webadmin token here, so activeClients always will have one entry with at least one token -->
<div class="card" ng-repeat="client in activeClients | activeOAuthClients:user">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12">
<h4 class="text-muted">
{{client.name}} <span ng-show="client.location !== 'external'">on {{client.location}}{{ config.isCustomDomain ? '.' : '-' }}{{config.fqdn}}</span>
</h4>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12">
<b>{{ client.activeTokens.length }}</b> tokens are active for this application.
<button class="btn btn-xs btn-default pull-right" ng-click="removeAccessTokens(client)" ng-disabled="!client.activeTokens.length || client.busy"><i class="fa fa-spinner fa-pulse" ng-show="client.busy"></i> Revoke access</button>
<br/>
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse{{client.id}}" ng-show="user.admin">Advanced</a>
<div id="collapse{{client.id}}" class="panel-collapse collapse">
<div class="panel-body">
<hr/>
<h4 class="text-muted">Credentials <button class="btn btn-xs btn-danger pull-right" ng-click="clientRemove.show(client)" title="Remove API Client" ng-show="client.location === 'external'">Remove API Client</button></h4>
<p>Scope: <b ng-click-select>{{ client.scope }}</b></p>
<p>RedirectURI: <b ng-click-select>{{ client.redirectURI }}</b></p>
<p>Client ID: <b ng-click-select>{{ client.id }}</b></p>
<p ng-show="client.clientSecret">Client Secret: <b ng-click-select>{{ client.clientSecret }}</b></p>
<hr/>
<h4 class="text-muted">Your Tokens <button class="btn btn-xs btn-primary btn-outline pull-right" ng-click="tokenAdd.show(client)"><i class="fa fa-plus"></i> New Token</button></h4>
<p ng-repeat="token in client.activeTokens">
<b ng-click-select>{{ token.accessToken }}</b> <button class="btn btn-xs btn-danger pull-right" ng-click="removeToken(client, token)" title="Revoke Token"><i class="fa fa-trash-o"></i></button>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Offset the footer -->
<br/><br/>

View File

@@ -0,0 +1,160 @@
'use strict';
angular.module('Application').controller('TokensController', ['$scope', 'Client', function ($scope, Client) {
$scope.user = Client.getUserInfo();
$scope.config = Client.getConfig();
$scope.activeClients = [];
$scope.tokenInUse = null;
$scope.clientAdd = {
busy: false,
error: {},
name: '',
scope: '',
redirectURI: '',
show: function () {
$scope.clientAdd.busy = false;
$scope.clientAdd.error = {};
$scope.clientAdd.name = '';
$scope.clientAdd.scope = '*';
$scope.clientAdd.redirectURI = '';
$scope.clientAddForm.$setUntouched();
$scope.clientAddForm.$setPristine();
$('#clientAddModal').modal('show');
},
submit: function () {
$scope.clientAdd.busy = true;
$scope.clientAdd.error = {};
var CLIENT_REDIRECT_URI_FALLBACK = Client.apiOrigin;
Client.createOAuthClient($scope.clientAdd.name, $scope.clientAdd.scope, $scope.clientAdd.redirectURI || CLIENT_REDIRECT_URI_FALLBACK, function (error) {
$scope.clientAdd.busy = false;
if (error && error.statusCode === 400) {
if (error.message.indexOf('redirectURI must be a valid uri') === 0) {
$scope.clientAdd.error.redirectURI = error.message;
$scope.clientAddForm.redirectURI.$setPristine();
$('#clientAddRedirectURI').focus();
} else if (error.message.indexOf('Invalid scope') === 0) {
$scope.clientAdd.error.scope = error.message;
$scope.clientAddForm.scope.$setPristine();
$('#clientAddScope').focus();
} else {
console.error(error);
}
return;
}
if (error) return console.error('Unable to create API client.', error.statusCode, error.message);
refresh();
$('#clientAddModal').modal('hide');
});
}
};
$scope.clientRemove = {
busy: false,
client: {},
show: function (client) {
$scope.clientRemove.busy = false;
$scope.clientRemove.client = client;
$('#clientRemoveModal').modal('show');
},
submit: function () {
$scope.clientRemove.busy = true;
Client.delOAuthClient($scope.clientRemove.client.id, function (error) {
if (error) console.error(error);
$scope.clientRemove.busy = false;
$scope.clientRemove.client = {};
refresh();
$('#clientRemoveModal').modal('hide');
});
}
};
$scope.tokenAdd = {
busy: false,
token: {},
show: function (client) {
$scope.tokenAdd.busy = true;
$scope.tokenAdd.token = {};
var expiresAt = Date.now() + 100 * 365 * 24 * 60 * 60 * 1000; // ~100 years from now
Client.createTokenByClientId(client.id, expiresAt, function (error, result) {
if (error) console.error(error);
$scope.tokenAdd.busy = false;
$scope.tokenAdd.token = result;
$('#tokenAddModal').modal('show');
refreshClientTokens(client);
});
}
};
$scope.removeToken = function (client, token) {
Client.delToken(client.id, token.accessToken, function (error) {
if (error) console.error(error);
refreshClientTokens(client);
});
};
$scope.removeAccessTokens = function (client) {
client.busy = true;
Client.delTokensByClientId(client.id, function (error) {
if (error) console.error(error);
client.busy = false;
refreshClientTokens(client);
});
};
function refreshClientTokens(client) {
Client.getTokensByClientId(client.id, function (error, result) {
if (error) console.error(error);
client.activeTokens = result || [];
});
}
function refresh() {
$scope.tokenInUse = Client._token;
Client.getOAuthClients(function (error, activeClients) {
if (error) return console.error(error);
$scope.activeClients = activeClients;
$scope.activeClients.forEach(refreshClientTokens);
});
}
Client.onReady(refresh);
// setup all the dialog focus handling
['clientAddModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find("[autofocus]:first").focus();
});
});
}]);