mirror of
https://github.com/bitwarden/web
synced 2025-12-10 13:23:15 +00:00
share login modal
This commit is contained in:
@@ -29,6 +29,13 @@
|
|||||||
del: { url: _apiUri + '/ciphers/:id/delete', method: 'POST', params: { id: '@id' } }
|
del: { url: _apiUri + '/ciphers/:id/delete', method: 'POST', params: { id: '@id' } }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_service.shares = $resource(_apiUri + '/shares/:id', {}, {
|
||||||
|
get: { method: 'GET', params: { id: '@id' } },
|
||||||
|
listCipher: { url: _apiUri + '/shares/:cipherId', method: 'GET', params: { cipherId: '@cipherId' } },
|
||||||
|
post: { method: 'POST', params: {} },
|
||||||
|
del: { url: _apiUri + '/shares/:id/delete', method: 'POST', params: { id: '@id' } }
|
||||||
|
});
|
||||||
|
|
||||||
_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: {} },
|
||||||
|
|||||||
@@ -18,16 +18,22 @@ angular
|
|||||||
_service.decryptLogin = function (encryptedLogin) {
|
_service.decryptLogin = function (encryptedLogin) {
|
||||||
if (!encryptedLogin) throw "encryptedLogin is undefined or null";
|
if (!encryptedLogin) throw "encryptedLogin is undefined or null";
|
||||||
|
|
||||||
|
var key = null;
|
||||||
|
if (encryptedLogin.Key) {
|
||||||
|
key = cryptoService.rsaDecrypt(encryptedLogin.Key);
|
||||||
|
}
|
||||||
|
|
||||||
var login = {
|
var login = {
|
||||||
id: encryptedLogin.Id,
|
id: encryptedLogin.Id,
|
||||||
'type': 1,
|
'type': 1,
|
||||||
folderId: encryptedLogin.FolderId,
|
folderId: encryptedLogin.FolderId,
|
||||||
favorite: encryptedLogin.Favorite,
|
favorite: encryptedLogin.Favorite,
|
||||||
name: cryptoService.decrypt(encryptedLogin.Name),
|
name: cryptoService.decrypt(encryptedLogin.Name, key),
|
||||||
uri: encryptedLogin.Uri && encryptedLogin.Uri !== '' ? cryptoService.decrypt(encryptedLogin.Uri) : null,
|
uri: encryptedLogin.Uri && encryptedLogin.Uri !== '' ? cryptoService.decrypt(encryptedLogin.Uri, key) : null,
|
||||||
username: encryptedLogin.Username && encryptedLogin.Username !== '' ? cryptoService.decrypt(encryptedLogin.Username) : null,
|
username: encryptedLogin.Username && encryptedLogin.Username !== '' ? cryptoService.decrypt(encryptedLogin.Username, key) : null,
|
||||||
password: encryptedLogin.Password && encryptedLogin.Password !== '' ? cryptoService.decrypt(encryptedLogin.Password) : null,
|
password: encryptedLogin.Password && encryptedLogin.Password !== '' ? cryptoService.decrypt(encryptedLogin.Password, key) : null,
|
||||||
notes: encryptedLogin.Notes && encryptedLogin.Notes !== '' ? cryptoService.decrypt(encryptedLogin.Notes) : null
|
notes: encryptedLogin.Notes && encryptedLogin.Notes !== '' ? cryptoService.decrypt(encryptedLogin.Notes, key) : null,
|
||||||
|
key: encryptedLogin.Key
|
||||||
};
|
};
|
||||||
|
|
||||||
if (encryptedLogin.Folder) {
|
if (encryptedLogin.Folder) {
|
||||||
@@ -39,6 +45,40 @@ angular
|
|||||||
return login;
|
return login;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_service.decryptLoginPreview = function (encryptedCipher) {
|
||||||
|
if (!encryptedCipher) throw "encryptedCipher is undefined or null";
|
||||||
|
|
||||||
|
var key = null;
|
||||||
|
if (encryptedCipher.Key) {
|
||||||
|
key = cryptoService.rsaDecrypt(encryptedCipher.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
var login = {
|
||||||
|
id: encryptedCipher.Id,
|
||||||
|
folderId: encryptedCipher.FolderId,
|
||||||
|
favorite: encryptedCipher.Favorite,
|
||||||
|
name: decryptProperty(encryptedCipher.Data.Name, key, false),
|
||||||
|
username: decryptProperty(encryptedCipher.Data.Username, key, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
login.name = cryptoService.decrypt(encryptedCipher.Data.Name, key);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
login.name = '[error: cannot decrypt]';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
login.username = encryptedCipher.Data.Username && encryptedCipher.Data.Username !== '' ?
|
||||||
|
cryptoService.decrypt(encryptedCipher.Data.Username, key) : null;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
login.username = '[error: cannot decrypt]';
|
||||||
|
}
|
||||||
|
|
||||||
|
return login;
|
||||||
|
};
|
||||||
|
|
||||||
_service.decryptFolders = function (encryptedFolders) {
|
_service.decryptFolders = function (encryptedFolders) {
|
||||||
if (!encryptedFolders) throw "encryptedFolders is undefined or null";
|
if (!encryptedFolders) throw "encryptedFolders is undefined or null";
|
||||||
|
|
||||||
@@ -60,6 +100,30 @@ angular
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_service.decryptFolderPreview = function (encryptedFolder) {
|
||||||
|
if (!encryptedFolder) throw "encryptedFolder is undefined or null";
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: encryptedFolder.Id,
|
||||||
|
name: decryptProperty(encryptedFolder.Data.Name, null, false)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function decryptProperty(property, key, checkEmpty) {
|
||||||
|
if (checkEmpty && (!property || property === '')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
property = cryptoService.decrypt(property, key);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
property = '[error: cannot decrypt]';
|
||||||
|
}
|
||||||
|
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
_service.encryptLogins = function (unencryptedLogins, key) {
|
_service.encryptLogins = function (unencryptedLogins, key) {
|
||||||
if (!unencryptedLogins) throw "unencryptedLogins is undefined or null";
|
if (!unencryptedLogins) throw "unencryptedLogins is undefined or null";
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ angular
|
|||||||
var _service = {},
|
var _service = {},
|
||||||
_key,
|
_key,
|
||||||
_b64Key,
|
_b64Key,
|
||||||
_privateKey;
|
_privateKey,
|
||||||
|
_publicKey;
|
||||||
|
|
||||||
_service.setKey = function (key) {
|
_service.setKey = function (key) {
|
||||||
_key = key;
|
_key = key;
|
||||||
@@ -64,25 +65,41 @@ angular
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($sessionStorage.privateKey) {
|
if ($sessionStorage.privateKey) {
|
||||||
_privateKey = forge.util.decode64($sessionStorage.privateKey);
|
var privateKeyBytes = forge.util.decode64($sessionStorage.privateKey);
|
||||||
|
_privateKey = forge.pki.privateKeyFromAsn1(forge.asn1.fromDer(privateKeyBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _privateKey;
|
return _privateKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_service.getPublicKey = function () {
|
||||||
|
if (_publicKey) {
|
||||||
|
return _publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
var privateKey = _service.getPrivateKey();
|
||||||
|
if (!privateKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_publicKey = forge.pki.setRsaPublicKey(privateKey.n, privateKey.e);
|
||||||
|
return _publicKey;
|
||||||
|
};
|
||||||
|
|
||||||
_service.clearKey = function () {
|
_service.clearKey = function () {
|
||||||
_key = _b64Key = null;
|
_key = _b64Key = null;
|
||||||
delete $sessionStorage.key;
|
delete $sessionStorage.key;
|
||||||
};
|
};
|
||||||
|
|
||||||
_service.clearPrivateKey = function () {
|
_service.clearKeyPair = function () {
|
||||||
_privateKey = null;
|
_privateKey = null;
|
||||||
|
_publicKey = null;
|
||||||
delete $sessionStorage.privateKey;
|
delete $sessionStorage.privateKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
_service.clearKeys = function () {
|
_service.clearKeys = function () {
|
||||||
_service.clearKey();
|
_service.clearKey();
|
||||||
_service.clearPrivateKey();
|
_service.clearKeyPair();
|
||||||
};
|
};
|
||||||
|
|
||||||
_service.makeKey = function (password, salt, b64) {
|
_service.makeKey = function (password, salt, b64) {
|
||||||
@@ -114,6 +131,10 @@ angular
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_service.makeShareKey = function () {
|
||||||
|
return forge.random.getBytesSync(32);
|
||||||
|
};
|
||||||
|
|
||||||
_service.hashPassword = function (password, key) {
|
_service.hashPassword = function (password, key) {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
key = _service.getKey();
|
key = _service.getKey();
|
||||||
@@ -163,6 +184,18 @@ angular
|
|||||||
return cipherString;
|
return cipherString;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_service.rsaEncrypt = function (plainValue, publicKey) {
|
||||||
|
publicKey = publicKey || _service.getPublicKey();
|
||||||
|
if (!publicKey) {
|
||||||
|
throw 'Public key unavailable.';
|
||||||
|
}
|
||||||
|
|
||||||
|
var encryptedBytes = publicKey.encrypt(plainValue, 'RSA-OAEP', {
|
||||||
|
md: forge.md.sha256.create()
|
||||||
|
});
|
||||||
|
return forge.util.encode64(encryptedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
_service.decrypt = function (encValue, key, outputEncoding) {
|
_service.decrypt = function (encValue, key, outputEncoding) {
|
||||||
if (!_service.getKey() && !key) {
|
if (!_service.getKey() && !key) {
|
||||||
throw 'Encryption key unavailable.';
|
throw 'Encryption key unavailable.';
|
||||||
@@ -208,6 +241,20 @@ angular
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_service.rsaDecrypt = function (encValue, privateKey) {
|
||||||
|
privateKey = privateKey || _service.getPrivateKey();
|
||||||
|
if (!privateKey) {
|
||||||
|
throw 'Private key unavailable.';
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctBytes = forge.util.decode64(encValue);
|
||||||
|
var decBytes = privateKey.decrypt(ctBytes, 'RSA-OAEP', {
|
||||||
|
md: forge.md.sha256.create()
|
||||||
|
});
|
||||||
|
|
||||||
|
return decBytes;
|
||||||
|
}
|
||||||
|
|
||||||
function computeMac(ct, iv, macKey) {
|
function computeMac(ct, iv, macKey) {
|
||||||
var hmac = forge.hmac.create();
|
var hmac = forge.hmac.create();
|
||||||
hmac.start('sha256', macKey || _service.getMacKey());
|
hmac.start('sha256', macKey || _service.getMacKey());
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
angular
|
angular
|
||||||
.module('bit.vault')
|
.module('bit.vault')
|
||||||
|
|
||||||
.controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr, cipherService) {
|
.controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr,
|
||||||
|
cipherService) {
|
||||||
$scope.logins = [];
|
$scope.logins = [];
|
||||||
$scope.folders = [];
|
$scope.folders = [];
|
||||||
|
|
||||||
@@ -17,30 +18,11 @@
|
|||||||
|
|
||||||
for (var i = 0; i < ciphers.Data.length; i++) {
|
for (var i = 0; i < ciphers.Data.length; i++) {
|
||||||
if (ciphers.Data[i].Type === 0) {
|
if (ciphers.Data[i].Type === 0) {
|
||||||
var decFolder = {
|
var decFolder = cipherService.decryptFolderPreview(ciphers.Data[i]);
|
||||||
id: ciphers.Data[i].Id
|
|
||||||
};
|
|
||||||
|
|
||||||
try { decFolder.name = cryptoService.decrypt(ciphers.Data[i].Data.Name); }
|
|
||||||
catch (err) { decFolder.name = '[error: cannot decrypt]'; }
|
|
||||||
|
|
||||||
decFolders.push(decFolder);
|
decFolders.push(decFolder);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var decLogin = {
|
var decLogin = cipherService.decryptLoginPreview(ciphers.Data[i]);
|
||||||
id: ciphers.Data[i].Id,
|
|
||||||
folderId: ciphers.Data[i].FolderId,
|
|
||||||
favorite: ciphers.Data[i].Favorite
|
|
||||||
};
|
|
||||||
|
|
||||||
try { decLogin.name = cryptoService.decrypt(ciphers.Data[i].Data.Name); }
|
|
||||||
catch (err) { decLogin.name = '[error: cannot decrypt]'; }
|
|
||||||
|
|
||||||
if (ciphers.Data[i].Data.Username) {
|
|
||||||
try { decLogin.username = cryptoService.decrypt(ciphers.Data[i].Data.Username); }
|
|
||||||
catch (err) { decLogin.username = '[error: cannot decrypt]'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
decLogins.push(decLogin);
|
decLogins.push(decLogin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,28 +165,18 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.shareLogin = function (login) {
|
$scope.shareLogin = function (login) {
|
||||||
share(login.id, login.name, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.shareFolder = function (folder) {
|
|
||||||
share(folder.id, folder.name, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
function share(id, name, isFolder) {
|
|
||||||
var shareModel = $uibModal.open({
|
var shareModel = $uibModal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
templateUrl: 'app/vault/views/vaultShare.html',
|
templateUrl: 'app/vault/views/vaultShareLogin.html',
|
||||||
controller: 'vaultShareController',
|
controller: 'vaultShareLoginController',
|
||||||
size: 'lg',
|
size: 'lg',
|
||||||
resolve: {
|
resolve: {
|
||||||
id: function () { return id; },
|
id: function () { return login.id; }
|
||||||
name: function () { return name; },
|
|
||||||
isFolder: function () { return isFolder; }
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
shareModel.result.then(function (result) {
|
shareModel.result.then(function (result) {
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
angular
|
|
||||||
.module('bit.vault')
|
|
||||||
|
|
||||||
.controller('vaultShareController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
|
||||||
id, name, isFolder, $analytics) {
|
|
||||||
$analytics.eventTrack('vaultShareController', { category: 'Modal' });
|
|
||||||
$scope.cipher = {
|
|
||||||
id: id,
|
|
||||||
name: name,
|
|
||||||
isFolder: isFolder
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.sharePromise = null;
|
|
||||||
$scope.share = function () {
|
|
||||||
$uibModalInstance.close({});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.close = function () {
|
|
||||||
$uibModalInstance.dismiss('cancel');
|
|
||||||
};
|
|
||||||
});
|
|
||||||
33
src/app/vault/vaultShareLoginController.js
Normal file
33
src/app/vault/vaultShareLoginController.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.vault')
|
||||||
|
|
||||||
|
.controller('vaultShareLoginController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
||||||
|
id, $analytics) {
|
||||||
|
$analytics.eventTrack('vaultShareLoginController', { category: 'Modal' });
|
||||||
|
|
||||||
|
apiService.logins.get({
|
||||||
|
id: id
|
||||||
|
}, function (login) {
|
||||||
|
$scope.login = cipherService.decryptLogin(login);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.enablePromise = null;
|
||||||
|
$scope.enable = function () {
|
||||||
|
var shareKey = cryptoService.makeShareKey();
|
||||||
|
var encLogin = cipherService.encryptLogin($scope.login, shareKey);
|
||||||
|
encLogin.key = cryptoService.rsaEncrypt(shareKey);
|
||||||
|
|
||||||
|
$scope.enablePromise = apiService.logins.put({ id: $scope.login.id }, encLogin, function (login) {
|
||||||
|
$scope.login = cipherService.decryptLogin(login);
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.sharePromise = null;
|
||||||
|
$scope.share = function () {
|
||||||
|
$uibModalInstance.close({});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,13 +1,27 @@
|
|||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
<h4 class="modal-title" id="editFolderModelLabel">
|
<h4 class="modal-title" id="editFolderModelLabel">
|
||||||
<i class="fa fa-user-plus"></i>
|
<i class="fa fa-user-plus"></i> Share Login <small>{{login.name}}</small>
|
||||||
<span ng-if="!cipher.isFolder">Share Login</span>
|
|
||||||
<span ng-if="cipher.isFolder">Share Folder</span>
|
|
||||||
<small>{{cipher.name}}</small>
|
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<form name="shareForm" ng-submit="shareForm.$valid && share()" api-form="savePromise">
|
<form name="enableForm" ng-submit="enableForm.$valid && enable()" api-form="savePromise" ng-if="!login.key">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="enableForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in enableForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
Sharing is not enabled.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="enableForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="enableForm.$loading"></i>Enable Sharing
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form name="shareForm" ng-submit="shareForm.$valid && share()" api-form="savePromise" ng-if="login.key">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="callout callout-danger validation-errors" ng-show="shareForm.$errors">
|
<div class="callout callout-danger validation-errors" ng-show="shareForm.$errors">
|
||||||
<h4>Errors have occured</h4>
|
<h4>Errors have occured</h4>
|
||||||
@@ -19,21 +33,15 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
<label for="email">Recipient Email address</label> <span>*</span>
|
<label for="email">Recipient Email address</label> <span>*</span>
|
||||||
<input type="email" id="email" name="Email" ng-model="cipher.email" class="form-control"
|
<input type="email" id="email" name="Email" ng-model="share.email" class="form-control"
|
||||||
placeholder="bob@example.com" required api-field />
|
placeholder="bob@example.com" required api-field />
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" ng-model="cipher.readonly" name="Edit" />
|
<input type="checkbox" ng-model="share.readonly" name="Edit" />
|
||||||
Recipient can edit?
|
Recipient can edit?
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" ng-model="cipher.reshare" name="Reshare" />
|
|
||||||
Recipient can re-share with others?
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
<script src="app/vault/vaultAddLoginController.js"></script>
|
<script src="app/vault/vaultAddLoginController.js"></script>
|
||||||
<script src="app/vault/vaultEditFolderController.js"></script>
|
<script src="app/vault/vaultEditFolderController.js"></script>
|
||||||
<script src="app/vault/vaultAddFolderController.js"></script>
|
<script src="app/vault/vaultAddFolderController.js"></script>
|
||||||
<script src="app/vault/vaultShareController.js"></script>
|
<script src="app/vault/vaultShareLoginController.js"></script>
|
||||||
|
|
||||||
<script src="app/shared/sharedModule.js"></script>
|
<script src="app/shared/sharedModule.js"></script>
|
||||||
<script src="app/shared/sharedController.js"></script>
|
<script src="app/shared/sharedController.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user