org user invites and confirmation

This commit is contained in:
Kyle Spearrin 2017-03-04 20:41:45 -05:00
parent b36799bf0c
commit a9e85f8765
9 changed files with 170 additions and 6 deletions

View File

@ -22,6 +22,24 @@ angular
$(document).off('click', '.sidebar li a'); $(document).off('click', '.sidebar li a');
} }
$('.table-responsive').on('shown.bs.dropdown', function (e) {
var t = $(this),
m = $(e.target).find('.dropdown-menu'),
tb = t.offset().top + t.height(),
mb = m.offset().top + m.outerHeight(true),
d = 20; // Space for shadow + scrollbar.
if (t[0].scrollWidth > t.innerWidth()) {
if (mb + d > tb) {
t.css('padding-bottom', ((mb + d) - tb));
}
}
else {
t.css('overflow', 'visible');
}
}).on('hidden.bs.dropdown', function () {
$(this).css({ 'padding-bottom': '', 'overflow': '' });
});
}); });
$scope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) { $scope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {

View File

@ -1,6 +1,52 @@
angular angular
.module('bit.organization') .module('bit.organization')
.controller('organizationPeopleController', function ($scope) { .controller('organizationPeopleController', function ($scope, $state, $uibModal, cryptoService, apiService, toastr) {
$scope.users = [];
loadList();
$scope.confirm = function (user) {
apiService.users.getPublicKey({ id: user.userId }, function (userKey) {
var key = cryptoService.rsaEncrypt('org key', userKey.PublicKey);
apiService.organizationUsers.confirm({ orgId: $state.params.orgId, id: user.id }, { key: key }, function () {
user.status = 2;
toastr.success(user.email + ' has been confirmed.', 'User Confirmed');
}, function () {
toastr.error('Unable to confirm user.', 'Error');
});
}, function () {
toastr.error('Unable to confirm user.', 'Error');
});
};
$scope.invite = function () {
var modal = $uibModal.open({
animation: true,
templateUrl: 'app/organization/views/organizationPeopleInvite.html',
controller: 'organizationPeopleInviteController'
});
modal.result.then(function () {
loadList();
});
};
function loadList() {
apiService.organizationUsers.list({ orgId: $state.params.orgId }, function (list) {
var users = [];
for (var i = 0; i < list.Data.length; i++) {
users.push({
id: list.Data[i].Id,
userId: list.Data[i].UserId,
name: list.Data[i].Name,
email: list.Data[i].Email,
status: list.Data[i].Status,
type: list.Data[i].Type
});
}
$scope.users = users;
});
}
}); });

View File

@ -0,0 +1,14 @@
angular
.module('bit.organization')
.controller('organizationPeopleInviteController', function ($scope, $state, $uibModalInstance, apiService) {
$scope.submit = function (model) {
apiService.organizationUsers.invite({ orgId: $state.params.orgId }, { email: model.email }, function () {
$uibModalInstance.close();
});
};
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@ -7,15 +7,53 @@
<section class="content"> <section class="content">
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">Users</h3> <h3 class="box-title">Organization Users</h3>
<div class="box-tools"> <div class="box-tools">
<button type="button" class="btn btn-primary btn-sm"> <button type="button" class="btn btn-primary btn-sm btn-flat" ng-click="invite()">
Invite user Invite user
</button> </button>
</div> </div>
</div> </div>
<div class="box-body"> <div class="box-body" ng-class="{'no-padding': users.length}">
Some data <div ng-show="!users.length">
Loading...
</div>
<div class="table-responsive" ng-show="users.length">
<table class="table table-striped table-hover">
<tbody>
<tr ng-repeat="user in users | orderBy: ['name', 'email']">
<td style="width: 70px;" valign="middle">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-cog"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<a href="javascript:void(0)" ng-click="confirm(user)">Confirm</a>
</li>
<li><a href="#">Re-send Invitation</a></li>
<li><a href="#" class="text-danger">Remove</a></li>
</ul>
</div>
</td>
<td style="width: 45px;" valign="middle">
<img src="//www.gravatar.com/avatar/{{user.email | gravatar}}.jpg?s=45&d=mm"
class="img-circle" alt="User Image">
</td>
<td valign="middle">
{{user.email}}
<div ng-if="user.name"><small class="text-muted">{{user.name}}</small></div>
</td>
<td style="width: 80px;" valign="middle">
{{user.type}}
</td>
<td style="width: 80px;" valign="middle">
{{user.status}}
</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@ -0,0 +1,29 @@
<div class="modal-header">
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"><i class="fa fa-user"></i> Invite User</h4>
</div>
<form name="inviteForm" ng-submit="inviteForm.$valid && submit(model)" api-form="submitPromise">
<div class="modal-body">
<p>
Invite a new user to your organization by entering their bitwarden account email address below. Users must already
be registered with bitwarden before you can invite them to your organization.
</p>
<div class="callout callout-danger validation-errors" ng-show="inviteForm.$errors">
<h4>Errors have occured</h4>
<ul>
<li ng-repeat="e in inviteForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group" show-errors>
<label for="email">Email</label>
<input type="email" id="email" name="Email" ng-model="model.email" class="form-control" required api-field />
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="inviteForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="inviteForm.$loading"></i>Send Invite
</button>
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div>
</form>

View File

@ -9,7 +9,7 @@
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">Subvaults</h3> <h3 class="box-title">Subvaults</h3>
<div class="box-tools"> <div class="box-tools">
<button type="button" class="btn btn-primary btn-sm"> <button type="button" class="btn btn-primary btn-sm btn-flat">
New subvault New subvault
</button> </button>
</div> </div>

View File

@ -38,6 +38,15 @@
del: { url: _apiUri + '/organizations/:id/delete', method: 'POST', params: { id: '@id' } } del: { url: _apiUri + '/organizations/:id/delete', method: 'POST', params: { id: '@id' } }
}); });
_service.organizationUsers = $resource(_apiUri + '/organizations/:orgId/users/:id', {}, {
get: { method: 'GET', params: { id: '@id', orgId: '@orgId' } },
list: { method: 'GET', params: { orgId: '@orgId' } },
invite: { url: _apiUri + '/organizations/:orgId/users/invite', method: 'POST', params: { orgId: '@orgId' } },
accept: { url: _apiUri + '/organizations/:orgId/users/:id/accept', method: 'POST', params: { id: '@id', orgId: '@orgId' } },
confirm: { url: _apiUri + '/organizations/:orgId/users/:id/confirm', method: 'POST', params: { id: '@id', orgId: '@orgId' } },
del: { url: _apiUri + '/organizations/:orgId/users/:id/delete', method: 'POST', params: { id: '@id', orgId: '@orgId' } }
});
_service.accounts = $resource(_apiUri + '/accounts', {}, { _service.accounts = $resource(_apiUri + '/accounts', {}, {
register: { url: _apiUri + '/accounts/register', method: 'POST', params: {} }, register: { url: _apiUri + '/accounts/register', method: 'POST', params: {} },
emailToken: { url: _apiUri + '/accounts/email-token', method: 'POST', params: {} }, emailToken: { url: _apiUri + '/accounts/email-token', method: 'POST', params: {} },
@ -61,6 +70,10 @@
putDomains: { url: _apiUri + '/settings/domains', method: 'POST', params: {} }, putDomains: { url: _apiUri + '/settings/domains', method: 'POST', params: {} },
}); });
_service.users = $resource(_apiUri + '/users/:id', {}, {
getPublicKey: { url: _apiUri + '/users/:id/public-key', method: 'GET', params: { id: '@id' } }
});
_service.identity = $resource(_apiUri + '/connect', {}, { _service.identity = $resource(_apiUri + '/connect', {}, {
token: { token: {
url: _apiUri + '/connect/token', url: _apiUri + '/connect/token',

View File

@ -190,6 +190,11 @@ angular
throw 'Public key unavailable.'; throw 'Public key unavailable.';
} }
if (typeof publicKey === 'string') {
var publicKeyBytes = forge.util.decode64(publicKey);
publicKey = forge.pki.publicKeyFromAsn1(forge.asn1.fromDer(publicKeyBytes));
}
var encryptedBytes = publicKey.encrypt(plainValue, 'RSA-OAEP', { var encryptedBytes = publicKey.encrypt(plainValue, 'RSA-OAEP', {
md: forge.md.sha256.create() md: forge.md.sha256.create()
}); });

View File

@ -123,6 +123,7 @@
<script src="app/organization/organizationModule.js"></script> <script src="app/organization/organizationModule.js"></script>
<script src="app/organization/organizationDashboardController.js"></script> <script src="app/organization/organizationDashboardController.js"></script>
<script src="app/organization/organizationPeopleController.js"></script> <script src="app/organization/organizationPeopleController.js"></script>
<script src="app/organization/organizationPeopleInviteController.js"></script>
<script src="app/organization/organizationSubvaultsController.js"></script> <script src="app/organization/organizationSubvaultsController.js"></script>
<script src="app/settings/settingsModule.js"></script> <script src="app/settings/settingsModule.js"></script>