Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16930aa422 | ||
|
|
263f5ba147 | ||
|
|
6a60c00e22 | ||
|
|
f3eaf644b0 | ||
|
|
a57110b935 | ||
|
|
cae8beaa8f | ||
|
|
df94d81d07 | ||
|
|
f03c22cc07 | ||
|
|
5b31fe37f2 | ||
|
|
c60a596995 | ||
|
|
b52ecd8085 | ||
|
|
4323341d19 | ||
|
|
e13992ba27 | ||
|
|
52a4317d09 | ||
|
|
d53187935b | ||
|
|
0d6c96e38b | ||
|
|
b0832578a4 | ||
|
|
805393b4db | ||
|
|
c3653577c6 | ||
|
|
1eb5a99ba3 | ||
|
|
a035d73545 | ||
|
|
79fc3056a6 | ||
|
|
e44cf6e7ee | ||
|
|
641c76ae62 | ||
|
|
1efcd69148 | ||
|
|
49ee41f7d3 | ||
|
|
598c7ea068 | ||
|
|
001a116c8b | ||
|
|
106e71fe54 | ||
|
|
cd93d6cc32 | ||
|
|
d63c89bae7 | ||
|
|
fb3a7733a3 | ||
|
|
852363cb77 | ||
|
|
7f6ee21a8e | ||
|
|
2963516d5c | ||
|
|
1f26ff5c80 | ||
|
|
de3f310082 | ||
|
|
4af2edafd3 | ||
|
|
4de08f2e71 | ||
|
|
d978e1dfa3 | ||
|
|
f828288b84 | ||
|
|
7a36f13034 | ||
|
|
422b48fa36 | ||
|
|
fe9e29a057 | ||
|
|
88c302ca2e | ||
|
|
52f3032483 | ||
|
|
b13edfeeae | ||
|
|
4046339569 | ||
|
|
52f4a9d961 | ||
|
|
ca0fb6d66a | ||
|
|
7c93c82d24 | ||
|
|
3b71760f9e | ||
|
|
c4d2045884 | ||
|
|
d28c59544f | ||
|
|
acff0b19d6 | ||
|
|
94bfcb2865 | ||
|
|
1bb6244337 |
14
build.sh
@@ -5,18 +5,12 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
echo ""
|
||||
|
||||
if [ $# -gt 0 -a "$1" == "push" ]
|
||||
if [ $# -gt 1 -a "$1" == "push" ]
|
||||
then
|
||||
echo "# Pushing Web"
|
||||
TAG=$2
|
||||
echo "# Pushing Web ($TAG)"
|
||||
echo ""
|
||||
|
||||
if [ $# -gt 1 ]
|
||||
then
|
||||
TAG=$2
|
||||
docker push bitwarden/web:$TAG
|
||||
else
|
||||
docker push bitwarden/web
|
||||
fi
|
||||
docker push bitwarden/web:$TAG
|
||||
elif [ $# -gt 1 -a "$1" == "tag" ]
|
||||
then
|
||||
TAG=$2
|
||||
|
||||
1
dist/.publish
vendored
Submodule
@@ -25,7 +25,7 @@ var gulp = require('gulp'),
|
||||
|
||||
var paths = {};
|
||||
paths.dist = './dist/';
|
||||
paths.webroot = './src/'
|
||||
paths.webroot = './src/';
|
||||
paths.js = paths.webroot + 'js/**/*.js';
|
||||
paths.minJs = paths.webroot + 'js/**/*.min.js';
|
||||
paths.concatJsDest = paths.webroot + 'js/bw.min.js';
|
||||
@@ -304,7 +304,7 @@ gulp.task('dist:move', function () {
|
||||
src: [
|
||||
paths.npmDir + 'bootstrap/dist/**/bootstrap.min.js',
|
||||
paths.npmDir + 'bootstrap/dist/**/bootstrap.min.css',
|
||||
paths.npmDir + 'bootstrap/dist/**/fonts/**/*',
|
||||
paths.npmDir + 'bootstrap/dist/**/fonts/**/*'
|
||||
],
|
||||
dest: paths.dist + 'lib/bootstrap'
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bitwarden",
|
||||
"version": "1.17.3",
|
||||
"version": "1.19.0",
|
||||
"env": "Production",
|
||||
"devDependencies": {
|
||||
"connect": "3.6.3",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"appSettings": {
|
||||
"apiUri": "https://preview-api.bitwarden.com",
|
||||
"identityUri": "https://preview-identity.bitwarden.com",
|
||||
"iconsUri": "https://icons.bitwarden.com",
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||
"whitelistDomains": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"appSettings": {
|
||||
"apiUri": "https://api.bitwarden.com",
|
||||
"identityUri": "https://identity.bitwarden.com",
|
||||
"iconsUri": "https://icons.bitwarden.com",
|
||||
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
||||
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
||||
"whitelistDomains": [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"appSettings": {
|
||||
"apiUri": "http://localhost:4000",
|
||||
"identityUri": "http://localhost:33656",
|
||||
"iconsUri": "https://icons.bitwarden.com",
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||
"whitelistDomains": [
|
||||
|
||||
@@ -4,7 +4,9 @@ angular
|
||||
.config(function ($stateProvider, $urlRouterProvider, $httpProvider, jwtInterceptorProvider, jwtOptionsProvider,
|
||||
$uibTooltipProvider, toastrConfig, $locationProvider, $qProvider, appSettings
|
||||
// @if !selfHosted
|
||||
/* jshint ignore:start */
|
||||
, stripeProvider
|
||||
/* jshint ignore:end */
|
||||
// @endif
|
||||
) {
|
||||
angular.extend(appSettings, window.bitwardenAppSettings);
|
||||
@@ -13,19 +15,20 @@ angular
|
||||
$locationProvider.hashPrefix('');
|
||||
|
||||
var jwtConfig = {
|
||||
// Using Content-Language header since it is unused and is a CORS-safelisted header. This avoids pre-flights.
|
||||
authHeader: 'Content-Language',
|
||||
whiteListedDomains: appSettings.whitelistDomains
|
||||
};
|
||||
|
||||
// Safari doesn't work with unconventional "Content-Language" header for CORS.
|
||||
// See notes here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
if (userAgent.indexOf('safari') > -1 && userAgent.indexOf('chrome') === -1) {
|
||||
jwtConfig = {
|
||||
urlParam: 'access_token',
|
||||
whiteListedDomains: appSettings.whitelistDomains
|
||||
};
|
||||
if (!appSettings.selfHosted) {
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
if (userAgent.indexOf('safari') > -1 && userAgent.indexOf('chrome') === -1) {
|
||||
// Safari doesn't work with unconventional "Content-Language" header for CORS.
|
||||
// See notes here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
|
||||
jwtConfig.urlParam = 'access_token';
|
||||
}
|
||||
else {
|
||||
// Using Content-Language header since it is unused and is a CORS-safelisted header. This avoids pre-flights.
|
||||
jwtConfig.authHeader = 'Content-Language';
|
||||
}
|
||||
}
|
||||
|
||||
jwtOptionsProvider.config(jwtConfig);
|
||||
@@ -48,7 +51,12 @@ angular
|
||||
return token;
|
||||
}
|
||||
|
||||
refreshPromise = authService.refreshAccessToken().then(function (newToken) {
|
||||
var p = authService.refreshAccessToken();
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
|
||||
refreshPromise = p.then(function (newToken) {
|
||||
refreshPromise = null;
|
||||
return newToken || token;
|
||||
});
|
||||
@@ -367,7 +375,7 @@ angular
|
||||
// user is guaranteed to be authenticated becuase of previous check
|
||||
if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) {
|
||||
// clear vault rootScope when visiting org admin section
|
||||
$rootScope.vaultLogins = $rootScope.vaultFolders = null;
|
||||
$rootScope.vaultCiphers = $rootScope.vaultFolders = null;
|
||||
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
var orgs = profile.organizations;
|
||||
|
||||
@@ -28,6 +28,17 @@ angular.module('bit')
|
||||
email: 1,
|
||||
remember: 5
|
||||
},
|
||||
cipherType: {
|
||||
login: 1,
|
||||
secureNote: 2,
|
||||
card: 3,
|
||||
identity: 4
|
||||
},
|
||||
fieldType: {
|
||||
text: 0,
|
||||
hidden: 1,
|
||||
boolean: 2
|
||||
},
|
||||
twoFactorProviderInfo: [
|
||||
{
|
||||
type: 0,
|
||||
|
||||
11
src/app/directives/fallbackSrcDirective.js
Normal file
@@ -0,0 +1,11 @@
|
||||
angular
|
||||
.module('bit.directives')
|
||||
|
||||
.directive('fallbackSrc', function () {
|
||||
return function (scope, element, attrs) {
|
||||
var el = $(element);
|
||||
el.bind('error', function (event) {
|
||||
el.attr('src', attrs.fallbackSrc);
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -49,16 +49,16 @@ angular
|
||||
vm.openControlSidebar = vm.usingControlSidebar && $document.width() > 768;
|
||||
});
|
||||
|
||||
$scope.addLogin = function () {
|
||||
$scope.$broadcast('vaultAddLogin');
|
||||
$scope.addCipher = function () {
|
||||
$scope.$broadcast('vaultAddCipher');
|
||||
};
|
||||
|
||||
$scope.addFolder = function () {
|
||||
$scope.$broadcast('vaultAddFolder');
|
||||
};
|
||||
|
||||
$scope.addOrganizationLogin = function () {
|
||||
$scope.$broadcast('organizationVaultAddLogin');
|
||||
$scope.addOrganizationCipher = function () {
|
||||
$scope.$broadcast('organizationVaultAddCipher');
|
||||
};
|
||||
|
||||
$scope.addOrganizationCollection = function () {
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
.controller('organizationBillingChangePaymentController', function ($scope, $state, $uibModalInstance, apiService,
|
||||
$analytics, toastr, existingPaymentMethod
|
||||
// @if !selfHosted
|
||||
/* jshint ignore:start */
|
||||
, stripe
|
||||
/* jshint ignore:end */
|
||||
// @endif
|
||||
) {
|
||||
$analytics.eventTrack('organizationBillingChangePaymentController', { category: 'Modal' });
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationBillingController', function ($scope, apiService, $state, $uibModal, toastr, $analytics,
|
||||
appSettings) {
|
||||
appSettings, tokenService, $window) {
|
||||
$scope.selfHosted = appSettings.selfHosted;
|
||||
$scope.charges = [];
|
||||
$scope.paymentSource = null;
|
||||
@@ -208,6 +208,15 @@
|
||||
});
|
||||
};
|
||||
|
||||
$scope.viewInvoice = function (charge) {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
var url = appSettings.apiUri + '/organizations/' + $state.params.orgId +
|
||||
'/billing-invoice/' + charge.invoiceId + '?access_token=' + tokenService.getToken();
|
||||
$window.open(url);
|
||||
};
|
||||
|
||||
function load() {
|
||||
apiService.organizations.getBilling({ id: $state.params.orgId }, function (org) {
|
||||
$scope.loading = false;
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
$scope.model = {
|
||||
name: org.Name,
|
||||
billingEmail: org.BillingEmail,
|
||||
businessName: org.BusinessName
|
||||
businessName: org.BusinessName,
|
||||
businessAddress1: org.BusinessAddress1,
|
||||
businessAddress2: org.BusinessAddress2,
|
||||
businessAddress3: org.BusinessAddress3,
|
||||
businessCountry: org.BusinessCountry,
|
||||
businessTaxNumber: org.BusinessTaxNumber
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationSettingsExportController', function ($scope, apiService, $uibModalInstance, cipherService,
|
||||
$q, toastr, $analytics, $state) {
|
||||
$q, toastr, $analytics, $state, constants) {
|
||||
$analytics.eventTrack('organizationSettingsExportController', { category: 'Modal' });
|
||||
$scope.export = function (model) {
|
||||
$scope.startedExport = true;
|
||||
var decLogins = [],
|
||||
var decCiphers = [],
|
||||
decCollections = [];
|
||||
|
||||
var collectionsPromise = apiService.collections.listOrganization({ orgId: $state.params.orgId },
|
||||
@@ -14,18 +14,13 @@
|
||||
decCollections = cipherService.decryptCollections(collections.Data, $state.params.orgId, true);
|
||||
}).$promise;
|
||||
|
||||
var loginsPromise = apiService.ciphers.listOrganizationDetails({ organizationId: $state.params.orgId },
|
||||
var ciphersPromise = apiService.ciphers.listOrganizationDetails({ organizationId: $state.params.orgId },
|
||||
function (ciphers) {
|
||||
for (var i = 0; i < ciphers.Data.length; i++) {
|
||||
if (ciphers.Data[i].Type === 1) {
|
||||
var decLogin = cipherService.decryptLogin(ciphers.Data[i]);
|
||||
decLogins.push(decLogin);
|
||||
}
|
||||
}
|
||||
decCiphers = cipherService.decryptCiphers(ciphers.Data);
|
||||
}).$promise;
|
||||
|
||||
$q.all([collectionsPromise, loginsPromise]).then(function () {
|
||||
if (!decLogins.length) {
|
||||
$q.all([collectionsPromise, ciphersPromise]).then(function () {
|
||||
if (!decCiphers.length) {
|
||||
toastr.error('Nothing to export.', 'Error!');
|
||||
$scope.close();
|
||||
return;
|
||||
@@ -37,45 +32,68 @@
|
||||
}
|
||||
|
||||
try {
|
||||
var exportLogins = [];
|
||||
for (i = 0; i < decLogins.length; i++) {
|
||||
var login = {
|
||||
name: decLogins[i].name,
|
||||
uri: decLogins[i].uri,
|
||||
username: decLogins[i].username,
|
||||
password: decLogins[i].password,
|
||||
notes: decLogins[i].notes,
|
||||
totp: decLogins[i].totp,
|
||||
var exportCiphers = [];
|
||||
for (i = 0; i < decCiphers.length; i++) {
|
||||
// only export logins and secure notes
|
||||
if (decCiphers[i].type !== constants.cipherType.login &&
|
||||
decCiphers[i].type !== constants.cipherType.secureNote) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var cipher = {
|
||||
collections: [],
|
||||
fields: null
|
||||
type: null,
|
||||
name: decCiphers[i].name,
|
||||
notes: decCiphers[i].notes,
|
||||
fields: null,
|
||||
// Login props
|
||||
login_uri: null,
|
||||
login_username: null,
|
||||
login_password: null,
|
||||
login_totp: null
|
||||
};
|
||||
|
||||
var j;
|
||||
if (decLogins[i].fields) {
|
||||
for (j = 0; j < decLogins[i].fields.length; j++) {
|
||||
if (!login.fields) {
|
||||
login.fields = '';
|
||||
if (decCiphers[i].collectionIds) {
|
||||
for (j = 0; j < decCiphers[i].collectionIds.length; j++) {
|
||||
if (collectionsDict.hasOwnProperty(decCiphers[i].collectionIds[j])) {
|
||||
cipher.collections.push(collectionsDict[decCiphers[i].collectionIds[j]].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (decCiphers[i].fields) {
|
||||
for (j = 0; j < decCiphers[i].fields.length; j++) {
|
||||
if (!cipher.fields) {
|
||||
cipher.fields = '';
|
||||
}
|
||||
else {
|
||||
login.fields += '\n';
|
||||
cipher.fields += '\n';
|
||||
}
|
||||
|
||||
login.fields += ((decLogins[i].fields[j].name || '') + ': ' + decLogins[i].fields[j].value);
|
||||
|
||||
cipher.fields += ((decCiphers[i].fields[j].name || '') + ': ' + decCiphers[i].fields[j].value);
|
||||
}
|
||||
}
|
||||
|
||||
if (decLogins[i].collectionIds) {
|
||||
for (j = 0; j < decLogins[i].collectionIds.length; j++) {
|
||||
if (collectionsDict.hasOwnProperty(decLogins[i].collectionIds[j])) {
|
||||
login.collections.push(collectionsDict[decLogins[i].collectionIds[j]].name);
|
||||
}
|
||||
}
|
||||
switch (decCiphers[i].type) {
|
||||
case constants.cipherType.login:
|
||||
cipher.type = 'login';
|
||||
cipher.login_uri = decCiphers[i].login.uri;
|
||||
cipher.login_username = decCiphers[i].login.username;
|
||||
cipher.login_password = decCiphers[i].login.password;
|
||||
cipher.login_totp = decCiphers[i].login.totp;
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.type = 'note';
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
exportLogins.push(login);
|
||||
exportCiphers.push(cipher);
|
||||
}
|
||||
|
||||
var csvString = Papa.unparse(exportLogins);
|
||||
var csvString = Papa.unparse(exportCiphers);
|
||||
var csvBlob = new Blob([csvString]);
|
||||
|
||||
// IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
|
||||
|
||||
@@ -55,15 +55,15 @@
|
||||
importService.importOrg(model.source, file || model.fileContents, importSuccess, importError);
|
||||
};
|
||||
|
||||
function importSuccess(collections, logins, collectionRelationships) {
|
||||
if (!collections.length && !logins.length) {
|
||||
function importSuccess(collections, ciphers, collectionRelationships) {
|
||||
if (!collections.length && !ciphers.length) {
|
||||
importError('Nothing was imported.');
|
||||
return;
|
||||
}
|
||||
else if (logins.length) {
|
||||
var halfway = Math.floor(logins.length / 2);
|
||||
var last = logins.length - 1;
|
||||
if (loginIsBadData(logins[0]) && loginIsBadData(logins[halfway]) && loginIsBadData(logins[last])) {
|
||||
else if (ciphers.length) {
|
||||
var halfway = Math.floor(ciphers.length / 2);
|
||||
var last = ciphers.length - 1;
|
||||
if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) {
|
||||
importError('CSV data is not formatted correctly. Please check your import file and try again.');
|
||||
return;
|
||||
}
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
apiService.ciphers.importOrg({ orgId: $state.params.orgId }, {
|
||||
collections: cipherService.encryptCollections(collections, $state.params.orgId),
|
||||
ciphers: cipherService.encryptLogins(logins, cryptoService.getOrgKey($state.params.orgId)),
|
||||
ciphers: cipherService.encryptCiphers(ciphers, cryptoService.getOrgKey($state.params.orgId)),
|
||||
collectionRelationships: collectionRelationships
|
||||
}, function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
@@ -82,8 +82,9 @@
|
||||
}, importError);
|
||||
}
|
||||
|
||||
function loginIsBadData(login) {
|
||||
return (login.name === null || login.name === '--') && (login.password === null || login.password === '');
|
||||
function cipherIsBadData(cipher) {
|
||||
return (cipher.name === null || cipher.name === '--') &&
|
||||
(cipher.login && (cipher.login.password === null || cipher.login.password === ''));
|
||||
}
|
||||
|
||||
function importError(error) {
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationVaultAddLoginController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||
cipherService, passwordService, $analytics, authService, orgId, $uibModal) {
|
||||
$analytics.eventTrack('organizationVaultAddLoginController', { category: 'Modal' });
|
||||
$scope.login = {};
|
||||
.controller('organizationVaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||
cipherService, passwordService, $analytics, authService, orgId, $uibModal, constants) {
|
||||
$analytics.eventTrack('organizationVaultAddCipherController', { category: 'Modal' });
|
||||
$scope.constants = constants;
|
||||
$scope.selectedType = constants.cipherType.login.toString();
|
||||
$scope.cipher = {
|
||||
type: constants.cipherType.login,
|
||||
login: {},
|
||||
identity: {},
|
||||
card: {},
|
||||
secureNote: {
|
||||
type: '0'
|
||||
}
|
||||
};
|
||||
$scope.hideFolders = $scope.hideFavorite = $scope.fromOrg = true;
|
||||
|
||||
authService.getUserProfile().then(function (userProfile) {
|
||||
@@ -12,40 +22,44 @@
|
||||
$scope.useTotp = orgProfile.useTotp;
|
||||
});
|
||||
|
||||
$scope.typeChanged = function () {
|
||||
$scope.cipher.type = parseInt($scope.selectedType);
|
||||
};
|
||||
|
||||
$scope.savePromise = null;
|
||||
$scope.save = function (model) {
|
||||
model.organizationId = orgId;
|
||||
var login = cipherService.encryptLogin(model);
|
||||
$scope.savePromise = apiService.ciphers.postAdmin(login, function (loginResponse) {
|
||||
$analytics.eventTrack('Created Organization Login');
|
||||
var decLogin = cipherService.decryptLogin(loginResponse);
|
||||
$uibModalInstance.close(decLogin);
|
||||
$scope.save = function () {
|
||||
$scope.cipher.organizationId = orgId;
|
||||
var cipher = cipherService.encryptCipher($scope.cipher);
|
||||
$scope.savePromise = apiService.ciphers.postAdmin(cipher, function (cipherResponse) {
|
||||
$analytics.eventTrack('Created Organization Cipher');
|
||||
var decCipher = cipherService.decryptCipherPreview(cipherResponse);
|
||||
$uibModalInstance.close(decCipher);
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
$scope.generatePassword = function () {
|
||||
if (!$scope.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
$analytics.eventTrack('Generated Password From Add');
|
||||
$scope.login.password = passwordService.generatePassword({ length: 12, special: true });
|
||||
$scope.cipher.login.password = passwordService.generatePassword({ length: 12, special: true });
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addField = function () {
|
||||
if (!$scope.login.fields) {
|
||||
$scope.login.fields = [];
|
||||
if (!$scope.cipher.fields) {
|
||||
$scope.cipher.fields = [];
|
||||
}
|
||||
|
||||
$scope.login.fields.push({
|
||||
type: '0',
|
||||
$scope.cipher.fields.push({
|
||||
type: constants.fieldType.text.toString(),
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.login.fields.indexOf(field);
|
||||
var index = $scope.cipher.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.login.fields.splice(index, 1);
|
||||
$scope.cipher.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationVaultAttachmentsController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||
cipherService, loginId, $analytics, validationService, toastr, $timeout) {
|
||||
cipherService, cipherId, $analytics, validationService, toastr, $timeout) {
|
||||
$analytics.eventTrack('organizationVaultAttachmentsController', { category: 'Modal' });
|
||||
$scope.login = {};
|
||||
$scope.cipher = {};
|
||||
$scope.loading = true;
|
||||
$scope.isPremium = true;
|
||||
$scope.canUseAttachments = true;
|
||||
var closing = false;
|
||||
|
||||
apiService.ciphers.getAdmin({ id: loginId }, function (login) {
|
||||
$scope.login = cipherService.decryptLogin(login);
|
||||
apiService.ciphers.getAdmin({ id: cipherId }, function (cipher) {
|
||||
$scope.cipher = cipherService.decryptCipher(cipher);
|
||||
$scope.loading = false;
|
||||
}, function () {
|
||||
$scope.loading = false;
|
||||
@@ -24,12 +24,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var key = cryptoService.getOrgKey($scope.login.organizationId);
|
||||
var key = cryptoService.getOrgKey($scope.cipher.organizationId);
|
||||
$scope.savePromise = cipherService.encryptAttachmentFile(key, files[0]).then(function (encValue) {
|
||||
var fd = new FormData();
|
||||
var blob = new Blob([encValue.data], { type: 'application/octet-stream' });
|
||||
fd.append('data', blob, encValue.fileName);
|
||||
return apiService.ciphers.postAttachment({ id: loginId }, fd).$promise;
|
||||
return apiService.ciphers.postAttachment({ id: cipherId }, fd).$promise;
|
||||
}).then(function (response) {
|
||||
$analytics.eventTrack('Added Attachment');
|
||||
toastr.success('The attachment has been added.');
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
$scope.download = function (attachment) {
|
||||
attachment.loading = true;
|
||||
var key = cryptoService.getOrgKey($scope.login.organizationId);
|
||||
var key = cryptoService.getOrgKey($scope.cipher.organizationId);
|
||||
cipherService.downloadAndDecryptAttachment(key, attachment, true).then(function (res) {
|
||||
$timeout(function () {
|
||||
attachment.loading = false;
|
||||
@@ -65,12 +65,12 @@
|
||||
}
|
||||
|
||||
attachment.loading = true;
|
||||
apiService.ciphers.delAttachment({ id: loginId, attachmentId: attachment.id }).$promise.then(function () {
|
||||
apiService.ciphers.delAttachment({ id: cipherId, attachmentId: attachment.id }).$promise.then(function () {
|
||||
attachment.loading = false;
|
||||
$analytics.eventTrack('Deleted Organization Attachment');
|
||||
var index = $scope.login.attachments.indexOf(attachment);
|
||||
var index = $scope.cipher.attachments.indexOf(attachment);
|
||||
if (index > -1) {
|
||||
$scope.login.attachments.splice(index, 1);
|
||||
$scope.cipher.attachments.splice(index, 1);
|
||||
}
|
||||
}, function () {
|
||||
toastr.error('Cannot delete attachment.');
|
||||
@@ -89,6 +89,6 @@
|
||||
|
||||
e.preventDefault();
|
||||
closing = true;
|
||||
$uibModalInstance.close(!!$scope.login.attachments && $scope.login.attachments.length > 0);
|
||||
$uibModalInstance.close(!!$scope.cipher.attachments && $scope.cipher.attachments.length > 0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationVaultLoginCollectionsController', function ($scope, apiService, $uibModalInstance, cipherService,
|
||||
.controller('organizationVaultCipherCollectionsController', function ($scope, apiService, $uibModalInstance, cipherService,
|
||||
cipher, $analytics, collections) {
|
||||
$analytics.eventTrack('organizationVaultLoginCollectionsController', { category: 'Modal' });
|
||||
$analytics.eventTrack('organizationVaultCipherCollectionsController', { category: 'Modal' });
|
||||
$scope.cipher = {};
|
||||
$scope.collections = [];
|
||||
$scope.selectedCollections = {};
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
$scope.submitPromise = apiService.ciphers.putCollectionsAdmin({ id: cipher.id }, request)
|
||||
.$promise.then(function (response) {
|
||||
$analytics.eventTrack('Edited Login Collections');
|
||||
$analytics.eventTrack('Edited Cipher Collections');
|
||||
$uibModalInstance.close({
|
||||
action: 'collectionsEdit',
|
||||
collectionIds: request.collectionIds
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
.controller('organizationVaultController', function ($scope, apiService, cipherService, $analytics, $q, $state,
|
||||
$localStorage, $uibModal, $filter, authService) {
|
||||
$scope.logins = [];
|
||||
$scope.ciphers = [];
|
||||
$scope.collections = [];
|
||||
$scope.loading = true;
|
||||
|
||||
@@ -27,16 +27,14 @@
|
||||
|
||||
var cipherPromise = apiService.ciphers.listOrganizationDetails({ organizationId: $state.params.orgId },
|
||||
function (ciphers) {
|
||||
var decLogins = [];
|
||||
var decCiphers = [];
|
||||
|
||||
for (var i = 0; i < ciphers.Data.length; i++) {
|
||||
if (ciphers.Data[i].Type === 1) {
|
||||
var decLogin = cipherService.decryptLoginPreview(ciphers.Data[i]);
|
||||
decLogins.push(decLogin);
|
||||
}
|
||||
var decCipher = cipherService.decryptCipherPreview(ciphers.Data[i]);
|
||||
decCiphers.push(decCipher);
|
||||
}
|
||||
|
||||
$scope.logins = decLogins;
|
||||
$scope.ciphers = decCiphers;
|
||||
}).$promise;
|
||||
|
||||
$q.all([collectionPromise, cipherPromise]).then(function () {
|
||||
@@ -77,55 +75,59 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.editLogin = function (login) {
|
||||
$scope.editCipher = function (cipher) {
|
||||
var editModel = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultEditLogin.html',
|
||||
controller: 'organizationVaultEditLoginController',
|
||||
templateUrl: 'app/vault/views/vaultEditCipher.html',
|
||||
controller: 'organizationVaultEditCipherController',
|
||||
resolve: {
|
||||
loginId: function () { return login.id; },
|
||||
cipherId: function () { return cipher.id; },
|
||||
orgId: function () { return $state.params.orgId; }
|
||||
}
|
||||
});
|
||||
|
||||
editModel.result.then(function (returnVal) {
|
||||
var index;
|
||||
if (returnVal.action === 'edit') {
|
||||
login.name = returnVal.data.name;
|
||||
login.username = returnVal.data.username;
|
||||
index = $scope.ciphers.indexOf(cipher);
|
||||
if (index > -1) {
|
||||
returnVal.data.collectionIds = $scope.ciphers[index].collectionIds;
|
||||
$scope.ciphers[index] = returnVal.data;
|
||||
}
|
||||
}
|
||||
else if (returnVal.action === 'delete') {
|
||||
var index = $scope.logins.indexOf(login);
|
||||
index = $scope.ciphers.indexOf(cipher);
|
||||
if (index > -1) {
|
||||
$scope.logins.splice(index, 1);
|
||||
$scope.ciphers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on('organizationVaultAddLogin', function (event, args) {
|
||||
$scope.addLogin();
|
||||
$scope.$on('organizationVaultAddCipher', function (event, args) {
|
||||
$scope.addCipher();
|
||||
});
|
||||
|
||||
$scope.addLogin = function () {
|
||||
$scope.addCipher = function () {
|
||||
var addModel = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultAddLogin.html',
|
||||
controller: 'organizationVaultAddLoginController',
|
||||
templateUrl: 'app/vault/views/vaultAddCipher.html',
|
||||
controller: 'organizationVaultAddCipherController',
|
||||
resolve: {
|
||||
orgId: function () { return $state.params.orgId; }
|
||||
}
|
||||
});
|
||||
|
||||
addModel.result.then(function (addedLogin) {
|
||||
$scope.logins.push(addedLogin);
|
||||
addModel.result.then(function (addedCipher) {
|
||||
$scope.ciphers.push(addedCipher);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editCollections = function (cipher) {
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/organization/views/organizationVaultLoginCollections.html',
|
||||
controller: 'organizationVaultLoginCollectionsController',
|
||||
templateUrl: 'app/organization/views/organizationVaultCipherCollections.html',
|
||||
controller: 'organizationVaultCipherCollectionsController',
|
||||
resolve: {
|
||||
cipher: function () { return cipher; },
|
||||
collections: function () { return $scope.collections; }
|
||||
@@ -139,9 +141,9 @@
|
||||
});
|
||||
};
|
||||
|
||||
$scope.attachments = function (login) {
|
||||
$scope.attachments = function (cipher) {
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
return !!profile.organizations[login.organizationId].maxStorageGb;
|
||||
return !!profile.organizations[cipher.organizationId].maxStorageGb;
|
||||
}).then(function (useStorage) {
|
||||
if (!useStorage) {
|
||||
$uibModal.open({
|
||||
@@ -149,7 +151,7 @@
|
||||
templateUrl: 'app/views/paidOrgRequired.html',
|
||||
controller: 'paidOrgRequiredController',
|
||||
resolve: {
|
||||
orgId: function () { return login.organizationId; }
|
||||
orgId: function () { return cipher.organizationId; }
|
||||
}
|
||||
});
|
||||
return;
|
||||
@@ -160,18 +162,18 @@
|
||||
templateUrl: 'app/vault/views/vaultAttachments.html',
|
||||
controller: 'organizationVaultAttachmentsController',
|
||||
resolve: {
|
||||
loginId: function () { return login.id; }
|
||||
cipherId: function () { return cipher.id; }
|
||||
}
|
||||
});
|
||||
|
||||
attachmentModel.result.then(function (hasAttachments) {
|
||||
login.hasAttachments = hasAttachments;
|
||||
cipher.hasAttachments = hasAttachments;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeLogin = function (login, collection) {
|
||||
if (!confirm('Are you sure you want to remove this login (' + login.name + ') from the ' +
|
||||
$scope.removeCipher = function (cipher, collection) {
|
||||
if (!confirm('Are you sure you want to remove this item (' + cipher.name + ') from the ' +
|
||||
'collection (' + collection.name + ') ?')) {
|
||||
return;
|
||||
}
|
||||
@@ -180,28 +182,28 @@
|
||||
collectionIds: []
|
||||
};
|
||||
|
||||
for (var i = 0; i < login.collectionIds.length; i++) {
|
||||
if (login.collectionIds[i] !== collection.id) {
|
||||
request.collectionIds.push(login.collectionIds[i]);
|
||||
for (var i = 0; i < cipher.collectionIds.length; i++) {
|
||||
if (cipher.collectionIds[i] !== collection.id) {
|
||||
request.collectionIds.push(cipher.collectionIds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
apiService.ciphers.putCollections({ id: login.id }, request).$promise.then(function (response) {
|
||||
$analytics.eventTrack('Removed Login From Collection');
|
||||
login.collectionIds = request.collectionIds;
|
||||
apiService.ciphers.putCollections({ id: cipher.id }, request).$promise.then(function (response) {
|
||||
$analytics.eventTrack('Removed Cipher From Collection');
|
||||
cipher.collectionIds = request.collectionIds;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteLogin = function (login) {
|
||||
if (!confirm('Are you sure you want to delete this login (' + login.name + ')?')) {
|
||||
$scope.deleteCipher = function (cipher) {
|
||||
if (!confirm('Are you sure you want to delete this item (' + cipher.name + ')?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.ciphers.delAdmin({ id: login.id }, function () {
|
||||
$analytics.eventTrack('Deleted Login');
|
||||
var index = $scope.logins.indexOf(login);
|
||||
apiService.ciphers.delAdmin({ id: cipher.id }, function () {
|
||||
$analytics.eventTrack('Deleted Cipher');
|
||||
var index = $scope.ciphers.indexOf(cipher);
|
||||
if (index > -1) {
|
||||
$scope.logins.splice(index, 1);
|
||||
$scope.ciphers.splice(index, 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,52 +1,53 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationVaultEditLoginController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||
cipherService, passwordService, loginId, $analytics, orgId, $uibModal) {
|
||||
$analytics.eventTrack('organizationVaultEditLoginController', { category: 'Modal' });
|
||||
$scope.login = {};
|
||||
.controller('organizationVaultEditCipherController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||
cipherService, passwordService, cipherId, $analytics, orgId, $uibModal, constants) {
|
||||
$analytics.eventTrack('organizationVaultEditCipherController', { category: 'Modal' });
|
||||
$scope.cipher = {};
|
||||
$scope.hideFolders = $scope.hideFavorite = $scope.fromOrg = true;
|
||||
$scope.constants = constants;
|
||||
|
||||
apiService.ciphers.getAdmin({ id: loginId }, function (login) {
|
||||
$scope.login = cipherService.decryptLogin(login);
|
||||
$scope.useTotp = $scope.login.organizationUseTotp;
|
||||
apiService.ciphers.getAdmin({ id: cipherId }, function (cipher) {
|
||||
$scope.cipher = cipherService.decryptCipher(cipher);
|
||||
$scope.useTotp = $scope.cipher.organizationUseTotp;
|
||||
});
|
||||
|
||||
$scope.save = function (model) {
|
||||
var login = cipherService.encryptLogin(model);
|
||||
$scope.savePromise = apiService.ciphers.putAdmin({ id: loginId }, login, function (loginResponse) {
|
||||
$analytics.eventTrack('Edited Organization Login');
|
||||
var decLogin = cipherService.decryptLogin(loginResponse);
|
||||
var cipher = cipherService.encryptCipher(model, $scope.cipher.type);
|
||||
$scope.savePromise = apiService.ciphers.putAdmin({ id: cipherId }, cipher, function (cipherResponse) {
|
||||
$analytics.eventTrack('Edited Organization Cipher');
|
||||
var decCipher = cipherService.decryptCipherPreview(cipherResponse);
|
||||
$uibModalInstance.close({
|
||||
action: 'edit',
|
||||
data: decLogin
|
||||
data: decCipher
|
||||
});
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
$scope.generatePassword = function () {
|
||||
if (!$scope.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
$analytics.eventTrack('Generated Password From Edit');
|
||||
$scope.login.password = passwordService.generatePassword({ length: 12, special: true });
|
||||
$scope.cipher.login.password = passwordService.generatePassword({ length: 12, special: true });
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addField = function () {
|
||||
if (!$scope.login.fields) {
|
||||
$scope.login.fields = [];
|
||||
if (!$scope.cipher.login.fields) {
|
||||
$scope.cipher.login.fields = [];
|
||||
}
|
||||
|
||||
$scope.login.fields.push({
|
||||
type: '0',
|
||||
$scope.cipher.fields.push({
|
||||
type: constants.fieldType.text.toString(),
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.login.fields.indexOf(field);
|
||||
var index = $scope.cipher.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.login.fields.splice(index, 1);
|
||||
$scope.cipher.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -70,15 +71,15 @@
|
||||
}
|
||||
|
||||
$scope.delete = function () {
|
||||
if (!confirm('Are you sure you want to delete this login (' + $scope.login.name + ')?')) {
|
||||
if (!confirm('Are you sure you want to delete this item (' + $scope.cipher.name + ')?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.ciphers.delAdmin({ id: $scope.login.id }, function () {
|
||||
$analytics.eventTrack('Deleted Organization Login From Edit');
|
||||
apiService.ciphers.delAdmin({ id: $scope.cipher.id }, function () {
|
||||
$analytics.eventTrack('Deleted Organization Cipher From Edit');
|
||||
$uibModalInstance.close({
|
||||
action: 'delete',
|
||||
data: $scope.login.id
|
||||
data: $scope.cipher.id
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -201,6 +201,11 @@
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr ng-repeat="charge in charges">
|
||||
<td style="width: 30px">
|
||||
<a href="#" stop-click ng-click="viewInvoice(charge)" title="Invoice">
|
||||
<i class="fa fa-file-pdf-o"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td style="width: 200px">
|
||||
{{charge.date | date: 'mediumDate'}}
|
||||
</td>
|
||||
|
||||
@@ -25,15 +25,28 @@
|
||||
<input type="text" id="name" name="Name" ng-model="model.name" class="form-control"
|
||||
required api-field ng-readonly="selfHosted" />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Billing Email</label>
|
||||
<input type="email" id="billingEmail" name="BillingEmail" ng-model="model.billingEmail"
|
||||
class="form-control" required api-field ng-readonly="selfHosted" />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Business Name</label>
|
||||
<input type="text" id="businessName" name="BusinessName" ng-model="model.businessName"
|
||||
class="form-control" api-field ng-readonly="selfHosted" />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Billing Email</label>
|
||||
<input type="email" id="billingEmail" name="BillingEmail" ng-model="model.billingEmail"
|
||||
class="form-control" required api-field ng-readonly="selfHosted" />
|
||||
<div ng-if="!selfHosted">
|
||||
<hr />
|
||||
<strong>Tax Information</strong>
|
||||
<div>{{model.businessAddress1}}</div>
|
||||
<div>{{model.businessAddress2}}</div>
|
||||
<div>{{model.businessAddress3}}</div>
|
||||
<div>{{model.businessCountry}}</div>
|
||||
<div>{{model.businessTaxNumber}}</div>
|
||||
<p class="help-block">
|
||||
Please <a href="https://bitwarden.com/contact/" target="_blank">contact support</a>
|
||||
to provide (or update) tax information for your invoices.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3 settings-photo">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<span ng-pluralize
|
||||
count="collections.length > 0 ? collections.length - 1 : 0"
|
||||
when="{'1': '{} collection', 'other': '{} collections'}"></span>,
|
||||
<span ng-pluralize count="logins.length" when="{'1': '{} login', 'other': '{} logins'}"></span>
|
||||
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
@@ -13,12 +13,12 @@
|
||||
<p ng-show="loading && !collections.length">Loading...</p>
|
||||
<div class="box" ng-class="{'collapsed-box': collection.collapsed}" ng-repeat="collection in collections |
|
||||
orderBy: collectionSort track by collection.id"
|
||||
ng-show="collections.length && (!main.searchVaultText || collectionLogins.length)">
|
||||
ng-show="collections.length && (!main.searchVaultText || collectionCiphers.length)">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<i class="fa" ng-class="{'fa-cubes': collection.id, 'fa-sitemap': !collection.id}"></i>
|
||||
{{collection.name}}
|
||||
<small ng-pluralize count="collectionLogins.length" when="{'1': '{} login', 'other': '{} logins'}"></small>
|
||||
<small ng-pluralize count="collectionCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||
</h3>
|
||||
<div class="box-tools">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
|
||||
@@ -27,14 +27,14 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body" ng-class="{'no-padding': collectionLogins.length}">
|
||||
<div ng-show="!collectionLogins.length && collection.id">No logins in this collection.</div>
|
||||
<div ng-show="!collectionLogins.length && !collection.id">No unassigned logins.</div>
|
||||
<div class="table-responsive" ng-show="collectionLogins.length">
|
||||
<div class="box-body" ng-class="{'no-padding': collectionCiphers.length}">
|
||||
<div ng-show="!collectionCiphers.length && collection.id">No items in this collection.</div>
|
||||
<div ng-show="!collectionCiphers.length && !collection.id">No unassigned items.</div>
|
||||
<div class="table-responsive" ng-show="collectionCiphers.length">
|
||||
<table class="table table-striped table-hover table-vmiddle">
|
||||
<tbody>
|
||||
<tr ng-repeat="login in collectionLogins = (logins | filter: filterByCollection(collection) |
|
||||
filter: (main.searchVaultText || '') | orderBy: ['name', 'username']) track by login.id">
|
||||
<tr ng-repeat="cipher in collectionCiphers = (ciphers | filter: filterByCollection(collection) |
|
||||
filter: (main.searchVaultText || '') | orderBy: ['name', 'subTitle']) track by cipher.id">
|
||||
<td style="width: 70px;">
|
||||
<div class="btn-group" data-append-to="body">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
@@ -42,37 +42,44 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="editLogin(login)">
|
||||
<a href="#" stop-click ng-click="editCipher(cipher)">
|
||||
<i class="fa fa-fw fa-pencil"></i> Edit
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="attachments(login)">
|
||||
<a href="#" stop-click ng-click="attachments(cipher)">
|
||||
<i class="fa fa-fw fa-paperclip"></i> Attachments
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="editCollections(login)">
|
||||
<a href="#" stop-click ng-click="editCollections(cipher)">
|
||||
<i class="fa fa-fw fa-cubes"></i> Collections
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="removeLogin(login, collection)" class="text-red"
|
||||
<a href="#" stop-click ng-click="removeCipher(cipher, collection)" class="text-red"
|
||||
ng-if="collection.id">
|
||||
<i class="fa fa-fw fa-remove"></i> Remove
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="deleteLogin(login)" class="text-red">
|
||||
<a href="#" stop-click ng-click="deleteCipher(cipher)" class="text-red">
|
||||
<i class="fa fa-fw fa-trash"></i> Delete
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td class="vault-icon">
|
||||
<i class="fa fa-fw fa-lg {{::cipher.icon}}" ng-if="!cipher.meta.image"></i>
|
||||
<img alt="" ng-if="cipher.meta.image" ng-src="{{cipher.meta.image}}"
|
||||
fallback-src="images/fa-globe.png" />
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" stop-click ng-click="editLogin(login)">{{login.name}}</a>
|
||||
<div class="text-sm text-muted">{{login.username}}</div>
|
||||
<a href="#" stop-click ng-click="editCipher(cipher)">{{cipher.name}}</a>
|
||||
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="cipher.hasAttachments"
|
||||
stop-prop></i>
|
||||
<div class="text-sm text-muted">{{cipher.subTitle}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<p>Edit the collections that this login is being shared with.</p>
|
||||
<p>Edit the collections that this item is being shared with.</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
@@ -35,6 +35,7 @@
|
||||
delAdmin: { url: _apiUri + '/ciphers/:id/delete-admin', method: 'POST', params: { id: '@id' } },
|
||||
delMany: { url: _apiUri + '/ciphers/delete', method: 'POST' },
|
||||
moveMany: { url: _apiUri + '/ciphers/move', method: 'POST' },
|
||||
purge: { url: _apiUri + '/ciphers/purge', method: 'POST' },
|
||||
postAttachment: {
|
||||
url: _apiUri + '/ciphers/:id/attachment',
|
||||
method: 'POST',
|
||||
|
||||
@@ -95,7 +95,7 @@ angular
|
||||
_service.logOut = function () {
|
||||
tokenService.clearTokens();
|
||||
cryptoService.clearKeys();
|
||||
$rootScope.vaultFolders = $rootScope.vaultLogins = null;
|
||||
$rootScope.vaultFolders = $rootScope.vaultCiphers = null;
|
||||
_userProfile = null;
|
||||
};
|
||||
|
||||
@@ -158,8 +158,8 @@ angular
|
||||
cryptoService.setOrgKeys(orgs);
|
||||
_setDeferred.resolve(_userProfile);
|
||||
}
|
||||
}, function () {
|
||||
_setDeferred.reject();
|
||||
}, function (error) {
|
||||
_setDeferred.reject(error);
|
||||
});
|
||||
|
||||
return _setDeferred.promise;
|
||||
@@ -233,7 +233,9 @@ angular
|
||||
_service.refreshAccessToken = function () {
|
||||
var refreshToken = tokenService.getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
return null;
|
||||
return $q(function (resolve, reject) {
|
||||
resolve(null);
|
||||
});
|
||||
}
|
||||
|
||||
return apiService.identity.token({
|
||||
|
||||
@@ -1,64 +1,23 @@
|
||||
angular
|
||||
.module('bit.services')
|
||||
|
||||
.factory('cipherService', function (cryptoService, apiService, $q, $window) {
|
||||
var _service = {};
|
||||
|
||||
_service.decryptLogins = function (encryptedLogins) {
|
||||
if (!encryptedLogins) throw "encryptedLogins is undefined or null";
|
||||
|
||||
var unencryptedLogins = [];
|
||||
for (var i = 0; i < encryptedLogins.length; i++) {
|
||||
unencryptedLogins.push(_service.decryptLogin(encryptedLogins[i]));
|
||||
}
|
||||
|
||||
return unencryptedLogins;
|
||||
.factory('cipherService', function (cryptoService, apiService, $q, $window, constants, appSettings, $localStorage) {
|
||||
var _service = {
|
||||
disableWebsiteIcons: $localStorage.disableWebsiteIcons
|
||||
};
|
||||
|
||||
_service.decryptLogin = function (encryptedLogin, isCipher) {
|
||||
if (!encryptedLogin) throw "encryptedLogin is undefined or null";
|
||||
_service.decryptCiphers = function (encryptedCiphers) {
|
||||
if (!encryptedCiphers) throw "encryptedCiphers is undefined or null";
|
||||
|
||||
var key = null;
|
||||
if (encryptedLogin.OrganizationId) {
|
||||
key = cryptoService.getOrgKey(encryptedLogin.OrganizationId);
|
||||
var unencryptedCiphers = [];
|
||||
for (var i = 0; i < encryptedCiphers.length; i++) {
|
||||
unencryptedCiphers.push(_service.decryptCipher(encryptedCiphers[i]));
|
||||
}
|
||||
|
||||
var login = {
|
||||
id: encryptedLogin.Id,
|
||||
organizationId: encryptedLogin.OrganizationId,
|
||||
collectionIds: encryptedLogin.CollectionIds || [],
|
||||
'type': 1,
|
||||
folderId: encryptedLogin.FolderId,
|
||||
favorite: encryptedLogin.Favorite,
|
||||
edit: encryptedLogin.Edit,
|
||||
organizationUseTotp: encryptedLogin.OrganizationUseTotp,
|
||||
attachments: null
|
||||
};
|
||||
|
||||
var loginData = encryptedLogin.Data;
|
||||
if (loginData) {
|
||||
login.name = cryptoService.decrypt(loginData.Name, key);
|
||||
login.uri = loginData.Uri && loginData.Uri !== '' ? cryptoService.decrypt(loginData.Uri, key) : null;
|
||||
login.username = loginData.Username && loginData.Username !== '' ? cryptoService.decrypt(loginData.Username, key) : null;
|
||||
login.password = loginData.Password && loginData.Password !== '' ? cryptoService.decrypt(loginData.Password, key) : null;
|
||||
login.notes = loginData.Notes && loginData.Notes !== '' ? cryptoService.decrypt(loginData.Notes, key) : null;
|
||||
login.totp = loginData.Totp && loginData.Totp !== '' ? cryptoService.decrypt(loginData.Totp, key) : null;
|
||||
login.fields = _service.decryptFields(key, loginData.Fields);
|
||||
}
|
||||
|
||||
if (!encryptedLogin.Attachments) {
|
||||
return login;
|
||||
}
|
||||
|
||||
login.attachments = [];
|
||||
for (var i = 0; i < encryptedLogin.Attachments.length; i++) {
|
||||
login.attachments.push(_service.decryptAttachment(key, encryptedLogin.Attachments[i]));
|
||||
}
|
||||
|
||||
return login;
|
||||
return unencryptedCiphers;
|
||||
};
|
||||
|
||||
_service.decryptLoginPreview = function (encryptedCipher) {
|
||||
_service.decryptCipher = function (encryptedCipher) {
|
||||
if (!encryptedCipher) throw "encryptedCipher is undefined or null";
|
||||
|
||||
var key = null;
|
||||
@@ -66,27 +25,202 @@ angular
|
||||
key = cryptoService.getOrgKey(encryptedCipher.OrganizationId);
|
||||
}
|
||||
|
||||
var login = {
|
||||
var cipher = {
|
||||
id: encryptedCipher.Id,
|
||||
organizationId: encryptedCipher.OrganizationId,
|
||||
collectionIds: encryptedCipher.CollectionIds || [],
|
||||
'type': encryptedCipher.Type,
|
||||
folderId: encryptedCipher.FolderId,
|
||||
favorite: encryptedCipher.Favorite,
|
||||
edit: encryptedCipher.Edit,
|
||||
organizationUseTotp: encryptedCipher.OrganizationUseTotp,
|
||||
hasAttachments: !!encryptedCipher.Attachments && encryptedCipher.Attachments.length > 0
|
||||
attachments: null,
|
||||
icon: null
|
||||
};
|
||||
|
||||
var loginData = encryptedCipher.Data;
|
||||
if (loginData) {
|
||||
login.name = _service.decryptProperty(loginData.Name, key, false);
|
||||
login.username = _service.decryptProperty(loginData.Username, key, true);
|
||||
login.password = _service.decryptProperty(loginData.Password, key, true);
|
||||
var cipherData = encryptedCipher.Data;
|
||||
if (cipherData) {
|
||||
cipher.name = cryptoService.decrypt(cipherData.Name, key);
|
||||
cipher.notes = _service.decryptProperty(cipherData.Notes, key, true, false);
|
||||
cipher.fields = _service.decryptFields(key, cipherData.Fields);
|
||||
|
||||
var dataObj = {};
|
||||
switch (cipher.type) {
|
||||
case constants.cipherType.login:
|
||||
dataObj.uri = _service.decryptProperty(cipherData.Uri, key, true, false);
|
||||
dataObj.username = _service.decryptProperty(cipherData.Username, key, true, false);
|
||||
dataObj.password = _service.decryptProperty(cipherData.Password, key, true, false);
|
||||
dataObj.totp = _service.decryptProperty(cipherData.Totp, key, true, false);
|
||||
cipher.login = dataObj;
|
||||
cipher.icon = 'fa-globe';
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
dataObj.type = cipherData.Type;
|
||||
cipher.secureNote = dataObj;
|
||||
cipher.icon = 'fa-sticky-note-o';
|
||||
break;
|
||||
case constants.cipherType.card:
|
||||
dataObj.cardholderName = _service.decryptProperty(cipherData.CardholderName, key, true, false);
|
||||
dataObj.number = _service.decryptProperty(cipherData.Number, key, true, false);
|
||||
dataObj.brand = _service.decryptProperty(cipherData.Brand, key, true, false);
|
||||
dataObj.expMonth = _service.decryptProperty(cipherData.ExpMonth, key, true, false);
|
||||
dataObj.expYear = _service.decryptProperty(cipherData.ExpYear, key, true, false);
|
||||
dataObj.code = _service.decryptProperty(cipherData.Code, key, true, false);
|
||||
cipher.card = dataObj;
|
||||
cipher.icon = 'fa-credit-card';
|
||||
break;
|
||||
case constants.cipherType.identity:
|
||||
dataObj.title = _service.decryptProperty(cipherData.Title, key, true, false);
|
||||
dataObj.firstName = _service.decryptProperty(cipherData.FirstName, key, true, false);
|
||||
dataObj.middleName = _service.decryptProperty(cipherData.MiddleName, key, true, false);
|
||||
dataObj.lastName = _service.decryptProperty(cipherData.LastName, key, true, false);
|
||||
dataObj.address1 = _service.decryptProperty(cipherData.Address1, key, true, false);
|
||||
dataObj.address2 = _service.decryptProperty(cipherData.Address2, key, true, false);
|
||||
dataObj.address3 = _service.decryptProperty(cipherData.Address3, key, true, false);
|
||||
dataObj.city = _service.decryptProperty(cipherData.City, key, true, false);
|
||||
dataObj.state = _service.decryptProperty(cipherData.State, key, true, false);
|
||||
dataObj.postalCode = _service.decryptProperty(cipherData.PostalCode, key, true, false);
|
||||
dataObj.country = _service.decryptProperty(cipherData.Country, key, true, false);
|
||||
dataObj.company = _service.decryptProperty(cipherData.Company, key, true, false);
|
||||
dataObj.email = _service.decryptProperty(cipherData.Email, key, true, false);
|
||||
dataObj.phone = _service.decryptProperty(cipherData.Phone, key, true, false);
|
||||
dataObj.ssn = _service.decryptProperty(cipherData.SSN, key, true, false);
|
||||
dataObj.username = _service.decryptProperty(cipherData.Username, key, true, false);
|
||||
dataObj.passportNumber = _service.decryptProperty(cipherData.PassportNumber, key, true, false);
|
||||
dataObj.licenseNumber = _service.decryptProperty(cipherData.LicenseNumber, key, true, false);
|
||||
cipher.identity = dataObj;
|
||||
cipher.icon = 'fa-id-card-o';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return login;
|
||||
if (!encryptedCipher.Attachments) {
|
||||
return cipher;
|
||||
}
|
||||
|
||||
cipher.attachments = [];
|
||||
for (var i = 0; i < encryptedCipher.Attachments.length; i++) {
|
||||
cipher.attachments.push(_service.decryptAttachment(key, encryptedCipher.Attachments[i]));
|
||||
}
|
||||
|
||||
return cipher;
|
||||
};
|
||||
|
||||
_service.decryptCipherPreview = function (encryptedCipher) {
|
||||
if (!encryptedCipher) throw "encryptedCipher is undefined or null";
|
||||
|
||||
var key = null;
|
||||
if (encryptedCipher.OrganizationId) {
|
||||
key = cryptoService.getOrgKey(encryptedCipher.OrganizationId);
|
||||
}
|
||||
|
||||
var cipher = {
|
||||
id: encryptedCipher.Id,
|
||||
organizationId: encryptedCipher.OrganizationId,
|
||||
collectionIds: encryptedCipher.CollectionIds || [],
|
||||
'type': encryptedCipher.Type,
|
||||
folderId: encryptedCipher.FolderId,
|
||||
favorite: encryptedCipher.Favorite,
|
||||
edit: encryptedCipher.Edit,
|
||||
organizationUseTotp: encryptedCipher.OrganizationUseTotp,
|
||||
hasAttachments: !!encryptedCipher.Attachments && encryptedCipher.Attachments.length > 0,
|
||||
meta: {},
|
||||
icon: null
|
||||
};
|
||||
|
||||
var cipherData = encryptedCipher.Data;
|
||||
if (cipherData) {
|
||||
cipher.name = _service.decryptProperty(cipherData.Name, key, false, true);
|
||||
|
||||
var dataObj = {};
|
||||
switch (cipher.type) {
|
||||
case constants.cipherType.login:
|
||||
cipher.subTitle = _service.decryptProperty(cipherData.Username, key, true, true);
|
||||
cipher.meta.password = _service.decryptProperty(cipherData.Password, key, true, true);
|
||||
cipher.meta.uri = _service.decryptProperty(cipherData.Uri, key, true, true);
|
||||
setLoginIcon(cipher, cipher.meta.uri, true);
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.subTitle = null;
|
||||
cipher.icon = 'fa-sticky-note-o';
|
||||
break;
|
||||
case constants.cipherType.card:
|
||||
cipher.subTitle = '';
|
||||
cipher.meta.number = _service.decryptProperty(cipherData.Number, key, true, true);
|
||||
var brand = _service.decryptProperty(cipherData.Brand, key, true, true);
|
||||
if (brand) {
|
||||
cipher.subTitle = brand;
|
||||
}
|
||||
if (cipher.meta.number && cipher.meta.number.length >= 4) {
|
||||
if (cipher.subTitle !== '') {
|
||||
cipher.subTitle += ', ';
|
||||
}
|
||||
cipher.subTitle += ('*' + cipher.meta.number.substr(cipher.meta.number.length - 4));
|
||||
}
|
||||
cipher.icon = 'fa-credit-card';
|
||||
break;
|
||||
case constants.cipherType.identity:
|
||||
var firstName = _service.decryptProperty(cipherData.FirstName, key, true, true);
|
||||
var lastName = _service.decryptProperty(cipherData.LastName, key, true, true);
|
||||
cipher.subTitle = '';
|
||||
if (firstName) {
|
||||
cipher.subTitle = firstName;
|
||||
}
|
||||
if (lastName) {
|
||||
if (cipher.subTitle !== '') {
|
||||
cipher.subTitle += ' ';
|
||||
}
|
||||
cipher.subTitle += lastName;
|
||||
}
|
||||
cipher.icon = 'fa-id-card-o';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (cipher.subTitle === '') {
|
||||
cipher.subTitle = null;
|
||||
}
|
||||
}
|
||||
|
||||
return cipher;
|
||||
};
|
||||
|
||||
function setLoginIcon(cipher, uri, setImage) {
|
||||
if (!_service.disableWebsiteIcons && uri) {
|
||||
var hostnameUri = uri,
|
||||
isWebsite = false;
|
||||
|
||||
if (hostnameUri.indexOf('androidapp://') === 0) {
|
||||
cipher.icon = 'fa-android';
|
||||
}
|
||||
else if (hostnameUri.indexOf('iosapp://') === 0) {
|
||||
cipher.icon = 'fa-apple';
|
||||
}
|
||||
else if (hostnameUri.indexOf('://') === -1 && hostnameUri.indexOf('.') > -1) {
|
||||
hostnameUri = "http://" + hostnameUri;
|
||||
isWebsite = true;
|
||||
}
|
||||
else {
|
||||
isWebsite = hostnameUri.indexOf('http') === 0 && hostnameUri.indexOf('.') > -1;
|
||||
}
|
||||
|
||||
if (setImage && isWebsite) {
|
||||
try {
|
||||
var url = new URL(hostnameUri);
|
||||
cipher.meta.image = appSettings.iconsUri + '/' + url.hostname + '/icon.png';
|
||||
}
|
||||
catch (e) { }
|
||||
}
|
||||
}
|
||||
|
||||
if (!cipher.icon) {
|
||||
cipher.icon = 'fa-globe';
|
||||
}
|
||||
}
|
||||
|
||||
_service.decryptAttachment = function (key, encryptedAttachment) {
|
||||
if (!encryptedAttachment) throw "encryptedAttachment is undefined or null";
|
||||
|
||||
@@ -182,7 +316,7 @@ angular
|
||||
|
||||
return {
|
||||
id: encryptedFolder.Id,
|
||||
name: _service.decryptProperty(encryptedFolder.Name, null, false)
|
||||
name: _service.decryptProperty(encryptedFolder.Name, null, false, true)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -206,12 +340,12 @@ angular
|
||||
|
||||
return {
|
||||
id: encryptedCollection.Id,
|
||||
name: catchError ? _service.decryptProperty(encryptedCollection.Name, key, false) :
|
||||
name: catchError ? _service.decryptProperty(encryptedCollection.Name, key, false, true) :
|
||||
cryptoService.decrypt(encryptedCollection.Name, key)
|
||||
};
|
||||
};
|
||||
|
||||
_service.decryptProperty = function (property, key, checkEmpty) {
|
||||
_service.decryptProperty = function (property, key, checkEmpty, showError) {
|
||||
if (checkEmpty && (!property || property === '')) {
|
||||
return null;
|
||||
}
|
||||
@@ -223,53 +357,100 @@ angular
|
||||
property = null;
|
||||
}
|
||||
|
||||
return property || '[error: cannot decrypt]';
|
||||
return property || (showError ? '[error: cannot decrypt]' : null);
|
||||
};
|
||||
|
||||
_service.encryptLogins = function (unencryptedLogins, key) {
|
||||
if (!unencryptedLogins) throw "unencryptedLogins is undefined or null";
|
||||
_service.encryptCiphers = function (unencryptedCiphers, key) {
|
||||
if (!unencryptedCiphers) throw "unencryptedCiphers is undefined or null";
|
||||
|
||||
var encryptedLogins = [];
|
||||
for (var i = 0; i < unencryptedLogins.length; i++) {
|
||||
encryptedLogins.push(_service.encryptLogin(unencryptedLogins[i], key));
|
||||
var encryptedCiphers = [];
|
||||
for (var i = 0; i < unencryptedCiphers.length; i++) {
|
||||
encryptedCiphers.push(_service.encryptCipher(unencryptedCiphers[i], null, key));
|
||||
}
|
||||
|
||||
return encryptedLogins;
|
||||
return encryptedCiphers;
|
||||
};
|
||||
|
||||
_service.encryptLogin = function (unencryptedLogin, key, attachments) {
|
||||
if (!unencryptedLogin) throw "unencryptedLogin is undefined or null";
|
||||
_service.encryptCipher = function (unencryptedCipher, type, key, attachments) {
|
||||
if (!unencryptedCipher) throw "unencryptedCipher is undefined or null";
|
||||
|
||||
if (unencryptedLogin.organizationId) {
|
||||
key = key || cryptoService.getOrgKey(unencryptedLogin.organizationId);
|
||||
if (unencryptedCipher.organizationId) {
|
||||
key = key || cryptoService.getOrgKey(unencryptedCipher.organizationId);
|
||||
}
|
||||
|
||||
var login = {
|
||||
id: unencryptedLogin.id,
|
||||
'type': 1,
|
||||
organizationId: unencryptedLogin.organizationId || null,
|
||||
folderId: unencryptedLogin.folderId === '' ? null : unencryptedLogin.folderId,
|
||||
favorite: unencryptedLogin.favorite !== null ? unencryptedLogin.favorite : false,
|
||||
name: cryptoService.encrypt(unencryptedLogin.name, key),
|
||||
notes: !unencryptedLogin.notes || unencryptedLogin.notes === '' ? null : cryptoService.encrypt(unencryptedLogin.notes, key),
|
||||
login: {
|
||||
uri: !unencryptedLogin.uri || unencryptedLogin.uri === '' ? null : cryptoService.encrypt(unencryptedLogin.uri, key),
|
||||
username: !unencryptedLogin.username || unencryptedLogin.username === '' ? null : cryptoService.encrypt(unencryptedLogin.username, key),
|
||||
password: !unencryptedLogin.password || unencryptedLogin.password === '' ? null : cryptoService.encrypt(unencryptedLogin.password, key),
|
||||
totp: !unencryptedLogin.totp || unencryptedLogin.totp === '' ? null : cryptoService.encrypt(unencryptedLogin.totp, key)
|
||||
},
|
||||
fields: _service.encryptFields(unencryptedLogin.fields, key)
|
||||
var cipher = {
|
||||
id: unencryptedCipher.id,
|
||||
'type': type || unencryptedCipher.type,
|
||||
organizationId: unencryptedCipher.organizationId || null,
|
||||
folderId: unencryptedCipher.folderId === '' ? null : unencryptedCipher.folderId,
|
||||
favorite: unencryptedCipher.favorite !== null ? unencryptedCipher.favorite : false,
|
||||
name: cryptoService.encrypt(unencryptedCipher.name, key),
|
||||
notes: encryptProperty(unencryptedCipher.notes, key),
|
||||
fields: _service.encryptFields(unencryptedCipher.fields, key)
|
||||
};
|
||||
|
||||
if (unencryptedLogin.attachments && attachments) {
|
||||
login.attachments = {};
|
||||
for (var i = 0; i < unencryptedLogin.attachments.length; i++) {
|
||||
login.attachments[unencryptedLogin.attachments[i].id] =
|
||||
cryptoService.encrypt(unencryptedLogin.attachments[i].fileName, key);
|
||||
switch (cipher.type) {
|
||||
case constants.cipherType.login:
|
||||
var loginData = unencryptedCipher.login;
|
||||
cipher.login = {
|
||||
uri: encryptProperty(loginData.uri, key),
|
||||
username: encryptProperty(loginData.username, key),
|
||||
password: encryptProperty(loginData.password, key),
|
||||
totp: encryptProperty(loginData.totp, key)
|
||||
};
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.secureNote = {
|
||||
type: unencryptedCipher.secureNote.type
|
||||
};
|
||||
break;
|
||||
case constants.cipherType.card:
|
||||
var cardData = unencryptedCipher.card;
|
||||
cipher.card = {
|
||||
cardholderName: encryptProperty(cardData.cardholderName, key),
|
||||
brand: encryptProperty(cardData.brand, key),
|
||||
number: encryptProperty(cardData.number, key),
|
||||
expMonth: encryptProperty(cardData.expMonth, key),
|
||||
expYear: encryptProperty(cardData.expYear, key),
|
||||
code: encryptProperty(cardData.code, key)
|
||||
};
|
||||
break;
|
||||
case constants.cipherType.identity:
|
||||
var identityData = unencryptedCipher.identity;
|
||||
cipher.identity = {
|
||||
title: encryptProperty(identityData.title, key),
|
||||
firstName: encryptProperty(identityData.firstName, key),
|
||||
middleName: encryptProperty(identityData.middleName, key),
|
||||
lastName: encryptProperty(identityData.lastName, key),
|
||||
address1: encryptProperty(identityData.address1, key),
|
||||
address2: encryptProperty(identityData.address2, key),
|
||||
address3: encryptProperty(identityData.address3, key),
|
||||
city: encryptProperty(identityData.city, key),
|
||||
state: encryptProperty(identityData.state, key),
|
||||
postalCode: encryptProperty(identityData.postalCode, key),
|
||||
country: encryptProperty(identityData.country, key),
|
||||
company: encryptProperty(identityData.company, key),
|
||||
email: encryptProperty(identityData.email, key),
|
||||
phone: encryptProperty(identityData.phone, key),
|
||||
ssn: encryptProperty(identityData.ssn, key),
|
||||
username: encryptProperty(identityData.username, key),
|
||||
passportNumber: encryptProperty(identityData.passportNumber, key),
|
||||
licenseNumber: encryptProperty(identityData.licenseNumber, key)
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (unencryptedCipher.attachments && attachments) {
|
||||
cipher.attachments = {};
|
||||
for (var i = 0; i < unencryptedCipher.attachments.length; i++) {
|
||||
cipher.attachments[unencryptedCipher.attachments[i].id] =
|
||||
cryptoService.encrypt(unencryptedCipher.attachments[i].fileName, key);
|
||||
}
|
||||
}
|
||||
|
||||
return login;
|
||||
return cipher;
|
||||
};
|
||||
|
||||
_service.encryptAttachmentFile = function (key, unencryptedFile) {
|
||||
@@ -365,5 +546,9 @@ angular
|
||||
};
|
||||
};
|
||||
|
||||
function encryptProperty(property, key) {
|
||||
return !property || property === '' ? null : cryptoService.encrypt(property, key);
|
||||
}
|
||||
|
||||
return _service;
|
||||
});
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
angular.module("bit")
|
||||
.constant("appSettings", {"apiUri":"https://api.bitwarden.com","identityUri":"https://identity.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","whitelistDomains":["api.bitwarden.com"],"selfHosted":false,"version":"1.17.3","environment":"Production"});
|
||||
.constant("appSettings", {"apiUri":"https://api.bitwarden.com","identityUri":"https://identity.bitwarden.com","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","whitelistDomains":["api.bitwarden.com"],"selfHosted":false,"version":"1.18.0","environment":"Production"});
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
.controller('settingsBillingChangePaymentController', function ($scope, $state, $uibModalInstance, apiService,
|
||||
$analytics, toastr, existingPaymentMethod, appSettings, $timeout
|
||||
// @if !selfHosted
|
||||
/* jshint ignore:start */
|
||||
, stripe
|
||||
/* jshint ignore:end */
|
||||
// @endif
|
||||
) {
|
||||
$analytics.eventTrack('settingsBillingChangePaymentController', { category: 'Modal' });
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
angular
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsController', function ($scope, $state, $uibModal, apiService, toastr, authService) {
|
||||
.controller('settingsController', function ($scope, $state, $uibModal, apiService, toastr, authService, $localStorage,
|
||||
$rootScope, cipherService) {
|
||||
$scope.model = {
|
||||
profile: {},
|
||||
twoFactorEnabled: false,
|
||||
email: null
|
||||
email: null,
|
||||
disableWebsiteIcons: false
|
||||
};
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
@@ -17,7 +18,7 @@
|
||||
culture: user.Culture
|
||||
},
|
||||
email: user.Email,
|
||||
twoFactorEnabled: user.TwoFactorEnabled
|
||||
disableWebsiteIcons: $localStorage.disableWebsiteIcons
|
||||
};
|
||||
|
||||
if (user.Organizations) {
|
||||
@@ -58,6 +59,13 @@
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
$scope.optionsSave = function () {
|
||||
$localStorage.disableWebsiteIcons = cipherService.disableWebsiteIcons = $scope.model.disableWebsiteIcons;
|
||||
$rootScope.vaultCiphers = null;
|
||||
|
||||
toastr.success('Options have been updated.', 'Success!');
|
||||
};
|
||||
|
||||
$scope.changePassword = function () {
|
||||
$uibModal.open({
|
||||
animation: true,
|
||||
@@ -121,6 +129,14 @@
|
||||
});
|
||||
};
|
||||
|
||||
$scope.purge = function () {
|
||||
$uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/settings/views/settingsPurge.html',
|
||||
controller: 'settingsPurgeController'
|
||||
});
|
||||
};
|
||||
|
||||
function scrollToTop() {
|
||||
$('html, body').animate({ scrollTop: 0 }, 200);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
.controller('settingsCreateOrganizationController', function ($scope, $state, apiService, cryptoService,
|
||||
toastr, $analytics, authService, constants, appSettings, validationService
|
||||
// @if !selfHosted
|
||||
/* jshint ignore:start */
|
||||
, stripe
|
||||
/* jshint ignore:end */
|
||||
// @endif
|
||||
) {
|
||||
$scope.plans = constants.plans;
|
||||
@@ -113,6 +115,7 @@
|
||||
additionalStorageGb: model.additionalStorageGb,
|
||||
billingEmail: model.billingEmail,
|
||||
businessName: model.ownedBusiness ? model.businessName : null,
|
||||
country: $scope.paymentMethod === 'card' ? model.card.address_country : null,
|
||||
collectionName: defaultCollectionCt
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
.controller('settingsPremiumController', function ($scope, $state, apiService, toastr, $analytics, authService,
|
||||
constants, $timeout, appSettings, validationService
|
||||
// @if !selfHosted
|
||||
/* jshint ignore:start */
|
||||
, stripe
|
||||
/* jshint ignore:end */
|
||||
// @endif
|
||||
) {
|
||||
var profile = null;
|
||||
|
||||
24
src/app/settings/settingsPurgeController.js
Normal file
@@ -0,0 +1,24 @@
|
||||
angular
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsPurgeController', function ($scope, $state, apiService, $uibModalInstance, cryptoService,
|
||||
authService, toastr, $analytics, tokenService) {
|
||||
$analytics.eventTrack('settingsPurgeController', { category: 'Modal' });
|
||||
$scope.submit = function (model) {
|
||||
$scope.submitPromise = cryptoService.hashPassword(model.masterPassword).then(function (hash) {
|
||||
return apiService.ciphers.purge({
|
||||
masterPasswordHash: hash
|
||||
}).$promise;
|
||||
}).then(function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
$analytics.eventTrack('Purged Vault');
|
||||
return $state.go('backend.user.vault', { refreshFromServer: true });
|
||||
}).then(function () {
|
||||
toastr.success('All items in your vault have been deleted.', 'Vault Purged');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
||||
@@ -31,19 +31,19 @@
|
||||
function updateKey(masterPasswordHash) {
|
||||
var madeEncKey = cryptoService.makeEncKey(null);
|
||||
|
||||
var reencryptedLogins = [];
|
||||
var loginsPromise = apiService.ciphers.list({}, function (encryptedLogins) {
|
||||
var filteredEncryptedLogins = [];
|
||||
for (var i = 0; i < encryptedLogins.Data.length; i++) {
|
||||
if (encryptedLogins.Data[i].OrganizationId) {
|
||||
var reencryptedCiphers = [];
|
||||
var ciphersPromise = apiService.ciphers.list({}, function (encryptedCiphers) {
|
||||
var filteredEncryptedCiphers = [];
|
||||
for (var i = 0; i < encryptedCiphers.Data.length; i++) {
|
||||
if (encryptedCiphers.Data[i].OrganizationId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filteredEncryptedLogins.push(encryptedLogins.Data[i]);
|
||||
filteredEncryptedCiphers.push(encryptedCiphers.Data[i]);
|
||||
}
|
||||
|
||||
var unencryptedLogins = cipherService.decryptLogins(filteredEncryptedLogins);
|
||||
reencryptedLogins = cipherService.encryptLogins(unencryptedLogins, madeEncKey.encKey);
|
||||
var unencryptedCiphers = cipherService.decryptCiphers(filteredEncryptedCiphers);
|
||||
reencryptedCiphers = cipherService.encryptCiphers(unencryptedCiphers, madeEncKey.encKey);
|
||||
}).$promise;
|
||||
|
||||
var reencryptedFolders = [];
|
||||
@@ -58,10 +58,10 @@
|
||||
reencryptedPrivateKey = cryptoService.encrypt(privateKey, madeEncKey.encKey, 'raw');
|
||||
}
|
||||
|
||||
return $q.all([loginsPromise, foldersPromise]).then(function () {
|
||||
return $q.all([ciphersPromise, foldersPromise]).then(function () {
|
||||
var request = {
|
||||
masterPasswordHash: masterPasswordHash,
|
||||
ciphers: reencryptedLogins,
|
||||
ciphers: reencryptedCiphers,
|
||||
folders: reencryptedFolders,
|
||||
privateKey: reencryptedPrivateKey,
|
||||
key: madeEncKey.encKeyEnc
|
||||
|
||||
@@ -86,6 +86,27 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Web Vault Options</h3>
|
||||
</div>
|
||||
<form role="form" name="optionsForm" ng-submit="optionsForm.$valid && optionsSave()" autocomplete="off">
|
||||
<div class="box-body">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="model.disableWebsiteIcons">
|
||||
Disable Website Icons
|
||||
</label>
|
||||
<p class="help-block">Website Icons provide a recognizable image next to each login item in your vault.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="optionsForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="optionsForm.$loading"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Organizations</h3>
|
||||
@@ -129,10 +150,13 @@
|
||||
Careful, these actions are not reversible!
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="submit" class="btn btn-default btn-flat" ng-click="sessions()">
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="sessions()">
|
||||
Deauthorize Sessions
|
||||
</button>
|
||||
<button type="submit" class="btn btn-default btn-flat" ng-click="delete()">
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="purge()">
|
||||
Purge Vault
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="delete()">
|
||||
Delete Account
|
||||
</button>
|
||||
</div>
|
||||
|
||||
33
src/app/settings/views/settingsPurge.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-trash"></i> Purge Vault</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" api-form="submitPromise">
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Continue below to delete all items in your vault. Items that belong to an organization that you share
|
||||
with will not be deleted.
|
||||
</p>
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||
Purging your vault is permanent. It cannot be undone.
|
||||
</div>
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in form.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="masterPassword">Master Password</label>
|
||||
<input type="password" id="masterPassword" name="MasterPasswordHash" ng-model="model.masterPassword"
|
||||
class="form-control" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Purge
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -2,23 +2,23 @@
|
||||
.module('bit.tools')
|
||||
|
||||
.controller('toolsExportController', function ($scope, apiService, $uibModalInstance, cipherService, $q,
|
||||
toastr, $analytics) {
|
||||
toastr, $analytics, constants) {
|
||||
$analytics.eventTrack('toolsExportController', { category: 'Modal' });
|
||||
$scope.export = function (model) {
|
||||
$scope.startedExport = true;
|
||||
var decLogins = [],
|
||||
var decCiphers = [],
|
||||
decFolders = [];
|
||||
|
||||
var folderPromise = apiService.folders.list({}, function (folders) {
|
||||
decFolders = cipherService.decryptFolders(folders.Data);
|
||||
}).$promise;
|
||||
|
||||
var loginsPromise = apiService.ciphers.list({}, function (logins) {
|
||||
decLogins = cipherService.decryptLogins(logins.Data);
|
||||
var ciphersPromise = apiService.ciphers.list({}, function (ciphers) {
|
||||
decCiphers = cipherService.decryptCiphers(ciphers.Data);
|
||||
}).$promise;
|
||||
|
||||
$q.all([folderPromise, loginsPromise]).then(function () {
|
||||
if (!decLogins.length) {
|
||||
$q.all([folderPromise, ciphersPromise]).then(function () {
|
||||
if (!decCiphers.length) {
|
||||
toastr.error('Nothing to export.', 'Error!');
|
||||
$scope.close();
|
||||
return;
|
||||
@@ -30,38 +30,61 @@
|
||||
}
|
||||
|
||||
try {
|
||||
var exportLogins = [];
|
||||
for (i = 0; i < decLogins.length; i++) {
|
||||
var login = {
|
||||
name: decLogins[i].name,
|
||||
uri: decLogins[i].uri,
|
||||
username: decLogins[i].username,
|
||||
password: decLogins[i].password,
|
||||
notes: decLogins[i].notes,
|
||||
folder: decLogins[i].folderId && (decLogins[i].folderId in foldersDict) ?
|
||||
foldersDict[decLogins[i].folderId].name : null,
|
||||
favorite: decLogins[i].favorite ? 1 : null,
|
||||
totp: decLogins[i].totp,
|
||||
fields: null
|
||||
var exportCiphers = [];
|
||||
for (i = 0; i < decCiphers.length; i++) {
|
||||
// only export logins and secure notes
|
||||
if (decCiphers[i].type !== constants.cipherType.login &&
|
||||
decCiphers[i].type !== constants.cipherType.secureNote) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var cipher = {
|
||||
folder: decCiphers[i].folderId && (decCiphers[i].folderId in foldersDict) ?
|
||||
foldersDict[decCiphers[i].folderId].name : null,
|
||||
favorite: decCiphers[i].favorite ? 1 : null,
|
||||
type: null,
|
||||
name: decCiphers[i].name,
|
||||
notes: decCiphers[i].notes,
|
||||
fields: null,
|
||||
// Login props
|
||||
login_uri: null,
|
||||
login_username: null,
|
||||
login_password: null,
|
||||
login_totp: null
|
||||
};
|
||||
|
||||
if (decLogins[i].fields) {
|
||||
for (var j = 0; j < decLogins[i].fields.length; j++) {
|
||||
if (!login.fields) {
|
||||
login.fields = '';
|
||||
if (decCiphers[i].fields) {
|
||||
for (var j = 0; j < decCiphers[i].fields.length; j++) {
|
||||
if (!cipher.fields) {
|
||||
cipher.fields = '';
|
||||
}
|
||||
else {
|
||||
login.fields += '\n';
|
||||
cipher.fields += '\n';
|
||||
}
|
||||
|
||||
login.fields += ((decLogins[i].fields[j].name || '') + ': ' + decLogins[i].fields[j].value);
|
||||
cipher.fields += ((decCiphers[i].fields[j].name || '') + ': ' + decCiphers[i].fields[j].value);
|
||||
}
|
||||
}
|
||||
|
||||
exportLogins.push(login);
|
||||
switch (decCiphers[i].type) {
|
||||
case constants.cipherType.login:
|
||||
cipher.type = 'login';
|
||||
cipher.login_uri = decCiphers[i].login.uri;
|
||||
cipher.login_username = decCiphers[i].login.username;
|
||||
cipher.login_password = decCiphers[i].login.password;
|
||||
cipher.login_totp = decCiphers[i].login.totp;
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.type = 'note';
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
exportCiphers.push(cipher);
|
||||
}
|
||||
|
||||
var csvString = Papa.unparse(exportLogins);
|
||||
var csvString = Papa.unparse(exportCiphers);
|
||||
var csvBlob = new Blob([csvString]);
|
||||
|
||||
// IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
|
||||
|
||||
@@ -102,7 +102,8 @@
|
||||
id: 'enpasscsv',
|
||||
name: 'Enpass (csv)',
|
||||
instructions: $sce.trustAsHtml('Using the Enpass desktop application, navigate to "File" > "Export" > ' +
|
||||
'"As CSV". Select "Yes" to the warning alert and save the CSV file.')
|
||||
'"As CSV". Select "Yes" to the warning alert and save the CSV file. Note that the importer only fully ' +
|
||||
'supports files exported while Enpass is set to the English language, so adjust your settings accordingly.')
|
||||
},
|
||||
{
|
||||
id: 'safeincloudxml',
|
||||
@@ -237,7 +238,7 @@
|
||||
{
|
||||
id: 'gnomejson',
|
||||
name: 'GNOME Passwords and Keys/Seahorse (json)',
|
||||
instructions: $sce.trustAsHtml('Make sure you have python-keyring and python-gnomekeyring installed. ' +
|
||||
instructions: $sce.trustAsHtml('Make sure you have python-keyring and python-gnomekeyring installed. ' +
|
||||
'Save the <a target="_blank" href="http://bit.ly/2sMldAI">GNOME Keyring Import/Export</a> ' +
|
||||
'python script by Luke Plant to your desktop as <code>pw_helper.py</code>. Open terminal and run ' +
|
||||
'<code>chmod +rx Desktop/pw_helper.py</code> and then ' +
|
||||
@@ -272,15 +273,15 @@
|
||||
importService.import(model.source, file || model.fileContents, importSuccess, importError);
|
||||
};
|
||||
|
||||
function importSuccess(folders, logins, folderRelationships) {
|
||||
if (!folders.length && !logins.length) {
|
||||
function importSuccess(folders, ciphers, folderRelationships) {
|
||||
if (!folders.length && !ciphers.length) {
|
||||
importError('Nothing was imported.');
|
||||
return;
|
||||
}
|
||||
else if (logins.length) {
|
||||
var halfway = Math.floor(logins.length / 2);
|
||||
var last = logins.length - 1;
|
||||
if (loginIsBadData(logins[0]) && loginIsBadData(logins[halfway]) && loginIsBadData(logins[last])) {
|
||||
else if (ciphers.length) {
|
||||
var halfway = Math.floor(ciphers.length / 2);
|
||||
var last = ciphers.length - 1;
|
||||
if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) {
|
||||
importError('CSV data is not formatted correctly. Please check your import file and try again.');
|
||||
return;
|
||||
}
|
||||
@@ -288,7 +289,7 @@
|
||||
|
||||
apiService.ciphers.import({
|
||||
folders: cipherService.encryptFolders(folders),
|
||||
ciphers: cipherService.encryptLogins(logins),
|
||||
ciphers: cipherService.encryptCiphers(ciphers),
|
||||
folderRelationships: folderRelationships
|
||||
}, function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
@@ -299,8 +300,9 @@
|
||||
}, importError);
|
||||
}
|
||||
|
||||
function loginIsBadData(login) {
|
||||
return (login.name === null || login.name === '--') && (login.password === null || login.password === '');
|
||||
function cipherIsBadData(cipher) {
|
||||
return (cipher.name === null || cipher.name === '--') &&
|
||||
(cipher.login && (cipher.login.password === null || cipher.login.password === ''));
|
||||
}
|
||||
|
||||
function importError(error) {
|
||||
|
||||
@@ -1,57 +1,70 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultAddLoginController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
||||
passwordService, selectedFolder, $analytics, checkedFavorite, $rootScope, authService, $uibModal) {
|
||||
$analytics.eventTrack('vaultAddLoginController', { category: 'Modal' });
|
||||
.controller('vaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
||||
passwordService, selectedFolder, $analytics, checkedFavorite, $rootScope, authService, $uibModal, constants) {
|
||||
$analytics.eventTrack('vaultAddCipherController', { category: 'Modal' });
|
||||
$scope.folders = $rootScope.vaultFolders;
|
||||
$scope.login = {
|
||||
$scope.constants = constants;
|
||||
$scope.selectedType = constants.cipherType.login.toString();
|
||||
$scope.cipher = {
|
||||
folderId: selectedFolder ? selectedFolder.id : null,
|
||||
favorite: checkedFavorite === true
|
||||
favorite: checkedFavorite === true,
|
||||
type: constants.cipherType.login,
|
||||
login: {},
|
||||
identity: {},
|
||||
card: {},
|
||||
secureNote: {
|
||||
type: '0'
|
||||
}
|
||||
};
|
||||
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
$scope.useTotp = profile.premium;
|
||||
});
|
||||
|
||||
$scope.typeChanged = function () {
|
||||
$scope.cipher.type = parseInt($scope.selectedType);
|
||||
};
|
||||
|
||||
$scope.savePromise = null;
|
||||
$scope.save = function (model) {
|
||||
var login = cipherService.encryptLogin(model);
|
||||
$scope.savePromise = apiService.ciphers.post(login, function (loginResponse) {
|
||||
$analytics.eventTrack('Created Login');
|
||||
var decLogin = cipherService.decryptLogin(loginResponse);
|
||||
$uibModalInstance.close(decLogin);
|
||||
$scope.save = function () {
|
||||
var cipher = cipherService.encryptCipher($scope.cipher);
|
||||
$scope.savePromise = apiService.ciphers.post(cipher, function (cipherResponse) {
|
||||
$analytics.eventTrack('Created Cipher');
|
||||
var decCipher = cipherService.decryptCipherPreview(cipherResponse);
|
||||
$uibModalInstance.close(decCipher);
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
$scope.generatePassword = function () {
|
||||
if (!$scope.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
$analytics.eventTrack('Generated Password From Add');
|
||||
$scope.login.password = passwordService.generatePassword({ length: 12, special: true });
|
||||
$scope.cipher.login.password = passwordService.generatePassword({ length: 12, special: true });
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addField = function () {
|
||||
if (!$scope.login.fields) {
|
||||
$scope.login.fields = [];
|
||||
if (!$scope.cipher.fields) {
|
||||
$scope.cipher.fields = [];
|
||||
}
|
||||
|
||||
$scope.login.fields.push({
|
||||
type: '0',
|
||||
$scope.cipher.fields.push({
|
||||
type: constants.fieldType.text.toString(),
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.login.fields.indexOf(field);
|
||||
var index = $scope.cipher.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.login.fields.splice(index, 1);
|
||||
$scope.cipher.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleFavorite = function () {
|
||||
$scope.login.favorite = !$scope.login.favorite;
|
||||
$scope.cipher.favorite = !$scope.cipher.favorite;
|
||||
};
|
||||
|
||||
$scope.clipboardSuccess = function (e) {
|
||||
@@ -2,9 +2,9 @@
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultAttachmentsController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
||||
loginId, $analytics, validationService, toastr, $timeout, authService, $uibModal) {
|
||||
cipherId, $analytics, validationService, toastr, $timeout, authService, $uibModal) {
|
||||
$analytics.eventTrack('vaultAttachmentsController', { category: 'Modal' });
|
||||
$scope.login = {};
|
||||
$scope.cipher = {};
|
||||
$scope.readOnly = true;
|
||||
$scope.loading = true;
|
||||
$scope.isPremium = true;
|
||||
@@ -13,11 +13,11 @@
|
||||
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
$scope.isPremium = profile.premium;
|
||||
return apiService.ciphers.get({ id: loginId }).$promise;
|
||||
return apiService.ciphers.get({ id: cipherId }).$promise;
|
||||
}).then(function (cipher) {
|
||||
$scope.login = cipherService.decryptLogin(cipher);
|
||||
$scope.readOnly = !$scope.login.edit;
|
||||
$scope.canUseAttachments = $scope.isPremium || $scope.login.organizationId;
|
||||
$scope.cipher = cipherService.decryptCipher(cipher);
|
||||
$scope.readOnly = !$scope.cipher.edit;
|
||||
$scope.canUseAttachments = $scope.isPremium || $scope.cipher.organizationId;
|
||||
$scope.loading = false;
|
||||
}, function () {
|
||||
$scope.loading = false;
|
||||
@@ -31,14 +31,14 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.savePromise = cipherService.encryptAttachmentFile(getKeyForLogin(), files[0]).then(function (encValue) {
|
||||
$scope.savePromise = cipherService.encryptAttachmentFile(getKeyForCipher(), files[0]).then(function (encValue) {
|
||||
var fd = new FormData();
|
||||
var blob = new Blob([encValue.data], { type: 'application/octet-stream' });
|
||||
fd.append('data', blob, encValue.fileName);
|
||||
return apiService.ciphers.postAttachment({ id: loginId }, fd).$promise;
|
||||
return apiService.ciphers.postAttachment({ id: cipherId }, fd).$promise;
|
||||
}).then(function (response) {
|
||||
$analytics.eventTrack('Added Attachment');
|
||||
$scope.login = cipherService.decryptLogin(response);
|
||||
$scope.cipher = cipherService.decryptCipher(response);
|
||||
|
||||
// reset file input
|
||||
// ref: https://stackoverflow.com/a/20552042
|
||||
@@ -64,7 +64,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
cipherService.downloadAndDecryptAttachment(getKeyForLogin(), attachment, true).then(function (res) {
|
||||
cipherService.downloadAndDecryptAttachment(getKeyForCipher(), attachment, true).then(function (res) {
|
||||
$timeout(function () {
|
||||
attachment.loading = false;
|
||||
});
|
||||
@@ -75,9 +75,9 @@
|
||||
});
|
||||
};
|
||||
|
||||
function getKeyForLogin() {
|
||||
if ($scope.login.organizationId) {
|
||||
return cryptoService.getOrgKey($scope.login.organizationId);
|
||||
function getKeyForCipher() {
|
||||
if ($scope.cipher.organizationId) {
|
||||
return cryptoService.getOrgKey($scope.cipher.organizationId);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -89,12 +89,12 @@
|
||||
}
|
||||
|
||||
attachment.loading = true;
|
||||
apiService.ciphers.delAttachment({ id: loginId, attachmentId: attachment.id }).$promise.then(function () {
|
||||
apiService.ciphers.delAttachment({ id: cipherId, attachmentId: attachment.id }).$promise.then(function () {
|
||||
attachment.loading = false;
|
||||
$analytics.eventTrack('Deleted Attachment');
|
||||
var index = $scope.login.attachments.indexOf(attachment);
|
||||
var index = $scope.cipher.attachments.indexOf(attachment);
|
||||
if (index > -1) {
|
||||
$scope.login.attachments.splice(index, 1);
|
||||
$scope.cipher.attachments.splice(index, 1);
|
||||
}
|
||||
}, function () {
|
||||
toastr.error('Cannot delete attachment.');
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
e.preventDefault();
|
||||
closing = true;
|
||||
$uibModalInstance.close(!!$scope.login.attachments && $scope.login.attachments.length > 0);
|
||||
$uibModalInstance.close(!!$scope.cipher.attachments && $scope.cipher.attachments.length > 0);
|
||||
});
|
||||
|
||||
$scope.showUpgrade = function () {
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultLoginCollectionsController', function ($scope, apiService, $uibModalInstance, cipherService,
|
||||
loginId, $analytics) {
|
||||
$analytics.eventTrack('vaultLoginCollectionsController', { category: 'Modal' });
|
||||
$scope.login = {};
|
||||
.controller('vaultCipherCollectionsController', function ($scope, apiService, $uibModalInstance, cipherService,
|
||||
cipherId, $analytics) {
|
||||
$analytics.eventTrack('vaultCipherCollectionsController', { category: 'Modal' });
|
||||
$scope.cipher = {};
|
||||
$scope.readOnly = false;
|
||||
$scope.loadingLogin = true;
|
||||
$scope.loadingCipher = true;
|
||||
$scope.loadingCollections = true;
|
||||
$scope.selectedCollections = {};
|
||||
$scope.collections = [];
|
||||
|
||||
var cipherAndCols = null;
|
||||
$uibModalInstance.opened.then(function () {
|
||||
apiService.ciphers.getDetails({ id: loginId }).$promise.then(function (cipher) {
|
||||
$scope.loadingLogin = false;
|
||||
apiService.ciphers.getDetails({ id: cipherId }).$promise.then(function (cipher) {
|
||||
$scope.loadingCipher = false;
|
||||
|
||||
$scope.readOnly = !cipher.Edit;
|
||||
if (cipher.Edit && cipher.OrganizationId) {
|
||||
if (cipher.Type === 1) {
|
||||
$scope.login = cipherService.decryptLoginPreview(cipher);
|
||||
$scope.cipher = cipherService.decryptCipherPreview(cipher);
|
||||
}
|
||||
|
||||
var collections = {};
|
||||
@@ -35,34 +36,40 @@
|
||||
}
|
||||
|
||||
return null;
|
||||
}).then(function (cipherAndCols) {
|
||||
if (!cipherAndCols) {
|
||||
}).then(function (result) {
|
||||
if (!result) {
|
||||
$scope.loadingCollections = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
cipherAndCols = result;
|
||||
return apiService.collections.listMe({ writeOnly: true }).$promise;
|
||||
}).then(function (response) {
|
||||
if (response === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.collections.listMe({ writeOnly: true }, function (response) {
|
||||
var collections = [];
|
||||
var selectedCollections = {};
|
||||
var collections = [];
|
||||
var selectedCollections = {};
|
||||
var writeableCollections = response.Data;
|
||||
|
||||
for (var i = 0; i < response.Data.length; i++) {
|
||||
// clean out selectCollections that aren't from this organization or read only
|
||||
if (response.Data[i].Id in cipherAndCols.cipherCollections &&
|
||||
response.Data[i].OrganizationId === cipherAndCols.cipher.OrganizationId) {
|
||||
selectedCollections[response.Data[i].Id] = true;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
var decCollection = cipherService.decryptCollection(response.Data[i]);
|
||||
collections.push(decCollection);
|
||||
for (var i = 0; i < writeableCollections.length; i++) {
|
||||
// clean out selectCollections that aren't from this organization
|
||||
if (writeableCollections[i].OrganizationId !== cipherAndCols.cipher.OrganizationId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scope.loadingCollections = false;
|
||||
$scope.collections = collections;
|
||||
$scope.selectedCollections = selectedCollections;
|
||||
});
|
||||
if (writeableCollections[i].Id in cipherAndCols.cipherCollections) {
|
||||
selectedCollections[writeableCollections[i].Id] = true;
|
||||
}
|
||||
|
||||
var decCollection = cipherService.decryptCollection(writeableCollections[i]);
|
||||
collections.push(decCollection);
|
||||
}
|
||||
|
||||
$scope.loadingCollections = false;
|
||||
$scope.collections = collections;
|
||||
$scope.selectedCollections = selectedCollections;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -105,9 +112,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
$scope.submitPromise = apiService.ciphers.putCollections({ id: loginId }, request)
|
||||
$scope.submitPromise = apiService.ciphers.putCollections({ id: cipherId }, request)
|
||||
.$promise.then(function (response) {
|
||||
$analytics.eventTrack('Edited Login Collections');
|
||||
$analytics.eventTrack('Edited Cipher Collections');
|
||||
$uibModalInstance.close({
|
||||
action: 'collectionsEdit',
|
||||
collectionIds: request.collectionIds
|
||||
@@ -2,21 +2,23 @@
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr,
|
||||
cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics) {
|
||||
cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics, constants) {
|
||||
$scope.loading = true;
|
||||
$scope.logins = [];
|
||||
$scope.ciphers = [];
|
||||
$scope.constants = constants;
|
||||
$scope.favoriteCollapsed = $localStorage.collapsedFolders && 'favorite' in $localStorage.collapsedFolders;
|
||||
$scope.folderIdFilter = undefined;
|
||||
$scope.typeFilter = undefined;
|
||||
|
||||
if ($state.params.refreshFromServer) {
|
||||
$rootScope.vaultFolders = $rootScope.vaultLogins = null;
|
||||
$rootScope.vaultFolders = $rootScope.vaultCiphers = null;
|
||||
}
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
if ($rootScope.vaultFolders && $rootScope.vaultLogins) {
|
||||
if ($rootScope.vaultFolders && $rootScope.vaultCiphers) {
|
||||
$scope.loading = false;
|
||||
loadFolderData($rootScope.vaultFolders);
|
||||
loadLoginData($rootScope.vaultLogins);
|
||||
loadCipherData($rootScope.vaultCiphers);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,17 +41,15 @@
|
||||
}).$promise;
|
||||
|
||||
var cipherPromise = apiService.ciphers.list({}, function (ciphers) {
|
||||
var decLogins = [];
|
||||
var decCiphers = [];
|
||||
|
||||
for (var i = 0; i < ciphers.Data.length; i++) {
|
||||
if (ciphers.Data[i].Type === 1) {
|
||||
var decLogin = cipherService.decryptLoginPreview(ciphers.Data[i]);
|
||||
decLogins.push(decLogin);
|
||||
}
|
||||
var decCipher = cipherService.decryptCipherPreview(ciphers.Data[i]);
|
||||
decCiphers.push(decCipher);
|
||||
}
|
||||
|
||||
$q.when(folderPromise).then(function () {
|
||||
loadLoginData(decLogins);
|
||||
folderPromise.then(function () {
|
||||
loadCipherData(decCiphers);
|
||||
});
|
||||
}).$promise;
|
||||
|
||||
@@ -62,26 +62,26 @@
|
||||
$rootScope.vaultFolders = $filter('orderBy')(decFolders, folderSort);
|
||||
}
|
||||
|
||||
function loadLoginData(decLogins) {
|
||||
function loadCipherData(decCiphers) {
|
||||
angular.forEach($rootScope.vaultFolders, function (folderValue, folderIndex) {
|
||||
folderValue.collapsed = $localStorage.collapsedFolders &&
|
||||
(folderValue.id || 'none') in $localStorage.collapsedFolders;
|
||||
|
||||
angular.forEach(decLogins, function (loginValue) {
|
||||
if (loginValue.favorite) {
|
||||
loginValue.sort = -1;
|
||||
angular.forEach(decCiphers, function (cipherValue) {
|
||||
if (cipherValue.favorite) {
|
||||
cipherValue.sort = -1;
|
||||
}
|
||||
else if (loginValue.folderId == folderValue.id) {
|
||||
loginValue.sort = folderIndex;
|
||||
else if (cipherValue.folderId == folderValue.id) {
|
||||
cipherValue.sort = folderIndex;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$rootScope.vaultLogins = $scope.logins = $filter('orderBy')(decLogins, ['sort', 'name', 'username']);
|
||||
$rootScope.vaultCiphers = $scope.ciphers = $filter('orderBy')(decCiphers, ['sort', 'name', 'subTitle']);
|
||||
|
||||
var chunks = chunk($rootScope.vaultLogins, 400);
|
||||
var chunks = chunk($rootScope.vaultCiphers, 400);
|
||||
if (chunks.length > 0) {
|
||||
$scope.logins = chunks[0];
|
||||
$scope.ciphers = chunks[0];
|
||||
var delay = 200;
|
||||
angular.forEach(chunks, function (value, index) {
|
||||
delay += 200;
|
||||
@@ -89,15 +89,15 @@
|
||||
// skip the first chuck
|
||||
if (index > 0) {
|
||||
$timeout(function () {
|
||||
Array.prototype.push.apply($scope.logins, value);
|
||||
Array.prototype.push.apply($scope.ciphers, value);
|
||||
}, delay);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function sortScopedLoginData() {
|
||||
$rootScope.vaultLogins = $scope.logins = $filter('orderBy')($rootScope.vaultLogins, ['name', 'username']);
|
||||
function sortScopedCipherData() {
|
||||
$rootScope.vaultCiphers = $scope.ciphers = $filter('orderBy')($rootScope.vaultCiphers, ['name', 'subTitle']);
|
||||
}
|
||||
|
||||
function chunk(arr, len) {
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
$scope.clipboardError = function (e) {
|
||||
alert('Your web browser does not support easy clipboard copying. ' +
|
||||
'Edit the login and copy it manually instead.');
|
||||
'Edit the item and copy it manually instead.');
|
||||
};
|
||||
|
||||
$scope.collapseExpand = function (folder, favorite) {
|
||||
@@ -137,89 +137,87 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.editLogin = function (login) {
|
||||
$scope.editCipher = function (cipher) {
|
||||
var editModel = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultEditLogin.html',
|
||||
controller: 'vaultEditLoginController',
|
||||
templateUrl: 'app/vault/views/vaultEditCipher.html',
|
||||
controller: 'vaultEditCipherController',
|
||||
resolve: {
|
||||
loginId: function () { return login.id; }
|
||||
cipherId: function () { return cipher.id; }
|
||||
}
|
||||
});
|
||||
|
||||
editModel.result.then(function (returnVal) {
|
||||
if (returnVal.action === 'edit') {
|
||||
login.folderId = returnVal.data.folderId;
|
||||
login.name = returnVal.data.name;
|
||||
login.username = returnVal.data.username;
|
||||
login.password = returnVal.data.password;
|
||||
login.favorite = returnVal.data.favorite;
|
||||
|
||||
sortScopedLoginData();
|
||||
var index = $scope.ciphers.indexOf(cipher);
|
||||
if (index > -1) {
|
||||
$rootScope.vaultCiphers[index] = returnVal.data;
|
||||
}
|
||||
sortScopedCipherData();
|
||||
}
|
||||
else if (returnVal.action === 'partialEdit') {
|
||||
login.folderId = returnVal.data.folderId;
|
||||
login.favorite = returnVal.data.favorite;
|
||||
cipher.folderId = returnVal.data.folderId;
|
||||
cipher.favorite = returnVal.data.favorite;
|
||||
}
|
||||
else if (returnVal.action === 'delete') {
|
||||
removeLoginFromScopes(login);
|
||||
removeCipherFromScopes(cipher);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on('vaultAddLogin', function (event, args) {
|
||||
$scope.addLogin();
|
||||
$scope.$on('vaultAddCipher', function (event, args) {
|
||||
$scope.addCipher();
|
||||
});
|
||||
|
||||
$scope.addLogin = function (folder, favorite) {
|
||||
$scope.addCipher = function (folder, favorite) {
|
||||
var addModel = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultAddLogin.html',
|
||||
controller: 'vaultAddLoginController',
|
||||
templateUrl: 'app/vault/views/vaultAddCipher.html',
|
||||
controller: 'vaultAddCipherController',
|
||||
resolve: {
|
||||
selectedFolder: function () { return folder; },
|
||||
checkedFavorite: function () { return favorite; }
|
||||
}
|
||||
});
|
||||
|
||||
addModel.result.then(function (addedLogin) {
|
||||
$rootScope.vaultLogins.push(addedLogin);
|
||||
sortScopedLoginData();
|
||||
addModel.result.then(function (addedCipher) {
|
||||
$rootScope.vaultCiphers.push(addedCipher);
|
||||
sortScopedCipherData();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteLogin = function (login) {
|
||||
if (!confirm('Are you sure you want to delete this login (' + login.name + ')?')) {
|
||||
$scope.deleteCipher = function (cipher) {
|
||||
if (!confirm('Are you sure you want to delete this item (' + cipher.name + ')?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.ciphers.del({ id: login.id }, function () {
|
||||
$analytics.eventTrack('Deleted Login');
|
||||
removeLoginFromScopes(login);
|
||||
apiService.ciphers.del({ id: cipher.id }, function () {
|
||||
$analytics.eventTrack('Deleted Item');
|
||||
removeCipherFromScopes(cipher);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.attachments = function (login) {
|
||||
$scope.attachments = function (cipher) {
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
return {
|
||||
isPremium: profile.premium,
|
||||
orgUseStorage: login.organizationId && !!profile.organizations[login.organizationId].maxStorageGb
|
||||
orgUseStorage: cipher.organizationId && !!profile.organizations[cipher.organizationId].maxStorageGb
|
||||
};
|
||||
}).then(function (perms) {
|
||||
if (!login.hasAttachments) {
|
||||
if (login.organizationId && !perms.orgUseStorage) {
|
||||
if (!cipher.hasAttachments) {
|
||||
if (cipher.organizationId && !perms.orgUseStorage) {
|
||||
$uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/views/paidOrgRequired.html',
|
||||
controller: 'paidOrgRequiredController',
|
||||
resolve: {
|
||||
orgId: function () { return login.organizationId; }
|
||||
orgId: function () { return cipher.organizationId; }
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!login.organizationId && !perms.isPremium) {
|
||||
if (!cipher.organizationId && !perms.isPremium) {
|
||||
$uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/views/premiumRequired.html',
|
||||
@@ -229,7 +227,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (!login.organizationId && !cryptoService.getEncKey()) {
|
||||
if (!cipher.organizationId && !cryptoService.getEncKey()) {
|
||||
toastr.error('You cannot use this feature until you update your encryption key.', 'Feature Unavailable');
|
||||
return;
|
||||
}
|
||||
@@ -239,12 +237,12 @@
|
||||
templateUrl: 'app/vault/views/vaultAttachments.html',
|
||||
controller: 'vaultAttachmentsController',
|
||||
resolve: {
|
||||
loginId: function () { return login.id; }
|
||||
cipherId: function () { return cipher.id; }
|
||||
}
|
||||
});
|
||||
|
||||
attachmentModel.result.then(function (hasAttachments) {
|
||||
login.hasAttachments = hasAttachments;
|
||||
cipher.hasAttachments = hasAttachments;
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -298,42 +296,42 @@
|
||||
};
|
||||
|
||||
$scope.canDeleteFolder = function (folder) {
|
||||
if (!folder || !folder.id || !$rootScope.vaultLogins) {
|
||||
if (!folder || !folder.id || !$rootScope.vaultCiphers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var logins = $filter('filter')($rootScope.vaultLogins, { folderId: folder.id });
|
||||
return logins && logins.length === 0;
|
||||
var ciphers = $filter('filter')($rootScope.vaultCiphers, { folderId: folder.id });
|
||||
return ciphers && ciphers.length === 0;
|
||||
};
|
||||
|
||||
$scope.share = function (login) {
|
||||
$scope.share = function (cipher) {
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultShareLogin.html',
|
||||
controller: 'vaultShareLoginController',
|
||||
templateUrl: 'app/vault/views/vaultShareCipher.html',
|
||||
controller: 'vaultShareCipherController',
|
||||
resolve: {
|
||||
loginId: function () { return login.id; }
|
||||
cipherId: function () { return cipher.id; }
|
||||
}
|
||||
});
|
||||
|
||||
modal.result.then(function (orgId) {
|
||||
login.organizationId = orgId;
|
||||
cipher.organizationId = orgId;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.collections = function (login) {
|
||||
$scope.collections = function (cipher) {
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultLoginCollections.html',
|
||||
controller: 'vaultLoginCollectionsController',
|
||||
templateUrl: 'app/vault/views/vaultCipherCollections.html',
|
||||
controller: 'vaultCipherCollectionsController',
|
||||
resolve: {
|
||||
loginId: function () { return login.id; }
|
||||
cipherId: function () { return cipher.id; }
|
||||
}
|
||||
});
|
||||
|
||||
modal.result.then(function (response) {
|
||||
if (response.collectionIds && !response.collectionIds.length) {
|
||||
removeLoginFromScopes(login);
|
||||
removeCipherFromScopes(cipher);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -348,8 +346,19 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.filterType = function (type) {
|
||||
$scope.typeFilter = type;
|
||||
|
||||
if ($.AdminLTE && $.AdminLTE.layout) {
|
||||
$timeout(function () {
|
||||
$.AdminLTE.layout.fix();
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.clearFilters = function () {
|
||||
$scope.folderIdFilter = undefined;
|
||||
$scope.typeFilter = undefined;
|
||||
|
||||
if ($.AdminLTE && $.AdminLTE.layout) {
|
||||
$timeout(function () {
|
||||
@@ -362,17 +371,21 @@
|
||||
return $scope.folderIdFilter === undefined || folder.id === $scope.folderIdFilter;
|
||||
};
|
||||
|
||||
$scope.cipherFilter = function (cipher) {
|
||||
return $scope.typeFilter === undefined || cipher.type === $scope.typeFilter;
|
||||
};
|
||||
|
||||
$scope.unselectAll = function () {
|
||||
selectAll(false);
|
||||
};
|
||||
|
||||
$scope.selectFolder = function (folder, $event) {
|
||||
var checkbox = $($event.currentTarget).closest('.box').find('input[name="loginSelection"]');
|
||||
var checkbox = $($event.currentTarget).closest('.box').find('input[name="cipherSelection"]');
|
||||
checkbox.prop('checked', true);
|
||||
};
|
||||
|
||||
$scope.select = function ($event) {
|
||||
var checkbox = $($event.currentTarget).closest('tr').find('input[name="loginSelection"]');
|
||||
var checkbox = $($event.currentTarget).closest('tr').find('input[name="cipherSelection"]');
|
||||
checkbox.prop('checked', !checkbox.prop('checked'));
|
||||
};
|
||||
|
||||
@@ -380,18 +393,18 @@
|
||||
return self.indexOf(value) === index;
|
||||
}
|
||||
|
||||
function getSelectedLogins() {
|
||||
return $('input[name="loginSelection"]:checked').map(function () {
|
||||
function getSelectedCiphers() {
|
||||
return $('input[name="cipherSelection"]:checked').map(function () {
|
||||
return $(this).val();
|
||||
}).get().filter(distinct);
|
||||
}
|
||||
|
||||
function selectAll(select) {
|
||||
$('input[name="loginSelection"]').prop('checked', select);
|
||||
$('input[name="cipherSelection"]').prop('checked', select);
|
||||
}
|
||||
|
||||
$scope.bulkMove = function () {
|
||||
var ids = getSelectedLogins();
|
||||
var ids = getSelectedCiphers();
|
||||
if (ids.length === 0) {
|
||||
alert('You have not selected anything.');
|
||||
return;
|
||||
@@ -399,8 +412,8 @@
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultMoveLogins.html',
|
||||
controller: 'vaultMoveLoginsController',
|
||||
templateUrl: 'app/vault/views/vaultMoveCiphers.html',
|
||||
controller: 'vaultMoveCiphersController',
|
||||
size: 'sm',
|
||||
resolve: {
|
||||
ids: function () { return ids; }
|
||||
@@ -409,37 +422,37 @@
|
||||
|
||||
modal.result.then(function (folderId) {
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var login = $filter('filter')($rootScope.vaultLogins, { id: ids[i] });
|
||||
if (login.length) {
|
||||
login[0].folderId = folderId;
|
||||
var cipher = $filter('filter')($rootScope.vaultCiphers, { id: ids[i] });
|
||||
if (cipher.length) {
|
||||
cipher[0].folderId = folderId;
|
||||
}
|
||||
}
|
||||
|
||||
selectAll(false);
|
||||
sortScopedLoginData();
|
||||
sortScopedCipherData();
|
||||
toastr.success('Items have been moved!');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.bulkDelete = function () {
|
||||
var ids = getSelectedLogins();
|
||||
var ids = getSelectedCiphers();
|
||||
if (ids.length === 0) {
|
||||
alert('You have not selected anything.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to delete the selected logins (total: ' + ids.length + ')?')) {
|
||||
if (!confirm('Are you sure you want to delete the selected items (total: ' + ids.length + ')?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.bulkActionLoading = true;
|
||||
apiService.ciphers.delMany({ ids: ids }, function () {
|
||||
$analytics.eventTrack('Bulk Deleted Logins');
|
||||
$analytics.eventTrack('Bulk Deleted Items');
|
||||
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var login = $filter('filter')($rootScope.vaultLogins, { id: ids[i] });
|
||||
if (login.length && login[0].edit) {
|
||||
removeLoginFromScopes(login[0]);
|
||||
var cipher = $filter('filter')($rootScope.vaultCiphers, { id: ids[i] });
|
||||
if (cipher.length && cipher[0].edit) {
|
||||
removeCipherFromScopes(cipher[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,15 +465,15 @@
|
||||
});
|
||||
};
|
||||
|
||||
function removeLoginFromScopes(login) {
|
||||
var index = $rootScope.vaultLogins.indexOf(login);
|
||||
function removeCipherFromScopes(cipher) {
|
||||
var index = $rootScope.vaultCiphers.indexOf(cipher);
|
||||
if (index > -1) {
|
||||
$rootScope.vaultLogins.splice(index, 1);
|
||||
$rootScope.vaultCiphers.splice(index, 1);
|
||||
}
|
||||
|
||||
index = $scope.logins.indexOf(login);
|
||||
index = $scope.ciphers.indexOf(cipher);
|
||||
if (index > -1) {
|
||||
$scope.logins.splice(index, 1);
|
||||
$scope.ciphers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultEditLoginController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
||||
passwordService, loginId, $analytics, $rootScope, authService, $uibModal) {
|
||||
$analytics.eventTrack('vaultEditLoginController', { category: 'Modal' });
|
||||
.controller('vaultEditCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
||||
passwordService, cipherId, $analytics, $rootScope, authService, $uibModal, constants) {
|
||||
$analytics.eventTrack('vaultEditCipherController', { category: 'Modal' });
|
||||
$scope.folders = $rootScope.vaultFolders;
|
||||
$scope.login = {};
|
||||
$scope.cipher = {};
|
||||
$scope.readOnly = false;
|
||||
$scope.constants = constants;
|
||||
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
$scope.useTotp = profile.premium;
|
||||
return apiService.ciphers.get({ id: loginId }).$promise;
|
||||
}).then(function (login) {
|
||||
$scope.login = cipherService.decryptLogin(login);
|
||||
$scope.readOnly = !$scope.login.edit;
|
||||
$scope.useTotp = $scope.useTotp || $scope.login.organizationUseTotp;
|
||||
return apiService.ciphers.get({ id: cipherId }).$promise;
|
||||
}).then(function (cipher) {
|
||||
$scope.cipher = cipherService.decryptCipher(cipher);
|
||||
$scope.readOnly = !$scope.cipher.edit;
|
||||
$scope.useTotp = $scope.useTotp || $scope.cipher.organizationUseTotp;
|
||||
});
|
||||
|
||||
$scope.save = function (model) {
|
||||
if ($scope.readOnly) {
|
||||
$scope.savePromise = apiService.ciphers.putPartial({ id: loginId }, {
|
||||
$scope.savePromise = apiService.ciphers.putPartial({ id: cipherId }, {
|
||||
folderId: model.folderId,
|
||||
favorite: model.favorite
|
||||
}, function (response) {
|
||||
$analytics.eventTrack('Partially Edited Login');
|
||||
$analytics.eventTrack('Partially Edited Cipher');
|
||||
$uibModalInstance.close({
|
||||
action: 'partialEdit',
|
||||
data: {
|
||||
id: loginId,
|
||||
id: cipherId,
|
||||
favorite: model.favorite,
|
||||
folderId: model.folderId && model.folderId !== '' ? model.folderId : null
|
||||
}
|
||||
@@ -35,46 +36,46 @@
|
||||
}).$promise;
|
||||
}
|
||||
else {
|
||||
var login = cipherService.encryptLogin(model);
|
||||
$scope.savePromise = apiService.ciphers.put({ id: loginId }, login, function (loginResponse) {
|
||||
$analytics.eventTrack('Edited Login');
|
||||
var decLogin = cipherService.decryptLogin(loginResponse);
|
||||
var cipher = cipherService.encryptCipher(model, $scope.cipher.type);
|
||||
$scope.savePromise = apiService.ciphers.put({ id: cipherId }, cipher, function (cipherResponse) {
|
||||
$analytics.eventTrack('Edited Cipher');
|
||||
var decCipher = cipherService.decryptCipherPreview(cipherResponse);
|
||||
$uibModalInstance.close({
|
||||
action: 'edit',
|
||||
data: decLogin
|
||||
data: decCipher
|
||||
});
|
||||
}).$promise;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.generatePassword = function () {
|
||||
if (!$scope.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
$analytics.eventTrack('Generated Password From Edit');
|
||||
$scope.login.password = passwordService.generatePassword({ length: 12, special: true });
|
||||
$scope.cipher.login.password = passwordService.generatePassword({ length: 12, special: true });
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addField = function () {
|
||||
if (!$scope.login.fields) {
|
||||
$scope.login.fields = [];
|
||||
if (!$scope.cipher.fields) {
|
||||
$scope.cipher.fields = [];
|
||||
}
|
||||
|
||||
$scope.login.fields.push({
|
||||
type: '0',
|
||||
$scope.cipher.fields.push({
|
||||
type: constants.fieldType.text.toString(),
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.login.fields.indexOf(field);
|
||||
var index = $scope.cipher.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.login.fields.splice(index, 1);
|
||||
$scope.cipher.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleFavorite = function () {
|
||||
$scope.login.favorite = !$scope.login.favorite;
|
||||
$scope.cipher.favorite = !$scope.cipher.favorite;
|
||||
};
|
||||
|
||||
$scope.clipboardSuccess = function (e) {
|
||||
@@ -105,15 +106,15 @@
|
||||
}
|
||||
|
||||
$scope.delete = function () {
|
||||
if (!confirm('Are you sure you want to delete this login (' + $scope.login.name + ')?')) {
|
||||
if (!confirm('Are you sure you want to delete this item (' + $scope.cipher.name + ')?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.ciphers.del({ id: $scope.login.id }, function () {
|
||||
$analytics.eventTrack('Deleted Login From Edit');
|
||||
apiService.ciphers.del({ id: $scope.cipher.id }, function () {
|
||||
$analytics.eventTrack('Deleted Cipher From Edit');
|
||||
$uibModalInstance.close({
|
||||
action: 'delete',
|
||||
data: $scope.login.id
|
||||
data: $scope.cipher.id
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultMoveLoginsController', function ($scope, apiService, $uibModalInstance, ids, $analytics,
|
||||
.controller('vaultMoveCiphersController', function ($scope, apiService, $uibModalInstance, ids, $analytics,
|
||||
$rootScope) {
|
||||
$analytics.eventTrack('vaultMoveLoginsController', { category: 'Modal' });
|
||||
$analytics.eventTrack('vaultMoveCiphersController', { category: 'Modal' });
|
||||
$scope.folders = $rootScope.vaultFolders;
|
||||
$scope.count = ids.length;
|
||||
|
||||
$scope.save = function () {
|
||||
$scope.savePromise = apiService.ciphers.moveMany({ ids: ids, folderId: $scope.folderId }, function () {
|
||||
$analytics.eventTrack('Bulk Moved Logins');
|
||||
$analytics.eventTrack('Bulk Moved Ciphers');
|
||||
$uibModalInstance.close($scope.folderId || null);
|
||||
}).$promise;
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultShareLoginController', function ($scope, apiService, $uibModalInstance, authService, cipherService,
|
||||
loginId, $analytics, $state, cryptoService, $q, toastr) {
|
||||
$analytics.eventTrack('vaultShareLoginController', { category: 'Modal' });
|
||||
.controller('vaultShareCipherController', function ($scope, apiService, $uibModalInstance, authService, cipherService,
|
||||
cipherId, $analytics, $state, cryptoService, $q, toastr) {
|
||||
$analytics.eventTrack('vaultShareCipherController', { category: 'Modal' });
|
||||
$scope.model = {};
|
||||
$scope.login = {};
|
||||
$scope.cipher = {};
|
||||
$scope.collections = [];
|
||||
$scope.selectedCollections = {};
|
||||
$scope.organizations = [];
|
||||
@@ -14,13 +14,13 @@
|
||||
$scope.loading = true;
|
||||
$scope.readOnly = false;
|
||||
|
||||
apiService.ciphers.get({ id: loginId }).$promise.then(function (login) {
|
||||
$scope.readOnly = !login.Edit;
|
||||
if (login.Edit) {
|
||||
$scope.login = cipherService.decryptLogin(login);
|
||||
apiService.ciphers.get({ id: cipherId }).$promise.then(function (cipher) {
|
||||
$scope.readOnly = !cipher.Edit;
|
||||
if (cipher.Edit) {
|
||||
$scope.cipher = cipherService.decryptCipher(cipher);
|
||||
}
|
||||
|
||||
return login.Edit;
|
||||
return cipher.Edit;
|
||||
}).then(function (canEdit) {
|
||||
$scope.loading = false;
|
||||
if (!canEdit) {
|
||||
@@ -110,8 +110,9 @@
|
||||
|
||||
var errorOnUpload = false;
|
||||
var attachmentSharePromises = [];
|
||||
if ($scope.login.attachments) {
|
||||
for (var i = 0; i < $scope.login.attachments.length; i++) {
|
||||
if ($scope.cipher.attachments) {
|
||||
for (var i = 0; i < $scope.cipher.attachments.length; i++) {
|
||||
/* jshint ignore:start */
|
||||
(function (attachment) {
|
||||
var promise = cipherService.downloadAndDecryptAttachment(null, attachment, false)
|
||||
.then(function (decData) {
|
||||
@@ -127,7 +128,7 @@
|
||||
fd.append('data', blob, encFilename);
|
||||
|
||||
return apiService.ciphers.postShareAttachment({
|
||||
id: loginId,
|
||||
id: cipherId,
|
||||
attachmentId: attachment.id,
|
||||
orgId: model.organizationId
|
||||
}, fd).$promise;
|
||||
@@ -135,7 +136,8 @@
|
||||
errorOnUpload = true;
|
||||
});
|
||||
attachmentSharePromises.push(promise);
|
||||
})($scope.login.attachments[i]);
|
||||
})($scope.cipher.attachments[i]);
|
||||
/* jshint ignore:end */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +146,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.login.organizationId = model.organizationId;
|
||||
$scope.cipher.organizationId = model.organizationId;
|
||||
|
||||
var request = {
|
||||
collectionIds: [],
|
||||
cipher: cipherService.encryptLogin($scope.login, null, true)
|
||||
cipher: cipherService.encryptCipher($scope.cipher, $scope.cipher.type, null, true)
|
||||
};
|
||||
|
||||
for (var id in $scope.selectedCollections) {
|
||||
@@ -157,10 +159,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
return apiService.ciphers.putShare({ id: loginId }, request).$promise;
|
||||
return apiService.ciphers.putShare({ id: cipherId }, request).$promise;
|
||||
}).then(function (response) {
|
||||
$analytics.eventTrack('Shared Login');
|
||||
toastr.success('Login has been shared.');
|
||||
$analytics.eventTrack('Shared Cipher');
|
||||
toastr.success('Item has been shared.');
|
||||
$uibModalInstance.close(model.organizationId);
|
||||
});
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
.controller('vaultSharedController', function ($scope, apiService, cipherService, $analytics, $q, $localStorage,
|
||||
$uibModal, $filter, $rootScope, authService, cryptoService) {
|
||||
$scope.logins = [];
|
||||
$scope.ciphers = [];
|
||||
$scope.collections = [];
|
||||
$scope.loading = true;
|
||||
|
||||
@@ -22,16 +22,14 @@
|
||||
}).$promise;
|
||||
|
||||
var cipherPromise = apiService.ciphers.listDetails({}, function (ciphers) {
|
||||
var decLogins = [];
|
||||
var decCiphers = [];
|
||||
|
||||
for (var i = 0; i < ciphers.Data.length; i++) {
|
||||
if (ciphers.Data[i].Type === 1) {
|
||||
var decLogin = cipherService.decryptLoginPreview(ciphers.Data[i]);
|
||||
decLogins.push(decLogin);
|
||||
}
|
||||
var decCipher = cipherService.decryptCipherPreview(ciphers.Data[i]);
|
||||
decCiphers.push(decCipher);
|
||||
}
|
||||
|
||||
if (decLogins.length) {
|
||||
if (decCiphers.length) {
|
||||
$scope.collections.push({
|
||||
id: null,
|
||||
name: 'Unassigned',
|
||||
@@ -39,7 +37,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
$scope.logins = decLogins;
|
||||
$scope.ciphers = decCiphers;
|
||||
}).$promise;
|
||||
|
||||
$q.all([collectionPromise, cipherPromise]).then(function () {
|
||||
@@ -49,29 +47,29 @@
|
||||
|
||||
$scope.clipboardError = function (e) {
|
||||
alert('Your web browser does not support easy clipboard copying. ' +
|
||||
'Edit the login and copy it manually instead.');
|
||||
'Edit the item and copy it manually instead.');
|
||||
};
|
||||
|
||||
$scope.attachments = function (login) {
|
||||
$scope.attachments = function (cipher) {
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
return {
|
||||
isPremium: profile.premium,
|
||||
orgUseStorage: login.organizationId && !!profile.organizations[login.organizationId].maxStorageGb
|
||||
orgUseStorage: cipher.organizationId && !!profile.organizations[cipher.organizationId].maxStorageGb
|
||||
};
|
||||
}).then(function (perms) {
|
||||
if (login.organizationId && !perms.orgUseStorage) {
|
||||
if (cipher.organizationId && !perms.orgUseStorage) {
|
||||
$uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/views/paidOrgRequired.html',
|
||||
controller: 'paidOrgRequiredController',
|
||||
resolve: {
|
||||
orgId: function () { return login.organizationId; }
|
||||
orgId: function () { return cipher.organizationId; }
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!login.organizationId && !perms.isPremium) {
|
||||
if (!cipher.organizationId && !perms.isPremium) {
|
||||
$uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/views/premiumRequired.html',
|
||||
@@ -80,7 +78,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (!login.organizationId && !cryptoService.getEncKey()) {
|
||||
if (!cipher.organizationId && !cryptoService.getEncKey()) {
|
||||
toastr.error('You cannot use this feature until you update your encryption key.', 'Feature Unavailable');
|
||||
return;
|
||||
}
|
||||
@@ -90,12 +88,12 @@
|
||||
templateUrl: 'app/vault/views/vaultAttachments.html',
|
||||
controller: 'vaultAttachmentsController',
|
||||
resolve: {
|
||||
loginId: function () { return login.id; }
|
||||
cipherId: function () { return cipher.id; }
|
||||
}
|
||||
});
|
||||
|
||||
attachmentModel.result.then(function (hasAttachments) {
|
||||
login.hasAttachments = hasAttachments;
|
||||
cipher.hasAttachments = hasAttachments;
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -133,62 +131,70 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.editLogin = function (login) {
|
||||
$scope.editCipher = function (cipher) {
|
||||
var editModel = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultEditLogin.html',
|
||||
controller: 'vaultEditLoginController',
|
||||
templateUrl: 'app/vault/views/vaultEditCipher.html',
|
||||
controller: 'vaultEditCipherController',
|
||||
resolve: {
|
||||
loginId: function () { return login.id; }
|
||||
cipherId: function () { return cipher.id; }
|
||||
}
|
||||
});
|
||||
|
||||
editModel.result.then(function (returnVal) {
|
||||
var rootLogin = findRootLogin(login) || {};
|
||||
var rootCipher = findRootCipher(cipher) || { meta: {} },
|
||||
index;
|
||||
|
||||
if (returnVal.action === 'edit') {
|
||||
login.folderId = rootLogin.folderId = returnVal.data.folderId;
|
||||
login.name = rootLogin.name = returnVal.data.name;
|
||||
login.username = rootLogin.username = returnVal.data.username;
|
||||
login.password = rootLogin.password = returnVal.data.password;
|
||||
login.favorite = rootLogin.favorite = returnVal.data.favorite;
|
||||
index = $scope.ciphers.indexOf(cipher);
|
||||
if (index > -1) {
|
||||
returnVal.data.collectionIds = $scope.ciphers[index].collectionIds;
|
||||
$scope.ciphers[index] = returnVal.data;
|
||||
|
||||
if ($rootScope.vaultCiphers) {
|
||||
index = $rootScope.vaultCiphers.indexOf(rootCipher);
|
||||
if (index > -1) {
|
||||
$rootScope.vaultCiphers[index] = returnVal.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (returnVal.action === 'partialEdit') {
|
||||
login.folderId = rootLogin.folderId = returnVal.data.folderId;
|
||||
login.favorite = rootLogin.favorite = returnVal.data.favorite;
|
||||
cipher.folderId = rootCipher.folderId = returnVal.data.folderId;
|
||||
cipher.favorite = rootCipher.favorite = returnVal.data.favorite;
|
||||
}
|
||||
else if (returnVal.action === 'delete') {
|
||||
var index = $scope.logins.indexOf(login);
|
||||
index = $scope.ciphers.indexOf(cipher);
|
||||
if (index > -1) {
|
||||
$scope.logins.splice(index, 1);
|
||||
$scope.ciphers.splice(index, 1);
|
||||
}
|
||||
|
||||
removeRootLogin(rootLogin);
|
||||
removeRootCipher(rootCipher);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editCollections = function (login) {
|
||||
$scope.editCollections = function (cipher) {
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultLoginCollections.html',
|
||||
controller: 'vaultLoginCollectionsController',
|
||||
templateUrl: 'app/vault/views/vaultCipherCollections.html',
|
||||
controller: 'vaultCipherCollectionsController',
|
||||
resolve: {
|
||||
loginId: function () { return login.id; }
|
||||
cipherId: function () { return cipher.id; }
|
||||
}
|
||||
});
|
||||
|
||||
modal.result.then(function (response) {
|
||||
if (response.collectionIds) {
|
||||
login.collectionIds = response.collectionIds;
|
||||
// TODO: if there are no collectionIds now, it is possible that the user no longer has access to this login
|
||||
// which means it should be removed by calling removeRootLogin(findRootLogin(login))
|
||||
cipher.collectionIds = response.collectionIds;
|
||||
// TODO: if there are no collectionIds now, it is possible that the user no longer has access to this cipher
|
||||
// which means it should be removed by calling removeRootCipher(findRootCipher(cipher))
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeLogin = function (login, collection) {
|
||||
if (!confirm('Are you sure you want to remove this login (' + login.name + ') from the ' +
|
||||
$scope.removeCipher = function (cipher, collection) {
|
||||
if (!confirm('Are you sure you want to remove this item (' + cipher.name + ') from the ' +
|
||||
'collection (' + collection.name + ') ?')) {
|
||||
return;
|
||||
}
|
||||
@@ -197,36 +203,36 @@
|
||||
collectionIds: []
|
||||
};
|
||||
|
||||
for (var i = 0; i < login.collectionIds.length; i++) {
|
||||
if (login.collectionIds[i] !== collection.id) {
|
||||
request.collectionIds.push(login.collectionIds[i]);
|
||||
for (var i = 0; i < cipher.collectionIds.length; i++) {
|
||||
if (cipher.collectionIds[i] !== collection.id) {
|
||||
request.collectionIds.push(cipher.collectionIds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
apiService.ciphers.putCollections({ id: login.id }, request).$promise.then(function (response) {
|
||||
apiService.ciphers.putCollections({ id: cipher.id }, request).$promise.then(function (response) {
|
||||
$analytics.eventTrack('Removed From Collection');
|
||||
login.collectionIds = request.collectionIds;
|
||||
// TODO: if there are no collectionIds now, it is possible that the user no longer has access to this login
|
||||
// which means it should be removed by calling removeRootLogin(findRootLogin(login))
|
||||
cipher.collectionIds = request.collectionIds;
|
||||
// TODO: if there are no collectionIds now, it is possible that the user no longer has access to this cipher
|
||||
// which means it should be removed by calling removeRootCipher(findRootCipher(cipher))
|
||||
});
|
||||
};
|
||||
|
||||
function findRootLogin(login) {
|
||||
if ($rootScope.vaultLogins) {
|
||||
var rootLogins = $filter('filter')($rootScope.vaultLogins, { id: login.id });
|
||||
if (rootLogins && rootLogins.length) {
|
||||
return rootLogins[0];
|
||||
function findRootCipher(cipher) {
|
||||
if ($rootScope.vaultCiphers) {
|
||||
var rootCiphers = $filter('filter')($rootScope.vaultCiphers, { id: cipher.id });
|
||||
if (rootCiphers && rootCiphers.length) {
|
||||
return rootCiphers[0];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function removeRootLogin(rootLogin) {
|
||||
if (rootLogin && rootLogin.id) {
|
||||
var index = $rootScope.vaultLogins.indexOf(rootLogin);
|
||||
function removeRootCipher(rootCipher) {
|
||||
if (rootCipher && rootCipher.id) {
|
||||
var index = $rootScope.vaultCiphers.indexOf(rootCipher);
|
||||
if (index > -1) {
|
||||
$rootScope.vaultLogins.splice(index, 1);
|
||||
$rootScope.vaultCiphers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<span ng-pluralize
|
||||
count="vaultFolders.length > 0 ? vaultFolders.length - 1 : 0"
|
||||
when="{'1': '{} folder', 'other': '{} folders'}"></span>,
|
||||
<span ng-pluralize count="logins.length" when="{'1': '{} login', 'other': '{} logins'}"></span>
|
||||
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
@@ -39,12 +39,12 @@
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div class="box box-primary" ng-class="{'collapsed-box': favoriteCollapsed}" style="margin-bottom: 40px;"
|
||||
ng-show="vaultFolders.length && folderIdFilter === undefined && (!main.searchVaultText || favoriteLogins.length)">
|
||||
ng-show="vaultFolders.length && folderIdFilter === undefined && (!main.searchVaultText || favoriteCiphers.length)">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<i class="fa fa-star"></i>
|
||||
Favorites
|
||||
<small ng-pluralize count="favoriteLogins.length" when="{'1': '{} login', 'other': '{} logins'}"></small>
|
||||
<small ng-pluralize count="favoriteCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||
</h3>
|
||||
<div class="box-tools">
|
||||
<div class="btn-group">
|
||||
@@ -53,8 +53,8 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="addLogin(null, true)">
|
||||
<i class="fa fa-fw fa-plus-circle"></i> Add Login
|
||||
<a href="#" stop-click ng-click="addCipher(null, true)">
|
||||
<i class="fa fa-fw fa-plus-circle"></i> New Item
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -65,16 +65,16 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body" ng-class="{'no-padding': favoriteLogins.length}">
|
||||
<div ng-show="!favoriteLogins.length">
|
||||
<p>No favorite logins.</p>
|
||||
<button type="button" ng-click="addLogin(null, true)" class="btn btn-default btn-flat">Add a Login</button>
|
||||
<div class="box-body" ng-class="{'no-padding': favoriteCiphers.length}">
|
||||
<div ng-show="!favoriteCiphers.length">
|
||||
<p>No favorite items.</p>
|
||||
<button type="button" ng-click="addCipher(null, true)" class="btn btn-default btn-flat">Add an Item</button>
|
||||
</div>
|
||||
<div class="table-responsive" ng-show="favoriteLogins.length">
|
||||
<div class="table-responsive" ng-show="favoriteCiphers.length">
|
||||
<table class="table table-striped table-hover table-vmiddle">
|
||||
<tbody>
|
||||
<tr ng-repeat="login in favoriteLogins = (logins | filter: { favorite: true } |
|
||||
filter: (main.searchVaultText || '')) track by login.id">
|
||||
<tr ng-repeat="cipher in favoriteCiphers = (ciphers | filter: { favorite: true } |
|
||||
filter: cipherFilter | filter: (main.searchVaultText || '')) track by cipher.id">
|
||||
<td style="width: 70px;">
|
||||
<div class="btn-group" data-append-to="body">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
@@ -82,49 +82,54 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="editLogin(login)">
|
||||
<a href="#" stop-click ng-click="editCipher(cipher)">
|
||||
<i class="fa fa-fw fa-pencil"></i> Edit
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="attachments(login)">
|
||||
<a href="#" stop-click ng-click="attachments(cipher)">
|
||||
<i class="fa fa-fw fa-paperclip"></i> Attachments
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="!login.organizationId">
|
||||
<a href="#" stop-click ng-click="share(login)">
|
||||
<li ng-show="!cipher.organizationId">
|
||||
<a href="#" stop-click ng-click="share(cipher)">
|
||||
<i class="fa fa-fw fa-share-alt"></i> Share
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="login.organizationId && login.edit">
|
||||
<a href="#" stop-click ng-click="collections(login)">
|
||||
<li ng-show="cipher.organizationId && cipher.edit">
|
||||
<a href="#" stop-click ng-click="collections(cipher)">
|
||||
<i class="fa fa-fw fa-cubes"></i> Collections
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="login.password">
|
||||
<li ng-show="cipher.meta.password">
|
||||
<a href="#" stop-click ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-text="{{login.password}}">
|
||||
data-clipboard-text="{{cipher.meta.password}}">
|
||||
<i class="fa fa-fw fa-clipboard"></i> Copy Password
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="login.edit">
|
||||
<a href="#" stop-click ng-click="deleteLogin(login)" class="text-red">
|
||||
<li ng-show="cipher.edit">
|
||||
<a href="#" stop-click ng-click="deleteCipher(cipher)" class="text-red">
|
||||
<i class="fa fa-fw fa-trash"></i> Delete
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td class="action-select">
|
||||
<input type="checkbox" value="{{::login.id}}" name="loginSelection" />
|
||||
<td class="action-select" ng-click="select($event)">
|
||||
<input type="checkbox" value="{{::cipher.id}}" name="cipherSelection" stop-prop />
|
||||
</td>
|
||||
<td class="vault-icon" ng-click="select($event)">
|
||||
<i class="fa fa-fw fa-lg {{::cipher.icon}}" ng-if="!cipher.meta.image"></i>
|
||||
<img alt="" ng-if="cipher.meta.image" ng-src="{{cipher.meta.image}}"
|
||||
fallback-src="images/fa-globe.png" />
|
||||
</td>
|
||||
<td ng-click="select($event)">
|
||||
<a href="#" stop-click ng-click="editLogin(login)" stop-prop>{{login.name}}</a>
|
||||
<i class="fa fa-share-alt text-muted" title="Shared" ng-if="login.organizationId"
|
||||
<a href="#" stop-click ng-click="editCipher(cipher)" stop-prop>{{cipher.name}}</a>
|
||||
<i class="fa fa-share-alt text-muted" title="Shared" ng-if="cipher.organizationId"
|
||||
stop-prop></i>
|
||||
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="login.hasAttachments"
|
||||
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="cipher.hasAttachments"
|
||||
stop-prop></i><br />
|
||||
<span class="text-sm text-muted" stop-prop>{{login.username}}</span>
|
||||
<span class="text-sm text-muted" stop-prop>{{cipher.subTitle}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -134,12 +139,12 @@
|
||||
</div>
|
||||
<div class="box" ng-class="{'collapsed-box': folder.collapsed}"
|
||||
ng-repeat="folder in filteredVaultFolders = (vaultFolders | filter: folderFilter) track by folder.id"
|
||||
ng-show="vaultFolders.length && (!main.searchVaultText || folderLogins.length)">
|
||||
ng-show="vaultFolders.length && (!main.searchVaultText || folderCiphers.length)">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<i class="fa" ng-class="{'fa-folder-open': folder.id !== null, 'fa-folder-open-o': folder.id === null}"></i>
|
||||
{{folder.name}}
|
||||
<small ng-pluralize count="folderLogins.length" when="{'1': '{} login', 'other': '{} logins'}"></small>
|
||||
<small ng-pluralize count="folderCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||
</h3>
|
||||
<div class="box-tools">
|
||||
<div class="btn-group">
|
||||
@@ -148,8 +153,8 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="addLogin(folder)">
|
||||
<i class="fa fa-fw fa-plus-circle"></i> Add Login
|
||||
<a href="#" stop-click ng-click="addCipher(folder)">
|
||||
<i class="fa fa-fw fa-plus-circle"></i> New Item
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="folder.id">
|
||||
@@ -175,16 +180,16 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body" ng-class="{'no-padding': folderLogins.length}">
|
||||
<div ng-show="!folderLogins.length">
|
||||
<p>No logins in this folder.</p>
|
||||
<button type="button" ng-click="addLogin(folder)" class="btn btn-default btn-flat">Add a Login</button>
|
||||
<div class="box-body" ng-class="{'no-padding': folderCiphers.length}">
|
||||
<div ng-show="!folderCiphers.length">
|
||||
<p>No items in this folder.</p>
|
||||
<button type="button" ng-click="addCipher(folder)" class="btn btn-default btn-flat">Add an Item</button>
|
||||
</div>
|
||||
<div class="table-responsive" ng-show="folderLogins.length">
|
||||
<div class="table-responsive" ng-show="folderCiphers.length">
|
||||
<table class="table table-striped table-hover table-vmiddle">
|
||||
<tbody>
|
||||
<tr ng-repeat="login in folderLogins = (logins | filter: { folderId: folder.id } |
|
||||
filter: (main.searchVaultText || '')) track by login.id">
|
||||
<tr ng-repeat="cipher in folderCiphers = (ciphers | filter: { folderId: folder.id } |
|
||||
filter: cipherFilter | filter: (main.searchVaultText || '')) track by cipher.id">
|
||||
<td style="width: 70px;">
|
||||
<div class="btn-group" data-append-to="body">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
@@ -192,33 +197,33 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="editLogin(login)">
|
||||
<a href="#" stop-click ng-click="editCipher(cipher)">
|
||||
<i class="fa fa-fw fa-pencil"></i> Edit
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="attachments(login)">
|
||||
<a href="#" stop-click ng-click="attachments(cipher)">
|
||||
<i class="fa fa-fw fa-paperclip"></i> Attachments
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="!login.organizationId">
|
||||
<a href="#" stop-click ng-click="share(login)">
|
||||
<li ng-show="!cipher.organizationId">
|
||||
<a href="#" stop-click ng-click="share(cipher)">
|
||||
<i class="fa fa-fw fa-share-alt"></i> Share
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="login.organizationId && login.edit">
|
||||
<a href="#" stop-click ng-click="collections(login)">
|
||||
<li ng-show="cipher.organizationId && cipher.edit">
|
||||
<a href="#" stop-click ng-click="collections(cipher)">
|
||||
<i class="fa fa-fw fa-cubes"></i> Collections
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="login.password">
|
||||
<li ng-show="cipher.meta.password">
|
||||
<a href="#" stop-click ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-text="{{login.password}}">
|
||||
data-clipboard-text="{{cipher.meta.password}}">
|
||||
<i class="fa fa-fw fa-clipboard"></i> Copy Password
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="login.edit">
|
||||
<a href="#" stop-click ng-click="deleteLogin(login)" class="text-red">
|
||||
<li ng-show="cipher.edit">
|
||||
<a href="#" stop-click ng-click="deleteCipher(cipher)" class="text-red">
|
||||
<i class="fa fa-fw fa-trash"></i> Delete
|
||||
</a>
|
||||
</li>
|
||||
@@ -226,17 +231,22 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="action-select" ng-click="select($event)">
|
||||
<input type="checkbox" value="{{::login.id}}" name="loginSelection" stop-prop />
|
||||
<input type="checkbox" value="{{::cipher.id}}" name="cipherSelection" stop-prop />
|
||||
</td>
|
||||
<td class="vault-icon" ng-click="select($event)">
|
||||
<i class="fa fa-fw fa-lg {{::cipher.icon}}" ng-if="!cipher.meta.image"></i>
|
||||
<img alt="" ng-if="cipher.meta.image" ng-src="{{cipher.meta.image}}"
|
||||
fallback-src="images/fa-globe.png" />
|
||||
</td>
|
||||
<td ng-click="select($event)">
|
||||
<a href="#" stop-click ng-click="editLogin(login)" stop-prop>{{login.name}}</a>
|
||||
<i class="fa fa-star text-muted" title="Favorite" ng-show="login.favorite" stop-prop></i>
|
||||
<i class="fa fa-share-alt text-muted" title="Shared" ng-show="login.organizationId"
|
||||
<a href="#" stop-click ng-click="editCipher(cipher)" stop-prop>{{cipher.name}}</a>
|
||||
<i class="fa fa-star text-muted" title="Favorite" ng-show="cipher.favorite" stop-prop></i>
|
||||
<i class="fa fa-share-alt text-muted" title="Shared" ng-show="cipher.organizationId"
|
||||
stop-prop></i>
|
||||
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="login.hasAttachments"
|
||||
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="cipher.hasAttachments"
|
||||
stop-prop></i>
|
||||
<br />
|
||||
<span class="text-sm text-muted" stop-prop>{{login.username}}</span>
|
||||
<span class="text-sm text-muted" stop-prop>{{cipher.subTitle}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -254,19 +264,53 @@
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="control-sidebar-heading">
|
||||
<i class="fa fa-tag fa-fw"></i> Types
|
||||
</h3>
|
||||
<div class="control-sidebar-section">
|
||||
<ul class="control-sidebar-menu">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.login)">
|
||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.login === typeFilter"></i>
|
||||
<i class="fa fa-globe fa-fw" ng-if="constants.cipherType.login !== typeFilter"></i> Login
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.card)">
|
||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.card === typeFilter"></i>
|
||||
<i class="fa fa-credit-card fa-fw" ng-if="constants.cipherType.card !== typeFilter"></i> Card
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.identity)">
|
||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.identity === typeFilter"></i>
|
||||
<i class="fa fa-id-card-o fa-fw" ng-if="constants.cipherType.identity !== typeFilter"></i> Identity
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.secureNote)">
|
||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.secureNote === typeFilter"></i>
|
||||
<i class="fa fa-sticky-note-o fa-fw" ng-if="constants.cipherType.secureNote !== typeFilter"></i> Secure Note
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h3 class="control-sidebar-heading">
|
||||
<i class="fa fa-folder fa-fw"></i> Folders
|
||||
</h3>
|
||||
<div ng-show="loading && !vaultFolders.length">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<ul class="control-sidebar-menu" ng-show="!loading && vaultFolders.length">
|
||||
<li ng-repeat="folder in vaultFolders track by folder.id">
|
||||
<a href="#" stop-click ng-click="filterFolder(folder)">
|
||||
<i class="fa fa-check" ng-if="folder.id === folderIdFilter"></i>
|
||||
{{folder.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="control-sidebar-section">
|
||||
<ul class="control-sidebar-menu" ng-show="!loading && vaultFolders.length">
|
||||
<li ng-repeat="folder in vaultFolders track by folder.id">
|
||||
<a href="#" stop-click ng-click="filterFolder(folder)">
|
||||
<i class="fa fa-check fa-fw" ng-if="folder.id === folderIdFilter"></i>
|
||||
<i class="fa fa-caret-right fa-fw" ng-if="folder.id !== folderIdFilter"></i>
|
||||
{{folder.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
583
src/app/vault/views/vaultAddCipher.html
Normal file
@@ -0,0 +1,583 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-plus-circle"></i> Add New Item</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && save()" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in form.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="type">What type of item is this?</label>
|
||||
<select id="type" name="Type" ng-model="selectedType" class="form-control" ng-change="typeChanged()">
|
||||
<option value="{{constants.cipherType.login}}">Login</option>
|
||||
<option value="{{constants.cipherType.card}}">Card</option>
|
||||
<option value="{{constants.cipherType.identity}}">Identity</option>
|
||||
<option value="{{constants.cipherType.secureNote}}">Secure Note</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Name</label> <span>*</span>
|
||||
<input type="text" id="name" name="Name" ng-model="cipher.name" class="form-control" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6" ng-if="!hideFolders">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="folder">Folder</label>
|
||||
<select id="folder" name="FolderId" ng-model="cipher.folderId" class="form-control" api-field>
|
||||
<option ng-repeat="folder in folders | orderBy: folderSort" value="{{folder.id}}">{{folder.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="uri">URI</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="uri" name="Login.Uri" ng-model="cipher.login.uri" class="form-control"
|
||||
placeholder="http://..." ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
||||
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#uri">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
<a href="{{cipher.login.uri}}" target="_blank" class="btn btn-default btn-flat"
|
||||
uib-tooltip="Go To Website" tooltip-placement="left">
|
||||
<i class="fa fa-share"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="username">Username</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="username" name="Login.Username" ng-model="cipher.login.username"
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#username">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors style="margin-bottom: 5px;">
|
||||
<div class="pull-right password-options">
|
||||
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left"
|
||||
ng-click="generatePassword()" ng-show="!readOnly"></i>
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Password" tooltip-placement="left"
|
||||
password-viewer="#password"></i>
|
||||
</div>
|
||||
<label for="password">Password</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-text="{{cipher.login.password}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div password-meter="cipher.login.password" password-meter-username="cipher.login.username"
|
||||
outer-class="xs" class="password-meter"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="totp">Authenticator Key (TOTP)</label>
|
||||
<input type="text" id="totp" name="Login.Totp" ng-model="cipher.login.totp"
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 totp-col">
|
||||
<div totp="cipher.login.totp" id="verification-code" ng-if="useTotp"></div>
|
||||
<div ng-if="!useTotp">
|
||||
<a href="#" stop-click ng-click="showUpgrade()"><img src="images/totp-countdown.png" alt="" /></a>
|
||||
<span class="label label-info clickable" ng-click="showUpgrade()">
|
||||
{{fromOrg ? 'UPGRADE' : 'PREMIUM'}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.card">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Cardholder Name</label>
|
||||
<input type="text" id="cardholderName" name="Card.CarholderName" ng-readonly="readOnly"
|
||||
ng-model="cipher.card.cardholderName" class="form-control" api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardBrand">Brand</label>
|
||||
<select id="cardBrand" name="Card.Brand" ng-model="cipher.card.brand" class="form-control"
|
||||
ng-readonly="readOnly" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="Visa">Visa</option>
|
||||
<option value="Mastercard">Mastercard</option>
|
||||
<option value="Amex">American Express</option>
|
||||
<option value="Discover">Discover</option>
|
||||
<option value="Diners Club">Diners Club</option>
|
||||
<option value="JCB">JCB</option>
|
||||
<option value="Maestro">Maestro</option>
|
||||
<option value="UnionPay">UnionPay</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardNumber">Number</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="cardNumber" name="Card.Number" ng-model="cipher.card.number"
|
||||
class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Number" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#cardNumber">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardExpMonth">Expiration Month</label>
|
||||
<select id="cardExpMonth" name="Card.ExpMonth" ng-model="cipher.card.expMonth"
|
||||
ng-readonly="readOnly" class="form-control" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="1">01 - January</option>
|
||||
<option value="2">02 - February</option>
|
||||
<option value="3">03 - March</option>
|
||||
<option value="4">04 - April</option>
|
||||
<option value="5">05 - May</option>
|
||||
<option value="6">06 - June</option>
|
||||
<option value="7">07 - July</option>
|
||||
<option value="8">08 - August</option>
|
||||
<option value="9">09 - September</option>
|
||||
<option value="10">10 - October</option>
|
||||
<option value="11">11 - November</option>
|
||||
<option value="12">12 - December</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardExpYear">Expiration Year</label>
|
||||
<input type="text" id="cardExpYear" name="Card.ExpYear" ng-readonly="readOnly"
|
||||
ng-model="cipher.card.expYear" class="form-control" api-field placeholder="ex. 2019" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardCode">Security Code</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="cardCode" name="Card.Code" ng-model="cipher.card.code"
|
||||
class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Code" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#cardCode">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.identity">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityTitle">Title</label>
|
||||
<select id="identityTitle" name="Identity.Title" ng-model="cipher.identity.title" class="form-control"
|
||||
ng-readonly="readOnly" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="Mr">Mr</option>
|
||||
<option value="Mrs">Mrs</option>
|
||||
<option value="Ms">Ms</option>
|
||||
<option value="Dr">Dr</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityFirstName">First Name</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityFirstName" name="Identity.FirstName"
|
||||
ng-model="cipher.identity.firstName" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy First Name" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityFirstName">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityMiddleName">Middle Name</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityMiddleName" name="Identity.FirstName"
|
||||
ng-model="cipher.identity.middleName" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Middle Name" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityMiddleName">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityLastName">Last Name</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityLastName" name="Identity.LastName"
|
||||
ng-model="cipher.identity.lastName" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Last Name" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityLastName">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityUsername">Username</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityUsername" name="Identity.Username"
|
||||
ng-model="cipher.identity.username" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityUsername">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityCompany">Company</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityCompany" name="Identity.Company"
|
||||
ng-model="cipher.identity.company" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Company" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityCompany">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identitySSN">Social Security Number</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identitySSN" name="Identity.SSN"
|
||||
ng-model="cipher.identity.ssn" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy SSN" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identitySSN">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityPassportNumber">Passport Number</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityPassportNumber" name="Identity.PassportNumber"
|
||||
ng-model="cipher.identity.passportNumber" class="form-control" ng-readonly="readOnly"
|
||||
api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Passport Number" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityPassportNumber">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityLicenseNumber">License Number</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityLicenseNumber" name="Identity.LicenseNumber"
|
||||
ng-model="cipher.identity.licenseNumber" class="form-control" ng-readonly="readOnly"
|
||||
api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy License Number" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityLicenseNumber">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityEmail">Email</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityEmail" name="Identity.Email"
|
||||
ng-model="cipher.identity.email" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Email" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityEmail">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityPhone">Phone</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityPhone" name="Identity.Phone"
|
||||
ng-model="cipher.identity.phone" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Phone" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityPhone">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityAddress1">Address 1</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityAddress1" name="Identity.Address1"
|
||||
ng-model="cipher.identity.address1" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Address 1" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityAddress1">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityAddress2">Address 2</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityAddress2" name="Identity.Address2"
|
||||
ng-model="cipher.identity.address2" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Address 2" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityAddress2">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityAddress3">Address 3</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityAddress3" name="Identity.Address3"
|
||||
ng-model="cipher.identity.address3" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Address 3" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityAddress3">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityCity">City / Town</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityCity" name="Identity.City"
|
||||
ng-model="cipher.identity.city" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy City/Town" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityCity">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityState">State / Province</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityState" name="Identity.State"
|
||||
ng-model="cipher.identity.state" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy State/Province" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityState">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityPostalCode">Zip / Postal Code</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityPostalCode" name="Identity.PostalCode"
|
||||
ng-model="cipher.identity.postalCode" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Zip/Postal Code" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityPostalCode">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityCountry">Country</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityCountry" name="Identity.Country"
|
||||
ng-model="cipher.identity.country" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Country" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityCountry">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.secureNote">
|
||||
<!-- Nothing for now -->
|
||||
</div>
|
||||
|
||||
<div class="form-group" show-errors>
|
||||
<label for="notes">Notes</label>
|
||||
<textarea id="notes" name="Notes" class="form-control" ng-model="cipher.notes" api-field
|
||||
ng-class="{'big-textarea': cipher.type === constants.cipherType.secureNote}"></textarea>
|
||||
</div>
|
||||
<hr />
|
||||
<h4><i class="fa fa-list-ul"></i> Custom Fields</h4>
|
||||
<div ng-repeat="field in cipher.fields">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_name{{$index}}">Name</label>
|
||||
<input type="text" id="field_name{{$index}}" name="Field.Name{{$index}}" class="form-control" ng-model="field.name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_type{{$index}}">Type</label>
|
||||
<select id="field_type{{$index}}" name="Field.Type{{$index}}" class="form-control" ng-model="field.type">
|
||||
<option value="0">Text</option>
|
||||
<option value="1">Hidden</option>
|
||||
<option value="2">Boolean</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="form-group">
|
||||
<div class="pull-right password-options" ng-if="field.type === '1'">
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Visibility" tooltip-placement="left"
|
||||
password-viewer="#field_value{{$index}}"></i>
|
||||
</div>
|
||||
<label for="field_value{{$index}}">Value</label>
|
||||
<div class="input-group" ng-if="field.type !== '2'">
|
||||
<input ng-attr-type="{{field.type === '0' ? 'text' : 'password'}}" id="field_value{{$index}}"
|
||||
name="Field.Value{{$index}}" class="form-control"
|
||||
ng-class="{'monospaced': field.type !== '0'}" ng-model="field.value" />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Value" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-text="{{field.value}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="field.type === '2'">
|
||||
<input type="checkbox" id="field_value{{$index}}" name="Field.Value{{$index}}" ng-model="field.value"
|
||||
data-ng-true-value="'true'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<br class="hidden-xs" />
|
||||
<a href="#" ng-click="removeField(field)" stop-click>
|
||||
<i class="fa fa-window-close-o fa-lg"></i>
|
||||
<span class="visible-xs-inline">Remove Custom Field</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="visible-xs-block" />
|
||||
</div>
|
||||
<a href="#" ng-click="addField()" stop-click>
|
||||
<i class="fa fa-plus-circle"></i> New Custom Field
|
||||
</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Submit
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
<button type="button" ng-if="!hideFavorite" class="btn btn-link pull-right" ng-click="toggleFavorite()"
|
||||
uib-tooltip="Toggle Favorite" tooltip-placement="left">
|
||||
<i class="fa fa-lg" ng-class="cipher.favorite ? 'fa-star' : 'fa-star-o'"></i>
|
||||
<span class="sr-only">Toggle Favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,171 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="addLoginModelLabel"><i class="fa fa-globe"></i> Add New Login</h4>
|
||||
</div>
|
||||
<form name="addLoginForm" ng-submit="addLoginForm.$valid && save(login)" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="addLoginForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in addLoginForm.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Name</label> <span>*</span>
|
||||
<input type="text" id="name" name="Name" ng-model="login.name" class="form-control" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6" ng-if="!hideFolders">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="folder">Folder</label>
|
||||
<select id="folder" name="FolderId" ng-model="login.folderId" class="form-control" api-field>
|
||||
<option ng-repeat="folder in folders | orderBy: folderSort" value="{{folder.id}}">{{folder.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="uri">URI</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="uri" ng-model="login.uri" name="Uri" class="form-control" placeholder="http://..." api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy URI" tooltip-placement="left">
|
||||
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#uri">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="username">Username</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="username" name="Username" ng-model="login.username" class="form-control" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
||||
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#username">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors style="margin-bottom: 5px;">
|
||||
<div class="pull-right password-options">
|
||||
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left" ng-click="generatePassword()"></i>
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Password" tooltip-placement="left" password-viewer="#password"></i>
|
||||
</div>
|
||||
<label for="password">Password</label>
|
||||
<div class="input-group">
|
||||
<input tabindex="-1" type="text" id="password-text" value="{{login.password}}" style="margin-left: -9999px;" />
|
||||
<input type="password" id="password" name="Password" ng-model="login.password" class="form-control" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)"
|
||||
ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-target="#password-text">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div password-meter="login.password" password-meter-username="login.username"
|
||||
outer-class="xs" class="password-meter"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="totp">Authenticator Key (TOTP)</label>
|
||||
<input type="text" id="totp" name="Totp" ng-model="login.totp" class="form-control"
|
||||
ng-readonly="readOnly" api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 totp-col">
|
||||
<div totp="login.totp" id="verification-code" ng-if="useTotp"></div>
|
||||
<div ng-if="!useTotp">
|
||||
<a href="#" stop-click ng-click="showUpgrade()"><img src="images/totp-countdown.png" alt="" /></a>
|
||||
<span class="label label-info clickable" ng-click="showUpgrade()">{{fromOrg ? 'UPGRADE' : 'PREMIUM'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="notes">Notes</label>
|
||||
<textarea id="notes" name="Notes" class="form-control" ng-model="login.notes" api-field></textarea>
|
||||
</div>
|
||||
<hr />
|
||||
<h4><i class="fa fa-list-ul"></i> Custom Fields</h4>
|
||||
<div ng-repeat="field in login.fields">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_name{{$index}}">Name</label>
|
||||
<input type="text" id="field_name{{$index}}" name="Field.Name{{$index}}" class="form-control" ng-model="field.name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_type{{$index}}">Type</label>
|
||||
<select id="field_type{{$index}}" name="Field.Type{{$index}}" class="form-control" ng-model="field.type">
|
||||
<option value="0">Text</option>
|
||||
<option value="1">Hidden</option>
|
||||
<option value="2">Boolean</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="form-group">
|
||||
<div class="pull-right password-options" ng-if="field.type === '1'">
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Visibility" tooltip-placement="left"
|
||||
password-viewer="#field_value{{$index}}"></i>
|
||||
</div>
|
||||
<label for="field_value{{$index}}">Value</label>
|
||||
<div class="input-group" ng-if="field.type !== '2'">
|
||||
<input ng-attr-type="{{field.type === '0' ? 'text' : 'password'}}" id="field_value{{$index}}"
|
||||
name="Field.Value{{$index}}" class="form-control" ng-model="field.value" />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Value" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-text="{{field.value}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="field.type === '2'">
|
||||
<input type="checkbox" id="field_value{{$index}}" name="Field.Value{{$index}}" ng-model="field.value"
|
||||
data-ng-true-value="'true'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<br class="hidden-xs" />
|
||||
<a href="#" ng-click="removeField(field)" stop-click>
|
||||
<i class="fa fa-window-close-o fa-lg"></i>
|
||||
<span class="visible-xs-inline">Remove Custom Field</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="visible-xs-block" />
|
||||
</div>
|
||||
<a href="#" ng-click="addField()" stop-click>
|
||||
<i class="fa fa-plus-circle"></i> New Custom Field
|
||||
</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="addLoginForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="addLoginForm.$loading"></i>Submit
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
<button type="button" ng-if="!hideFavorite" class="btn btn-link pull-right" ng-click="toggleFavorite()"
|
||||
uib-tooltip="Toggle Favorite" tooltip-placement="left">
|
||||
<i class="fa fa-lg" ng-class="login.favorite ? 'fa-star' : 'fa-star-o'"></i>
|
||||
<span class="sr-only">Toggle Favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa fa-paperclip"></i> Secure Attachments <small>{{login.name}}</small>
|
||||
<i class="fa fa-paperclip"></i> Secure Attachments <small>{{cipher.name}}</small>
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && save(form)" api-form="savePromise" autocomplete="off">
|
||||
@@ -9,13 +9,13 @@
|
||||
<div ng-if="loading">
|
||||
Loading...
|
||||
</div>
|
||||
<div ng-if="!loading && !login.attachments.length">
|
||||
There are no attachments for this login.
|
||||
<div ng-if="!loading && !cipher.attachments.length">
|
||||
There are no attachments for this item.
|
||||
</div>
|
||||
<div class="table-responsive" ng-if="login.attachments.length" style="margin: 0;">
|
||||
<div class="table-responsive" ng-if="cipher.attachments.length" style="margin: 0;">
|
||||
<table class="table table-striped table-hover table-vmiddle" style="margin: 0;">
|
||||
<tbody>
|
||||
<tr ng-repeat="attachment in login.attachments | orderBy: ['fileName']">
|
||||
<tr ng-repeat="attachment in cipher.attachments | orderBy: ['fileName']">
|
||||
<td style="width: 70px;">
|
||||
<div class="btn-group" data-append-to=".modal">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-cubes"></i> Collections <small>{{login.name}}</small></h4>
|
||||
<h4 class="modal-title"><i class="fa fa-cubes"></i> Collections <small>{{cipher.name}}</small></h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<p>Edit the collections that this login is being shared with.</p>
|
||||
<p>Edit the collections that this item is being shared with.</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
584
src/app/vault/views/vaultEditCipher.html
Normal file
@@ -0,0 +1,584 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa {{cipher.icon}}"></i> Item Information <small>{{cipher.name}}</small>
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && save(cipher)" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in form.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Name</label> <span>*</span>
|
||||
<input type="text" id="name" name="Name" ng-model="cipher.name" class="form-control"
|
||||
ng-readonly="readOnly" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6" ng-if="!hideFolders">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="folder">Folder</label>
|
||||
<select id="folder" name="FolderId" ng-model="cipher.folderId" class="form-control" api-field>
|
||||
<option ng-repeat="folder in folders | orderBy: folderSort" value="{{folder.id}}">
|
||||
{{folder.name}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="uri">URI</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="uri" name="Login.Uri" ng-model="cipher.login.uri" class="form-control"
|
||||
placeholder="http://..." ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
||||
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#uri">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
<a href="{{cipher.login.uri}}" target="_blank" class="btn btn-default btn-flat"
|
||||
uib-tooltip="Go To Website" tooltip-placement="left">
|
||||
<i class="fa fa-share"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="username">Username</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="username" name="Login.Username" ng-model="cipher.login.username"
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#username">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors style="margin-bottom: 5px;">
|
||||
<div class="pull-right password-options">
|
||||
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left"
|
||||
ng-click="generatePassword()" ng-show="!readOnly"></i>
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Password" tooltip-placement="left"
|
||||
password-viewer="#password"></i>
|
||||
</div>
|
||||
<label for="password">Password</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-text="{{cipher.login.password}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div password-meter="cipher.login.password" password-meter-username="cipher.login.username"
|
||||
outer-class="xs" class="password-meter"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="totp">Authenticator Key (TOTP)</label>
|
||||
<input type="text" id="totp" name="Login.Totp" ng-model="cipher.login.totp"
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 totp-col">
|
||||
<div totp="cipher.login.totp" id="verification-code" ng-if="useTotp"></div>
|
||||
<div ng-if="!useTotp">
|
||||
<a href="#" stop-click ng-click="showUpgrade()"><img src="images/totp-countdown.png" alt="" /></a>
|
||||
<span class="label label-info clickable" ng-click="showUpgrade()">
|
||||
{{fromOrg ? 'UPGRADE' : 'PREMIUM'}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.card">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Cardholder Name</label>
|
||||
<input type="text" id="cardholderName" name="Card.CarholderName" ng-readonly="readOnly"
|
||||
ng-model="cipher.card.cardholderName" class="form-control" api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardBrand">Brand</label>
|
||||
<select id="cardBrand" name="Card.Brand" ng-model="cipher.card.brand" class="form-control"
|
||||
ng-readonly="readOnly" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="Visa">Visa</option>
|
||||
<option value="Mastercard">Mastercard</option>
|
||||
<option value="Amex">American Express</option>
|
||||
<option value="Discover">Discover</option>
|
||||
<option value="Diners Club">Diners Club</option>
|
||||
<option value="JCB">JCB</option>
|
||||
<option value="Maestro">Maestro</option>
|
||||
<option value="UnionPay">UnionPay</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardNumber">Number</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="cardNumber" name="Card.Number" ng-model="cipher.card.number"
|
||||
class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Number" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#cardNumber">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardExpMonth">Expiration Month</label>
|
||||
<select id="cardExpMonth" name="Card.ExpMonth" ng-model="cipher.card.expMonth"
|
||||
ng-readonly="readOnly" class="form-control" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="1">01 - January</option>
|
||||
<option value="2">02 - February</option>
|
||||
<option value="3">03 - March</option>
|
||||
<option value="4">04 - April</option>
|
||||
<option value="5">05 - May</option>
|
||||
<option value="6">06 - June</option>
|
||||
<option value="7">07 - July</option>
|
||||
<option value="8">08 - August</option>
|
||||
<option value="9">09 - September</option>
|
||||
<option value="10">10 - October</option>
|
||||
<option value="11">11 - November</option>
|
||||
<option value="12">12 - December</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardExpYear">Expiration Year</label>
|
||||
<input type="text" id="cardExpYear" name="Card.ExpYear" ng-readonly="readOnly"
|
||||
ng-model="cipher.card.expYear" class="form-control" api-field placeholder="ex. 2019" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cardCode">Security Code</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="cardCode" name="Card.Code" ng-model="cipher.card.code"
|
||||
class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Code" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#cardCode">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.identity">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityTitle">Title</label>
|
||||
<select id="identityTitle" name="Identity.Title" ng-model="cipher.identity.title" class="form-control"
|
||||
ng-readonly="readOnly" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="Mr">Mr</option>
|
||||
<option value="Mrs">Mrs</option>
|
||||
<option value="Ms">Ms</option>
|
||||
<option value="Dr">Dr</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityFirstName">First Name</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityFirstName" name="Identity.FirstName"
|
||||
ng-model="cipher.identity.firstName" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy First Name" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityFirstName">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityMiddleName">Middle Name</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityMiddleName" name="Identity.FirstName"
|
||||
ng-model="cipher.identity.middleName" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Middle Name" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityMiddleName">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityLastName">Last Name</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityLastName" name="Identity.LastName"
|
||||
ng-model="cipher.identity.lastName" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Last Name" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityLastName">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityUsername">Username</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityUsername" name="Identity.Username"
|
||||
ng-model="cipher.identity.username" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityUsername">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityCompany">Company</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityCompany" name="Identity.Company"
|
||||
ng-model="cipher.identity.company" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Company" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityCompany">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identitySSN">Social Security Number</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identitySSN" name="Identity.SSN"
|
||||
ng-model="cipher.identity.ssn" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy SSN" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identitySSN">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityPassportNumber">Passport Number</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityPassportNumber" name="Identity.PassportNumber"
|
||||
ng-model="cipher.identity.passportNumber" class="form-control" ng-readonly="readOnly"
|
||||
api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Passport Number" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityPassportNumber">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityLicenseNumber">License Number</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityLicenseNumber" name="Identity.LicenseNumber"
|
||||
ng-model="cipher.identity.licenseNumber" class="form-control" ng-readonly="readOnly"
|
||||
api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy License Number" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityLicenseNumber">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityEmail">Email</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityEmail" name="Identity.Email"
|
||||
ng-model="cipher.identity.email" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Email" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityEmail">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityPhone">Phone</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityPhone" name="Identity.Phone"
|
||||
ng-model="cipher.identity.phone" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Phone" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityPhone">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityAddress1">Address 1</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityAddress1" name="Identity.Address1"
|
||||
ng-model="cipher.identity.address1" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Address 1" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityAddress1">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityAddress2">Address 2</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityAddress2" name="Identity.Address2"
|
||||
ng-model="cipher.identity.address2" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Address 2" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityAddress2">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityAddress3">Address 3</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityAddress3" name="Identity.Address3"
|
||||
ng-model="cipher.identity.address3" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Address 3" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityAddress3">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityCity">City / Town</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityCity" name="Identity.City"
|
||||
ng-model="cipher.identity.city" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy City/Town" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityCity">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityState">State / Province</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityState" name="Identity.State"
|
||||
ng-model="cipher.identity.state" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy State/Province" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityState">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityPostalCode">Zip / Postal Code</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityPostalCode" name="Identity.PostalCode"
|
||||
ng-model="cipher.identity.postalCode" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Zip/Postal Code" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityPostalCode">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="identityCountry">Country</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="identityCountry" name="Identity.Country"
|
||||
ng-model="cipher.identity.country" class="form-control" ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Country" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" data-clipboard-target="#identityCountry">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.secureNote">
|
||||
<!-- Nothing for now -->
|
||||
</div>
|
||||
|
||||
<div class="form-group" show-errors>
|
||||
<label for="notes">Notes</label>
|
||||
<textarea id="notes" name="Notes" class="form-control" ng-model="cipher.notes"
|
||||
ng-class="{'big-textarea': cipher.type === constants.cipherType.secureNote}"
|
||||
ng-readonly="readOnly" api-field></textarea>
|
||||
</div>
|
||||
<div ng-if="!readOnly || (cipher.fields && cipher.fields.length)">
|
||||
<hr />
|
||||
<h4><i class="fa fa-list-ul"></i> Custom Fields</h4>
|
||||
</div>
|
||||
<div ng-repeat="field in cipher.fields">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_name{{$index}}">Name</label>
|
||||
<input type="text" id="field_name{{$index}}" class="form-control" ng-model="field.name"
|
||||
ng-readonly="readOnly" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_type{{$index}}">Type</label>
|
||||
<select id="field_type{{$index}}" class="form-control" ng-model="field.type" ng-disabled="readOnly">
|
||||
<option value="0">Text</option>
|
||||
<option value="1">Hidden</option>
|
||||
<option value="2">Boolean</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="form-group">
|
||||
<div class="pull-right password-options" ng-if="field.type === '1'">
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Visibility" tooltip-placement="left"
|
||||
password-viewer="#field_value{{$index}}"></i>
|
||||
</div>
|
||||
<label for="field_value{{$index}}">Value</label>
|
||||
<div class="input-group" ng-if="field.type !== '2'">
|
||||
<input ng-attr-type="{{field.type === '0' ? 'text' : 'password'}}" id="field_value{{$index}}"
|
||||
class="form-control" ng-class="{'monospaced': field.type !== '0'}" ng-model="field.value"
|
||||
ng-readonly="readOnly" />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Value" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-text="{{field.value}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="field.type === '2'">
|
||||
<input type="checkbox" id="field_value{{$index}}" ng-model="field.value"
|
||||
data-ng-true-value="'true'" ng-disabled="readOnly" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<br class="hidden-xs" />
|
||||
<a href="#" ng-click="removeField(field)" stop-click ng-if="!readOnly">
|
||||
<i class="fa fa-window-close-o fa-lg"></i>
|
||||
<span class="visible-xs-inline">Remove Custom Field</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="visible-xs-block" />
|
||||
</div>
|
||||
<a href="#" ng-click="addField()" stop-click ng-if="!readOnly">
|
||||
<i class="fa fa-plus-circle"></i> New Custom Field
|
||||
</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
<button type="button" class="btn btn-link pull-right" ng-click="delete()" uib-tooltip="Delete"
|
||||
tooltip-placement="left" ng-disabled="readOnly">
|
||||
<i class="fa fa-trash fa-lg"></i>
|
||||
<span class="sr-only">Delete</span>
|
||||
</button>
|
||||
<button type="button" ng-if="!hideFavorite" class="btn btn-link pull-right" ng-click="toggleFavorite()"
|
||||
uib-tooltip="Toggle Favorite" tooltip-placement="left">
|
||||
<i class="fa fa-lg" ng-class="cipher.favorite ? 'fa-star' : 'fa-star-o'"></i>
|
||||
<span class="sr-only">Toggle Favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,192 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="editLoginModelLabel">
|
||||
<i class="fa fa-globe"></i> Login Information <small>{{login.name}}</small>
|
||||
</h4>
|
||||
</div>
|
||||
<form name="editLoginForm" ng-submit="editLoginForm.$valid && save(login)" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="editLoginForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in editLoginForm.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Name</label> <span>*</span>
|
||||
<input type="text" id="name" name="Name" ng-model="login.name" class="form-control"
|
||||
ng-readonly="readOnly" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6" ng-if="!hideFolders">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="folder">Folder</label>
|
||||
<select id="folder" name="FolderId" ng-model="login.folderId" class="form-control" api-field>
|
||||
<option ng-repeat="folder in folders | orderBy: folderSort" value="{{folder.id}}">
|
||||
{{folder.name}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="uri">URI</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="uri" name="Uri" ng-model="login.uri" class="form-control" placeholder="http://..."
|
||||
ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
||||
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#uri">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
<a href="{{login.uri}}" target="_blank" class="btn btn-default btn-flat" uib-tooltip="Go To Login"
|
||||
tooltip-placement="left">
|
||||
<i class="fa fa-share"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="username">Username</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="username" name="Username" ng-model="login.username" class="form-control"
|
||||
ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#username">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors style="margin-bottom: 5px;">
|
||||
<div class="pull-right password-options">
|
||||
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left"
|
||||
ng-click="generatePassword()" ng-show="!readOnly"></i>
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Password" tooltip-placement="left"
|
||||
password-viewer="#password"></i>
|
||||
</div>
|
||||
<label for="password">Password</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="password" name="Password" ng-model="login.password" class="form-control"
|
||||
ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-text="{{login.password}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div password-meter="login.password" password-meter-username="login.username"
|
||||
outer-class="xs" class="password-meter"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="totp">Authenticator Key (TOTP)</label>
|
||||
<input type="text" id="totp" name="Totp" ng-model="login.totp" class="form-control"
|
||||
ng-readonly="readOnly" api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 totp-col">
|
||||
<div totp="login.totp" id="verification-code" ng-if="useTotp"></div>
|
||||
<div ng-if="!useTotp">
|
||||
<a href="#" stop-click ng-click="showUpgrade()"><img src="images/totp-countdown.png" alt="" /></a>
|
||||
<span class="label label-info clickable" ng-click="showUpgrade()">{{fromOrg ? 'UPGRADE' : 'PREMIUM'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="notes">Notes</label>
|
||||
<textarea id="notes" name="Notes" class="form-control" ng-model="login.notes"
|
||||
ng-readonly="readOnly" api-field></textarea>
|
||||
</div>
|
||||
<div ng-if="!readOnly || (login.fields && login.fields.length)">
|
||||
<hr />
|
||||
<h4><i class="fa fa-list-ul"></i> Custom Fields</h4>
|
||||
</div>
|
||||
<div ng-repeat="field in login.fields">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_name{{$index}}">Name</label>
|
||||
<input type="text" id="field_name{{$index}}" class="form-control" ng-model="field.name"
|
||||
ng-readonly="readOnly" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_type{{$index}}">Type</label>
|
||||
<select id="field_type{{$index}}" class="form-control" ng-model="field.type" ng-disabled="readOnly">
|
||||
<option value="0">Text</option>
|
||||
<option value="1">Hidden</option>
|
||||
<option value="2">Boolean</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="form-group">
|
||||
<div class="pull-right password-options" ng-if="field.type === '1'">
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Visibility" tooltip-placement="left"
|
||||
password-viewer="#field_value{{$index}}"></i>
|
||||
</div>
|
||||
<label for="field_value{{$index}}">Value</label>
|
||||
<div class="input-group" ng-if="field.type !== '2'">
|
||||
<input ng-attr-type="{{field.type === '0' ? 'text' : 'password'}}" id="field_value{{$index}}"
|
||||
class="form-control" ng-model="field.value" ng-readonly="readOnly" />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Value" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-text="{{field.value}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="field.type === '2'">
|
||||
<input type="checkbox" id="field_value{{$index}}" ng-model="field.value"
|
||||
data-ng-true-value="'true'" ng-disabled="readOnly" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<br class="hidden-xs" />
|
||||
<a href="#" ng-click="removeField(field)" stop-click ng-if="!readOnly">
|
||||
<i class="fa fa-window-close-o fa-lg"></i>
|
||||
<span class="visible-xs-inline">Remove Custom Field</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="visible-xs-block" />
|
||||
</div>
|
||||
<a href="#" ng-click="addField()" stop-click ng-if="!readOnly">
|
||||
<i class="fa fa-plus-circle"></i> New Custom Field
|
||||
</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="editLoginForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="editLoginForm.$loading"></i>Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
<button type="button" class="btn btn-link pull-right" ng-click="delete()" uib-tooltip="Delete"
|
||||
tooltip-placement="left" ng-disabled="readOnly">
|
||||
<i class="fa fa-trash fa-lg"></i>
|
||||
<span class="sr-only">Delete</span>
|
||||
</button>
|
||||
<button type="button" ng-if="!hideFavorite" class="btn btn-link pull-right" ng-click="toggleFavorite()"
|
||||
uib-tooltip="Toggle Favorite" tooltip-placement="left">
|
||||
<i class="fa fa-lg" ng-class="login.favorite ? 'fa-star' : 'fa-star-o'"></i>
|
||||
<span class="sr-only">Toggle Favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa fa-share"></i> Move Logins
|
||||
<i class="fa fa-share"></i> Move Items
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && save()" api-form="savePromise" autocomplete="off">
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<p>
|
||||
Select a folder that you would like to move the <b>{{count}}</b> selected
|
||||
<span ng-pluralize count="count" when="{'1': 'login', 'other': 'logins'}"></span> to.
|
||||
<span ng-pluralize count="count" when="{'1': 'item', 'other': 'items'}"></span> to.
|
||||
</p>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="folder">Folder</label>
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-share-alt"></i> Share Login <small>{{login.name}}</small></h4>
|
||||
<h4 class="modal-title"><i class="fa fa-share-alt"></i> Share Item <small>{{cipher.name}}</small></h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<p>Choose an organization that you wish to share this login with.</p>
|
||||
<p>Choose an organization that you wish to share this item with.</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
@@ -14,7 +14,7 @@
|
||||
<p ng-show="loading">Loading...</p>
|
||||
<div ng-show="!loading && !organizations.length" class="callout callout-default">
|
||||
<h4><i class="fa fa-info-circle"></i> No Organizations</h4>
|
||||
<p>You do not belong to any organizations. Organizations allow you to share logins with other bitwarden users.</p>
|
||||
<p>You do not belong to any organizations. Organizations allow you to share items with other bitwarden users.</p>
|
||||
<a ng-click="createOrg()" class="btn btn-default btn-flat">
|
||||
Create an Organization
|
||||
</a>
|
||||
@@ -72,4 +72,4 @@
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
@@ -3,18 +3,18 @@
|
||||
Shared
|
||||
<small>
|
||||
<span ng-pluralize
|
||||
count="collections.length > 0 && logins.length ? collections.length - 1 : collections.length"
|
||||
count="collections.length > 0 && ciphers.length ? collections.length - 1 : collections.length"
|
||||
when="{'1': '{} collection', 'other': '{} collections'}"></span>,
|
||||
<span ng-pluralize count="logins.length" when="{'1': '{} login', 'other': '{} logins'}"></span>
|
||||
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<p ng-show="loading && !collections.length">Loading...</p>
|
||||
<div class="callout callout-default" style="background: #fff;" ng-show="!loading && !collections.length && !logins.length">
|
||||
<div class="callout callout-default" style="background: #fff;" ng-show="!loading && !collections.length && !ciphers.length">
|
||||
<h4>Nothing shared <i class="fa fa-frown-o"></i></h4>
|
||||
<p>
|
||||
You do not have any logins or collections being shared with you.
|
||||
You do not have any items or collections being shared with you.
|
||||
To start sharing, create an organization or ask an existing organization to invite you.
|
||||
</p>
|
||||
<a ui-sref="backend.user.settingsCreateOrg" class="btn btn-default btn-flat">
|
||||
@@ -28,7 +28,7 @@
|
||||
<h3 class="box-title">
|
||||
<i class="fa" ng-class="{'fa-cubes': collection.id, 'fa-sitemap': !collection.id}"></i>
|
||||
{{collection.name}}
|
||||
<small ng-pluralize count="collectionLogins.length" when="{'1': '{} login', 'other': '{} logins'}"></small>
|
||||
<small ng-pluralize count="collectionCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||
</h3>
|
||||
<div class="box-tools">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
|
||||
@@ -37,20 +37,20 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body" ng-class="{'no-padding': collectionLogins.length}">
|
||||
<div ng-show="!collectionLogins.length && collection.id">
|
||||
<p>No logins in this collection.</p>
|
||||
<div class="box-body" ng-class="{'no-padding': collectionCiphers.length}">
|
||||
<div ng-show="!collectionCiphers.length && collection.id">
|
||||
<p>No items in this collection.</p>
|
||||
<p>
|
||||
Share a login to this collection by selecting <i class="fa fa-share-alt"></i> <b>Share</b> or
|
||||
<i class="fa fa-cubes"></i> <b>Collections</b> from the login's options (<i class="fa fa-cog"></i>) menu.
|
||||
Share an item to this collection by selecting <i class="fa fa-share-alt"></i> <b>Share</b> or
|
||||
<i class="fa fa-cubes"></i> <b>Collections</b> from the item's options (<i class="fa fa-cog"></i>) menu.
|
||||
</p>
|
||||
</div>
|
||||
<div ng-show="!collectionLogins.length && !collection.id">No unassigned logins.</div>
|
||||
<div class="table-responsive" ng-show="collectionLogins.length">
|
||||
<div ng-show="!collectionCiphers.length && !collection.id">No unassigned items.</div>
|
||||
<div class="table-responsive" ng-show="collectionCiphers.length">
|
||||
<table class="table table-striped table-hover table-vmiddle">
|
||||
<tbody>
|
||||
<tr ng-repeat="login in collectionLogins = (logins | filter: filterByCollection(collection) |
|
||||
orderBy: ['name', 'username']) track by login.id">
|
||||
<tr ng-repeat="cipher in collectionCiphers = (ciphers | filter: filterByCollection(collection) |
|
||||
orderBy: ['name', 'subTitle']) track by cipher.id">
|
||||
<td style="width: 70px;">
|
||||
<div class="btn-group" data-append-to="body">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
@@ -58,28 +58,28 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="editLogin(login)">
|
||||
<a href="#" stop-click ng-click="editCipher(cipher)">
|
||||
<i class="fa fa-fw fa-pencil"></i> Edit
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="attachments(login)">
|
||||
<a href="#" stop-click ng-click="attachments(cipher)">
|
||||
<i class="fa fa-fw fa-paperclip"></i> Attachments
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="login.edit">
|
||||
<a href="#" stop-click ng-click="editCollections(login)">
|
||||
<li ng-show="cipher.edit">
|
||||
<a href="#" stop-click ng-click="editCollections(cipher)">
|
||||
<i class="fa fa-fw fa-cubes"></i> Collections
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="login.password">
|
||||
<li ng-show="cipher.meta.password">
|
||||
<a href="#" stop-click ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-text="{{login.password}}">
|
||||
data-clipboard-text="{{cipher.meta.password}}">
|
||||
<i class="fa fa-fw fa-clipboard"></i> Copy Password
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="login.edit">
|
||||
<a href="#" stop-click ng-click="removeLogin(login, collection)"
|
||||
<li ng-show="cipher.edit">
|
||||
<a href="#" stop-click ng-click="removeCipher(cipher, collection)"
|
||||
ng-if="collection.id" class="text-red">
|
||||
<i class="fa fa-fw fa-remove"></i> Remove
|
||||
</a>
|
||||
@@ -87,12 +87,17 @@
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td class="vault-icon">
|
||||
<i class="fa fa-fw fa-lg {{::cipher.icon}}" ng-if="!cipher.meta.image"></i>
|
||||
<img alt="" ng-if="cipher.meta.image" ng-src="{{cipher.meta.image}}"
|
||||
fallback-src="images/fa-globe.png" />
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" stop-click ng-click="editLogin(login)">{{login.name}}</a>
|
||||
<i class="fa fa-star text-muted" title="Favorite" ng-show="login.favorite"></i>
|
||||
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="login.hasAttachments"
|
||||
<a href="#" stop-click ng-click="editCipher(cipher)">{{cipher.name}}</a>
|
||||
<i class="fa fa-star text-muted" title="Favorite" ng-show="cipher.favorite"></i>
|
||||
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="cipher.hasAttachments"
|
||||
stop-prop></i><br />
|
||||
<div class="text-sm text-muted">{{login.username}}</div>
|
||||
<div class="text-sm text-muted">{{cipher.subTitle}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
</a>
|
||||
<ul class="treeview-menu" ng-class="{'menu-open': $state.includes('backend.org.vault')}">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="addOrganizationLogin()">
|
||||
<i class="fa fa-plus-circle fa-fw"></i> New Login
|
||||
<a href="#" stop-click ng-click="addOrganizationCipher()">
|
||||
<i class="fa fa-plus-circle fa-fw"></i> New Item
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
<a ui-sref="backend.user.vault"><i class="fa fa-lock fa-fw"></i> <span>My Vault</span></a>
|
||||
<ul class="treeview-menu" ng-class="{'menu-open': $state.includes('backend.user.vault')}">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="addLogin()">
|
||||
<i class="fa fa-plus-circle fa-fw"></i> New Login
|
||||
<a href="#" stop-click ng-click="addCipher()">
|
||||
<i class="fa fa-plus-circle fa-fw"></i> New Item
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
9
src/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="images/icons/mstile-150x150.png"/>
|
||||
<TileColor>#3c8dbc</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
src/favicon.ico
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 18 KiB |
BIN
src/images/fa-globe.png
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
src/images/icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/images/icons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/images/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/images/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 403 B |
BIN
src/images/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 636 B |
BIN
src/images/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
26
src/images/icons/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M824 4945 c-92 -16 -133 -30 -218 -71 -91 -45 -150 -90 -229 -173
|
||||
-112 -119 -182 -263 -207 -427 -7 -47 -10 -598 -8 -1759 l3 -1690 22 -70 c32
|
||||
-103 58 -160 108 -234 88 -132 222 -243 367 -300 157 -63 87 -61 1892 -61
|
||||
1797 0 1750 -1 1900 59 239 96 408 289 483 551 17 62 18 145 18 1790 0 1645
|
||||
-1 1728 -18 1790 -44 155 -111 272 -214 372 -106 105 -218 170 -362 211 l-76
|
||||
22 -1695 2 c-1357 1 -1709 -1 -1766 -12z m3196 -617 c18 -13 41 -39 51 -58 18
|
||||
-33 19 -79 19 -1000 -1 -866 -4 -1026 -25 -1131 -26 -136 -150 -397 -251 -534
|
||||
-81 -109 -302 -330 -439 -441 -105 -85 -296 -216 -390 -270 -11 -6 -60 -34
|
||||
-108 -62 -146 -86 -272 -142 -316 -142 -102 0 -530 247 -811 469 -143 112
|
||||
-385 359 -462 471 -102 148 -175 300 -226 475 l-26 90 -4 1010 c-2 719 0 1021
|
||||
8 1048 7 25 24 47 52 67 l41 30 1427 0 1427 0 33 -22z"/>
|
||||
<path d="M2560 2540 l0 -1350 38 19 c231 118 489 307 683 500 181 180 265 309
|
||||
321 488 21 66 21 87 25 881 l4 812 -535 0 -536 0 0 -1350z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -25,6 +25,7 @@
|
||||
img-src
|
||||
'self'
|
||||
data:
|
||||
https://icons.bitwarden.com
|
||||
https://*.paypal.com
|
||||
https://www.paypalobjects.com
|
||||
https://q.stripe.com
|
||||
@@ -76,6 +77,7 @@
|
||||
*;">
|
||||
<!-- @endif -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#3c8dbc">
|
||||
<base href="/" />
|
||||
|
||||
<title page-title>bitwarden Web Vault</title>
|
||||
@@ -102,6 +104,11 @@
|
||||
|
||||
<link rel="stylesheet" href="css/vault.css" />
|
||||
<!-- @endexclude -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="images/icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="images/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="images/icons/favicon-16x16.png">
|
||||
<link rel="mask-icon" href="images/icons/safari-pinned-tab.svg" color="#3c8dbc">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body ng-controller="mainController as main" class="layout-boxed {{main.skinClass}} sidebar-mini {{main.bodyClass}}"
|
||||
ng-class="{'using-control-sidebar': main.usingControlSidebar,
|
||||
@@ -173,6 +180,7 @@
|
||||
<script src="app/directives/stopClickDirective.js"></script>
|
||||
<script src="app/directives/stopPropDirective.js"></script>
|
||||
<script src="app/directives/totpDirective.js"></script>
|
||||
<script src="app/directives/fallbackSrcDirective.js"></script>
|
||||
|
||||
<script src="app/filters/filtersModule.js"></script>
|
||||
<script src="app/filters/enumNameFilter.js"></script>
|
||||
@@ -210,14 +218,14 @@
|
||||
|
||||
<script src="app/vault/vaultModule.js"></script>
|
||||
<script src="app/vault/vaultController.js"></script>
|
||||
<script src="app/vault/vaultEditLoginController.js"></script>
|
||||
<script src="app/vault/vaultAddLoginController.js"></script>
|
||||
<script src="app/vault/vaultEditCipherController.js"></script>
|
||||
<script src="app/vault/vaultAddCipherController.js"></script>
|
||||
<script src="app/vault/vaultEditFolderController.js"></script>
|
||||
<script src="app/vault/vaultAddFolderController.js"></script>
|
||||
<script src="app/vault/vaultShareLoginController.js"></script>
|
||||
<script src="app/vault/vaultShareCipherController.js"></script>
|
||||
<script src="app/vault/vaultSharedController.js"></script>
|
||||
<script src="app/vault/vaultLoginCollectionsController.js"></script>
|
||||
<script src="app/vault/vaultMoveLoginsController.js"></script>
|
||||
<script src="app/vault/vaultCipherCollectionsController.js"></script>
|
||||
<script src="app/vault/vaultMoveCiphersController.js"></script>
|
||||
<script src="app/vault/vaultAttachmentsController.js"></script>
|
||||
|
||||
<script src="app/organization/organizationModule.js"></script>
|
||||
@@ -242,9 +250,9 @@
|
||||
<script src="app/organization/organizationBillingChangePlanController.js"></script>
|
||||
<script src="app/organization/organizationBillingVerifyBankController.js"></script>
|
||||
<script src="app/organization/organizationVaultController.js"></script>
|
||||
<script src="app/organization/organizationVaultAddLoginController.js"></script>
|
||||
<script src="app/organization/organizationVaultEditLoginController.js"></script>
|
||||
<script src="app/organization/organizationVaultLoginCollectionsController.js"></script>
|
||||
<script src="app/organization/organizationVaultAddCipherController.js"></script>
|
||||
<script src="app/organization/organizationVaultEditCipherController.js"></script>
|
||||
<script src="app/organization/organizationVaultCipherCollectionsController.js"></script>
|
||||
<script src="app/organization/organizationVaultAttachmentsController.js"></script>
|
||||
<script src="app/organization/organizationGroupsController.js"></script>
|
||||
<script src="app/organization/organizationGroupsAddController.js"></script>
|
||||
@@ -266,6 +274,7 @@
|
||||
<script src="app/settings/settingsTwoStepController.js"></script>
|
||||
<script src="app/settings/settingsAddEditEquivalentDomainController.js"></script>
|
||||
<script src="app/settings/settingsDeleteController.js"></script>
|
||||
<script src="app/settings/settingsPurgeController.js"></script>
|
||||
<script src="app/settings/settingsCreateOrganizationController.js"></script>
|
||||
<script src="app/settings/settingsBillingController.js"></script>
|
||||
<script src="app/settings/settingsBillingAdjustStorageController.js"></script>
|
||||
|
||||
@@ -53,7 +53,7 @@ body.skin-blue {
|
||||
}
|
||||
|
||||
.content-wrapper, .main-footer {
|
||||
border-left: 1px solid transparent;
|
||||
border-left: 1px solid @sidebar-dark-bg;
|
||||
}
|
||||
|
||||
.sidebar-menu > li {
|
||||
@@ -159,6 +159,7 @@ body.skin-blue {
|
||||
li a {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
li.active a {
|
||||
@@ -166,6 +167,13 @@ body.skin-blue {
|
||||
}
|
||||
}
|
||||
|
||||
.control-sidebar-section {
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
padding-right: 15px;
|
||||
margin-right: -15px;
|
||||
}
|
||||
|
||||
.control-sidebar-open {
|
||||
@media (min-width: @screen-sm) {
|
||||
.main-footer {
|
||||
@@ -193,10 +201,6 @@ body.skin-blue {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.layout-boxed .wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-wrapper, .control-sidebar, .main-sidebar, .main-header .logo, .main-header .navbar, .main-footer {
|
||||
.transition(initial);
|
||||
}
|
||||
@@ -432,6 +436,24 @@ form .btn .loading-icon {
|
||||
.table td.action-select {
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
max-width: 30px;
|
||||
|
||||
@media (max-width: @screen-sm-max) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.table td.vault-icon {
|
||||
text-align: center;
|
||||
width: 41px;
|
||||
max-width: 41px;
|
||||
color: @text-muted;
|
||||
|
||||
img {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-sm-max) {
|
||||
display: none;
|
||||
@@ -785,3 +807,13 @@ h1, h2, h3, h4, h5, h6 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
&#notes {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
&.big-textarea {
|
||||
height: 200px !important;
|
||||
}
|
||||
}
|
||||
|
||||
17
src/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "bitwarden vault",
|
||||
"icons": [
|
||||
{
|
||||
"src": "images/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/icons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#3c8dbc",
|
||||
"background_color": "#3c8dbc"
|
||||
}
|
||||