mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-26 17:08:33 +01:00
delete old popup
This commit is contained in:
parent
9d81843643
commit
9ec9a6cf88
@ -1,43 +0,0 @@
|
||||
angular
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsHintController', function ($scope, $state, apiService, toastr, $q, popupUtilsService,
|
||||
$analytics, i18nService, $timeout) {
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
document.getElementById('email').focus();
|
||||
}, 500);
|
||||
|
||||
$scope.i18n = i18nService;
|
||||
$scope.model = {};
|
||||
|
||||
$scope.submitPromise = null;
|
||||
$scope.submit = function (model) {
|
||||
if (!model.email) {
|
||||
toastr.error(i18nService.emailRequired, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
if (model.email.indexOf('@') === -1) {
|
||||
toastr.error(i18nService.invalidEmail, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new PasswordHintRequest(model.email);
|
||||
$scope.submitPromise = hintPromise(request);
|
||||
$scope.submitPromise.then(function () {
|
||||
$analytics.eventTrack('Requested Hint');
|
||||
toastr.success(i18nService.masterPassSent);
|
||||
$state.go('login');
|
||||
});
|
||||
};
|
||||
|
||||
function hintPromise(request) {
|
||||
return $q(function (resolve, reject) {
|
||||
apiService.postPasswordHint(request).then(function () {
|
||||
resolve();
|
||||
}, function (error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
@ -1,58 +0,0 @@
|
||||
angular
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsLoginController', function ($scope, $state, $stateParams, authService, userService, toastr,
|
||||
popupUtilsService, $analytics, i18nService, $timeout) {
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
if ($stateParams.email) {
|
||||
document.getElementById('master-password').focus();
|
||||
}
|
||||
else {
|
||||
document.getElementById('email').focus();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
$scope.i18n = i18nService;
|
||||
$scope.model = {
|
||||
email: $stateParams.email
|
||||
};
|
||||
|
||||
$scope.loginPromise = null;
|
||||
$scope.login = function (model) {
|
||||
if (!model.email) {
|
||||
toastr.error(i18nService.emailRequired, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
if (model.email.indexOf('@') === -1) {
|
||||
toastr.error(i18nService.invalidEmail, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
if (!model.masterPassword) {
|
||||
toastr.error(i18nService.masterPassRequired, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.loginPromise = authService.logIn(model.email, model.masterPassword);
|
||||
|
||||
$scope.loginPromise.then(function (response) {
|
||||
if (response.twoFactor) {
|
||||
$analytics.eventTrack('Logged In To Two-step');
|
||||
$state.go('twoFactor', {
|
||||
animation: 'in-slide-left',
|
||||
email: model.email,
|
||||
masterPassword: model.masterPassword,
|
||||
providers: response.twoFactorProviders,
|
||||
provider: null
|
||||
});
|
||||
}
|
||||
else {
|
||||
$analytics.eventTrack('Logged In');
|
||||
$state.go('tabs.vault', {
|
||||
animation: 'in-slide-left',
|
||||
syncOnLoad: true
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
@ -1,215 +0,0 @@
|
||||
angular
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsLoginTwoFactorController', function ($scope, $state, authService, toastr, platformUtilsService,
|
||||
$analytics, i18nService, $stateParams, $filter, constantsService, $timeout, $window, cryptoService, apiService,
|
||||
environmentService, SweetAlert, popupUtilsService) {
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
|
||||
$scope.i18n = i18nService;
|
||||
$scope.showNewWindowMessage = platformUtilsService.isSafari();
|
||||
|
||||
var customWebVaultUrl = null;
|
||||
if (environmentService.baseUrl) {
|
||||
customWebVaultUrl = environmentService.baseUrl;
|
||||
}
|
||||
else if (environmentService.webVaultUrl) {
|
||||
customWebVaultUrl = environmentService.webVaultUrl;
|
||||
}
|
||||
|
||||
var u2f = new $window.U2f($window, customWebVaultUrl, function (data) {
|
||||
$timeout(function () {
|
||||
$scope.login(data);
|
||||
});
|
||||
}, function (error) {
|
||||
$timeout(function () {
|
||||
toastr.error(error, i18nService.errorsOccurred);
|
||||
});
|
||||
}, function (info) {
|
||||
$timeout(function () {
|
||||
if (info === 'ready') {
|
||||
$scope.u2fReady = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var constants = constantsService;
|
||||
var email = authService.email;
|
||||
var masterPasswordHash = authService.masterPasswordHash;
|
||||
var providers = authService.twoFactorProviders;
|
||||
|
||||
if (!email || !masterPasswordHash || !providers) {
|
||||
$state.go('login');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.providerType = $stateParams.provider || $stateParams.provider === 0 ? $stateParams.provider :
|
||||
authService.getDefaultTwoFactorProvider(platformUtilsService.supportsU2f($window));
|
||||
$scope.twoFactorEmail = null;
|
||||
$scope.token = null;
|
||||
$scope.constantsProvider = constants.twoFactorProvider;
|
||||
$scope.u2fReady = false;
|
||||
$scope.remember = { checked: false };
|
||||
init();
|
||||
|
||||
$scope.loginPromise = null;
|
||||
$scope.login = function (token, sendSuccessToTab) {
|
||||
if (!token) {
|
||||
toastr.error(i18nService.verificationCodeRequired, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.providerType === constants.twoFactorProvider.u2f) {
|
||||
if (u2f) {
|
||||
u2f.stop();
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if ($scope.providerType === constants.twoFactorProvider.email ||
|
||||
$scope.providerType === constants.twoFactorProvider.authenticator) {
|
||||
token = token.replace(' ', '');
|
||||
}
|
||||
|
||||
$scope.loginPromise = authService.logInTwoFactor($scope.providerType, token, $scope.remember.checked);
|
||||
$scope.loginPromise.then(function () {
|
||||
$analytics.eventTrack('Logged In From Two-step');
|
||||
$state.go('tabs.vault', { animation: 'in-slide-left', syncOnLoad: true }).then(function () {
|
||||
if (sendSuccessToTab) {
|
||||
$timeout(function () {
|
||||
BrowserApi.tabSendMessage(sendSuccessToTab, {
|
||||
command: '2faPageData',
|
||||
data: { type: 'success' }
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}, function () {
|
||||
u2f.start();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.sendEmail = function (doToast) {
|
||||
if ($scope.providerType !== constants.twoFactorProvider.email) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new TwoFactorEmailRequest(email, masterPasswordHash);
|
||||
apiService.postTwoFactorEmail(request, function () {
|
||||
if (doToast) {
|
||||
toastr.success('Verification email sent to ' + $scope.twoFactorEmail + '.');
|
||||
}
|
||||
}, function () {
|
||||
toastr.error('Could not send verification email.');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.anotherMethod = function () {
|
||||
$state.go('twoFactorMethods', {
|
||||
animation: 'in-slide-up',
|
||||
provider: $scope.providerType
|
||||
});
|
||||
};
|
||||
|
||||
$scope.back = function () {
|
||||
$state.go('login', {
|
||||
animation: 'out-slide-right'
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
u2f.stop();
|
||||
u2f.cleanup();
|
||||
u2f = null;
|
||||
});
|
||||
|
||||
$scope.$on('2faPageResponse', (event, details) => {
|
||||
if (details.type === 'duo') {
|
||||
$scope.login(details.data.sigValue, details.tab);
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
u2f.stop();
|
||||
u2f.cleanup();
|
||||
|
||||
$timeout(function () {
|
||||
var codeInput = document.getElementById('code');
|
||||
if (codeInput) {
|
||||
codeInput.focus();
|
||||
}
|
||||
|
||||
var params = providers.get($scope.providerType);
|
||||
if ($scope.providerType === constants.twoFactorProvider.duo ||
|
||||
$scope.providerType === constants.twoFactorProvider.organizationDuo) {
|
||||
if (platformUtilsService.isSafari()) {
|
||||
var tab = BrowserApi.createNewTab(BrowserApi.getAssetUrl('2fa/index.html'));
|
||||
var tabToSend = BrowserApi.makeTabObject(tab);
|
||||
$timeout(() => {
|
||||
BrowserApi.tabSendMessage(tabToSend, {
|
||||
command: '2faPageData',
|
||||
data: {
|
||||
type: 'duo',
|
||||
host: params.Host,
|
||||
signature: params.Signature
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
else {
|
||||
$window.Duo.init({
|
||||
host: params.Host,
|
||||
sig_request: params.Signature,
|
||||
submit_callback: function (theForm) {
|
||||
var sigElement = theForm.querySelector('input[name="sig_response"]');
|
||||
if (sigElement) {
|
||||
$scope.login(sigElement.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if ($scope.providerType === constants.twoFactorProvider.u2f) {
|
||||
var challenges = JSON.parse(params.Challenges);
|
||||
|
||||
u2f.init({
|
||||
appId: challenges[0].appId,
|
||||
challenge: challenges[0].challenge,
|
||||
keys: [{
|
||||
version: challenges[0].version,
|
||||
keyHandle: challenges[0].keyHandle
|
||||
}]
|
||||
});
|
||||
}
|
||||
else if ($scope.providerType === constants.twoFactorProvider.email) {
|
||||
$scope.twoFactorEmail = params.Email;
|
||||
|
||||
if (!platformUtilsService.isSafari() && BrowserApi.isPopupOpen() &&
|
||||
!popupUtilsService.inSidebar($window) && !popupUtilsService.inTab($window) &&
|
||||
!popupUtilsService.inPopout($window)) {
|
||||
SweetAlert.swal({
|
||||
title: i18nService.twoStepLogin,
|
||||
text: i18nService.popup2faCloseMessage,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: i18nService.yes,
|
||||
cancelButtonText: i18nService.no
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
BrowserApi.createNewTab('/popup/index.html?uilocation=tab#!/login', true);
|
||||
return;
|
||||
}
|
||||
else if (providers.size > 1) {
|
||||
$scope.sendEmail(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (providers.size > 1) {
|
||||
$scope.sendEmail(false);
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
});
|
@ -1,2 +0,0 @@
|
||||
angular
|
||||
.module('bit.accounts', ['toastr', 'ngAnimate']);
|
@ -1,64 +0,0 @@
|
||||
angular
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller(
|
||||
'accountsRegisterController',
|
||||
function ($scope, $state, cryptoService, toastr, $q, apiService, popupUtilsService, $analytics,
|
||||
i18nService, $timeout) {
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
document.getElementById('email').focus();
|
||||
}, 500);
|
||||
|
||||
$scope.i18n = i18nService;
|
||||
$scope.model = {};
|
||||
$scope.submitPromise = null;
|
||||
$scope.submit = function (model) {
|
||||
if (!model.email) {
|
||||
toastr.error(i18nService.emailRequired, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
if (model.email.indexOf('@') === -1) {
|
||||
toastr.error(i18nService.invalidEmail, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
if (!model.masterPassword) {
|
||||
toastr.error(i18nService.masterPassRequired, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
if (model.masterPassword.length < 8) {
|
||||
toastr.error(i18nService.masterPassLength, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
if (model.masterPassword !== model.masterPasswordRetype) {
|
||||
toastr.error(i18nService.masterPassDoesntMatch, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
var email = model.email.toLowerCase();
|
||||
var key = cryptoService.makeKey(model.masterPassword, email);
|
||||
$scope.submitPromise = registerPromise(key, model.masterPassword, email, model.hint);
|
||||
$scope.submitPromise.then(function () {
|
||||
$analytics.eventTrack('Registered');
|
||||
toastr.success(i18nService.newAccountCreated);
|
||||
$state.go('login', { email: email, animation: 'in-slide-left' });
|
||||
});
|
||||
};
|
||||
|
||||
function registerPromise(key, masterPassword, email, hint) {
|
||||
var deferred = $q.defer();
|
||||
var encKey;
|
||||
cryptoService.makeEncKey(key).then(function (theEncKey) {
|
||||
encKey = theEncKey;
|
||||
return cryptoService.hashPassword(masterPassword, key);
|
||||
}).then(function (hashedPassword) {
|
||||
var request = new RegisterRequest(email, hashedPassword, hint, encKey.encryptedString);
|
||||
apiService.postRegister(request).then(function () {
|
||||
deferred.resolve();
|
||||
}, function (error) {
|
||||
deferred.reject(error);
|
||||
});
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
angular
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsTwoFactorMethodsController', function ($scope, $state, $stateParams, constantsService,
|
||||
utilsService, i18nService, $analytics, platformUtilsService, authService, $window) {
|
||||
$scope.i18n = i18nService;
|
||||
|
||||
var constants = constantsService;
|
||||
var providers = authService.twoFactorProviders;
|
||||
var provider = $stateParams.provider;
|
||||
|
||||
$scope.providers = [];
|
||||
|
||||
if (providers.has(constants.twoFactorProvider.organizationDuo)) {
|
||||
add(constants.twoFactorProvider.organizationDuo);
|
||||
}
|
||||
if (providers.has(constants.twoFactorProvider.authenticator)) {
|
||||
add(constants.twoFactorProvider.authenticator);
|
||||
}
|
||||
if (providers.has(constants.twoFactorProvider.yubikey)) {
|
||||
add(constants.twoFactorProvider.yubikey);
|
||||
}
|
||||
if (providers.has(constants.twoFactorProvider.email)) {
|
||||
add(constants.twoFactorProvider.email);
|
||||
}
|
||||
if (providers.has(constants.twoFactorProvider.duo)) {
|
||||
add(constants.twoFactorProvider.duo);
|
||||
}
|
||||
if (providers.has(constants.twoFactorProvider.u2f) && platformUtilsService.supportsU2f($window)) {
|
||||
add(constants.twoFactorProvider.u2f);
|
||||
}
|
||||
|
||||
$scope.choose = function (p) {
|
||||
$state.go('twoFactor', {
|
||||
animation: 'out-slide-down',
|
||||
provider: p.type
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$state.go('twoFactor', {
|
||||
animation: 'out-slide-down',
|
||||
provider: provider
|
||||
});
|
||||
};
|
||||
|
||||
$scope.recover = function () {
|
||||
$analytics.eventTrack('Selected Recover');
|
||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/lost-two-step-device/');
|
||||
};
|
||||
|
||||
function add(type) {
|
||||
for (var i = 0; i < constants.twoFactorProviderInfo.length; i++) {
|
||||
if (constants.twoFactorProviderInfo[i].type === type) {
|
||||
$scope.providers.push(constants.twoFactorProviderInfo[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,28 +0,0 @@
|
||||
<form name="theForm" ng-submit="submit(model)" bit-form="submitPromise">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="login({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.submit}}</button>
|
||||
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||
</div>
|
||||
<div class="title">{{i18n.passwordHint}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-envelope fa-lg fa-fw"></i>
|
||||
<label for="email" class="sr-only">{{i18n.emailAddress}}</label>
|
||||
<input id="email" type="text" name="Email" placeholder="{{i18n.emailAddress}}" ng-model="model.email">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{i18n.enterEmailToGetHint}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,34 +0,0 @@
|
||||
<form name="theForm" ng-submit="login(model)" bit-form="loginPromise">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="home({animation: 'out-slide-down'})">{{i18n.cancel}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.login}}</button>
|
||||
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||
</div>
|
||||
<div class="title">{{i18n.appName}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-envelope fa-lg fa-fw"></i>
|
||||
<label for="email" class="sr-only">{{i18n.emailAddress}}</label>
|
||||
<input id="email" type="text" name="Email" placeholder="{{i18n.emailAddress}}" ng-model="model.email">
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="master-password" class="sr-only">{{i18n.masterPass}}</label>
|
||||
<input id="master-password" type="password" name="MasterPassword" placeholder="{{i18n.masterPass}}"
|
||||
ng-model="model.masterPassword">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-accent">
|
||||
<a ui-sref="hint({animation: 'in-slide-left'})">{{i18n.getMasterPasswordHint}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
@ -1,170 +0,0 @@
|
||||
<form name="theForm" ng-submit="login(token)" bit-form="loginPromise"
|
||||
ng-if="providerType === constantsProvider.authenticator || providerType === constantsProvider.email">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a href="#" ng-click="back()"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.continue}}</button>
|
||||
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||
</div>
|
||||
<div class="title">{{i18n.verificationCode}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="two-factor-key-page">
|
||||
<p ng-if="providerType === constantsProvider.authenticator">
|
||||
{{i18n.enterVerificationCodeApp}}
|
||||
</p>
|
||||
<p ng-if="providerType === constantsProvider.email">
|
||||
{{i18n.enterVerificationCodeEmail}} <b>{{twoFactorEmail}}</b>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="code" class="sr-only">{{i18n.verificationCode}}</label>
|
||||
<input id="code" type="text" name="Code" placeholder="{{i18n.verificationCode}}" ng-model="token"
|
||||
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="remember">{{i18n.rememberMe}}</label>
|
||||
<input id="remember" name="Remember" type="checkbox" ng-model="remember.checked">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-accent" ng-if="providerType === constantsProvider.email">
|
||||
<a href="#" stop-click ng-click="sendEmail(true)">{{i18n.sendVerificationCodeEmailAgain}}</a>
|
||||
</p>
|
||||
<p class="text-center text-accent">
|
||||
<a href="#" stop-click ng-click="anotherMethod()">{{i18n.useAnotherTwoStepMethod}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="theForm" bit-form="loginPromise" ng-if="providerType === constantsProvider.duo ||
|
||||
providerType === constantsProvider.organizationDuo" autocomplete="off">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="login({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
|
||||
</div>
|
||||
<div class="title">
|
||||
Duo
|
||||
<span ng-if="providerType === constantsProvider.organizationDuo">({{i18n.organization}})</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div id="duoFrameWrapper" ng-if="!showNewWindowMessage">
|
||||
<iframe id="duo_iframe"></iframe>
|
||||
</div>
|
||||
<div ng-if="showNewWindowMessage" class="two-factor-key-page">
|
||||
<p>{{i18n.twoStepNewWindowMessage}}</p>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="remember">{{i18n.rememberMe}}</label>
|
||||
<input id="remember" name="Remember" type="checkbox" ng-model="remember.checked">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-accent text-center">
|
||||
<a href="#" stop-click ng-click="anotherMethod()">{{i18n.useAnotherTwoStepMethod}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="theForm" ng-submit="login(token)" bit-form="loginPromise" ng-if="providerType === constantsProvider.yubikey"
|
||||
autocomplete="off">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="login({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.continue}}</button>
|
||||
<i class="fa fa-spinner fa-lg fa-spin" ng-show="theForm.$loading"></i>
|
||||
</div>
|
||||
<div class="title">YubiKey</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="two-factor-key-page">
|
||||
<p>{{i18n.insertYubiKey}}</p>
|
||||
<img src="../../../images/yubikey.jpg" alt="" class="img-rounded img-responsive" />
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="code" class="sr-only">{{i18n.verificationCode}}</label>
|
||||
<input id="code" type="password" name="Code" ng-model="token">
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="remember">{{i18n.rememberMe}}</label>
|
||||
<input id="remember" name="Remember" type="checkbox" ng-model="remember.checked">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-accent">
|
||||
<a href="#" stop-click ng-click="anotherMethod()">{{i18n.useAnotherTwoStepMethod}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="theForm" bit-form="loginPromise" ng-if="providerType === constantsProvider.u2f" autocomplete="off">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="login({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<i class="fa fa-spinner fa-lg fa-spin" ng-show="theForm.$loading"></i>
|
||||
</div>
|
||||
<div class="title">FIDO U2F</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="two-factor-key-page">
|
||||
<iframe id="u2f_iframe" class="hide"></iframe>
|
||||
<p ng-if="!u2fReady">Loading...</p>
|
||||
<div ng-if="u2fReady">
|
||||
<p>{{i18n.insertU2f}}</p>
|
||||
<img src="../../../images/u2fkey.jpg" alt="" class="img-rounded img-responsive" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="remember">{{i18n.rememberMe}}</label>
|
||||
<input id="remember" name="Remember" type="checkbox" ng-model="remember.checked">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-accent text-center">
|
||||
<a href="#" stop-click ng-click="anotherMethod()">{{i18n.useAnotherTwoStepMethod}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div ng-if="providerType === null">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="login({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
|
||||
</div>
|
||||
<div class="title">{{i18n.loginUnavailable}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="two-factor-key-page">
|
||||
<p>{{i18n.noTwoStepProviders}}</p>
|
||||
<p>{{i18n.noTwoStepProviders2}}</p>
|
||||
</div>
|
||||
<p class="text-accent text-center">
|
||||
<a href="#" stop-click ng-click="anotherMethod()">{{i18n.useAnotherTwoStepMethod}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
@ -1,53 +0,0 @@
|
||||
<form name="theForm" ng-submit="submit(model)" bit-form="submitPromise">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="home({animation: 'out-slide-down'})">{{i18n.cancel}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.submit}}</button>
|
||||
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||
</div>
|
||||
<div class="title">{{i18n.createAccount}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-envelope fa-lg fa-fw"></i>
|
||||
<label for="email" class="sr-only">{{i18n.emailAddress}}</label>
|
||||
<input id="email" type="text" name="Email" placeholder="{{i18n.emailAddress}}" ng-model="model.email">
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="master-password" class="sr-only">{{i18n.masterPass}}</label>
|
||||
<input id="master-password" type="password" name="MasterPassword"
|
||||
placeholder="{{i18n.masterPass}}" ng-model="model.masterPassword">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{i18n.masterPassDesc}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="master-password-retype" class="sr-only">{{i18n.reTypeMasterPass}}</label>
|
||||
<input id="master-password-retype" type="password" name="MasterPasswordRetype"
|
||||
placeholder="{{i18n.reTypeMasterPass}}" ng-model="model.masterPasswordRetype">
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lightbulb-o fa-lg fa-fw"></i>
|
||||
<label for="hint" class="sr-only">{{i18n.masterPassHint}}</label>
|
||||
<input id="hint" type="text" name="Hint" placeholder="{{i18n.masterPassHint}}"
|
||||
ng-model="model.hint">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{i18n.masterPassHintDesc}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,25 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a href="#" ng-click="cancel()" stop-click>{{i18n.cancel}}</a>
|
||||
</div>
|
||||
<div class="title">{{i18n.twoStepOptions}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item wrap" href="#" stop-click ng-click="choose(provider)"
|
||||
ng-repeat="provider in providers | orderBy: 'displayOrder'">
|
||||
<span class="text">{{provider.name}}</span>
|
||||
<span class="detail">{{provider.description}}</span>
|
||||
</a>
|
||||
<a class="list-section-item wrap" href="#" stop-click ng-click="recover()">
|
||||
<span class="text">{{i18n.recoveryCodeTitle}}</span>
|
||||
<span class="detail">
|
||||
{{i18n.recoveryCodeDesc}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
1
src/popup-old/app/app.d.ts
vendored
1
src/popup-old/app/app.d.ts
vendored
@ -1 +0,0 @@
|
||||
declare module '*.html';
|
@ -1,224 +0,0 @@
|
||||
require('clipboard');
|
||||
require('angular');
|
||||
|
||||
require('angular-animate');
|
||||
const uiRouter = require('@uirouter/angularjs').default;
|
||||
require('angular-toastr');
|
||||
|
||||
require('ngclipboard');
|
||||
|
||||
require('sweetalert');
|
||||
require('angular-sweetalert');
|
||||
require('angulartics');
|
||||
require('angulartics-google-analytics');
|
||||
require('ng-infinite-scroll');
|
||||
|
||||
require('../../scripts/duo.js');
|
||||
|
||||
require('../less/libs.less');
|
||||
require('../less/popup.less');
|
||||
|
||||
import DirectivesModule from './directives/directives.module';
|
||||
import ComponentsModule from './components/components.module';
|
||||
import ToolsModule from './tools/tools.module';
|
||||
import ServicesModule from './services/services.module';
|
||||
import LockModule from './lock/lock.module';
|
||||
import CurrentModule from './current/current.module';
|
||||
import GlobalModule from './global/global.module';
|
||||
import SettingsModule from './settings/settings.module';
|
||||
|
||||
import { BrowserApi } from '../../browser/browserApi';
|
||||
window.BrowserApi = BrowserApi;
|
||||
import { U2f } from '../../scripts/u2f';
|
||||
window.U2f = U2f;
|
||||
|
||||
import { Analytics } from '../../../jslib/src/misc/analytics';
|
||||
|
||||
if (BrowserApi.getBackgroundPage()) {
|
||||
new Analytics(window, () => BrowserApi.gaFilter(), null, null, null, () => {
|
||||
const bgPage = BrowserApi.getBackgroundPage();
|
||||
if (!bgPage || !bgPage.bitwardenMain) {
|
||||
throw 'Cannot resolve background page main.';
|
||||
}
|
||||
return bgPage.bitwardenMain;
|
||||
});
|
||||
}
|
||||
|
||||
// Model imports
|
||||
import { Attachment } from '../../../jslib/src/models/domain/attachment';
|
||||
import { Card } from '../../../jslib/src/models/domain/card';
|
||||
import { Cipher } from '../../../jslib/src/models/domain/cipher';
|
||||
import { CipherString } from '../../../jslib/src/models/domain/cipherString';
|
||||
import { Field } from '../../../jslib/src/models/domain/field';
|
||||
import { Folder } from '../../../jslib/src/models/domain/folder';
|
||||
import { Identity } from '../../../jslib/src/models/domain/identity';
|
||||
import { Login } from '../../../jslib/src/models/domain/login';
|
||||
import { SecureNote } from '../../../jslib/src/models/domain/secureNote';
|
||||
window.Attachment = Attachment;
|
||||
window.Card = Card;
|
||||
window.Cipher = Cipher;
|
||||
window.CipherString = CipherString;
|
||||
window.Field = Field;
|
||||
window.Folder = Folder;
|
||||
window.Identity = Identity;
|
||||
window.Login = Login;
|
||||
window.SecureNote = SecureNote;
|
||||
|
||||
import { AttachmentData } from '../../../jslib/src/models/data/attachmentData';
|
||||
import { CardData } from '../../../jslib/src/models/data/cardData';
|
||||
import { CipherData } from '../../../jslib/src/models/data/cipherData';
|
||||
import { CollectionData } from '../../../jslib/src/models/data/collectionData';
|
||||
import { FieldData } from '../../../jslib/src/models/data/fieldData';
|
||||
import { FolderData } from '../../../jslib/src/models/data/folderData';
|
||||
import { IdentityData } from '../../../jslib/src/models/data/identityData';
|
||||
import { LoginData } from '../../../jslib/src/models/data/loginData';
|
||||
import { SecureNoteData } from '../../../jslib/src/models/data/secureNoteData';
|
||||
window.AttachmentData = AttachmentData;
|
||||
window.CardData = CardData;
|
||||
window.CipherData = CipherData;
|
||||
window.CollectionData = CollectionData;
|
||||
window.FieldData = FieldData;
|
||||
window.FolderData = FolderData;
|
||||
window.IdentityData = IdentityData;
|
||||
window.LoginData = LoginData;
|
||||
window.SecureNoteData = SecureNoteData;
|
||||
|
||||
import { CipherRequest } from '../../../jslib/src/models/request/cipherRequest';
|
||||
import { DeviceRequest } from '../../../jslib/src/models/request/deviceRequest';
|
||||
import { DeviceTokenRequest } from '../../../jslib/src/models/request/deviceTokenRequest';
|
||||
import { FolderRequest } from '../../../jslib/src/models/request/folderRequest';
|
||||
import { PasswordHintRequest } from '../../../jslib/src/models/request/passwordHintRequest';
|
||||
import { RegisterRequest } from '../../../jslib/src/models/request/registerRequest';
|
||||
import { TokenRequest } from '../../../jslib/src/models/request/tokenRequest';
|
||||
import { TwoFactorEmailRequest } from '../../../jslib/src/models/request/twoFactorEmailRequest';
|
||||
window.CipherRequest = CipherRequest;
|
||||
window.DeviceRequest = DeviceRequest;
|
||||
window.DeviceTokenRequest = DeviceTokenRequest;
|
||||
window.FolderRequest = FolderRequest;
|
||||
window.PasswordHintRequest = PasswordHintRequest;
|
||||
window.RegisterRequest = RegisterRequest;
|
||||
window.TokenRequest = TokenRequest;
|
||||
window.TwoFactorEmailRequest = TwoFactorEmailRequest;
|
||||
|
||||
import { AttachmentResponse } from '../../../jslib/src/models/response/attachmentResponse';
|
||||
import { CipherResponse } from '../../../jslib/src/models/response/cipherResponse';
|
||||
import { CollectionResponse } from '../../../jslib/src/models/response/collectionResponse';
|
||||
import { DeviceResponse } from '../../../jslib/src/models/response/deviceResponse';
|
||||
import { DomainsResponse } from '../../../jslib/src/models/response/domainsResponse';
|
||||
import { ErrorResponse } from '../../../jslib/src/models/response/errorResponse';
|
||||
import { FolderResponse } from '../../../jslib/src/models/response/folderResponse';
|
||||
import { GlobalDomainResponse } from '../../../jslib/src/models/response/globalDomainResponse';
|
||||
import { IdentityTokenResponse } from '../../../jslib/src/models/response/identityTokenResponse';
|
||||
import { KeysResponse } from '../../../jslib/src/models/response/keysResponse';
|
||||
import { ListResponse } from '../../../jslib/src/models/response/listResponse';
|
||||
import { ProfileOrganizationResponse } from '../../../jslib/src/models/response/profileOrganizationResponse';
|
||||
import { ProfileResponse } from '../../../jslib/src/models/response/profileResponse';
|
||||
import { SyncResponse } from '../../../jslib/src/models/response/syncResponse';
|
||||
window.AttachmentResponse = AttachmentResponse;
|
||||
window.CipherResponse = CipherResponse;
|
||||
window.CollectionResponse = CollectionResponse;
|
||||
window.DeviceResponse = DeviceResponse;
|
||||
window.DomainsResponse = DomainsResponse;
|
||||
window.ErrorResponse = ErrorResponse;
|
||||
window.FolderResponse = FolderResponse;
|
||||
window.GlobalDomainResponse = GlobalDomainResponse;
|
||||
window.IdentityTokenResponse = IdentityTokenResponse;
|
||||
window.KeysResponse = KeysResponse;
|
||||
window.ListResponse = ListResponse;
|
||||
window.ProfileOrganizationResponse = ProfileOrganizationResponse;
|
||||
window.ProfileResponse = ProfileResponse;
|
||||
window.SyncResponse = SyncResponse;
|
||||
|
||||
angular
|
||||
.module('bit', [
|
||||
uiRouter,
|
||||
'ngAnimate',
|
||||
'toastr',
|
||||
'angulartics',
|
||||
'angulartics.google.analytics',
|
||||
|
||||
DirectivesModule,
|
||||
ComponentsModule,
|
||||
ServicesModule,
|
||||
|
||||
GlobalModule,
|
||||
'bit.accounts',
|
||||
CurrentModule,
|
||||
'bit.vault',
|
||||
SettingsModule,
|
||||
ToolsModule,
|
||||
LockModule
|
||||
]);
|
||||
|
||||
require('./config');
|
||||
require('./accounts/accountsModule.js');
|
||||
require('./accounts/accountsLoginController.js');
|
||||
require('./accounts/accountsLoginTwoFactorController.js');
|
||||
require('./accounts/accountsTwoFactorMethodsController.js');
|
||||
require('./accounts/accountsHintController.js');
|
||||
require('./accounts/accountsRegisterController.js');
|
||||
require('./vault/vaultModule.js');
|
||||
require('./vault/vaultController.js');
|
||||
require('./vault/vaultViewGroupingController.js');
|
||||
require('./vault/vaultAddCipherController.js');
|
||||
require('./vault/vaultEditCipherController.js');
|
||||
require('./vault/vaultViewCipherController.js');
|
||||
require('./vault/vaultAttachmentsController.js');
|
||||
|
||||
// $$ngIsClass fix issue with "class constructors must be invoked with |new|" on Firefox ESR
|
||||
// ref: https://github.com/angular/angular.js/issues/14240
|
||||
import { ActionButtonsController } from './components/action-buttons.component';
|
||||
ActionButtonsController.$$ngIsClass = true;
|
||||
import { CipherItemsController } from './components/cipher-items.component';
|
||||
CipherItemsController.$$ngIsClass = true;
|
||||
import { IconController } from './components/icon.component';
|
||||
IconController.$$ngIsClass = true;
|
||||
import { PopOutController } from './components/pop-out.component';
|
||||
PopOutController.$$ngIsClass = true;
|
||||
import { CurrentController } from './current/current.component';
|
||||
CurrentController.$$ngIsClass = true;
|
||||
import { LockController } from './lock/lock.component';
|
||||
LockController.$$ngIsClass = true;
|
||||
import { ExportController } from './tools/export.component';
|
||||
ExportController.$$ngIsClass = true;
|
||||
import { PasswordGeneratorController } from './tools/password-generator.component';
|
||||
PasswordGeneratorController.$$ngIsClass = true;
|
||||
import { PasswordGeneratorHistoryController } from './tools/password-generator-history.component';
|
||||
PasswordGeneratorHistoryController.$$ngIsClass = true;
|
||||
import { ToolsController } from './tools/tools.component';
|
||||
ToolsController.$$ngIsClass = true;
|
||||
import { AddFolderController } from './settings/folders/add-folder.component';
|
||||
AddFolderController.$$ngIsClass = true;
|
||||
import { EditFolderController } from './settings/folders/edit-folder.component';
|
||||
EditFolderController.$$ngIsClass = true;
|
||||
import { FoldersController } from './settings/folders/folders.component';
|
||||
FoldersController.$$ngIsClass = true;
|
||||
import { AboutController } from './settings/about.component';
|
||||
AboutController.$$ngIsClass = true;
|
||||
import { CreditsController } from './settings/credits.component';
|
||||
CreditsController.$$ngIsClass = true;
|
||||
import { EnvironmentController } from './settings/environment.component';
|
||||
EnvironmentController.$$ngIsClass = true;
|
||||
import { OptionsController } from './settings/options.component';
|
||||
OptionsController.$$ngIsClass = true;
|
||||
import { HelpController } from './settings/help.component';
|
||||
HelpController.$$ngIsClass = true;
|
||||
import { PremiumController } from './settings/premium.component';
|
||||
PremiumController.$$ngIsClass = true;
|
||||
import { SettingsController } from './settings/settings.component';
|
||||
SettingsController.$$ngIsClass = true;
|
||||
import { SyncController } from './settings/sync.component';
|
||||
SyncController.$$ngIsClass = true;
|
||||
import { BaseController } from './global/base.controller';
|
||||
BaseController.$$ngIsClass = true;
|
||||
import { MainController } from './global/main.controller';
|
||||
MainController.$$ngIsClass = true;
|
||||
import { PrivateModeController } from './global/private-mode.controller';
|
||||
PrivateModeController.$$ngIsClass = true;
|
||||
import { TabsController } from './global/tabs.controller';
|
||||
TabsController.$$ngIsClass = true;
|
||||
|
||||
// Bootstrap the angular application
|
||||
angular.element(function () {
|
||||
angular.bootstrap(document, ['bit']);
|
||||
});
|
@ -1,60 +0,0 @@
|
||||
<div class="action-buttons">
|
||||
<div ng-if="$ctrl.cipher.type === $ctrl.constants.cipherType.login">
|
||||
<span class="btn-list" stop-prop stop-click title="{{::$ctrl.i18n.launchWebsite}}" ng-click="$ctrl.launch()"
|
||||
ng-if="!$ctrl.showView" ng-class="{disabled: !$ctrl.cipher.login.uri}">
|
||||
<i class="fa fa-lg fa-share-square-o"></i>
|
||||
</span>
|
||||
<span class="btn-list" ng-click="$ctrl.onView($ctrl.cipher)" stop-prop stop-click title="{{::$ctrl.i18n.view}}"
|
||||
ng-if="$ctrl.showView">
|
||||
<i class="fa fa-lg fa-eye"></i>
|
||||
</span>
|
||||
<span class="btn-list" stop-prop stop-click title="{{::$ctrl.i18n.copyUsername}}" ngclipboard
|
||||
ngclipboard-error="$ctrl.clipboardError(e)"
|
||||
ngclipboard-success="$ctrl.clipboardSuccess(e, $ctrl.i18n.username, 'Username')"
|
||||
data-clipboard-text="{{$ctrl.cipher.login.username}}" ng-class="{disabled: !$ctrl.cipher.login.username}">
|
||||
<i class="fa fa-lg fa-user"></i>
|
||||
</span>
|
||||
<span class="btn-list" stop-prop stop-click title="{{::$ctrl.i18n.copyPassword}}" ngclipboard
|
||||
ngclipboard-error="$ctrl.clipboardError(e)"
|
||||
ngclipboard-success="$ctrl.clipboardSuccess(e, $ctrl.i18n.password, 'Password')"
|
||||
data-clipboard-text="{{$ctrl.cipher.login.password}}" ng-class="{disabled: !$ctrl.cipher.login.password}">
|
||||
<i class="fa fa-lg fa-key"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="$ctrl.cipher.type === $ctrl.constants.cipherType.card">
|
||||
<span class="btn-list" ng-click="$ctrl.onView($ctrl.cipher)" stop-prop stop-click title="{{::$ctrl.i18n.view}}"
|
||||
ng-if="$ctrl.showView">
|
||||
<i class="fa fa-lg fa-eye"></i>
|
||||
</span>
|
||||
<span class="btn-list" stop-prop stop-click title="{{::$ctrl.i18n.copyNumber}}" ngclipboard
|
||||
ngclipboard-error="$ctrl.clipboardError(e)"
|
||||
ngclipboard-success="$ctrl.clipboardSuccess(e, $ctrl.i18n.number, 'Card Number')"
|
||||
data-clipboard-text="{{$ctrl.cipher.card.number}}" ng-class="{disabled: !$ctrl.cipher.card.number}">
|
||||
<i class="fa fa-lg fa-hashtag"></i>
|
||||
</span>
|
||||
<span class="btn-list" stop-prop stop-click title="{{::$ctrl.i18n.copySecurityCode}}" ngclipboard
|
||||
ngclipboard-error="$ctrl.clipboardError(e)"
|
||||
ngclipboard-success="$ctrl.clipboardSuccess(e, $ctrl.i18n.securityCode, 'Security Code')"
|
||||
data-clipboard-text="{{$ctrl.cipher.card.code}}" ng-class="{disabled: !$ctrl.cipher.card.code}">
|
||||
<i class="fa fa-lg fa-key"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="$ctrl.cipher.type === $ctrl.constants.cipherType.identity">
|
||||
<span class="btn-list" ng-click="$ctrl.onView($ctrl.cipher)" stop-prop stop-click title="{{::$ctrl.i18n.view}}"
|
||||
ng-if="$ctrl.showView">
|
||||
<i class="fa fa-lg fa-eye"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="$ctrl.cipher.type === $ctrl.constants.cipherType.secureNote">
|
||||
<span class="btn-list" ng-click="$ctrl.onView($ctrl.cipher)" stop-prop stop-click title="{{::$ctrl.i18n.view}}"
|
||||
ng-if="$ctrl.showView">
|
||||
<i class="fa fa-lg fa-eye"></i>
|
||||
</span>
|
||||
<span class="btn-list" stop-prop stop-click title="{{::$ctrl.i18n.copyNote}}" ngclipboard
|
||||
ngclipboard-error="$ctrl.clipboardError(e)"
|
||||
ngclipboard-success="$ctrl.clipboardSuccess(e, $ctrl.i18n.note, 'Note')"
|
||||
data-clipboard-text="{{$ctrl.cipher.notes}}" ng-class="{disabled: !$ctrl.cipher.notes}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
@ -1,57 +0,0 @@
|
||||
import * as template from './action-buttons.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popupUtils.service';
|
||||
|
||||
export class ActionButtonsController implements ng.IController {
|
||||
onView: Function;
|
||||
|
||||
cipher: any;
|
||||
showView: boolean;
|
||||
i18n: any;
|
||||
constants: ConstantsService;
|
||||
|
||||
constructor(private i18nService: any, private $analytics: any, private constantsService: ConstantsService,
|
||||
private toastr: any, private $timeout: ng.ITimeoutService, private $window: ng.IWindowService) {
|
||||
this.i18n = i18nService;
|
||||
this.constants = constantsService;
|
||||
}
|
||||
|
||||
launch() {
|
||||
const self = this;
|
||||
this.$timeout(() => {
|
||||
if (self.cipher.login.uri.startsWith('http://') || self.cipher.login.uri.startsWith('https://')) {
|
||||
self.$analytics.eventTrack('Launched Website From Listing');
|
||||
BrowserApi.createNewTab(self.cipher.login.uri);
|
||||
if (PopupUtilsService.inPopup(self.$window)) {
|
||||
BrowserApi.closePopup(self.$window);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clipboardError(e: any) {
|
||||
this.toastr.info(this.i18nService.browserNotSupportClipboard);
|
||||
}
|
||||
|
||||
clipboardSuccess(e: any, type: string, aType: string) {
|
||||
e.clearSelection();
|
||||
this.$analytics.eventTrack('Copied ' + aType);
|
||||
this.toastr.info(type + this.i18nService.valueCopied);
|
||||
}
|
||||
}
|
||||
|
||||
ActionButtonsController.$inject = ['i18nService', '$analytics', 'constantsService', 'toastr', '$timeout', '$window'];
|
||||
|
||||
export const ActionButtonsComponent = {
|
||||
bindings: {
|
||||
cipher: '<',
|
||||
showView: '<',
|
||||
onView: '&',
|
||||
},
|
||||
controller: ActionButtonsController,
|
||||
template: template,
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
<a href="#" stop-click ng-click="$ctrl.select(cipher)" class="list-section-item condensed"
|
||||
title="{{::$ctrl.selectionTitle}}" ng-repeat="cipher in $ctrl.ciphers track by $index">
|
||||
<action-buttons cipher="cipher" show-view="true" on-view="$ctrl.view(cipher)"></action-buttons>
|
||||
<icon cipher="cipher"></icon>
|
||||
<span class="text">
|
||||
{{cipher.name}}
|
||||
<i class="fa fa-share-alt text-muted" ng-if="::cipher.organizationId" title="{{::$ctrl.i18n.shared}}"></i>
|
||||
<i class="fa fa-paperclip text-muted" ng-if="cipher.attachments" title="{{::$ctrl.i18n.attachments}}"></i>
|
||||
</span>
|
||||
<span class="detail">{{cipher.subTitle}}</span>
|
||||
</a>
|
@ -1,33 +0,0 @@
|
||||
import * as template from './cipher-items.component.html';
|
||||
|
||||
export class CipherItemsController implements ng.IController {
|
||||
onSelected: Function;
|
||||
onView: Function;
|
||||
|
||||
i18n: any;
|
||||
|
||||
constructor(private i18nService: any) {
|
||||
this.i18n = i18nService;
|
||||
}
|
||||
|
||||
view(cipher: any) {
|
||||
return this.onView({ cipher: cipher });
|
||||
}
|
||||
|
||||
select(cipher: any) {
|
||||
return this.onSelected({ cipher: cipher });
|
||||
}
|
||||
}
|
||||
|
||||
CipherItemsController.$inject = ['i18nService'];
|
||||
|
||||
export const CipherItemsComponent = {
|
||||
bindings: {
|
||||
ciphers: '<',
|
||||
selectionTitle: '<',
|
||||
onSelected: '&',
|
||||
onView: '&',
|
||||
},
|
||||
controller: CipherItemsController,
|
||||
template: template,
|
||||
};
|
@ -1,13 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import { ActionButtonsComponent } from './action-buttons.component';
|
||||
import { CipherItemsComponent } from './cipher-items.component';
|
||||
import { IconComponent } from './icon.component';
|
||||
import { PopOutComponent } from './pop-out.component';
|
||||
|
||||
export default angular
|
||||
.module('bit.components', [])
|
||||
.component('cipherItems', CipherItemsComponent)
|
||||
.component('icon', IconComponent)
|
||||
.component('actionButtons', ActionButtonsComponent)
|
||||
.component('popOut', PopOutComponent)
|
||||
.name;
|
@ -1,4 +0,0 @@
|
||||
<div class="icon">
|
||||
<img ng-src="{{$ctrl.image}}" fallback-src="{{$ctrl.fallbackImage}}" ng-if="$ctrl.imageEnabled && $ctrl.image" alt="" />
|
||||
<i class="fa fa-fw fa-lg {{$ctrl.icon}}" ng-if="!$ctrl.imageEnabled || !$ctrl.image"></i>
|
||||
</div>
|
@ -1,90 +0,0 @@
|
||||
import * as template from './icon.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { CipherType } from 'jslib/enums/cipherType';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
|
||||
export class IconController implements ng.IController {
|
||||
cipher: any;
|
||||
icon: string;
|
||||
image: string;
|
||||
fallbackImage: string;
|
||||
imageEnabled: boolean;
|
||||
|
||||
private iconsUrl: string;
|
||||
|
||||
constructor(private stateService: any, private environmentService: EnvironmentService) {
|
||||
this.imageEnabled = stateService.getState('faviconEnabled');
|
||||
|
||||
this.iconsUrl = environmentService.iconsUrl;
|
||||
if (!this.iconsUrl) {
|
||||
if (environmentService.baseUrl) {
|
||||
this.iconsUrl = environmentService.baseUrl + '/icons';
|
||||
} else {
|
||||
this.iconsUrl = 'https://icons.bitwarden.com';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$onChanges() {
|
||||
switch (this.cipher.type) {
|
||||
case CipherType.Login:
|
||||
this.icon = 'fa-globe';
|
||||
this.setLoginIcon();
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
this.icon = 'fa-sticky-note-o';
|
||||
break;
|
||||
case CipherType.Card:
|
||||
this.icon = 'fa-credit-card';
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
this.icon = 'fa-id-card-o';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private setLoginIcon() {
|
||||
if (this.cipher.login.uri) {
|
||||
let hostnameUri = this.cipher.login.uri;
|
||||
let isWebsite = false;
|
||||
|
||||
if (hostnameUri.indexOf('androidapp://') === 0) {
|
||||
this.icon = 'fa-android';
|
||||
this.image = null;
|
||||
} else if (hostnameUri.indexOf('iosapp://') === 0) {
|
||||
this.icon = 'fa-apple';
|
||||
this.image = null;
|
||||
} else if (this.imageEnabled && hostnameUri.indexOf('://') === -1 && hostnameUri.indexOf('.') > -1) {
|
||||
hostnameUri = 'http://' + hostnameUri;
|
||||
isWebsite = true;
|
||||
} else if (this.imageEnabled) {
|
||||
isWebsite = hostnameUri.indexOf('http') === 0 && hostnameUri.indexOf('.') > -1;
|
||||
}
|
||||
|
||||
if (this.imageEnabled && isWebsite) {
|
||||
try {
|
||||
const url = new URL(hostnameUri);
|
||||
this.image = this.iconsUrl + '/' + url.hostname + '/icon.png';
|
||||
this.fallbackImage = BrowserApi.getAssetUrl('images/fa-globe.png');
|
||||
} catch (e) { }
|
||||
}
|
||||
} else {
|
||||
this.image = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IconController.$inject = ['stateService', 'environmentService'];
|
||||
|
||||
export const IconComponent = {
|
||||
bindings: {
|
||||
cipher: '<',
|
||||
},
|
||||
controller: IconController,
|
||||
template: template,
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
<a href="" ng-click="$ctrl.expand()" title="{{::$ctrl.i18n.popOutNewWindow}}">
|
||||
<i class="fa fa-external-link fa-rotate-270 fa-lg"></i>
|
||||
</a>
|
@ -1,68 +0,0 @@
|
||||
import * as template from './pop-out.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popupUtils.service';
|
||||
|
||||
export class PopOutController implements ng.IController {
|
||||
i18n: any;
|
||||
|
||||
constructor(private $analytics: any, private $window: ng.IWindowService,
|
||||
private platformUtilsService: PlatformUtilsService, private i18nService: any) {
|
||||
this.i18n = i18nService;
|
||||
}
|
||||
|
||||
expand() {
|
||||
this.$analytics.eventTrack('Expand Vault');
|
||||
|
||||
let href = this.$window.location.href;
|
||||
if (this.platformUtilsService.isEdge()) {
|
||||
const popupIndex = href.indexOf('/popup/');
|
||||
if (popupIndex > -1) {
|
||||
href = href.substring(popupIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if ((typeof chrome !== 'undefined') && chrome.windows && chrome.windows.create) {
|
||||
if (href.indexOf('?uilocation=') > -1) {
|
||||
href = href.replace('uilocation=popup', 'uilocation=popout')
|
||||
.replace('uilocation=tab', 'uilocation=popout')
|
||||
.replace('uilocation=sidebar', 'uilocation=popout');
|
||||
} else {
|
||||
const hrefParts = href.split('#');
|
||||
href = hrefParts[0] + '?uilocation=popout' + (hrefParts.length > 0 ? '#' + hrefParts[1] : '');
|
||||
}
|
||||
|
||||
const bodyRect = document.querySelector('body').getBoundingClientRect();
|
||||
chrome.windows.create({
|
||||
url: href,
|
||||
type: 'popup',
|
||||
width: bodyRect.width + 60,
|
||||
height: bodyRect.height,
|
||||
});
|
||||
|
||||
if (PopupUtilsService.inPopup(this.$window)) {
|
||||
BrowserApi.closePopup(this.$window);
|
||||
}
|
||||
} else if ((typeof chrome !== 'undefined') && chrome.tabs && chrome.tabs.create) {
|
||||
href = href.replace('uilocation=popup', 'uilocation=tab')
|
||||
.replace('uilocation=popout', 'uilocation=tab')
|
||||
.replace('uilocation=sidebar', 'uilocation=tab');
|
||||
chrome.tabs.create({
|
||||
url: href,
|
||||
});
|
||||
} else if ((typeof safari !== 'undefined')) {
|
||||
// Safari can't open popup in full page tab :(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PopOutController.$inject = ['$analytics', '$window', 'platformUtilsService', 'i18nService'];
|
||||
|
||||
export const PopOutComponent = {
|
||||
bindings: {},
|
||||
controller: PopOutController,
|
||||
template: template,
|
||||
};
|
@ -1,282 +0,0 @@
|
||||
angular
|
||||
.module('bit')
|
||||
|
||||
.config(function ($stateProvider, $urlRouterProvider, $compileProvider, $sceDelegateProvider, toastrConfig) {
|
||||
$compileProvider.imgSrcSanitizationWhitelist(
|
||||
/^\s*((https?|ftp|file|blob):|data:image\/|(moz|chrome|ms-browser)-extension)/);
|
||||
|
||||
angular.extend(toastrConfig, {
|
||||
closeButton: true,
|
||||
progressBar: true,
|
||||
showMethod: 'slideDown',
|
||||
positionClass: 'toast-bottom-center'
|
||||
});
|
||||
|
||||
$urlRouterProvider.otherwise(function ($injector, $location) {
|
||||
var $state = $injector.get('$state');
|
||||
|
||||
if (!BrowserApi.getBackgroundPage()) {
|
||||
$state.go('privateMode');
|
||||
return;
|
||||
}
|
||||
|
||||
var userService = $injector.get('userService');
|
||||
var cryptoService = $injector.get('cryptoService');
|
||||
|
||||
var key;
|
||||
cryptoService.getKey().then(function (theKey) {
|
||||
key = theKey;
|
||||
return userService.isAuthenticated();
|
||||
}).then(function (isAuthenticated) {
|
||||
if (isAuthenticated) {
|
||||
if (!key) {
|
||||
$state.go('lock');
|
||||
}
|
||||
else {
|
||||
$state.go('tabs.current');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$state.go('home');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$stateProvider
|
||||
.state('splash', {
|
||||
url: '/splash',
|
||||
controller: 'baseController',
|
||||
template: require('./global/splash.html'),
|
||||
data: { authorize: false },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('privateMode', {
|
||||
url: '/private-mode',
|
||||
controller: 'privateModeController',
|
||||
template: require('./global/private-mode.html'),
|
||||
data: { authorize: false },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('home', {
|
||||
url: '/home',
|
||||
controller: 'baseController',
|
||||
template: require('./global/home.html'),
|
||||
data: { authorize: false },
|
||||
params: { animation: null }
|
||||
})
|
||||
|
||||
.state('login', {
|
||||
url: '/login',
|
||||
controller: 'accountsLoginController',
|
||||
template: require('./accounts/views/accountsLogin.html'),
|
||||
data: { authorize: false },
|
||||
params: { animation: null, email: null }
|
||||
})
|
||||
.state('hint', {
|
||||
url: '/hint',
|
||||
controller: 'accountsHintController',
|
||||
template: require('./accounts/views/accountsHint.html'),
|
||||
data: { authorize: false },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('twoFactor', {
|
||||
url: '/two-factor',
|
||||
controller: 'accountsLoginTwoFactorController',
|
||||
template: require('./accounts/views/accountsLoginTwoFactor.html'),
|
||||
data: { authorize: false },
|
||||
params: { animation: null, provider: null }
|
||||
})
|
||||
.state('twoFactorMethods', {
|
||||
url: '/two-factor-methods',
|
||||
controller: 'accountsTwoFactorMethodsController',
|
||||
template: require('./accounts/views/accountsTwoFactorMethods.html'),
|
||||
data: { authorize: false },
|
||||
params: { animation: null, provider: null }
|
||||
})
|
||||
.state('register', {
|
||||
url: '/register',
|
||||
controller: 'accountsRegisterController',
|
||||
template: require('./accounts/views/accountsRegister.html'),
|
||||
data: { authorize: false },
|
||||
params: { animation: null }
|
||||
})
|
||||
|
||||
.state('tabs', {
|
||||
url: '/tab',
|
||||
abstract: true,
|
||||
template: require('./global/tabs.html'),
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('tabs.current', {
|
||||
url: '/current',
|
||||
component: 'current'
|
||||
})
|
||||
.state('tabs.vault', {
|
||||
url: '/vault',
|
||||
template: require('./vault/views/vault.html'),
|
||||
controller: 'vaultController',
|
||||
params: { syncOnLoad: false, searchText: null }
|
||||
})
|
||||
.state('tabs.settings', {
|
||||
url: '/settings',
|
||||
component: 'settings',
|
||||
})
|
||||
.state('tabs.tools', {
|
||||
url: '/tools',
|
||||
component: 'tools'
|
||||
})
|
||||
|
||||
.state('viewGrouping', {
|
||||
url: '/view-grouping?folderId&collectionId',
|
||||
template: require('./vault/views/vaultViewGrouping.html'),
|
||||
controller: 'vaultViewGroupingController',
|
||||
data: { authorize: true },
|
||||
params: { animation: null, from: 'vault' }
|
||||
})
|
||||
.state('viewCipher', {
|
||||
url: '/view-cipher?cipherId',
|
||||
template: require('./vault/views/vaultViewCipher.html'),
|
||||
controller: 'vaultViewCipherController',
|
||||
data: { authorize: true },
|
||||
params: { animation: null, from: 'vault' }
|
||||
})
|
||||
.state('addCipher', {
|
||||
url: '/add-cipher',
|
||||
template: require('./vault/views/vaultAddCipher.html'),
|
||||
controller: 'vaultAddCipherController',
|
||||
data: { authorize: true },
|
||||
params: { animation: null, name: null, uri: null, folderId: null, cipher: null, from: 'vault' }
|
||||
})
|
||||
.state('editCipher', {
|
||||
url: '/edit-cipher?cipherId',
|
||||
template: require('./vault/views/vaultEditCipher.html'),
|
||||
controller: 'vaultEditCipherController',
|
||||
data: { authorize: true },
|
||||
params: { animation: null, fromView: true, cipher: null, from: 'vault' }
|
||||
})
|
||||
.state('attachments', {
|
||||
url: '/attachments?id',
|
||||
template: require('./vault/views/vaultAttachments.html'),
|
||||
controller: 'vaultAttachmentsController',
|
||||
data: { authorize: true },
|
||||
params: { animation: null, fromView: true, from: 'vault' }
|
||||
})
|
||||
|
||||
.state('passwordGenerator', {
|
||||
url: '/password-generator',
|
||||
component: 'passwordGenerator',
|
||||
data: { authorize: true },
|
||||
params: { animation: null, addState: null, editState: null }
|
||||
})
|
||||
.state('passwordGeneratorHistory', {
|
||||
url: '/password-generator-history',
|
||||
component: 'passwordGeneratorHistory',
|
||||
data: { authorize: true },
|
||||
params: { animation: null, addState: null, editState: null }
|
||||
})
|
||||
.state('export', {
|
||||
url: '/export',
|
||||
component: 'export',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
|
||||
.state('about', {
|
||||
url: '/about',
|
||||
component: 'about',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('credits', {
|
||||
url: '/credits',
|
||||
component: 'credits',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('options', {
|
||||
url: '/options',
|
||||
component: 'options',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('help', {
|
||||
url: '/help',
|
||||
component: 'help',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('sync', {
|
||||
url: '/sync',
|
||||
component: 'sync',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('premium', {
|
||||
url: '/premium',
|
||||
component: 'premium',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
|
||||
.state('folders', {
|
||||
url: '/folders',
|
||||
abstract: true,
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('folders.list', {
|
||||
url: '',
|
||||
component: 'folders',
|
||||
})
|
||||
.state('folders.add', {
|
||||
url: '/add',
|
||||
component: 'addFolder',
|
||||
})
|
||||
.state('folders.edit', {
|
||||
url: '/{folderId}/edit',
|
||||
component: 'editFolder',
|
||||
})
|
||||
.state('environment', {
|
||||
url: '/environment',
|
||||
component: 'environment',
|
||||
data: { authorize: false },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('lock', {
|
||||
url: '/lock',
|
||||
component: 'lock',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
});
|
||||
})
|
||||
.run(function ($trace, $transitions, userService, $state, constantsService, stateService,
|
||||
storageService, messagingService) {
|
||||
stateService.init();
|
||||
|
||||
$transitions.onStart({}, function (trans) {
|
||||
const $state = trans.router.stateService;
|
||||
const toState = trans.to();
|
||||
|
||||
if ($state.current.name.indexOf('tabs.') > -1 && toState.name.indexOf('tabs.') > -1) {
|
||||
stateService.removeState('vault');
|
||||
stateService.removeState('viewGrouping');
|
||||
}
|
||||
|
||||
const userService = trans.injector().get('userService');
|
||||
|
||||
if (!userService) {
|
||||
return;
|
||||
}
|
||||
|
||||
userService.isAuthenticated().then((isAuthenticated) => {
|
||||
if (isAuthenticated) {
|
||||
storageService.save(constantsService.lastActiveKey, (new Date()).getTime());
|
||||
}
|
||||
else if (toState.data && toState.data.authorize) {
|
||||
event.preventDefault();
|
||||
messagingService.send('logout');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
@ -1,63 +0,0 @@
|
||||
<div class="header header-search">
|
||||
<pop-out ng-if="$ctrl.showPopout" class="left"></pop-out>
|
||||
<div class="left" ng-if="$ctrl.inSidebar">
|
||||
<a href="" ng-click="$ctrl.refresh()" title="{{::$ctrl.i18n.refresh}}"><i class="fa fa-refresh fa-lg"></i></a>
|
||||
</div>
|
||||
<div class="search" ng-style="{'visibility': $ctrl.disableSearch ? 'hidden' : 'visible'}">
|
||||
<input type="search" placeholder="{{$ctrl.i18n.searchVault}}" id="search" ng-model="$ctrl.searchText" ng-change="$ctrl.searchVault()" />
|
||||
<i class="fa fa-search"></i>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a href="" ng-click="$ctrl.addCipher()" title="{{::$ctrl.i18n.addItem}}"><i class="fa fa-plus fa-lg"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content content-tabs">
|
||||
<div class="list" ng-if="$ctrl.loaded">
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.typeLogins}}
|
||||
</div>
|
||||
<div class="list-section-items" ng-class="{'list-no-selection': !$ctrl.loginCiphers.length}">
|
||||
<cipher-items ng-if="$ctrl.loginCiphers.length"
|
||||
ciphers="$ctrl.loginCiphers"
|
||||
on-view="$ctrl.viewCipher(cipher)"
|
||||
on-selected="$ctrl.fillCipher(cipher)"
|
||||
selection-title="$ctrl.i18n.autoFill">
|
||||
</cipher-items>
|
||||
<div class="list-section-item" ng-if="!$ctrl.loginCiphers.length">
|
||||
<p>{{$ctrl.i18n.autoFillInfo}}</p>
|
||||
<button ng-click="$ctrl.addCipher()" class="btn btn-link btn-block">{{$ctrl.i18n.addLogin}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="$ctrl.cardCiphers.length">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.cards}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<cipher-items ng-if="$ctrl.cardCiphers.length"
|
||||
ciphers="$ctrl.cardCiphers"
|
||||
on-view="$ctrl.viewCipher(cipher)"
|
||||
on-selected="$ctrl.fillCipher(cipher)"
|
||||
selection-title="$ctrl.i18n.autoFill">
|
||||
</cipher-items>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="$ctrl.identityCiphers.length">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.identities}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<cipher-items ng-if="$ctrl.identityCiphers.length"
|
||||
ciphers="$ctrl.identityCiphers"
|
||||
on-view="$ctrl.viewCipher(cipher)"
|
||||
on-selected="$ctrl.fillCipher(cipher)"
|
||||
selection-title="$ctrl.i18n.autoFill">
|
||||
</cipher-items>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-loading" ng-if="!$ctrl.loaded">
|
||||
<i class="fa fa-lg fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
@ -1,185 +0,0 @@
|
||||
import * as template from './current.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { CipherType } from 'jslib/enums/cipherType';
|
||||
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { UtilsService } from 'jslib/abstractions/utils.service';
|
||||
|
||||
import { AutofillService } from '../../../services/abstractions/autofill.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popupUtils.service';
|
||||
|
||||
export class CurrentController {
|
||||
i18n: any;
|
||||
pageDetails: any = [];
|
||||
loaded: boolean = false;
|
||||
cardCiphers: any = [];
|
||||
identityCiphers: any = [];
|
||||
loginCiphers: any = [];
|
||||
url: string;
|
||||
domain: string;
|
||||
canAutofill: boolean = false;
|
||||
searchText: string = null;
|
||||
inSidebar: boolean = false;
|
||||
showPopout: boolean = true;
|
||||
disableSearch: boolean = false;
|
||||
|
||||
constructor($scope: any, private cipherService: CipherService, private platformUtilsService: PlatformUtilsService,
|
||||
private utilsService: UtilsService, private toastr: any, private $window: ng.IWindowService,
|
||||
private $state: any, private $timeout: ng.ITimeoutService, private autofillService: AutofillService,
|
||||
private $analytics: any, private i18nService: any, private $filter: ng.IFilterService) {
|
||||
this.i18n = i18nService;
|
||||
this.inSidebar = PopupUtilsService.inSidebar($window);
|
||||
this.showPopout = !this.inSidebar && !platformUtilsService.isSafari();
|
||||
this.disableSearch = platformUtilsService.isEdge();
|
||||
|
||||
$scope.$on('syncCompleted', (event: any, successfully: boolean) => {
|
||||
if (this.loaded) {
|
||||
$timeout(this.loadVault.bind(this), 500);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('collectPageDetailsResponse', (event: any, details: any) => {
|
||||
this.pageDetails.push(details);
|
||||
});
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.$timeout(() => {
|
||||
document.getElementById('search').focus();
|
||||
}, 50);
|
||||
|
||||
this.loadVault();
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
await this.loadVault();
|
||||
}
|
||||
|
||||
addCipher() {
|
||||
this.$state.go('addCipher', {
|
||||
animation: 'in-slide-up',
|
||||
name: this.domain,
|
||||
uri: this.url,
|
||||
from: 'current',
|
||||
});
|
||||
}
|
||||
|
||||
viewCipher(cipher: any) {
|
||||
this.$state.go('viewCipher', {
|
||||
cipherId: cipher.id,
|
||||
animation: 'in-slide-up',
|
||||
from: 'current',
|
||||
});
|
||||
}
|
||||
|
||||
fillCipher(cipher: any) {
|
||||
if (!this.canAutofill) {
|
||||
this.$analytics.eventTrack('Autofilled Error');
|
||||
this.toastr.error(this.i18nService.autofillError);
|
||||
}
|
||||
|
||||
this.autofillService.doAutoFill({
|
||||
cipher: cipher,
|
||||
pageDetails: this.pageDetails,
|
||||
fromBackground: false,
|
||||
doc: this.$window.document,
|
||||
}).then((totpCode: string) => {
|
||||
this.$analytics.eventTrack('Autofilled');
|
||||
if (totpCode && this.platformUtilsService.isFirefox()) {
|
||||
this.utilsService.copyToClipboard(totpCode, document);
|
||||
}
|
||||
if (PopupUtilsService.inPopup(this.$window)) {
|
||||
BrowserApi.closePopup(this.$window);
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$analytics.eventTrack('Autofilled Error');
|
||||
this.toastr.error(this.i18nService.autofillError);
|
||||
});
|
||||
}
|
||||
|
||||
searchVault() {
|
||||
this.$state.go('tabs.vault', {
|
||||
searchText: this.searchText,
|
||||
});
|
||||
}
|
||||
|
||||
private async loadVault() {
|
||||
const tab = await BrowserApi.getTabFromCurrentWindow();
|
||||
if (tab) {
|
||||
this.url = tab.url;
|
||||
} else {
|
||||
this.$timeout(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.domain = this.platformUtilsService.getDomain(this.url);
|
||||
|
||||
BrowserApi.tabSendMessage(tab, {
|
||||
command: 'collectPageDetails',
|
||||
tab: tab,
|
||||
sender: 'currentController',
|
||||
}).then(() => {
|
||||
this.canAutofill = true;
|
||||
});
|
||||
|
||||
const otherTypes = [
|
||||
CipherType.Card,
|
||||
CipherType.Identity,
|
||||
];
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(this.url, otherTypes);
|
||||
const loginCiphers: any = [];
|
||||
const cardCiphers: any = [];
|
||||
const identityCiphers: any = [];
|
||||
|
||||
const sortedCiphers = this.$filter('orderBy')(ciphers,
|
||||
[this.sortUriMatch, this.sortLastUsed, 'name', 'subTitle']);
|
||||
|
||||
sortedCiphers.forEach((cipher: any) => {
|
||||
switch (cipher.type) {
|
||||
case CipherType.Login:
|
||||
loginCiphers.push(cipher);
|
||||
break;
|
||||
case CipherType.Card:
|
||||
cardCiphers.push(cipher);
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
identityCiphers.push(cipher);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.$timeout(() => {
|
||||
this.loginCiphers = loginCiphers;
|
||||
this.cardCiphers = cardCiphers;
|
||||
this.identityCiphers = identityCiphers;
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
private sortUriMatch(cipher: any) {
|
||||
// exact matches should sort earlier.
|
||||
return cipher.login && cipher.login.uri && this.url && this.url.startsWith(cipher.login.uri) ? 0 : 1;
|
||||
}
|
||||
|
||||
private sortLastUsed(cipher: any) {
|
||||
return cipher.localData && cipher.localData.lastUsedDate ? -1 * cipher.localData.lastUsedDate : 0;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentController.$inject = ['$scope', 'cipherService', 'platformUtilsService', 'utilsService', 'toastr', '$window',
|
||||
'$state', '$timeout', 'autofillService', '$analytics', 'i18nService', '$filter'];
|
||||
|
||||
export const CurrentComponent = {
|
||||
bindings: {},
|
||||
controller: CurrentController,
|
||||
template: template,
|
||||
};
|
@ -1,9 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import { CurrentComponent } from './current.component';
|
||||
|
||||
export default angular
|
||||
.module('bit.current', ['toastr', 'ngclipboard'])
|
||||
|
||||
.component('current', CurrentComponent)
|
||||
|
||||
.name;
|
@ -1,30 +0,0 @@
|
||||
import { ValidationService } from '../services/validation.service';
|
||||
|
||||
export function BitFormDirective($rootScope: ng.IRootScopeService, validationService: ValidationService) {
|
||||
return {
|
||||
require: 'form',
|
||||
restrict: 'A',
|
||||
link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, formCtrl: ng.IFormController) => {
|
||||
const watchPromise = attrs.bitForm || null;
|
||||
if (watchPromise) {
|
||||
scope.$watch(watchPromise, formSubmitted.bind(null, formCtrl, scope));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function formSubmitted(form: any, scope: ng.IScope, promise: any) {
|
||||
if (!promise || !promise.then) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start loading
|
||||
form.$loading = true;
|
||||
|
||||
promise.then((response: any) => {
|
||||
form.$loading = false;
|
||||
}, (reason: any) => {
|
||||
form.$loading = false;
|
||||
validationService.showError(reason);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
|
||||
import { BitFormDirective } from './bit-form.directive';
|
||||
import { FallbackSrcDirective } from './fallback-src.directive';
|
||||
import { StopClickDirective } from './stop-click.directive';
|
||||
import { StopPropDirective } from './stop-prop.directive';
|
||||
|
||||
export default angular
|
||||
.module('bit.directives', [])
|
||||
|
||||
.directive('fallbackSrc', FallbackSrcDirective)
|
||||
.directive('stopClick', StopClickDirective)
|
||||
.directive('stopProp', StopPropDirective)
|
||||
.directive('bitForm', BitFormDirective)
|
||||
|
||||
.name;
|
@ -1,7 +0,0 @@
|
||||
export function FallbackSrcDirective() {
|
||||
return (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => {
|
||||
element[0].addEventListener('error', (e: any) => {
|
||||
e.target.src = attrs.fallbackSrc;
|
||||
});
|
||||
};
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
export function StopClickDirective() {
|
||||
// ref: https://stackoverflow.com/a/14165848/1090359
|
||||
return (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => {
|
||||
element[0].addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
};
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
export function StopPropDirective() {
|
||||
return (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => {
|
||||
element[0].addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
};
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
export class BaseController implements ng.IController {
|
||||
constructor($scope: any, i18nService: any) {
|
||||
$scope.i18n = i18nService;
|
||||
}
|
||||
}
|
||||
|
||||
BaseController.$inject = ['$scope', 'i18nService'];
|
@ -1 +0,0 @@
|
||||
<div ui-view></div>
|
@ -1,15 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import { BaseController } from './base.controller';
|
||||
import { MainController } from './main.controller';
|
||||
import { PrivateModeController } from './private-mode.controller';
|
||||
import { TabsController } from './tabs.controller';
|
||||
|
||||
export default angular
|
||||
.module('bit.global', ['ngAnimate'])
|
||||
|
||||
.controller('mainController', MainController)
|
||||
.controller('baseController', BaseController)
|
||||
.controller('tabsController', TabsController)
|
||||
.controller('privateModeController', PrivateModeController)
|
||||
|
||||
.name;
|
@ -1,17 +0,0 @@
|
||||
<div class="home-page">
|
||||
<a ui-sref="environment({animation: 'in-slide-up'})" class="settings-icon">
|
||||
<i class="fa fa-cog fa-lg"></i><span> {{i18n.settings}}</span>
|
||||
</a>
|
||||
<img src="../../images/logo@2x.png" alt="bitwarden" />
|
||||
<p>{{i18n.loginOrCreateNewAccount}}</p>
|
||||
<div class="bottom-buttons">
|
||||
<a class="btn btn-lg btn-primary btn-block" ui-sref="register({animation: 'in-slide-up'})"
|
||||
analytics-on="click" analytics-event="Clicked Create Account">
|
||||
<b>{{i18n.createAccount}}</b>
|
||||
</a>
|
||||
<a class="btn btn-lg btn-link btn-block" ui-sref="login({animation: 'in-slide-up'})"
|
||||
analytics-on="click" analytics-event="Clicked Log In">
|
||||
{{i18n.login}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
@ -1,61 +0,0 @@
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||
import { UtilsService } from 'jslib/abstractions/utils.service';
|
||||
|
||||
export class MainController implements ng.IController {
|
||||
smBody: boolean;
|
||||
xsBody: boolean;
|
||||
animation: string;
|
||||
|
||||
constructor($scope: any, $transitions: any, $state: any, authService: AuthService, toastr: any,
|
||||
i18nService: any, $analytics: any, utilsService: UtilsService, $window: ng.IWindowService) {
|
||||
this.animation = '';
|
||||
this.xsBody = $window.screen.availHeight < 600;
|
||||
this.smBody = !this.xsBody && $window.screen.availHeight <= 800;
|
||||
|
||||
$transitions.onSuccess({}, (transition: any) => {
|
||||
const toParams = transition.params('to');
|
||||
|
||||
if (toParams.animation) {
|
||||
this.animation = toParams.animation;
|
||||
} else {
|
||||
this.animation = '';
|
||||
}
|
||||
});
|
||||
|
||||
$window.bitwardenPopupMainMessageListener = (msg: any, sender: any, sendResponse: any) => {
|
||||
if (msg.command === 'syncCompleted') {
|
||||
$scope.$broadcast('syncCompleted', msg.successfully);
|
||||
} else if (msg.command === 'syncStarted') {
|
||||
$scope.$broadcast('syncStarted');
|
||||
} else if (msg.command === 'doneLoggingOut') {
|
||||
authService.logOut(() => {
|
||||
$analytics.eventTrack('Logged Out');
|
||||
if (msg.expired) {
|
||||
toastr.warning(i18nService.loginExpired, i18nService.loggedOut);
|
||||
}
|
||||
$state.go('home');
|
||||
});
|
||||
} else if (msg.command === 'collectPageDetailsResponse' &&
|
||||
msg.sender === 'currentController') {
|
||||
$scope.$broadcast('collectPageDetailsResponse', {
|
||||
frameId: sender.frameId,
|
||||
tab: msg.tab,
|
||||
details: msg.details,
|
||||
});
|
||||
} else if (msg.command === '2faPageResponse') {
|
||||
$scope.$broadcast('2faPageResponse', {
|
||||
type: msg.type,
|
||||
data: msg.data,
|
||||
tab: sender.tab,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
BrowserApi.messageListener($window.bitwardenPopupMainMessageListener);
|
||||
}
|
||||
}
|
||||
|
||||
MainController.$inject = ['$scope', '$transitions', '$state', 'authService', 'toastr', 'i18nService', '$analytics',
|
||||
'utilsService', '$window'];
|
@ -1,13 +0,0 @@
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
export class PrivateModeController implements ng.IController {
|
||||
constructor($scope: any) {
|
||||
$scope.privateModeMessage = chrome.i18n.getMessage('privateModeMessage');
|
||||
$scope.learnMoreMessage = chrome.i18n.getMessage('learnMore');
|
||||
$scope.learnMore = () => {
|
||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/extension-wont-load-in-private-mode/');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
PrivateModeController.$inject = ['$scope'];
|
@ -1,6 +0,0 @@
|
||||
<div class="content text-center">
|
||||
<p>{{privateModeMessage}}</p>
|
||||
<button type="button" class="btn btn-lg btn-link btn-block" ng-click="learnMore()">
|
||||
{{learnMoreMessage}}
|
||||
</button>
|
||||
</div>
|
@ -1,5 +0,0 @@
|
||||
<div class="content">
|
||||
<div class="splash-page">
|
||||
<img src="../../images/logo@3x.png" alt="bitwarden" />
|
||||
</div>
|
||||
</div>
|
@ -1,8 +0,0 @@
|
||||
export class TabsController implements ng.IController {
|
||||
constructor($scope: any, $state: any, i18nService: any) {
|
||||
$scope.$state = $state;
|
||||
$scope.i18n = i18nService;
|
||||
}
|
||||
}
|
||||
|
||||
TabsController.$inject = ['$scope', '$state', 'i18nService'];
|
@ -1,17 +0,0 @@
|
||||
<div ui-view></div>
|
||||
<div class="tabs" ng-controller="tabsController">
|
||||
<ul>
|
||||
<li ng-class="{active: $state.includes('tabs.current')}">
|
||||
<a ui-sref="tabs.current"><i class="fa fa-folder fa-2x"></i>{{i18n.tab}}</a>
|
||||
</li>
|
||||
<li ng-class="{active: $state.includes('tabs.vault')}">
|
||||
<a ui-sref="tabs.vault"><i class="fa fa-lock fa-2x"></i>{{i18n.myVault}}</a>
|
||||
</li>
|
||||
<li ng-class="{active: $state.includes('tabs.tools')}">
|
||||
<a ui-sref="tabs.tools"><i class="fa fa-wrench fa-2x"></i>{{i18n.tools}}</a>
|
||||
</li>
|
||||
<li ng-class="{active: $state.includes('tabs.settings')}">
|
||||
<a ui-sref="tabs.settings"><i class="fa fa-cogs fa-2x"></i>{{i18n.settings}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
@ -1,25 +0,0 @@
|
||||
<form name="theForm" ng-submit="$ctrl.submit()">
|
||||
<div class="header">
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link">{{$ctrl.i18n.submit}}</button>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.verifyMasterPassword}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="master-password" class="sr-only">{{$ctrl.i18n.masterPass}}</label>
|
||||
<input id="master-password" type="password" name="MasterPassword"
|
||||
placeholder="{{$ctrl.i18n.masterPass}}" ng-model="$ctrl.masterPassword">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-accent">
|
||||
<a ng-click="$ctrl.logOut()" href="">{{$ctrl.i18n.logOut}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
@ -1,69 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import * as template from './lock.component.html';
|
||||
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popupUtils.service';
|
||||
|
||||
export class LockController {
|
||||
i18n: any;
|
||||
masterPassword: string;
|
||||
|
||||
constructor(public $state: any, public i18nService: any, private $timeout: ng.ITimeoutService,
|
||||
public cryptoService: CryptoService, public toastr: any, public userService: UserService,
|
||||
public messagingService: MessagingService, public SweetAlert: any) {
|
||||
this.i18n = i18nService;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.$timeout(() => {
|
||||
PopupUtilsService.initListSectionItemListeners(document, angular);
|
||||
document.getElementById('master-password').focus();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
logOut() {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.logOut,
|
||||
text: this.i18nService.logOutConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.cancel,
|
||||
}, (confirmed: boolean) => {
|
||||
if (confirmed) {
|
||||
this.messagingService.send('logout');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
this.toastr.error(this.i18nService.invalidMasterPassword, this.i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
const email = await this.userService.getEmail();
|
||||
const key = this.cryptoService.makeKey(this.masterPassword, email);
|
||||
const keyHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||
|
||||
if (storedKeyHash != null && keyHash != null && storedKeyHash === keyHash) {
|
||||
await this.cryptoService.setKey(key);
|
||||
this.messagingService.send('unlocked');
|
||||
this.$state.go('tabs.current');
|
||||
} else {
|
||||
this.toastr.error(this.i18nService.invalidMasterPassword, this.i18nService.errorsOccurred);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LockController.$inject = ['$state', 'i18nService', '$timeout', 'cryptoService', 'toastr', 'userService',
|
||||
'messagingService', 'SweetAlert'];
|
||||
|
||||
export const LockComponent = {
|
||||
bindings: {},
|
||||
controller: LockController,
|
||||
template: template,
|
||||
};
|
@ -1,9 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import { LockComponent } from './lock.component';
|
||||
|
||||
export default angular
|
||||
.module('bit.lock', ['ngAnimate', 'toastr'])
|
||||
|
||||
.component('lock', LockComponent)
|
||||
|
||||
.name;
|
@ -1,55 +0,0 @@
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { AppIdService } from 'jslib/abstractions/appId.service';
|
||||
import { AuditService } from 'jslib/abstractions/audit.service';
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { LockService } from 'jslib/abstractions/lock.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { SettingsService } from 'jslib/abstractions/settings.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
import { TokenService } from 'jslib/abstractions/token.service';
|
||||
import { TotpService } from 'jslib/abstractions/totp.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { UtilsService } from 'jslib/abstractions/utils.service';
|
||||
|
||||
import { AutofillService } from '../../../services/abstractions/autofill.service';
|
||||
|
||||
function getBackgroundService<T>(service: string) {
|
||||
return (): T => {
|
||||
const page = BrowserApi.getBackgroundPage();
|
||||
return page ? page.bitwardenMain[service] as T : null;
|
||||
};
|
||||
}
|
||||
|
||||
export const storageService = getBackgroundService<StorageService>('storageService');
|
||||
export const tokenService = getBackgroundService<TokenService>('tokenService');
|
||||
export const cryptoService = getBackgroundService<CryptoService>('cryptoService');
|
||||
export const userService = getBackgroundService<UserService>('userService');
|
||||
export const apiService = getBackgroundService<ApiService>('apiService');
|
||||
export const folderService = getBackgroundService<FolderService>('folderService');
|
||||
export const cipherService = getBackgroundService<CipherService>('cipherService');
|
||||
export const syncService = getBackgroundService<SyncService>('syncService');
|
||||
export const autofillService = getBackgroundService<AutofillService>('autofillService');
|
||||
export const passwordGenerationService = getBackgroundService<PasswordGenerationService>('passwordGenerationService');
|
||||
export const platformUtilsService = getBackgroundService<PlatformUtilsService>('platformUtilsService');
|
||||
export const utilsService = getBackgroundService<UtilsService>('utilsService');
|
||||
export const appIdService = getBackgroundService<AppIdService>('appIdService');
|
||||
export const i18nService = getBackgroundService<any>('i18nService');
|
||||
export const i18n2Service = getBackgroundService<I18nService>('i18n2Service');
|
||||
export const constantsService = getBackgroundService<ConstantsService>('constantsService');
|
||||
export const settingsService = getBackgroundService<SettingsService>('settingsService');
|
||||
export const lockService = getBackgroundService<LockService>('lockService');
|
||||
export const totpService = getBackgroundService<TotpService>('totpService');
|
||||
export const environmentService = getBackgroundService<EnvironmentService>('environmentService');
|
||||
export const collectionService = getBackgroundService<CollectionService>('collectionService');
|
||||
export const auditService = getBackgroundService<CollectionService>('auditService');
|
@ -1,120 +0,0 @@
|
||||
export class PopupUtilsService {
|
||||
static initListSectionItemListeners(doc: Document, angular: any): void {
|
||||
if (!doc) {
|
||||
throw new Error('doc parameter required');
|
||||
}
|
||||
|
||||
const sectionItems = doc.querySelectorAll(
|
||||
'.list-section-item:not([data-bw-events="1"])');
|
||||
const sectionFormItems = doc.querySelectorAll(
|
||||
'.list-section-item:not([data-bw-events="1"]) input, ' +
|
||||
'.list-section-item:not([data-bw-events="1"]) select, ' +
|
||||
'.list-section-item:not([data-bw-events="1"]) textarea');
|
||||
|
||||
sectionItems.forEach((item) => {
|
||||
(item as HTMLElement).dataset.bwEvents = '1';
|
||||
|
||||
item.addEventListener('click', (e) => {
|
||||
if (e.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = e.target as HTMLElement;
|
||||
|
||||
// Some elements will already focus properly
|
||||
if (el.tagName != null) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'label': case 'input': case 'textarea': case 'select':
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const cell = el.closest('.list-section-item');
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textFilter = 'input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"])';
|
||||
const text = cell.querySelectorAll(textFilter + ', textarea');
|
||||
const checkbox = cell.querySelectorAll('input[type="checkbox"]');
|
||||
const select = cell.querySelectorAll('select');
|
||||
|
||||
if (text.length > 0) {
|
||||
(text[0] as HTMLElement).focus();
|
||||
} else if (select.length > 0) {
|
||||
(select[0] as HTMLElement).focus();
|
||||
} else if (checkbox.length > 0) {
|
||||
const cb = checkbox[0] as HTMLInputElement;
|
||||
cb.checked = !cb.checked;
|
||||
if (angular) {
|
||||
angular.element(checkbox[0]).triggerHandler('click');
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
});
|
||||
|
||||
sectionFormItems.forEach((item) => {
|
||||
const itemCell = item.closest('.list-section-item');
|
||||
(itemCell as HTMLElement).dataset.bwEvents = '1';
|
||||
|
||||
item.addEventListener('focus', (e: Event) => {
|
||||
const el = e.target as HTMLElement;
|
||||
const cell = el.closest('.list-section-item');
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
|
||||
cell.classList.add('active');
|
||||
}, false);
|
||||
|
||||
item.addEventListener('blur', (e: Event) => {
|
||||
const el = e.target as HTMLElement;
|
||||
const cell = el.closest('.list-section-item');
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
|
||||
cell.classList.remove('active');
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
static inSidebar(theWindow: Window): boolean {
|
||||
return theWindow.location.search !== '' && theWindow.location.search.indexOf('uilocation=sidebar') > -1;
|
||||
}
|
||||
|
||||
static inTab(theWindow: Window): boolean {
|
||||
return theWindow.location.search !== '' && theWindow.location.search.indexOf('uilocation=tab') > -1;
|
||||
}
|
||||
|
||||
static inPopout(theWindow: Window): boolean {
|
||||
return theWindow.location.search !== '' && theWindow.location.search.indexOf('uilocation=popout') > -1;
|
||||
}
|
||||
|
||||
static inPopup(theWindow: Window): boolean {
|
||||
return theWindow.location.search === '' || theWindow.location.search.indexOf('uilocation=') === -1 ||
|
||||
theWindow.location.search.indexOf('uilocation=popup') > -1;
|
||||
}
|
||||
|
||||
initListSectionItemListeners(doc: Document, angular: any): void {
|
||||
PopupUtilsService.initListSectionItemListeners(doc, angular);
|
||||
}
|
||||
|
||||
inSidebar(theWindow: Window): boolean {
|
||||
return PopupUtilsService.inSidebar(theWindow);
|
||||
}
|
||||
|
||||
inTab(theWindow: Window): boolean {
|
||||
return PopupUtilsService.inTab(theWindow);
|
||||
}
|
||||
|
||||
inPopout(theWindow: Window): boolean {
|
||||
return PopupUtilsService.inPopout(theWindow);
|
||||
}
|
||||
|
||||
inPopup(theWindow: Window): boolean {
|
||||
return PopupUtilsService.inPopup(theWindow);
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import * as backgroundServices from './background.service';
|
||||
import { PopupUtilsService } from './popupUtils.service';
|
||||
import { StateService } from './state.service';
|
||||
import { ValidationService } from './validation.service';
|
||||
|
||||
import { AuthService } from 'jslib/services/auth.service';
|
||||
|
||||
import BrowserMessagingService from '../../../services/browserMessaging.service';
|
||||
|
||||
const messagingService = new BrowserMessagingService();
|
||||
const authService = new AuthService(backgroundServices.cryptoService(), backgroundServices.apiService(),
|
||||
backgroundServices.userService(), backgroundServices.tokenService(), backgroundServices.appIdService(),
|
||||
backgroundServices.i18n2Service(), backgroundServices.platformUtilsService(),
|
||||
backgroundServices.constantsService(), messagingService);
|
||||
|
||||
if (backgroundServices.i18n2Service()) {
|
||||
authService.init();
|
||||
}
|
||||
|
||||
export default angular
|
||||
.module('bit.services', ['toastr'])
|
||||
.service('stateService', StateService)
|
||||
.service('validationService', ValidationService)
|
||||
.service('popupUtilsService', PopupUtilsService)
|
||||
|
||||
.factory('authService', () => authService)
|
||||
.factory('messagingService', () => messagingService)
|
||||
.factory('storageService', backgroundServices.storageService)
|
||||
.factory('tokenService', backgroundServices.tokenService)
|
||||
.factory('cryptoService', backgroundServices.cryptoService)
|
||||
.factory('userService', backgroundServices.userService)
|
||||
.factory('apiService', backgroundServices.apiService)
|
||||
.factory('folderService', backgroundServices.folderService)
|
||||
.factory('cipherService', backgroundServices.cipherService)
|
||||
.factory('syncService', backgroundServices.syncService)
|
||||
.factory('autofillService', backgroundServices.autofillService)
|
||||
.factory('passwordGenerationService', backgroundServices.passwordGenerationService)
|
||||
.factory('platformUtilsService', backgroundServices.platformUtilsService)
|
||||
.factory('utilsService', backgroundServices.utilsService)
|
||||
.factory('appIdService', backgroundServices.appIdService)
|
||||
.factory('i18nService', backgroundServices.i18nService)
|
||||
.factory('constantsService', backgroundServices.constantsService)
|
||||
.factory('settingsService', backgroundServices.settingsService)
|
||||
.factory('lockService', backgroundServices.lockService)
|
||||
.factory('totpService', backgroundServices.totpService)
|
||||
.factory('environmentService', backgroundServices.environmentService)
|
||||
.factory('collectionService', backgroundServices.collectionService)
|
||||
.factory('auditService', backgroundServices.auditService)
|
||||
|
||||
.name;
|
@ -1,39 +0,0 @@
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
|
||||
export class StateService {
|
||||
private state: any = {};
|
||||
|
||||
constructor(private storageService: StorageService, private constantsService: ConstantsService) {
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.storageService != null) {
|
||||
const iconsDisabled = await this.storageService.get<boolean>(this.constantsService.disableFaviconKey);
|
||||
this.saveState('faviconEnabled', !iconsDisabled);
|
||||
}
|
||||
}
|
||||
|
||||
saveState(key: string, data: any) {
|
||||
this.state[key] = data;
|
||||
}
|
||||
|
||||
getState(key: string): any {
|
||||
if (key in this.state) {
|
||||
return this.state[key];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
removeState(key: string) {
|
||||
delete this.state[key];
|
||||
}
|
||||
|
||||
purgeState() {
|
||||
this.state = {};
|
||||
}
|
||||
}
|
||||
|
||||
StateService.$inject = ['storageService', 'constantsService'];
|
@ -1,36 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
|
||||
export class ValidationService {
|
||||
constructor(private toastr: any, private i18nService: any) {
|
||||
}
|
||||
|
||||
showError(data: any) {
|
||||
const defaultErrorMessage = this.i18nService.unexpectedError;
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!data || !angular.isObject(data)) {
|
||||
errors.push(defaultErrorMessage);
|
||||
} else if (!data.validationErrors) {
|
||||
errors.push(data.message ? data.message : defaultErrorMessage);
|
||||
} else {
|
||||
for (const key in data.validationErrors) {
|
||||
if (!data.validationErrors.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
data.validationErrors[key].forEach((item: string) => {
|
||||
errors.push(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
this.toastr.error(errors[0], this.i18nService.errorsOccurred);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ValidationService.$inject = ['toastr', 'i18nService'];
|
@ -1,23 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.about}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="about-page">
|
||||
<img src="../../images/logo@3x.png" alt="bitwarden" />
|
||||
{{$ctrl.i18n.version}} {{$ctrl.version}}<br />
|
||||
© 8bit Solutions LLC 2015-{{$ctrl.year}}
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item" ui-sref="credits({animation: 'in-slide-left'})">
|
||||
{{$ctrl.i18n.credits}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,23 +0,0 @@
|
||||
import * as template from './about.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
export class AboutController {
|
||||
version: string;
|
||||
year: number;
|
||||
i18n: any;
|
||||
|
||||
constructor(private i18nService: any) {
|
||||
this.i18n = i18nService;
|
||||
this.year = (new Date()).getFullYear();
|
||||
this.version = BrowserApi.getApplicationVersion();
|
||||
}
|
||||
}
|
||||
|
||||
AboutController.$inject = ['i18nService'];
|
||||
|
||||
export const AboutComponent = {
|
||||
bindings: {},
|
||||
controller: AboutController,
|
||||
template: template,
|
||||
};
|
@ -1,34 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="about({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.thankYou}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.translations}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<b>@sersoftin</b> - Russian<br />
|
||||
<b>@lbel</b> - Dutch<br />
|
||||
<b>@KarimGeiger</b> - German<br />
|
||||
<b>@Primokorn</b> - French<br />
|
||||
<b>@felixqu</b> - Chinese Simplified<br />
|
||||
<b>@thomassth</b> - Chinese Traditional<br />
|
||||
<b>@Igetin</b> - Finnish<br />
|
||||
<b>@LivingWithHippos</b> - Italian<br />
|
||||
<b>@King-Tut-Tut</b> - Swedish<br />
|
||||
<b>@majod</b> - Slovak<br />
|
||||
<b>@RixzZ</b> - Spanish<br />
|
||||
<b>@SW1FT</b> - Portuguese
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.contribute}} <a href="" ng-click="$ctrl.learnMore()">{{$ctrl.i18n.learnMore}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,24 +0,0 @@
|
||||
import * as template from './credits.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
export class CreditsController {
|
||||
i18n: any;
|
||||
|
||||
constructor(private i18nService: any, private $analytics: any) {
|
||||
this.i18n = i18nService;
|
||||
}
|
||||
|
||||
learnMore() {
|
||||
this.$analytics.eventTrack('Contribute Learn More');
|
||||
BrowserApi.createNewTab('https://github.com/bitwarden/browser/blob/master/CONTRIBUTING.md');
|
||||
}
|
||||
}
|
||||
|
||||
CreditsController.$inject = ['i18nService', '$analytics'];
|
||||
|
||||
export const CreditsComponent = {
|
||||
bindings: {},
|
||||
controller: CreditsController,
|
||||
template: template,
|
||||
};
|
@ -1,56 +0,0 @@
|
||||
<form name="theForm" ng-submit="$ctrl.save()">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="home({animation: 'out-slide-down'})">{{$ctrl.i18n.close}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link">{{$ctrl.i18n.save}}</button>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.settings}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.selfHostedEnvironment}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="baseUrl" class="item-label">{{$ctrl.i18n.baseUrl}}</label>
|
||||
<input id="baseUrl" type="text" name="BaseUrl" ng-model="$ctrl.baseUrl"
|
||||
placeholder="ex. https://bitwarden.company.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.selfHostedEnvironmentFooter}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.customEnvironment}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="webVaultUrl" class="item-label">{{$ctrl.i18n.webVaultUrl}}</label>
|
||||
<input id="webVaultUrl" type="text" name="WebVaultUrl" ng-model="$ctrl.webVaultUrl">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="apiUrl" class="item-label">{{$ctrl.i18n.apiUrl}}</label>
|
||||
<input id="apiUrl" type="text" name="ApiUrl" ng-model="$ctrl.apiUrl">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityUrl" class="item-label">{{$ctrl.i18n.identityUrl}}</label>
|
||||
<input id="identityUrl" type="text" name="IdentityUrl" ng-model="$ctrl.identityUrl">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="iconsUrl" class="item-label">{{$ctrl.i18n.iconsUrl}}</label>
|
||||
<input id="iconsUrl" type="text" name="IconsUrl" ng-model="$ctrl.iconsUrl">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.customEnvironmentFooter}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,60 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import * as template from './environment.component.html';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popupUtils.service';
|
||||
|
||||
export class EnvironmentController {
|
||||
iconsUrl: string;
|
||||
identityUrl: string;
|
||||
apiUrl: string;
|
||||
webVaultUrl: string;
|
||||
baseUrl: string;
|
||||
i18n: any;
|
||||
|
||||
constructor(private i18nService: any, private $analytics: any, private environmentService: EnvironmentService,
|
||||
private toastr: any, private $timeout: ng.ITimeoutService) {
|
||||
this.i18n = i18nService;
|
||||
|
||||
$timeout(() => {
|
||||
PopupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
|
||||
this.baseUrl = environmentService.baseUrl || '';
|
||||
this.webVaultUrl = environmentService.webVaultUrl || '';
|
||||
this.apiUrl = environmentService.apiUrl || '';
|
||||
this.identityUrl = environmentService.identityUrl || '';
|
||||
this.iconsUrl = environmentService.iconsUrl || '';
|
||||
}
|
||||
|
||||
save() {
|
||||
this.environmentService.setUrls({
|
||||
base: this.baseUrl,
|
||||
api: this.apiUrl,
|
||||
identity: this.identityUrl,
|
||||
webVault: this.webVaultUrl,
|
||||
icons: this.iconsUrl,
|
||||
}).then((resUrls: any) => {
|
||||
this.$timeout(() => {
|
||||
// re-set urls since service can change them, ex: prefixing https://
|
||||
this.baseUrl = resUrls.base;
|
||||
this.apiUrl = resUrls.api;
|
||||
this.identityUrl = resUrls.identity;
|
||||
this.webVaultUrl = resUrls.webVault;
|
||||
this.iconsUrl = resUrls.icons;
|
||||
|
||||
this.$analytics.eventTrack('Set Environment URLs');
|
||||
this.toastr.success(this.i18nService.environmentSaved);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
EnvironmentController.$inject = ['i18nService', '$analytics', 'environmentService', 'toastr', '$timeout'];
|
||||
|
||||
export const EnvironmentComponent = {
|
||||
bindings: {},
|
||||
controller: EnvironmentController,
|
||||
template: template,
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
<form name="theForm" ng-submit="$ctrl.save($ctrl.folder)" bit-form="$ctrl.savePromise" autocomplete="off">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="^.list({animation: 'out-slide-down'})">{{$ctrl.i18n.cancel}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{$ctrl.i18n.save}}</button>
|
||||
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.addFolder}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="name" class="item-label">{{$ctrl.i18n.name}}</label>
|
||||
<input id="name" type="text" name="Name" ng-model="$ctrl.folder.name">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,49 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import * as template from './add-folder.component.html';
|
||||
|
||||
import { Folder } from 'jslib/models/domain/folder';
|
||||
|
||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||
|
||||
import { PopupUtilsService } from '../../services/popupUtils.service';
|
||||
|
||||
export class AddFolderController {
|
||||
savePromise: any;
|
||||
folder: {};
|
||||
i18n: any;
|
||||
|
||||
constructor(private folderService: FolderService, private $state: any, private toastr: any,
|
||||
private $analytics: any, private i18nService: any, $timeout: ng.ITimeoutService) {
|
||||
$timeout(() => {
|
||||
PopupUtilsService.initListSectionItemListeners(document, angular);
|
||||
document.getElementById('name').focus();
|
||||
}, 500);
|
||||
|
||||
this.i18n = i18nService;
|
||||
this.folder = {};
|
||||
this.savePromise = null;
|
||||
}
|
||||
|
||||
save(model: any) {
|
||||
if (!model.name) {
|
||||
this.toastr.error(this.i18nService.nameRequired, this.i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
this.savePromise = this.folderService.encrypt(model).then((folder: Folder) => {
|
||||
return this.folderService.saveWithServer(folder);
|
||||
}).then(() => {
|
||||
this.$analytics.eventTrack('Added Folder');
|
||||
this.toastr.success(this.i18nService.addedFolder);
|
||||
this.$state.go('^.list', { animation: 'out-slide-down' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AddFolderController.$inject = ['folderService', '$state', 'toastr', '$analytics', 'i18nService', '$timeout'];
|
||||
|
||||
export const AddFolderComponent = {
|
||||
bindings: {},
|
||||
controller: AddFolderController,
|
||||
template: template,
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
<form name="theForm" ng-submit="$ctrl.save($ctrl.folder)" bit-form="savePromise" autocomplete="off">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="^.list({animation: 'out-slide-down'})">{{$ctrl.i18n.cancel}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{$ctrl.i18n.save}}</button>
|
||||
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.editFolder}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="name" class="item-label">{{$ctrl.i18n.name}}</label>
|
||||
<input id="name" type="text" name="Name" ng-model="$ctrl.folder.name">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<a href="" ng-click="$ctrl.delete()" class="list-section-item text-danger">
|
||||
<i class="fa fa-trash fa-fw fa-lg"></i>{{$ctrl.i18n.deleteFolder}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,84 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import * as template from './edit-folder.component.html';
|
||||
|
||||
import { Folder } from 'jslib/models/domain/folder';
|
||||
|
||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||
|
||||
import { PopupUtilsService } from '../../services/popupUtils.service';
|
||||
|
||||
export class EditFolderController {
|
||||
$transition$: any;
|
||||
folderId: string;
|
||||
savePromise: Promise<any> = null;
|
||||
i18n: any;
|
||||
folder: Folder;
|
||||
|
||||
constructor($scope: any, $stateParams: any, private folderService: FolderService, private toastr: any,
|
||||
private $state: any, private SweetAlert: any, private $analytics: any, private i18nService: any,
|
||||
$timeout: ng.ITimeoutService) {
|
||||
this.i18n = i18nService;
|
||||
|
||||
$timeout(() => {
|
||||
PopupUtilsService.initListSectionItemListeners(document, angular);
|
||||
document.getElementById('name').focus();
|
||||
}, 500);
|
||||
|
||||
$scope.folder = {};
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.folderId = this.$transition$.params('to').folderId;
|
||||
this.folderService.get(this.folderId).then((folder: any) => {
|
||||
return folder.decrypt();
|
||||
}).then((model: Folder) => {
|
||||
this.folder = model;
|
||||
});
|
||||
}
|
||||
|
||||
save(model: any) {
|
||||
if (!model.name) {
|
||||
this.toastr.error(this.i18nService.nameRequired, this.i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
this.savePromise = this.folderService.encrypt(model).then((folder: Folder) => {
|
||||
return this.folderService.saveWithServer(folder);
|
||||
}).then(() => {
|
||||
this.$analytics.eventTrack('Edited Folder');
|
||||
this.toastr.success(this.i18nService.editedFolder);
|
||||
this.$state.go('^.list', { animation: 'out-slide-down' });
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.deleteFolder,
|
||||
text: this.i18nService.deleteFolderConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.no,
|
||||
}, (confirmed: boolean) => {
|
||||
if (confirmed) {
|
||||
this.folderService.deleteWithServer(this.folderId).then(() => {
|
||||
this.$analytics.eventTrack('Deleted Folder');
|
||||
this.toastr.success(this.i18nService.deletedFolder);
|
||||
this.$state.go('^.list', {
|
||||
animation: 'out-slide-down',
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
EditFolderController.$inject = ['$scope', '$stateParams', 'folderService', 'toastr', '$state', 'SweetAlert',
|
||||
'$analytics', 'i18nService', '$timeout'];
|
||||
|
||||
export const EditFolderComponent = {
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
},
|
||||
controller: EditFolderController,
|
||||
template: template,
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a ui-sref="^.add({animation: 'in-slide-up'})" title="{{::$ctrl.i18n.addFolder}}"><i class="fa fa-plus fa-lg"></i></a>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.folders}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div ng-if="$ctrl.folders.length">
|
||||
<div class="list">
|
||||
<div class="list-grouped">
|
||||
<a href="" ng-click="$ctrl.editFolder(folder)" class="list-grouped-item" title="{{::$ctrl.i18n.edit}}"
|
||||
ng-repeat="folder in $ctrl.folders track by $index">
|
||||
<span class="text">{{folder.name}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="centered-message" ng-if="$ctrl.loaded && !$ctrl.folders.length">
|
||||
<p>
|
||||
{{$ctrl.i18n.noFolders}}
|
||||
<a ui-sref="^.add({animation: 'in-slide-up'})" style="margin-top: 20px;"
|
||||
class="btn btn-link btn-block">{{$ctrl.i18n.addFolder}}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="page-loading" ng-if="!$ctrl.loaded">
|
||||
<i class="fa fa-lg fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
@ -1,44 +0,0 @@
|
||||
import * as template from './folders.component.html';
|
||||
|
||||
import { Folder } from 'jslib/models/domain/folder';
|
||||
|
||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||
|
||||
export class FoldersController {
|
||||
folders: Folder[] = [];
|
||||
i18n: any;
|
||||
loaded = false;
|
||||
|
||||
constructor(private folderService: FolderService, private $state: any, i18nService: any) {
|
||||
this.i18n = i18nService;
|
||||
this.load();
|
||||
}
|
||||
|
||||
load() {
|
||||
this.folderService.getAllDecrypted().then((folders: any) => {
|
||||
if (folders.length > 0 && folders[folders.length - 1].id === null) {
|
||||
// remove the "none" folder
|
||||
this.folders = folders.slice(0, folders.length - 1);
|
||||
} else {
|
||||
this.folders = folders;
|
||||
}
|
||||
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
editFolder(folder: any) {
|
||||
this.$state.go('^.edit', {
|
||||
folderId: folder.id,
|
||||
animation: 'in-slide-up',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
FoldersController.$inject = ['folderService', '$state', 'i18nService'];
|
||||
|
||||
export const FoldersComponent = {
|
||||
bindings: {},
|
||||
controller: FoldersController,
|
||||
template: template,
|
||||
};
|
@ -1,54 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.helpFeedback}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.email()">
|
||||
{{$ctrl.i18n.emailUs}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.emailUsDirectly}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.website()">
|
||||
{{$ctrl.i18n.visitOurWebsite}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.visitOurWebsiteDirectly}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.tutorial()">
|
||||
{{$ctrl.i18n.gettingStartedTutorial}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.gettingStartedTutorialVideo}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.bug()">
|
||||
{{$ctrl.i18n.fileBugReport}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.gitHubIssue}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,39 +0,0 @@
|
||||
import * as template from './help.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
export class HelpController {
|
||||
i18n: any;
|
||||
|
||||
constructor(private i18nService: any, private $analytics: any) {
|
||||
this.i18n = i18nService;
|
||||
}
|
||||
|
||||
email() {
|
||||
this.$analytics.eventTrack('Selected Help Email');
|
||||
BrowserApi.createNewTab('mailto:hello@bitwarden.com');
|
||||
}
|
||||
|
||||
website() {
|
||||
this.$analytics.eventTrack('Selected Help Website');
|
||||
BrowserApi.createNewTab('https://bitwarden.com/contact/');
|
||||
}
|
||||
|
||||
tutorial() {
|
||||
this.$analytics.eventTrack('Selected Help Tutorial');
|
||||
BrowserApi.createNewTab('https://bitwarden.com/browser-start/');
|
||||
}
|
||||
|
||||
bug() {
|
||||
this.$analytics.eventTrack('Selected Help Bug Report');
|
||||
BrowserApi.createNewTab('https://github.com/bitwarden/browser');
|
||||
}
|
||||
}
|
||||
|
||||
HelpController.$inject = ['i18nService', '$analytics'];
|
||||
|
||||
export const HelpComponent = {
|
||||
bindings: {},
|
||||
controller: HelpController,
|
||||
template: template,
|
||||
};
|
@ -1,82 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.options}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="auto-fill">{{$ctrl.i18n.enableAutoFillOnPageLoad}}</label>
|
||||
<input id="auto-fill" type="checkbox" ng-model="$ctrl.enableAutoFillOnPageLoad"
|
||||
ng-change="$ctrl.updateAutoFillOnPageLoad()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.enableAutoFillOnPageLoadDesc}}
|
||||
<b>{{$ctrl.i18n.warning}}</b>: {{$ctrl.i18n.experimentalFeature}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="totp-copy">{{$ctrl.i18n.disableAutoTotpCopy}}</label>
|
||||
<input id="totp-copy" type="checkbox" ng-model="$ctrl.disableAutoTotpCopy"
|
||||
ng-change="$ctrl.updateAutoTotpCopy()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.disableAutoTotpCopyDesc}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="ga">{{$ctrl.i18n.disableGa}}</label>
|
||||
<input id="ga" type="checkbox" ng-model="$ctrl.disableGa" ng-change="$ctrl.updateGa()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.gaDesc}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="notification-bar">{{$ctrl.i18n.disableAddLoginNotification}}</label>
|
||||
<input id="notification-bar" type="checkbox" ng-model="$ctrl.disableAddLoginNotification"
|
||||
ng-change="$ctrl.updateAddLoginNotification()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.addLoginNotificationDesc}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="$ctrl.showDisableContextMenu">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="context-menu">{{$ctrl.i18n.disableContextMenuItem}}</label>
|
||||
<input id="context-menu" type="checkbox" ng-model="$ctrl.disableContextMenuItem"
|
||||
ng-change="$ctrl.updateDisableContextMenuItem()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.disableContextMenuItemDesc}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="favicon">{{$ctrl.i18n.disableFavicon}}</label>
|
||||
<input id="favicon" type="checkbox" ng-model="$ctrl.disableFavicon"
|
||||
ng-change="$ctrl.updateDisableFavicon()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.disableFaviconDesc}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,107 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import * as template from './options.component.html';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
import { TotpService } from 'jslib/abstractions/totp.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popupUtils.service';
|
||||
import { StateService } from '../services/state.service';
|
||||
|
||||
export class OptionsController {
|
||||
disableFavicon = false;
|
||||
enableAutoFillOnPageLoad = false;
|
||||
disableAutoTotpCopy = false;
|
||||
disableContextMenuItem = false;
|
||||
disableAddLoginNotification = false;
|
||||
showDisableContextMenu = true;
|
||||
disableGa = false;
|
||||
i18n: any;
|
||||
|
||||
constructor(private i18nService: any, private $analytics: any, private constantsService: ConstantsService,
|
||||
private platformUtilsService: PlatformUtilsService, private totpService: TotpService,
|
||||
private stateService: StateService, private storageService: StorageService,
|
||||
public messagingService: MessagingService, private $timeout: ng.ITimeoutService) {
|
||||
this.i18n = i18nService;
|
||||
this.showDisableContextMenu = !platformUtilsService.isSafari();
|
||||
|
||||
$timeout(() => {
|
||||
PopupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
|
||||
this.loadSettings();
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.enableAutoFillOnPageLoad = await this.storageService.get<boolean>(
|
||||
this.constantsService.enableAutoFillOnPageLoadKey);
|
||||
|
||||
const disableGa = await this.storageService.get<boolean>(
|
||||
this.constantsService.disableGaKey);
|
||||
this.disableGa = disableGa || (this.platformUtilsService.isFirefox() && disableGa == null);
|
||||
|
||||
this.disableAddLoginNotification = await this.storageService.get<boolean>(
|
||||
this.constantsService.disableAddLoginNotificationKey);
|
||||
|
||||
this.disableContextMenuItem = await this.storageService.get<boolean>(
|
||||
this.constantsService.disableContextMenuItemKey);
|
||||
|
||||
this.disableAutoTotpCopy = !await this.totpService.isAutoCopyEnabled();
|
||||
|
||||
this.disableFavicon = await this.storageService.get<boolean>(
|
||||
this.constantsService.disableFaviconKey);
|
||||
}
|
||||
|
||||
callAnalytics(name: string, enabled: boolean) {
|
||||
const status = enabled ? 'Enabled' : 'Disabled';
|
||||
this.$analytics.eventTrack(`${status} ${name}`);
|
||||
}
|
||||
|
||||
updateGa() {
|
||||
this.storageService.save(this.constantsService.disableGaKey, this.disableGa);
|
||||
this.callAnalytics('Analytics', !this.disableGa);
|
||||
}
|
||||
|
||||
updateAddLoginNotification() {
|
||||
this.storageService.save(this.constantsService.disableAddLoginNotificationKey,
|
||||
this.disableAddLoginNotification);
|
||||
this.callAnalytics('Add Login Notification', !this.disableAddLoginNotification);
|
||||
}
|
||||
|
||||
updateDisableContextMenuItem() {
|
||||
this.storageService.save(this.constantsService.disableContextMenuItemKey,
|
||||
this.disableContextMenuItem).then(() => {
|
||||
this.messagingService.send('bgUpdateContextMenu');
|
||||
});
|
||||
this.callAnalytics('Context Menu Item', !this.disableContextMenuItem);
|
||||
}
|
||||
|
||||
updateAutoTotpCopy() {
|
||||
this.storageService.save(this.constantsService.disableAutoTotpCopyKey, this.disableAutoTotpCopy);
|
||||
this.callAnalytics('Auto Copy TOTP', !this.disableAutoTotpCopy);
|
||||
}
|
||||
|
||||
updateAutoFillOnPageLoad() {
|
||||
this.storageService.save(this.constantsService.enableAutoFillOnPageLoadKey,
|
||||
this.enableAutoFillOnPageLoad);
|
||||
this.callAnalytics('Auto-fill Page Load', this.enableAutoFillOnPageLoad);
|
||||
}
|
||||
|
||||
updateDisableFavicon() {
|
||||
this.storageService.save(this.constantsService.disableFaviconKey, this.disableFavicon);
|
||||
this.stateService.saveState('faviconEnabled', !this.disableFavicon);
|
||||
this.callAnalytics('Favicon', !this.disableFavicon);
|
||||
}
|
||||
}
|
||||
|
||||
OptionsController.$inject = ['i18nService', '$analytics', 'constantsService', 'platformUtilsService', 'totpService',
|
||||
'stateService', 'storageService', 'messagingService', '$timeout'];
|
||||
|
||||
export const OptionsComponent = {
|
||||
bindings: {},
|
||||
controller: OptionsController,
|
||||
template: template,
|
||||
};
|
@ -1,54 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.premiumMembership}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="premium-page">
|
||||
<div ng-if="!$ctrl.isPremium">
|
||||
<p class="text-center lead">{{$ctrl.i18n.premiumNotCurrentMember}}</p>
|
||||
<p>{{$ctrl.i18n.premiumSignUpAndGet}}</p>
|
||||
<ul class="fa-ul">
|
||||
<li>
|
||||
<i class="fa-li fa fa-check text-success"></i>
|
||||
{{$ctrl.i18n.ppremiumSignUpStorage}}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa-li fa fa-check text-success"></i>
|
||||
{{$ctrl.i18n.ppremiumSignUpTwoStep}}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa-li fa fa-check text-success"></i>
|
||||
{{$ctrl.i18n.ppremiumSignUpTotp}}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa-li fa fa-check text-success"></i>
|
||||
{{$ctrl.i18n.ppremiumSignUpSupport}}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa-li fa fa-check text-success"></i>
|
||||
{{$ctrl.i18n.ppremiumSignUpFuture}}
|
||||
</li>
|
||||
</ul>
|
||||
<p class="text-center lead">{{$ctrl.i18n.premiumPrice.replace('%price%', $ctrl.price)}}</p>
|
||||
<div class="bottom-buttons">
|
||||
<a class="btn btn-lg btn-primary btn-block" href="#" stop-click ng-click="$ctrl.purchase()">
|
||||
<b>{{$ctrl.i18n.premiumPurchase}}</b>
|
||||
</a>
|
||||
<a class="btn btn-lg btn-link btn-block" href="#" stop-click ng-click="$ctrl.refresh()">
|
||||
{{$ctrl.i18n.premiumRefresh}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="$ctrl.isPremium">
|
||||
<p class="text-center lead">{{$ctrl.i18n.premiumCurrentMember}}</p>
|
||||
<p class="text-center">{{$ctrl.i18n.premiumCurrentMemberThanks}}</p>
|
||||
<div class="bottom-buttons">
|
||||
<a class="btn btn-lg btn-primary btn-block" href="#" stop-click ng-click="$ctrl.manage()">
|
||||
<b>{{$ctrl.i18n.premiumManage}}</b>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,68 +0,0 @@
|
||||
import * as template from './premium.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { TokenService } from 'jslib/abstractions/token.service';
|
||||
|
||||
export class PremiumController {
|
||||
isPremium: boolean;
|
||||
i18n: any;
|
||||
price = '$10';
|
||||
|
||||
constructor(private i18nService: any, private tokenService: TokenService, private apiService: ApiService,
|
||||
private toastr: any, private SweetAlert: any, private $analytics: any, private $timeout: ng.ITimeoutService) {
|
||||
this.i18n = i18nService;
|
||||
this.isPremium = tokenService.getPremium();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.apiService.refreshIdentityToken().then(() => {
|
||||
this.toastr.success(this.i18nService.refreshComplete);
|
||||
this.$timeout(() => {
|
||||
this.isPremium = this.tokenService.getPremium();
|
||||
});
|
||||
}, (err: any) => {
|
||||
this.toastr.error(this.i18nService.errorsOccurred);
|
||||
});
|
||||
}
|
||||
|
||||
purchase() {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.premiumPurchase,
|
||||
text: this.i18nService.premiumPurchaseAlert,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.cancel,
|
||||
}, (confirmed: boolean) => {
|
||||
this.$analytics.eventTrack('Clicked Purchase Premium');
|
||||
if (confirmed) {
|
||||
BrowserApi.createNewTab('https://vault.bitwarden.com/#/?premium=purchase');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
manage() {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.premiumManage,
|
||||
text: this.i18nService.premiumManageAlert,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.cancel,
|
||||
}, (confirmed: boolean) => {
|
||||
this.$analytics.eventTrack('Clicked Manage Membership');
|
||||
if (confirmed) {
|
||||
BrowserApi.createNewTab('https://vault.bitwarden.com/#/?premium=manage');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
PremiumController.$inject = ['i18nService', 'tokenService', 'apiService', 'toastr', 'SweetAlert', '$analytics',
|
||||
'$timeout'];
|
||||
|
||||
export const PremiumComponent = {
|
||||
bindings: {},
|
||||
controller: PremiumController,
|
||||
template: template,
|
||||
};
|
@ -1,101 +0,0 @@
|
||||
<div class="header">
|
||||
<pop-out ng-if="$ctrl.showPopout" class="left"></pop-out>
|
||||
<div class="title">{{$ctrl.i18n.settings}}</div>
|
||||
</div>
|
||||
<div class="content content-tabs">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.security}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="lock-option" class="item-label">{{$ctrl.i18n.lockOptions}}</label>
|
||||
<select id="lock-option" name="LockOption" ng-model="$ctrl.lockOption" ng-change="$ctrl.changeLockOption()">
|
||||
<option value="0">{{$ctrl.i18n.immediately}}</option>
|
||||
<option value="1">{{$ctrl.i18n.oneMinute}}</option>
|
||||
<option value="5">{{$ctrl.i18n.fiveMinutes}}</option>
|
||||
<option value="15">{{$ctrl.i18n.fifteenMinutes}}</option>
|
||||
<option value="30">{{$ctrl.i18n.thirtyMinutes}}</option>
|
||||
<option value="60">{{$ctrl.i18n.oneHour}}</option>
|
||||
<option value="240">{{$ctrl.i18n.fourHours}}</option>
|
||||
<option value="-2" ng-if="$ctrl.showOnLocked">{{$ctrl.i18n.onLocked}}</option>
|
||||
<option value="-1">{{$ctrl.i18n.onRestart}}</option>
|
||||
<option value="">{{$ctrl.i18n.never}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.lock()">
|
||||
{{$ctrl.i18n.lockNow}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.twoStep()">
|
||||
{{$ctrl.i18n.twoStepLogin}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.account}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item text-primary" ui-sref="premium({animation: 'in-slide-left'})">
|
||||
<i class="fa fa-star fa-fw"></i> <b>{{$ctrl.i18n.premiumMembership}}</b>
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.changePassword()">
|
||||
{{$ctrl.i18n.changeMasterPassword}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.changeEmail()">
|
||||
{{$ctrl.i18n.changeEmail}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.logOut()">
|
||||
{{$ctrl.i18n.logOut}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.manage}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item" ui-sref="folders.list({animation: 'in-slide-left'})">
|
||||
{{$ctrl.i18n.folders}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" ui-sref="sync({animation: 'in-slide-left'})">
|
||||
{{$ctrl.i18n.sync}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.other}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item" ui-sref="options({animation: 'in-slide-left'})">
|
||||
{{$ctrl.i18n.options}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" ui-sref="about({animation: 'in-slide-left'})">
|
||||
{{$ctrl.i18n.about}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" ui-sref="help({animation: 'in-slide-left'})">
|
||||
{{$ctrl.i18n.helpFeedback}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" href="" ng-click="$ctrl.rate()">
|
||||
{{$ctrl.i18n.rateExtension}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{$ctrl.i18n.rateExtensionDesc}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,172 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import * as template from './settings.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { DeviceType } from 'jslib/enums/deviceType';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { LockService } from 'jslib/abstractions/lock.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popupUtils.service';
|
||||
|
||||
const RateUrls = {
|
||||
[DeviceType.Chrome]:
|
||||
'https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews',
|
||||
[DeviceType.Firefox]:
|
||||
'https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews',
|
||||
[DeviceType.Opera]:
|
||||
'https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container',
|
||||
[DeviceType.Edge]:
|
||||
'https://www.microsoft.com/store/p/bitwarden-free-password-manager/9p6kxl0svnnl',
|
||||
[DeviceType.Vivaldi]:
|
||||
'https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews',
|
||||
[DeviceType.Safari]:
|
||||
'https://itunes.apple.com/app/bitwarden-password-manager/id1137397744',
|
||||
};
|
||||
|
||||
export class SettingsController {
|
||||
lockOption = '';
|
||||
i18n: any;
|
||||
showOnLocked: boolean;
|
||||
showPopout: boolean = true;
|
||||
|
||||
constructor(private $state: any, private SweetAlert: any,
|
||||
private platformUtilsService: PlatformUtilsService, private $analytics: any,
|
||||
private i18nService: any, private constantsService: ConstantsService,
|
||||
private cryptoService: CryptoService, private lockService: LockService,
|
||||
private storageService: StorageService, public messagingService: MessagingService,
|
||||
private $timeout: ng.ITimeoutService) {
|
||||
this.i18n = i18nService;
|
||||
this.showPopout = !platformUtilsService.isSafari();
|
||||
|
||||
$timeout(() => {
|
||||
PopupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
|
||||
this.showOnLocked = !platformUtilsService.isFirefox() && !platformUtilsService.isEdge()
|
||||
&& !platformUtilsService.isSafari();
|
||||
this.storageService.get(constantsService.lockOptionKey).then((lockOption: number) => {
|
||||
if (lockOption != null) {
|
||||
let option = lockOption.toString();
|
||||
if (option === '-2' && !this.showOnLocked) {
|
||||
option = '-1';
|
||||
}
|
||||
this.lockOption = option;
|
||||
} else {
|
||||
this.lockOption = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changeLockOption() {
|
||||
const option = this.lockOption && this.lockOption !== '' ? parseInt(this.lockOption, 10) : null;
|
||||
this.storageService.save(this.constantsService.lockOptionKey, option).then(() => {
|
||||
return this.cryptoService.getKeyHash();
|
||||
}).then((keyHash) => {
|
||||
if (keyHash) {
|
||||
this.cryptoService.toggleKey();
|
||||
} else {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.loggingOut,
|
||||
text: this.i18nService.loggingOutConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.cancel,
|
||||
}, (confirmed: boolean) => {
|
||||
if (confirmed) {
|
||||
this.cryptoService.toggleKey();
|
||||
this.messagingService.send('logout');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lock() {
|
||||
this.$analytics.eventTrack('Lock Now');
|
||||
this.lockService.lock().then(() => {
|
||||
return this.$state.go('lock', {
|
||||
animation: 'in-slide-down',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logOut() {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.logOut,
|
||||
text: this.i18nService.logOutConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.cancel,
|
||||
}, (confirmed: boolean) => {
|
||||
if (confirmed) {
|
||||
this.messagingService.send('logout');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changePassword() {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.changeMasterPassword,
|
||||
text: this.i18nService.changeMasterPasswordConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.cancel,
|
||||
}, (confirmed: boolean) => {
|
||||
this.$analytics.eventTrack('Clicked Change Password');
|
||||
if (confirmed) {
|
||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/change-your-master-password/');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changeEmail() {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.changeEmail,
|
||||
text: this.i18nService.changeEmailConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.cancel,
|
||||
}, (confirmed: boolean) => {
|
||||
this.$analytics.eventTrack('Clicked Change Email');
|
||||
if (confirmed) {
|
||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/change-your-email/');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
twoStep() {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.twoStepLogin,
|
||||
text: this.i18nService.twoStepLoginConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.cancel,
|
||||
}, (confirmed: boolean) => {
|
||||
this.$analytics.eventTrack('Clicked Two-step Login');
|
||||
if (confirmed) {
|
||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/setup-two-step-login/');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rate() {
|
||||
this.$analytics.eventTrack('Rate Extension');
|
||||
BrowserApi.createNewTab((RateUrls as any)[this.platformUtilsService.getDevice()]);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsController.$inject = ['$state', 'SweetAlert', 'platformUtilsService', '$analytics', 'i18nService',
|
||||
'constantsService', 'cryptoService', 'lockService', 'storageService', 'messagingService', '$timeout'];
|
||||
|
||||
export const SettingsComponent = {
|
||||
bindings: {},
|
||||
controller: SettingsController,
|
||||
template: template,
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import { AboutComponent } from './about.component';
|
||||
import { CreditsComponent } from './credits.component';
|
||||
import { EnvironmentComponent } from './environment.component';
|
||||
import { AddFolderComponent } from './folders/add-folder.component';
|
||||
import { EditFolderComponent } from './folders/edit-folder.component';
|
||||
import { FoldersComponent } from './folders/folders.component';
|
||||
import { HelpComponent } from './help.component';
|
||||
import { OptionsComponent } from './options.component';
|
||||
import { PremiumComponent } from './premium.component';
|
||||
import { SettingsComponent } from './settings.component';
|
||||
import { SyncComponent } from './sync.component';
|
||||
|
||||
export default angular
|
||||
.module('bit.settings', ['oitozero.ngSweetAlert', 'toastr'])
|
||||
|
||||
.component('settings', SettingsComponent)
|
||||
.component('environment', EnvironmentComponent)
|
||||
.component('options', OptionsComponent)
|
||||
.component('about', AboutComponent)
|
||||
.component('credits', CreditsComponent)
|
||||
.component('help', HelpComponent)
|
||||
.component('folders', FoldersComponent)
|
||||
.component('addFolder', AddFolderComponent)
|
||||
.component('editFolder', EditFolderComponent)
|
||||
.component('premium', PremiumComponent)
|
||||
.component('sync', SyncComponent)
|
||||
|
||||
.name;
|
@ -1,19 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.sync}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="centered-message">
|
||||
<p style="margin-top: -50px;">
|
||||
<a href="" class="btn btn-lg btn-link btn-block" style="display: inline-block;" ng-click="$ctrl.sync()">
|
||||
{{$ctrl.i18n.syncVaultNow}}
|
||||
</a>
|
||||
<small class="text-muted">{{$ctrl.i18n.lastSync}} {{$ctrl.lastSync}}</small>
|
||||
<span ng-show="$ctrl.loading" style="display: block; margin-top: 20px;" class="text-center">
|
||||
<i class="text-muted fa fa-lg fa-spinner fa-spin"></i>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
@ -1,49 +0,0 @@
|
||||
import * as template from './sync.component.html';
|
||||
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
|
||||
export class SyncController {
|
||||
i18n: any;
|
||||
lastSync = '--';
|
||||
loading = false;
|
||||
|
||||
constructor(private syncService: SyncService, private toastr: any, private $analytics: any,
|
||||
private i18nService: any, private $timeout: ng.ITimeoutService) {
|
||||
this.i18n = i18nService;
|
||||
this.setLastSync();
|
||||
}
|
||||
|
||||
sync() {
|
||||
this.loading = true;
|
||||
this.syncService.fullSync(true).then((success: boolean) => {
|
||||
this.loading = false;
|
||||
if (success) {
|
||||
this.setLastSync();
|
||||
this.$analytics.eventTrack('Synced Full');
|
||||
this.toastr.success(this.i18n.syncingComplete);
|
||||
} else {
|
||||
this.toastr.error(this.i18n.syncingFailed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setLastSync() {
|
||||
this.syncService.getLastSync().then((last: Date) => {
|
||||
this.$timeout(() => {
|
||||
if (last) {
|
||||
this.lastSync = last.toLocaleDateString() + ' ' + last.toLocaleTimeString();
|
||||
} else {
|
||||
this.lastSync = this.i18n.never;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SyncController.$inject = ['syncService', 'toastr', '$analytics', 'i18nService', '$timeout'];
|
||||
|
||||
export const SyncComponent = {
|
||||
bindings: {},
|
||||
controller: SyncController,
|
||||
template: template,
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
<form name="theForm" ng-submit="$ctrl.submit()">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="tabs.tools({animation: 'out-slide-down'})">{{$ctrl.i18n.close}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link">{{$ctrl.i18n.submit}}</button>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.exportVault}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="master-password" class="sr-only">{{$ctrl.i18n.masterPass}}</label>
|
||||
<input id="master-password" type="password" name="MasterPassword"
|
||||
placeholder="{{$ctrl.i18n.masterPass}}" ng-model="$ctrl.masterPassword">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
<p>{{$ctrl.i18n.exportMasterPassword}}</p>
|
||||
<b>{{$ctrl.i18n.warning}}</b>: {{$ctrl.i18n.exportWarning}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,174 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import * as papa from 'papaparse';
|
||||
import * as template from './export.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { CipherType } from 'jslib/enums/cipherType';
|
||||
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
import { FolderView } from 'jslib/models/view/folderView';
|
||||
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { UtilsService } from 'jslib/abstractions/utils.service';
|
||||
|
||||
export class ExportController {
|
||||
i18n: any;
|
||||
masterPassword: string;
|
||||
|
||||
constructor(private $state: any, private cryptoService: CryptoService,
|
||||
private toastr: any, private utilsService: UtilsService, private $analytics: any,
|
||||
private i18nService: any, private folderService: FolderService, private cipherService: CipherService,
|
||||
private $window: ng.IWindowService, private userService: UserService) {
|
||||
this.i18n = i18nService;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
document.getElementById('master-password').focus();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
this.toastr.error(this.i18nService.invalidMasterPassword, this.i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
const email = await this.userService.getEmail();
|
||||
const key = this.cryptoService.makeKey(this.masterPassword, email);
|
||||
const keyHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||
|
||||
if (storedKeyHash != null && keyHash != null && storedKeyHash === keyHash) {
|
||||
const csv = await this.getCsv();
|
||||
this.$analytics.eventTrack('Exported Data');
|
||||
this.downloadFile(csv);
|
||||
this.$state.go('tabs.tools', { animation: 'out-slide-down' });
|
||||
} else {
|
||||
this.toastr.error(this.i18n.invalidMasterPassword, this.i18n.errorsOccurred);
|
||||
}
|
||||
}
|
||||
|
||||
private async checkPassword() {
|
||||
const email = await this.userService.getEmail();
|
||||
const key = this.cryptoService.makeKey(this.masterPassword, email);
|
||||
const keyHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||
if (storedKeyHash == null || keyHash == null || storedKeyHash !== keyHash) {
|
||||
throw new Error('Invalid password.');
|
||||
}
|
||||
}
|
||||
|
||||
private async getCsv(): Promise<string> {
|
||||
let decFolders: FolderView[] = [];
|
||||
let decCiphers: CipherView[] = [];
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.folderService.getAllDecrypted().then((folders) => {
|
||||
decFolders = folders;
|
||||
}));
|
||||
|
||||
promises.push(this.cipherService.getAllDecrypted().then((ciphers) => {
|
||||
decCiphers = ciphers;
|
||||
}));
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const foldersMap = new Map<string, FolderView>();
|
||||
decFolders.forEach((f) => {
|
||||
foldersMap.set(f.id, f);
|
||||
});
|
||||
|
||||
const exportCiphers: any[] = [];
|
||||
decCiphers.forEach((c) => {
|
||||
// only export logins and secure notes
|
||||
if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher: any = {
|
||||
folder: c.folderId && foldersMap.has(c.folderId) ? foldersMap.get(c.folderId).name : null,
|
||||
favorite: c.favorite ? 1 : null,
|
||||
type: null,
|
||||
name: c.name,
|
||||
notes: c.notes,
|
||||
fields: null,
|
||||
// Login props
|
||||
login_uri: null,
|
||||
login_username: null,
|
||||
login_password: null,
|
||||
login_totp: null,
|
||||
};
|
||||
|
||||
if (c.fields) {
|
||||
c.fields.forEach((f: any) => {
|
||||
if (!cipher.fields) {
|
||||
cipher.fields = '';
|
||||
} else {
|
||||
cipher.fields += '\n';
|
||||
}
|
||||
|
||||
cipher.fields += ((f.name || '') + ': ' + f.value);
|
||||
});
|
||||
}
|
||||
|
||||
switch (c.type) {
|
||||
case CipherType.Login:
|
||||
cipher.type = 'login';
|
||||
cipher.login_username = c.login.username;
|
||||
cipher.login_password = c.login.password;
|
||||
cipher.login_totp = c.login.totp;
|
||||
|
||||
if (c.login.uris) {
|
||||
cipher.login_uri = [];
|
||||
c.login.uris.forEach((u) => {
|
||||
cipher.login_uri.push(u.uri);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
cipher.type = 'note';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
exportCiphers.push(cipher);
|
||||
});
|
||||
|
||||
const csv = papa.unparse(exportCiphers);
|
||||
return csv;
|
||||
}
|
||||
|
||||
private downloadFile(csv: string): void {
|
||||
const fileName = this.makeFileName();
|
||||
BrowserApi.downloadFile(this.$window, csv, { type: 'text/plain' }, fileName);
|
||||
}
|
||||
|
||||
private makeFileName(): string {
|
||||
const now = new Date();
|
||||
const dateString =
|
||||
now.getFullYear() + '' + this.padNumber(now.getMonth() + 1, 2) + '' + this.padNumber(now.getDate(), 2) +
|
||||
this.padNumber(now.getHours(), 2) + '' + this.padNumber(now.getMinutes(), 2) +
|
||||
this.padNumber(now.getSeconds(), 2);
|
||||
|
||||
return 'bitwarden_export_' + dateString + '.csv';
|
||||
}
|
||||
|
||||
private padNumber(num: number, width: number, padCharacter: string = '0'): string {
|
||||
const numString = num.toString();
|
||||
return numString.length >= width ? numString :
|
||||
new Array(width - numString.length + 1).join(padCharacter) + numString;
|
||||
}
|
||||
}
|
||||
|
||||
ExportController.$inject = ['$state', 'cryptoService', 'toastr', 'utilsService', '$analytics', 'i18nService',
|
||||
'folderService', 'cipherService', '$window', 'userService'];
|
||||
|
||||
export const ExportComponent = {
|
||||
bindings: {},
|
||||
controller: ExportController,
|
||||
template: template,
|
||||
};
|
@ -1,33 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ng-click="$ctrl.close()" href=""><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a ng-click="$ctrl.clear()" href="">{{$ctrl.i18n.clear}}</a>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.passwordHistory}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list" ng-if="$ctrl.loaded">
|
||||
<div class="list-grouped" ng-if="$ctrl.history.length !== 0">
|
||||
<div class="list-grouped-item condensed wrap"
|
||||
ng-repeat="item in $ctrl.history | orderBy: 'date':true track by $index">
|
||||
<div class="action-buttons">
|
||||
<span class="btn-list" stop-prop stop-click title="{{$ctrl.i18n.copyPassword}}"
|
||||
ngclipboard ngclipboard-error="$ctrl.clipboardError(e)"
|
||||
ngclipboard-success="$ctrl.clipboardSuccess(e, $ctrl.i18n.password)"
|
||||
data-clipboard-text="{{item.password}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</span>
|
||||
</div>
|
||||
<span class="text monospaced">
|
||||
{{item.password}}
|
||||
</span>
|
||||
<span class="detail">{{item.date | date: 'medium'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-loading" ng-if="!$ctrl.loaded">
|
||||
<i class="fa fa-lg fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
@ -1,64 +0,0 @@
|
||||
import * as template from './password-generator-history.component.html';
|
||||
|
||||
import { PasswordHistory } from 'jslib/models/domain/passwordHistory';
|
||||
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
|
||||
export class PasswordGeneratorHistoryController {
|
||||
$transition$: any;
|
||||
history: PasswordHistory[];
|
||||
editState: any;
|
||||
addState: any;
|
||||
i18n: any;
|
||||
loaded: boolean = false;
|
||||
|
||||
constructor(private $state: any, private passwordGenerationService: PasswordGenerationService,
|
||||
private toastr: any, private $analytics: any, private i18nService: any) {
|
||||
this.i18n = i18nService;
|
||||
|
||||
passwordGenerationService.getHistory().then((history) => {
|
||||
this.history = history;
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
const params = this.$transition$.params('to');
|
||||
this.addState = params.addState;
|
||||
this.editState = params.editState;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.history = [];
|
||||
this.passwordGenerationService.clear();
|
||||
}
|
||||
|
||||
clipboardError(e: any, password: any) {
|
||||
this.toastr.info(this.i18nService.browserNotSupportClipboard);
|
||||
}
|
||||
|
||||
clipboardSuccess(e: any) {
|
||||
this.$analytics.eventTrack('Copied Historical Password');
|
||||
e.clearSelection();
|
||||
this.toastr.info(this.i18nService.passwordCopied);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$state.go('^.passwordGenerator', {
|
||||
animation: 'out-slide-right',
|
||||
addState: this.addState,
|
||||
editState: this.editState,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
PasswordGeneratorHistoryController.$inject = ['$state', 'passwordGenerationService', 'toastr', '$analytics',
|
||||
'i18nService'];
|
||||
|
||||
export const PasswordGeneratorHistoryComponent = {
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
},
|
||||
controller: PasswordGeneratorHistoryController,
|
||||
template: template,
|
||||
};
|
@ -1,90 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ng-click="$ctrl.close()" href="">{{$ctrl.i18n.close}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a ng-click="$ctrl.select()" ng-show="$ctrl.showSelect" href="">{{$ctrl.i18n.select}}</a>
|
||||
</div>
|
||||
<div class="title">{{$ctrl.i18n.generatePassword}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="generate-password-block">
|
||||
{{$ctrl.password}}
|
||||
</div>
|
||||
<div class="list" style="margin-top: 0;">
|
||||
<div class="list-section" style="padding-top: 0;">
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item text-primary" href="" ng-click="$ctrl.regenerate(true)">
|
||||
{{$ctrl.i18n.regeneratePassword}}
|
||||
</a>
|
||||
<a class="list-section-item text-primary" href="" ngclipboard ngclipboard-error="$ctrl.clipboardError(e)"
|
||||
ngclipboard-success="$ctrl.clipboardSuccess(e)" data-clipboard-text="{{$ctrl.password}}">
|
||||
{{$ctrl.i18n.copyPassword}}
|
||||
</a>
|
||||
<a class="list-section-item text-primary" href="" ng-click="$ctrl.goHistory()">
|
||||
{{$ctrl.i18n.passwordHistory}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{$ctrl.i18n.options}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-slider">
|
||||
<label for="length">{{$ctrl.i18n.length}}</label>
|
||||
<span class="slider-value">{{$ctrl.options.length}}</span>
|
||||
<div class="slider-wrapper">
|
||||
<input id="length" type="range" min="5" max="128" step="1" ng-model="$ctrl.options.length"
|
||||
ng-change="$ctrl.sliderMoved()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="uppercase">A-Z</label>
|
||||
<input id="uppercase" type="checkbox" ng-model="$ctrl.options.uppercase"
|
||||
ng-change="$ctrl.saveOptions($ctrl.options)">
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="lowercase">a-z</label>
|
||||
<input id="lowercase" type="checkbox" ng-model="$ctrl.options.lowercase"
|
||||
ng-change="$ctrl.saveOptions($ctrl.options)">
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="numbers">0-9</label>
|
||||
<input id="numbers" type="checkbox" ng-model="$ctrl.options.number"
|
||||
ng-change="$ctrl.saveOptions($ctrl.options)">
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="special">!@#$%^&*</label>
|
||||
<input id="special" type="checkbox" ng-model="$ctrl.options.special"
|
||||
ng-change="$ctrl.saveOptions($ctrl.options)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-input">
|
||||
<label for="min-numbers">{{$ctrl.i18n.minNumbers}}</label>
|
||||
<input id="min-numbers" type="number" min="0" max="5" ng-model="$ctrl.options.minNumber"
|
||||
ng-change="$ctrl.saveOptions($ctrl.options)">
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-input">
|
||||
<label for="min-special">{{$ctrl.i18n.minSpecial}}</label>
|
||||
<input id="min-special" type="number" min="0" max="5" ng-model="$ctrl.options.minSpecial"
|
||||
ng-change="$ctrl.saveOptions($ctrl.options)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="ambiguous">{{$ctrl.i18n.avoidAmbChar}}</label>
|
||||
<input id="ambiguous" type="checkbox" ng-model="$ctrl.options.ambiguous"
|
||||
ng-true-value="false" ng-false-value="true"
|
||||
ng-change="$ctrl.saveOptions($ctrl.options)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,147 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import * as template from './password-generator.component.html';
|
||||
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popupUtils.service';
|
||||
|
||||
export class PasswordGeneratorController {
|
||||
$transition$: any;
|
||||
options: any;
|
||||
showSelect: boolean;
|
||||
password: string = '-';
|
||||
editState: any;
|
||||
addState: any;
|
||||
i18n: any;
|
||||
|
||||
constructor(private $state: any, private passwordGenerationService: PasswordGenerationService,
|
||||
private toastr: any, private $analytics: any, private i18nService: any, private $timeout: ng.ITimeoutService) {
|
||||
this.i18n = i18nService;
|
||||
|
||||
passwordGenerationService.getOptions().then((options: any) => {
|
||||
this.options = options;
|
||||
this.regenerate(false);
|
||||
$analytics.eventTrack('Generated Password');
|
||||
passwordGenerationService.addHistory(this.password);
|
||||
});
|
||||
|
||||
// Save password once the slider stop moving.
|
||||
document.querySelector('#length').addEventListener('change', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
$analytics.eventTrack('Generated Password');
|
||||
this.saveOptions(this.options, false);
|
||||
passwordGenerationService.addHistory(this.password);
|
||||
});
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
const params = this.$transition$.params('to');
|
||||
this.addState = params.addState;
|
||||
this.editState = params.editState;
|
||||
|
||||
this.showSelect = this.addState || this.editState;
|
||||
|
||||
this.$timeout(() => {
|
||||
PopupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
sliderMoved() {
|
||||
this.regenerate(false);
|
||||
}
|
||||
|
||||
regenerate(trackEvent: any) {
|
||||
this.password = this.passwordGenerationService.generatePassword(this.options);
|
||||
|
||||
if (trackEvent) {
|
||||
this.$analytics.eventTrack('Regenerated Password');
|
||||
this.passwordGenerationService.addHistory(this.password);
|
||||
}
|
||||
}
|
||||
|
||||
saveOptions(options: any, regenerate: boolean = true) {
|
||||
if (!options.uppercase && !options.lowercase && !options.number && !options.special) {
|
||||
options.lowercase = this.options.lowercase = true;
|
||||
}
|
||||
if (!options.minNumber) {
|
||||
options.minNumber = this.options.minNumber = 0;
|
||||
}
|
||||
if (!options.minSpecial) {
|
||||
options.minSpecial = this.options.minSpecial = 0;
|
||||
}
|
||||
|
||||
this.passwordGenerationService.saveOptions(options);
|
||||
if (regenerate) {
|
||||
this.regenerate(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
clipboardError(e: any, password: any) {
|
||||
this.toastr.info(this.i18nService.browserNotSupportClipboard);
|
||||
}
|
||||
|
||||
clipboardSuccess(e: any) {
|
||||
this.$analytics.eventTrack('Copied Generated Password');
|
||||
e.clearSelection();
|
||||
this.toastr.info(this.i18nService.passwordCopied);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dismiss();
|
||||
}
|
||||
|
||||
select() {
|
||||
this.$analytics.eventTrack('Selected Generated Password');
|
||||
|
||||
if (this.addState) {
|
||||
this.addState.cipher.login.password = this.password;
|
||||
} else if (this.editState) {
|
||||
this.editState.cipher.login.password = this.password;
|
||||
}
|
||||
|
||||
this.dismiss();
|
||||
}
|
||||
|
||||
goHistory() {
|
||||
this.$state.go('^.passwordGeneratorHistory', {
|
||||
animation: 'in-slide-left',
|
||||
addState: this.addState,
|
||||
editState: this.editState,
|
||||
});
|
||||
}
|
||||
|
||||
private dismiss() {
|
||||
if (this.addState) {
|
||||
this.$state.go('addCipher', {
|
||||
animation: 'out-slide-down',
|
||||
from: this.addState.from,
|
||||
cipher: this.addState.cipher,
|
||||
});
|
||||
} else if (this.editState) {
|
||||
this.$state.go('editCipher', {
|
||||
animation: 'out-slide-down',
|
||||
cipher: this.editState.cipher,
|
||||
fromView: this.editState.fromView,
|
||||
cipherId: this.editState.cipherId,
|
||||
from: this.editState.from,
|
||||
});
|
||||
} else {
|
||||
this.$state.go('tabs.tools', {
|
||||
animation: 'out-slide-down',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PasswordGeneratorController.$inject = ['$state', 'passwordGenerationService', 'toastr', '$analytics', 'i18nService',
|
||||
'$timeout'];
|
||||
|
||||
export const PasswordGeneratorComponent = {
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
},
|
||||
controller: PasswordGeneratorController,
|
||||
template: template,
|
||||
};
|
@ -1,47 +0,0 @@
|
||||
<div class="header">
|
||||
<pop-out ng-if="$ctrl.showPopout" class="left"></pop-out>
|
||||
<div class="title">{{$ctrl.i18n.tools}}</div>
|
||||
</div>
|
||||
<div class="content content-tabs">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item wrap" ui-sref="passwordGenerator({animation: 'in-slide-up'})">
|
||||
<span class="leading-icon" style="color: #eba776;"><i class="fa fa-refresh fa-fw"></i></span>
|
||||
<span class="text">{{$ctrl.i18n.passGen}}</span>
|
||||
<span class="detail">{{$ctrl.i18n.passGenInfo}}</span>
|
||||
</a>
|
||||
<a class="list-section-item wrap" href="" ng-click="$ctrl.launchWebVault()">
|
||||
<span class="leading-icon" style="color: #5bb630;"><i class="fa fa-globe fa-fw"></i></span>
|
||||
<span class="text">{{$ctrl.i18n.bitWebVault}}</span>
|
||||
<span class="detail">{{$ctrl.i18n.bitWebVaultInfo}}</span>
|
||||
</a>
|
||||
<a class="list-section-item wrap" href="" ng-click="$ctrl.launchiOS()">
|
||||
<span class="leading-icon" style="color: #999999;"><i class="fa fa-apple fa-fw"></i></span>
|
||||
<span class="text">{{$ctrl.i18n.bitIosVault}}</span>
|
||||
<span class="detail">{{$ctrl.i18n.bitIosVaultInfo}}</span>
|
||||
</a>
|
||||
<a class="list-section-item wrap" href="" ng-click="$ctrl.launchAndroid()">
|
||||
<span class="leading-icon" style="color: #a4c639;"><i class="fa fa-android fa-fw"></i></span>
|
||||
<span class="text">{{$ctrl.i18n.bitAndrVault}}</span>
|
||||
<span class="detail">{{$ctrl.i18n.bitAndrVaultInfo}}</span>
|
||||
</a>
|
||||
<a class="list-section-item wrap" href="" ng-click="$ctrl.launchWebVault(true)">
|
||||
<span class="leading-icon" style="color: #8977af;"><i class="fa fa-share-alt fa-fw"></i></span>
|
||||
<span class="text">{{$ctrl.i18n.shareVault}}</span>
|
||||
<span class="detail">{{$ctrl.i18n.shareVaultInfo}}</span>
|
||||
</a>
|
||||
<a class="list-section-item wrap" href="" ng-click="$ctrl.launchImport()">
|
||||
<span class="leading-icon" style="color: #6fc2ff;"><i class="fa fa-cloud-upload fa-fw"></i></span>
|
||||
<span class="text">{{$ctrl.i18n.importItems}}</span>
|
||||
<span class="detail">{{$ctrl.i18n.importItemsInfo}}</span>
|
||||
</a>
|
||||
<a class="list-section-item wrap" ui-sref="export({animation: 'in-slide-up'})" ng-if="$ctrl.showExport">
|
||||
<span class="leading-icon" style="color: #ff6f6f;"><i class="fa fa-cloud-download fa-fw"></i></span>
|
||||
<span class="text">{{$ctrl.i18n.exportVault}}</span>
|
||||
<span class="detail">{{$ctrl.i18n.exportVaultInfo}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,64 +0,0 @@
|
||||
import * as template from './tools.component.html';
|
||||
|
||||
import { BrowserApi } from '../../../browser/browserApi';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
export class ToolsController {
|
||||
showExport: boolean;
|
||||
showPopout: boolean = true;
|
||||
i18n: any;
|
||||
private webVaultBaseUrl: string = 'https://vault.bitwarden.com';
|
||||
|
||||
constructor(private SweetAlert: any, private i18nService: any, private $analytics: any,
|
||||
private platformUtilsService: PlatformUtilsService, private environmentService: EnvironmentService) {
|
||||
this.i18n = i18nService;
|
||||
this.showExport = !platformUtilsService.isEdge();
|
||||
this.showPopout = !platformUtilsService.isSafari();
|
||||
if (environmentService.baseUrl) {
|
||||
this.webVaultBaseUrl = environmentService.baseUrl;
|
||||
} else if (environmentService.webVaultUrl) {
|
||||
this.webVaultBaseUrl = environmentService.webVaultUrl;
|
||||
}
|
||||
}
|
||||
|
||||
launchWebVault(createOrg: any) {
|
||||
this.$analytics.eventTrack('Launch Web Vault' + (createOrg ? ' For Share' : ''));
|
||||
BrowserApi.createNewTab(this.webVaultBaseUrl + '/#/' + (createOrg ? '?org=free' : ''));
|
||||
}
|
||||
|
||||
launchAndroid() {
|
||||
this.$analytics.eventTrack('Launch Android');
|
||||
BrowserApi.createNewTab('https://play.google.com/store/apps/details?id=com.x8bit.bitwarden');
|
||||
}
|
||||
|
||||
launchiOS() {
|
||||
this.$analytics.eventTrack('Launch iOS');
|
||||
BrowserApi.createNewTab('https://itunes.apple.com/us/app/bitwarden-free-password-manager/' +
|
||||
'id1137397744?mt=8');
|
||||
}
|
||||
|
||||
launchImport() {
|
||||
this.SweetAlert.swal({
|
||||
title: this.i18nService.importItems,
|
||||
text: this.i18nService.importItemsConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: this.i18nService.yes,
|
||||
cancelButtonText: this.i18nService.cancel,
|
||||
}, (confirmed: boolean) => {
|
||||
if (confirmed) {
|
||||
this.$analytics.eventTrack('Launch Web Vault For Import');
|
||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/import-data/');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ToolsController.$inject = ['SweetAlert', 'i18nService', '$analytics', 'platformUtilsService', 'environmentService'];
|
||||
|
||||
export const ToolsComponent = {
|
||||
bindings: {},
|
||||
controller: ToolsController,
|
||||
template: template,
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
import * as angular from 'angular';
|
||||
import { ExportComponent } from './export.component';
|
||||
import { PasswordGeneratorHistoryComponent } from './password-generator-history.component';
|
||||
import { PasswordGeneratorComponent } from './password-generator.component';
|
||||
import { ToolsComponent } from './tools.component';
|
||||
|
||||
export default angular
|
||||
.module('bit.tools', ['ngAnimate', 'ngclipboard', 'toastr', 'oitozero.ngSweetAlert'])
|
||||
|
||||
.component('tools', ToolsComponent)
|
||||
.component('passwordGeneratorHistory', PasswordGeneratorHistoryComponent)
|
||||
.component('passwordGenerator', PasswordGeneratorComponent)
|
||||
.component('export', ExportComponent)
|
||||
|
||||
.name;
|
@ -1,196 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultAddCipherController', function ($scope, $state, $stateParams, cipherService, folderService,
|
||||
cryptoService, toastr, popupUtilsService, $analytics, i18nService, constantsService, $timeout, auditService) {
|
||||
$scope.i18n = i18nService;
|
||||
$scope.constants = constantsService;
|
||||
$scope.addFieldType = constantsService.fieldType.text.toString();
|
||||
$scope.selectedType = constantsService.cipherType.login.toString();
|
||||
var from = $stateParams.from,
|
||||
folderId = $stateParams.folderId && $stateParams.folderId !== '0' ? $stateParams.folderId : null;
|
||||
|
||||
$scope.cipher = {
|
||||
folderId: folderId,
|
||||
name: $stateParams.name,
|
||||
type: constantsService.cipherType.login,
|
||||
login: {
|
||||
uris: [{
|
||||
uri: null,
|
||||
match: null,
|
||||
matchValue: null
|
||||
}]
|
||||
},
|
||||
identity: {},
|
||||
card: {},
|
||||
secureNote: {
|
||||
type: 0 // generic note
|
||||
}
|
||||
};
|
||||
|
||||
if ($stateParams.uri) {
|
||||
$scope.cipher.login.uris[0].uri = $stateParams.uri;
|
||||
}
|
||||
|
||||
if ($stateParams.cipher) {
|
||||
angular.extend($scope.cipher, $stateParams.cipher);
|
||||
}
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
|
||||
if (!$stateParams.cipher && $scope.cipher.name && $scope.cipher.login && $scope.cipher.login.uri) {
|
||||
document.getElementById('loginUsername').focus();
|
||||
}
|
||||
else {
|
||||
document.getElementById('name').focus();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
folderService.getAllDecrypted().then(function (folders) {
|
||||
$scope.folders = folders;
|
||||
});
|
||||
|
||||
$scope.typeChanged = function () {
|
||||
$scope.cipher.type = parseInt($scope.selectedType);
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
$scope.savePromise = null;
|
||||
$scope.save = function () {
|
||||
if (!$scope.cipher.name || $scope.cipher.name === '') {
|
||||
toastr.error(i18nService.nameRequired, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.savePromise = cipherService.encrypt($scope.cipher).then(function (cipherModel) {
|
||||
var cipher = new Cipher(cipherModel, true);
|
||||
return cipherService.saveWithServer(cipher);
|
||||
}).then(function (c) {
|
||||
$analytics.eventTrack('Added Cipher');
|
||||
toastr.success(i18nService.addedItem);
|
||||
$scope.close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
if (from === 'current') {
|
||||
$state.go('tabs.current', {
|
||||
animation: 'out-slide-down'
|
||||
});
|
||||
}
|
||||
else if (from === 'grouping') {
|
||||
$state.go('viewGrouping', {
|
||||
animation: 'out-slide-down'
|
||||
});
|
||||
}
|
||||
else {
|
||||
$state.go('tabs.vault', {
|
||||
animation: 'out-slide-down'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showPassword = false;
|
||||
$scope.togglePassword = function () {
|
||||
$analytics.eventTrack('Toggled Password');
|
||||
$scope.showPassword = !$scope.showPassword;
|
||||
};
|
||||
|
||||
$scope.checkPassword = function () {
|
||||
if (!$scope.cipher.login || !$scope.cipher.login.password || $scope.cipher.login.password === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$analytics.eventTrack('Check Password');
|
||||
auditService.passwordLeaked($scope.cipher.login.password).then(function (matches) {
|
||||
if (matches != 0) {
|
||||
toastr.error(i18nService.passwordExposed);
|
||||
} else {
|
||||
toastr.success(i18nService.passwordSafe);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addUri = function () {
|
||||
if (!$scope.cipher.login) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.cipher.login.uris) {
|
||||
$scope.cipher.login.uris = [];
|
||||
}
|
||||
|
||||
$scope.cipher.login.uris.push({
|
||||
uri: null,
|
||||
match: null,
|
||||
matchValue: null
|
||||
});
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
$scope.removeUri = function (uri) {
|
||||
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
|
||||
return;
|
||||
}
|
||||
|
||||
var index = $scope.cipher.login.uris.indexOf(uri);
|
||||
if (index > -1) {
|
||||
$scope.cipher.login.uris.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.uriMatchChanged = function (uri) {
|
||||
uri.showOptions = uri.showOptions == null ? true : uri.showOptions;
|
||||
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
|
||||
uri.match = null;
|
||||
}
|
||||
else {
|
||||
uri.match = parseInt(uri.matchValue);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleUriOptions = function (u) {
|
||||
u.showOptions = u.showOptions == null && u.match != null ? false : !u.showOptions;
|
||||
};
|
||||
|
||||
$scope.addField = function (type) {
|
||||
if (!$scope.cipher.fields) {
|
||||
$scope.cipher.fields = [];
|
||||
}
|
||||
|
||||
$scope.cipher.fields.push({
|
||||
type: parseInt(type),
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.cipher.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.cipher.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.generatePassword = function () {
|
||||
$analytics.eventTrack('Clicked Generate Password');
|
||||
$state.go('passwordGenerator', {
|
||||
animation: 'in-slide-up',
|
||||
addState: {
|
||||
from: from,
|
||||
cipher: $scope.cipher
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
@ -1,127 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultAttachmentsController', function ($scope, $state, $stateParams, cipherService, toastr,
|
||||
SweetAlert, popupUtilsService, $analytics, i18nService, cryptoService, tokenService, $timeout) {
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
|
||||
$scope.i18n = i18nService;
|
||||
$scope.isPremium = tokenService.getPremium();
|
||||
$scope.canAccessAttachments = $scope.isPremium;
|
||||
$scope.hasUpdatedKey = false;
|
||||
|
||||
cipherService.get($stateParams.id).then(function (cipher) {
|
||||
return cipher.decrypt();
|
||||
}).then(function (model) {
|
||||
$scope.cipher = model;
|
||||
$scope.canAccessAttachments = $scope.isPremium || !!$scope.cipher.organizationId;
|
||||
|
||||
if (!$scope.canAccessAttachments) {
|
||||
SweetAlert.swal({
|
||||
title: i18nService.premiumRequired,
|
||||
text: i18nService.premiumRequiredDesc,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: i18nService.learnMore,
|
||||
cancelButtonText: i18nService.cancel
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
BrowserApi.createNewTab('https://vault.bitwarden.com/#/?premium=purchase');
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
else {
|
||||
cryptoService.getEncKey().then(function (key) {
|
||||
$scope.hasUpdatedKey = !!key;
|
||||
if (!$scope.hasUpdatedKey) {
|
||||
SweetAlert.swal({
|
||||
title: i18nService.featureUnavailable,
|
||||
text: i18nService.updateKey,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: i18nService.learnMore,
|
||||
cancelButtonText: i18nService.cancel
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/update-encryption-key/');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.submitPromise = null;
|
||||
$scope.submit = function () {
|
||||
if (!$scope.hasUpdatedKey) {
|
||||
toastr.error(i18nService.updateKey);
|
||||
return;
|
||||
}
|
||||
|
||||
var fileEl = document.getElementById('file');
|
||||
var files = fileEl.files;
|
||||
if (!files || !files.length) {
|
||||
toastr.error(i18nService.selectFile, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
if (files[0].size > 104857600) { // 100 MB
|
||||
toastr.error(i18nService.maxFileSize, i18nService.errorsOccurred);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
$scope.submitPromise = cipherService.saveAttachmentWithServer($scope.cipher, files[0]).then(function (cipher) {
|
||||
cipher.decrypt().then(function (model) {
|
||||
$scope.cipher = model;
|
||||
});
|
||||
$analytics.eventTrack('Added Attachment');
|
||||
toastr.success(i18nService.attachmentSaved);
|
||||
|
||||
// reset file input
|
||||
// ref: https://stackoverflow.com/a/20552042
|
||||
fileEl.type = '';
|
||||
fileEl.type = 'file';
|
||||
fileEl.value = '';
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
toastr.error(err);
|
||||
}
|
||||
else {
|
||||
toastr.error(i18nService.errorsOccurred);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.delete = function (attachment) {
|
||||
SweetAlert.swal({
|
||||
title: i18nService.deleteAttachment,
|
||||
text: i18nService.deleteAttachmentConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: i18nService.yes,
|
||||
cancelButtonText: i18nService.no
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
cipherService.deleteAttachmentWithServer($stateParams.id, attachment.id).then(function () {
|
||||
var index = $scope.cipher.attachments.indexOf(attachment);
|
||||
if (index > -1) {
|
||||
$scope.cipher.attachments.splice(index, 1);
|
||||
}
|
||||
$analytics.eventTrack('Deleted Attachment');
|
||||
toastr.success(i18nService.deletedAttachment);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$state.go('editCipher', {
|
||||
cipherId: $stateParams.id,
|
||||
animation: 'out-slide-down',
|
||||
from: $stateParams.from,
|
||||
fromView: $stateParams.fromView
|
||||
});
|
||||
|
||||
return;
|
||||
};
|
||||
});
|
@ -1,246 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultController', function ($scope, $rootScope, cipherService, folderService, $q, $state, $stateParams, toastr,
|
||||
syncService, platformUtilsService, $analytics, i18nService, stateService, $timeout, $window, collectionService, $filter) {
|
||||
var stateKey = 'vault',
|
||||
state = stateService.getState(stateKey) || {};
|
||||
stateService.removeState('viewGrouping');
|
||||
|
||||
$scope.i18n = i18nService;
|
||||
$scope.showGroupingCounts = !platformUtilsService.isEdge();
|
||||
$scope.disableSearch = platformUtilsService.isEdge();
|
||||
$scope.showPopout = !platformUtilsService.isSafari();
|
||||
document.getElementById('search').focus();
|
||||
|
||||
var syncOnLoad = $stateParams.syncOnLoad;
|
||||
if (syncOnLoad) {
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
$timeout(function () {
|
||||
syncService.fullSync(true);
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
var delayLoad = true;
|
||||
$scope.loaded = true;
|
||||
if (!$rootScope.vaultCiphers) {
|
||||
$rootScope.vaultCiphers = [];
|
||||
$scope.favoriteCiphers = [];
|
||||
delayLoad = false;
|
||||
}
|
||||
else {
|
||||
$scope.favoriteCiphers = $filter('filter')($rootScope.vaultCiphers, { favorite: true });
|
||||
|
||||
if (!$rootScope.vaultCollections || !$rootScope.vaultCollections.length) {
|
||||
$scope.noFolderCiphers = $filter('filter')($rootScope.vaultCiphers, { folderId: null });
|
||||
if ($scope.noFolderCiphers.length >= 100) {
|
||||
$scope.noFolderCiphers = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$rootScope.vaultFolders) {
|
||||
$rootScope.vaultFolders = [];
|
||||
delayLoad = false;
|
||||
$scope.loaded = false;
|
||||
}
|
||||
|
||||
if (!$rootScope.vaultCollections) {
|
||||
$rootScope.vaultCollections = [];
|
||||
delayLoad = false;
|
||||
$scope.loaded = false;
|
||||
}
|
||||
|
||||
if (delayLoad) {
|
||||
$timeout(setScrollY, 100);
|
||||
$timeout(loadVault, 1000);
|
||||
}
|
||||
else if (!syncOnLoad) {
|
||||
loadVault();
|
||||
}
|
||||
|
||||
function loadVault() {
|
||||
var decFolders = [];
|
||||
var decCollections = [];
|
||||
var decCiphers = [];
|
||||
|
||||
var folderPromise = folderService.getAllDecrypted().then(function (folders) {
|
||||
decFolders = folders;
|
||||
});
|
||||
|
||||
var collectionPromise = collectionService.getAllDecrypted().then(function (collections) {
|
||||
decCollections = collections;
|
||||
});
|
||||
|
||||
var cipherPromise = cipherService.getAllDecrypted().then(function (ciphers) {
|
||||
decCiphers = ciphers;
|
||||
});
|
||||
|
||||
$q.all([folderPromise, collectionPromise, cipherPromise]).then(function () {
|
||||
$scope.loaded = true;
|
||||
$rootScope.vaultFolders = decFolders;
|
||||
$rootScope.vaultCollections = decCollections;
|
||||
$rootScope.vaultCiphers = decCiphers;
|
||||
$scope.favoriteCiphers = $filter('filter')($rootScope.vaultCiphers, { favorite: true });
|
||||
|
||||
if (!$rootScope.vaultCollections || !$rootScope.vaultCollections.length) {
|
||||
$scope.noFolderCiphers = $filter('filter')($rootScope.vaultCiphers, { folderId: null });
|
||||
if ($scope.noFolderCiphers.length >= 100) {
|
||||
$scope.noFolderCiphers = null;
|
||||
}
|
||||
|
||||
if ($scope.noFolderCiphers && $rootScope.vaultFolders && $rootScope.vaultFolders.length &&
|
||||
!$rootScope.vaultFolders[$rootScope.vaultFolders.length - 1].id) {
|
||||
$rootScope.vaultFolders = $rootScope.vaultFolders.slice(0, $rootScope.vaultFolders.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($scope.showGroupingCounts) {
|
||||
var folderCounts = { 'none': 0 };
|
||||
var collectionCounts = {};
|
||||
|
||||
decCiphers.forEach((cipher) => {
|
||||
if (cipher.folderId) {
|
||||
if (!folderCounts.hasOwnProperty(cipher.folderId)) {
|
||||
folderCounts[cipher.folderId] = 0;
|
||||
}
|
||||
folderCounts[cipher.folderId]++;
|
||||
}
|
||||
else {
|
||||
folderCounts.none++;
|
||||
}
|
||||
|
||||
if (cipher.collectionIds) {
|
||||
cipher.collectionIds.forEach((collectionId) => {
|
||||
if (!collectionCounts.hasOwnProperty(collectionId)) {
|
||||
collectionCounts[collectionId] = 0;
|
||||
}
|
||||
collectionCounts[collectionId]++;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.vaultFolders.forEach((folder) => {
|
||||
folder.itemCount = folderCounts[folder.id || 'none'] || 0;
|
||||
});
|
||||
|
||||
$rootScope.vaultCollections.forEach((collection) => {
|
||||
collection.itemCount = collectionCounts[collection.id] || 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (!delayLoad) {
|
||||
setScrollY();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.searchText = null;
|
||||
if (state.searchText || $stateParams.searchText) {
|
||||
$scope.searchText = state.searchText || $stateParams.searchText;
|
||||
}
|
||||
|
||||
$scope.searchCiphers = function () {
|
||||
if (!$scope.searchText || $scope.searchText.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
return searchCipher;
|
||||
};
|
||||
|
||||
function searchCipher(cipher) {
|
||||
var searchTerm = $scope.searchText.toLowerCase();
|
||||
if (cipher.name && cipher.name.toLowerCase().indexOf(searchTerm) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (cipher.subTitle && cipher.subTitle.toLowerCase().indexOf(searchTerm) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (cipher.login && cipher.login.uri && cipher.login.uri.toLowerCase().indexOf(searchTerm) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$scope.addCipher = function () {
|
||||
storeState();
|
||||
$state.go('addCipher', {
|
||||
animation: 'in-slide-up',
|
||||
from: 'vault'
|
||||
});
|
||||
};
|
||||
|
||||
$scope.viewCipher = function (cipher) {
|
||||
var canLaunch = cipher.login && cipher.login.uri &&
|
||||
(cipher.login.uri.startsWith('http://') || cipher.login.uri.startsWith('https://'));
|
||||
if (canLaunch && cipher.clicked) {
|
||||
cipher.cancelClick = true;
|
||||
cipher.clicked = false;
|
||||
$scope.launchWebsite(cipher);
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.clicked = true;
|
||||
cipher.cancelClick = false;
|
||||
|
||||
$timeout(function () {
|
||||
if (cipher.cancelClick) {
|
||||
cipher.cancelClick = false;
|
||||
cipher.clicked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
storeState();
|
||||
$state.go('viewCipher', {
|
||||
cipherId: cipher.id,
|
||||
animation: 'in-slide-up',
|
||||
from: 'vault'
|
||||
});
|
||||
|
||||
// clean up
|
||||
cipher.cancelClick = false;
|
||||
cipher.clicked = false;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
$scope.launchWebsite = function (cipher) {
|
||||
if (cipher.login && cipher.login.uri) {
|
||||
$analytics.eventTrack('Launched Website');
|
||||
BrowserApi.createNewTab(cipher.login.uri);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.viewGrouping = function (grouping, folder) {
|
||||
storeState();
|
||||
$state.go('viewGrouping', {
|
||||
folderId: (folder && grouping.id) || '0',
|
||||
collectionId: (!folder && grouping.id) || '0',
|
||||
animation: 'in-slide-left'
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on('syncCompleted', function (event, successfully) {
|
||||
$timeout(loadVault, 500);
|
||||
});
|
||||
|
||||
function storeState() {
|
||||
stateService.saveState(stateKey, {
|
||||
scrollY: getScrollY(),
|
||||
searchText: $scope.searchText
|
||||
});
|
||||
}
|
||||
|
||||
function getScrollY() {
|
||||
var content = document.getElementsByClassName('content')[0];
|
||||
return content.scrollTop;
|
||||
}
|
||||
|
||||
function setScrollY() {
|
||||
if (state.scrollY) {
|
||||
var content = document.getElementsByClassName('content')[0];
|
||||
content.scrollTop = state.scrollY;
|
||||
}
|
||||
}
|
||||
});
|
@ -1,242 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultEditCipherController', function ($scope, $state, $stateParams, cipherService, folderService,
|
||||
cryptoService, toastr, SweetAlert, platformUtilsService, $analytics, i18nService, constantsService, $timeout,
|
||||
popupUtilsService, auditService) {
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
document.getElementById('name').focus();
|
||||
}, 500);
|
||||
|
||||
$scope.i18n = i18nService;
|
||||
$scope.constants = constantsService;
|
||||
$scope.showAttachments = !platformUtilsService.isEdge();
|
||||
$scope.addFieldType = constantsService.fieldType.text.toString();
|
||||
$scope.selectedType = constantsService.cipherType.login.toString();
|
||||
var cipherId = $stateParams.cipherId;
|
||||
var fromView = $stateParams.fromView;
|
||||
var from = $stateParams.from;
|
||||
|
||||
$scope.cipher = {
|
||||
folderId: null
|
||||
};
|
||||
|
||||
if ($stateParams.cipher) {
|
||||
angular.extend($scope.cipher, $stateParams.cipher);
|
||||
setUriMatchValues();
|
||||
}
|
||||
else {
|
||||
cipherService.get(cipherId).then(function (cipher) {
|
||||
return cipher.decrypt();
|
||||
}).then(function (model) {
|
||||
$scope.cipher = model;
|
||||
setUriMatchValues();
|
||||
});
|
||||
}
|
||||
|
||||
folderService.getAllDecrypted().then(function (folders) {
|
||||
$scope.folders = folders;
|
||||
});
|
||||
|
||||
$scope.typeChanged = function () {
|
||||
$scope.cipher.type = parseInt($scope.selectedType);
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
$scope.savePromise = null;
|
||||
$scope.save = function () {
|
||||
if (!$scope.cipher.name || $scope.cipher.name === '') {
|
||||
toastr.error(i18nService.nameRequired, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.savePromise = cipherService.encrypt($scope.cipher).then(function (cipherModel) {
|
||||
var cipher = new Cipher(cipherModel, true);
|
||||
return cipherService.saveWithServer(cipher).then(function (c) {
|
||||
$analytics.eventTrack('Edited Cipher');
|
||||
toastr.success(i18nService.editedItem);
|
||||
$scope.close();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.delete = function () {
|
||||
SweetAlert.swal({
|
||||
title: i18nService.deleteItem,
|
||||
text: i18nService.deleteItemConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: i18nService.yes,
|
||||
cancelButtonText: i18nService.no
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
cipherService.deleteWithServer(cipherId).then(function () {
|
||||
$analytics.eventTrack('Deleted Cipher');
|
||||
toastr.success(i18nService.deletedItem);
|
||||
$state.go('tabs.vault', {
|
||||
animation: 'out-slide-down'
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.attachments = function () {
|
||||
$state.go('attachments', {
|
||||
id: cipherId,
|
||||
animation: 'in-slide-up',
|
||||
from: from,
|
||||
fromView: fromView
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
if (fromView) {
|
||||
$state.go('viewCipher', {
|
||||
cipherId: cipherId,
|
||||
animation: 'out-slide-down',
|
||||
from: from
|
||||
});
|
||||
}
|
||||
else {
|
||||
$state.go('tabs.vault', {
|
||||
animation: 'out-slide-down'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showPassword = false;
|
||||
$scope.togglePassword = function () {
|
||||
$analytics.eventTrack('Toggled Password');
|
||||
$scope.showPassword = !$scope.showPassword;
|
||||
};
|
||||
|
||||
$scope.checkPassword = function () {
|
||||
if (!$scope.cipher.login || !$scope.cipher.login.password || $scope.cipher.login.password === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$analytics.eventTrack('Check Password');
|
||||
auditService.passwordLeaked($scope.cipher.login.password).then(function (matches) {
|
||||
if (matches != 0) {
|
||||
toastr.error(i18nService.passwordExposed);
|
||||
} else {
|
||||
toastr.success(i18nService.passwordSafe);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addUri = function () {
|
||||
if (!$scope.cipher.login) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.cipher.login.uris) {
|
||||
$scope.cipher.login.uris = [];
|
||||
}
|
||||
|
||||
$scope.cipher.login.uris.push({
|
||||
uri: null,
|
||||
match: null,
|
||||
matchValue: null
|
||||
});
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
$scope.removeUri = function (uri) {
|
||||
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
|
||||
return;
|
||||
}
|
||||
|
||||
var index = $scope.cipher.login.uris.indexOf(uri);
|
||||
if (index > -1) {
|
||||
$scope.cipher.login.uris.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.uriMatchChanged = function (uri) {
|
||||
uri.showOptions = uri.showOptions == null ? true : uri.showOptions;
|
||||
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
|
||||
uri.match = null;
|
||||
}
|
||||
else {
|
||||
uri.match = parseInt(uri.matchValue);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleUriOptions = function (u) {
|
||||
u.showOptions = u.showOptions == null && u.match != null ? false : !u.showOptions;
|
||||
};
|
||||
|
||||
$scope.addField = function (type) {
|
||||
if (!$scope.cipher.fields) {
|
||||
$scope.cipher.fields = [];
|
||||
}
|
||||
|
||||
$scope.cipher.fields.push({
|
||||
type: parseInt(type),
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.cipher.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.cipher.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.generatePassword = function () {
|
||||
if ($scope.cipher.login.password) {
|
||||
SweetAlert.swal({
|
||||
title: i18nService.overwritePassword,
|
||||
text: i18nService.overwritePasswordConfirmation,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: i18nService.yes,
|
||||
cancelButtonText: i18nService.no
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
goPasswordGenerator();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
goPasswordGenerator();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function goPasswordGenerator() {
|
||||
$analytics.eventTrack('Clicked Generate Password');
|
||||
$state.go('passwordGenerator', {
|
||||
animation: 'in-slide-up',
|
||||
editState: {
|
||||
fromView: fromView,
|
||||
cipherId: cipherId,
|
||||
cipher: $scope.cipher,
|
||||
from: from
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setUriMatchValues() {
|
||||
if ($scope.cipher.login && $scope.cipher.login.uris) {
|
||||
for (var i = 0; i < $scope.cipher.login.uris.length; i++) {
|
||||
$scope.cipher.login.uris[i].matchValue =
|
||||
$scope.cipher.login.uris[i].match || $scope.cipher.login.uris[i].match === 0 ?
|
||||
$scope.cipher.login.uris[i].match.toString() : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,2 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault', ['ngAnimate', 'toastr', 'ngclipboard', 'oitozero.ngSweetAlert', 'infinite-scroll']);
|
@ -1,216 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultViewCipherController', function ($scope, $state, $stateParams, cipherService, toastr,
|
||||
$analytics, i18nService, platformUtilsService, totpService, $timeout, tokenService, $window, cryptoService,
|
||||
SweetAlert, constantsService, auditService) {
|
||||
$scope.constants = constantsService;
|
||||
$scope.i18n = i18nService;
|
||||
$scope.showAttachments = !platformUtilsService.isEdge();
|
||||
var from = $stateParams.from,
|
||||
totpInterval = null;
|
||||
|
||||
$scope.isPremium = tokenService.getPremium();
|
||||
$scope.cipher = null;
|
||||
var cipherObj = null;
|
||||
cipherService.get($stateParams.cipherId).then(function (cipher) {
|
||||
if (!cipher) {
|
||||
return;
|
||||
}
|
||||
|
||||
cipherObj = cipher;
|
||||
return cipher.decrypt();
|
||||
}).then(function (model) {
|
||||
$timeout(function () {
|
||||
$scope.cipher = model;
|
||||
if (model.login && model.login.totp && (cipherObj.organizationUseTotp || tokenService.getPremium())) {
|
||||
totpUpdateCode();
|
||||
totpTick();
|
||||
|
||||
if (totpInterval) {
|
||||
clearInterval(totpInterval);
|
||||
}
|
||||
|
||||
totpInterval = setInterval(function () {
|
||||
totpTick();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.edit = function (cipher) {
|
||||
$state.go('editCipher', {
|
||||
animation: 'in-slide-up',
|
||||
cipherId: cipher.id,
|
||||
fromView: true,
|
||||
from: from
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleFieldValue = function (field) {
|
||||
field.showValue = !field.showValue;
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
if (from === 'current') {
|
||||
$state.go('tabs.current', {
|
||||
animation: 'out-slide-down'
|
||||
});
|
||||
}
|
||||
else if (from === 'grouping') {
|
||||
$state.go('viewGrouping', {
|
||||
animation: 'out-slide-down'
|
||||
});
|
||||
}
|
||||
else {
|
||||
$state.go('tabs.vault', {
|
||||
animation: 'out-slide-down'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.launch = function (uri) {
|
||||
if (!uri.canLaunch) {
|
||||
return;
|
||||
}
|
||||
|
||||
$analytics.eventTrack('Launched Login URI');
|
||||
BrowserApi.createNewTab(uri.uri);
|
||||
};
|
||||
|
||||
$scope.clipboardError = function (e, password) {
|
||||
toastr.info(i18n.browserNotSupportClipboard);
|
||||
};
|
||||
|
||||
$scope.clipboardSuccess = function (e, type, aType) {
|
||||
e.clearSelection();
|
||||
$analytics.eventTrack('Copied ' + aType);
|
||||
toastr.info(type + i18nService.valueCopied);
|
||||
};
|
||||
|
||||
$scope.showPassword = false;
|
||||
$scope.togglePassword = function () {
|
||||
$analytics.eventTrack('Toggled Password');
|
||||
$scope.showPassword = !$scope.showPassword;
|
||||
};
|
||||
|
||||
$scope.download = function (attachment) {
|
||||
if (!$scope.cipher.organizationId && !tokenService.getPremium()) {
|
||||
SweetAlert.swal({
|
||||
title: i18nService.premiumRequired,
|
||||
text: i18nService.premiumRequiredDesc,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: i18nService.learnMore,
|
||||
cancelButtonText: i18nService.cancel
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
BrowserApi.createNewTab('https://bitwarden.com');
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (attachment.downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
attachment.downloading = true;
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', attachment.url, true);
|
||||
req.responseType = 'arraybuffer';
|
||||
req.onload = function (evt) {
|
||||
if (!req.response) {
|
||||
toastr.error(i18n.errorsOccurred);
|
||||
$timeout(function () {
|
||||
attachment.downloading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
cryptoService.getOrgKey($scope.cipher.organizationId).then(function (key) {
|
||||
return cryptoService.decryptFromBytes(req.response, key);
|
||||
}).then(function (decBuf) {
|
||||
BrowserApi.downloadFile($window, decBuf, null, attachment.fileName);
|
||||
|
||||
$timeout(function () {
|
||||
attachment.downloading = false;
|
||||
});
|
||||
}, function () {
|
||||
toastr.error(i18n.errorsOccurred);
|
||||
$timeout(function () {
|
||||
attachment.downloading = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
req.send(null);
|
||||
};
|
||||
|
||||
$scope.$on("$destroy", function () {
|
||||
if (totpInterval) {
|
||||
clearInterval(totpInterval);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.formatYear = function (year) {
|
||||
if (year.length == 2) {
|
||||
return '20' + year;
|
||||
}
|
||||
|
||||
return year;
|
||||
};
|
||||
|
||||
$scope.maskValue = function (value) {
|
||||
return value ? '••••••••' : null;
|
||||
};
|
||||
|
||||
function totpUpdateCode() {
|
||||
if ($scope.cipher.type !== constantsService.cipherType.login || !$scope.cipher.login.totp) {
|
||||
return;
|
||||
}
|
||||
|
||||
totpService.getCode($scope.cipher.login.totp).then(function (code) {
|
||||
$timeout(function () {
|
||||
if (code) {
|
||||
$scope.totpCodeFormatted = code.substring(0, 3) + ' ' + code.substring(3);
|
||||
$scope.totpCode = code;
|
||||
}
|
||||
else {
|
||||
$scope.totpCode = $scope.totpCodeFormatted = null;
|
||||
if (totpInterval) {
|
||||
clearInterval(totpInterval);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$scope.checkPassword = function () {
|
||||
if (!$scope.cipher.login || !$scope.cipher.login.password || $scope.cipher.login.password === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$analytics.eventTrack('Check Password');
|
||||
auditService.passwordLeaked($scope.cipher.login.password).then(function (matches) {
|
||||
if (matches != 0) {
|
||||
toastr.error(i18nService.passwordExposed);
|
||||
} else {
|
||||
toastr.success(i18nService.passwordSafe);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function totpTick() {
|
||||
$timeout(function () {
|
||||
var epoch = Math.round(new Date().getTime() / 1000.0);
|
||||
var mod = epoch % 30;
|
||||
var sec = 30 - mod;
|
||||
|
||||
$scope.totpSec = sec;
|
||||
$scope.totpDash = (2.62 * mod).toFixed(2);
|
||||
$scope.totpLow = sec <= 7;
|
||||
if (mod === 0) {
|
||||
totpUpdateCode();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -1,243 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultViewGroupingController', function ($scope, cipherService, folderService, $q, $state, $stateParams, toastr,
|
||||
syncService, $analytics, i18nService, stateService, platformUtilsService, $timeout, $window, collectionService) {
|
||||
var stateKey = 'viewGrouping',
|
||||
state = stateService.getState(stateKey) || {};
|
||||
|
||||
state.folderId = $stateParams.folderId || state.folderId;
|
||||
state.collectionId = $stateParams.collectionId || state.collectionId;
|
||||
|
||||
var pageSize = 100,
|
||||
decGrouping = null,
|
||||
decCiphers = [];
|
||||
|
||||
$scope.grouping = {
|
||||
id: null,
|
||||
name: i18nService.noneFolder
|
||||
};
|
||||
$scope.folderGrouping = true;
|
||||
$scope.collectionGrouping = false;
|
||||
|
||||
if (state.folderId && state.folderId !== '0') {
|
||||
$scope.grouping.id = state.folderId;
|
||||
}
|
||||
else if (state.collectionId && state.collectionId !== '0') {
|
||||
$scope.grouping.id = state.collectionId;
|
||||
$scope.folderGrouping = false;
|
||||
$scope.collectionGrouping = true;
|
||||
}
|
||||
|
||||
$scope.i18n = i18nService;
|
||||
document.getElementById('search').focus();
|
||||
|
||||
$scope.loaded = false;
|
||||
$scope.vaultCiphers = [];
|
||||
$scope.pagedVaultCiphers = [];
|
||||
$scope.searchText = null;
|
||||
loadVault();
|
||||
|
||||
function loadVault() {
|
||||
var promises = [];
|
||||
|
||||
if ($scope.grouping.id && $scope.folderGrouping) {
|
||||
var getFolderPromise = folderService.get($scope.grouping.id).then(function (folder) {
|
||||
return folder.decrypt();
|
||||
}).then(function (model) {
|
||||
decGrouping = model;
|
||||
});
|
||||
promises.push(getFolderPromise);
|
||||
}
|
||||
else if ($scope.grouping.id && $scope.collectionGrouping) {
|
||||
var getCollectionPromise = collectionService.get($scope.grouping.id).then(function (collection) {
|
||||
return collection.decrypt();
|
||||
}).then(function (model) {
|
||||
decGrouping = model;
|
||||
});
|
||||
promises.push(getCollectionPromise);
|
||||
}
|
||||
|
||||
var cipherPromise = cipherService.getAllDecryptedForGrouping($scope.grouping.id, $scope.folderGrouping)
|
||||
.then(function (ciphers) {
|
||||
if (platformUtilsService.isEdge()) {
|
||||
// Edge is super slow at sorting
|
||||
decCiphers = ciphers;
|
||||
}
|
||||
else {
|
||||
decCiphers = ciphers.sort(cipherSort);
|
||||
}
|
||||
});
|
||||
promises.push(cipherPromise);
|
||||
|
||||
$q.all(promises).then(function () {
|
||||
$scope.loaded = true;
|
||||
$scope.vaultCiphers = decCiphers;
|
||||
|
||||
if (decGrouping) {
|
||||
$scope.grouping.name = decGrouping.name;
|
||||
}
|
||||
|
||||
if (state.searchText) {
|
||||
$scope.searchText = state.searchText;
|
||||
$scope.searchCiphers();
|
||||
}
|
||||
|
||||
$timeout(setScrollY, 200);
|
||||
});
|
||||
}
|
||||
|
||||
function cipherSort(a, b) {
|
||||
if (!a.name) {
|
||||
return -1;
|
||||
}
|
||||
if (!b.name) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var aName = a.name.toLowerCase(),
|
||||
bName = b.name.toLowerCase();
|
||||
if (aName > bName) {
|
||||
return 1;
|
||||
}
|
||||
if (aName < bName) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!a.subTitle) {
|
||||
return -1;
|
||||
}
|
||||
if (!b.subTitle) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var aSubTitle = a.subTitle.toLowerCase(),
|
||||
bSubTitle = b.subTitle.toLowerCase();
|
||||
if (aSubTitle > bSubTitle) {
|
||||
return 1;
|
||||
}
|
||||
if (aSubTitle < bSubTitle) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
}
|
||||
|
||||
$scope.loadMore = function () {
|
||||
var pagedLength = $scope.pagedVaultCiphers.length;
|
||||
if ($scope.vaultCiphers.length > pagedLength) {
|
||||
$scope.pagedVaultCiphers =
|
||||
$scope.pagedVaultCiphers.concat($scope.vaultCiphers.slice(pagedLength, pagedLength + pageSize));
|
||||
}
|
||||
};
|
||||
|
||||
$scope.searchCiphers = function () {
|
||||
if (!$scope.searchText || $scope.searchText.length < 2) {
|
||||
if ($scope.vaultCiphers.length !== decCiphers.length) {
|
||||
resetList(decCiphers);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var matchedCiphers = [];
|
||||
for (var i = 0; i < decCiphers.length; i++) {
|
||||
if (searchCipher(decCiphers[i])) {
|
||||
matchedCiphers.push(decCiphers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
resetList(matchedCiphers);
|
||||
};
|
||||
|
||||
function resetList(ciphers) {
|
||||
$scope.vaultCiphers = ciphers;
|
||||
$scope.pagedVaultCiphers = [];
|
||||
$scope.loadMore();
|
||||
}
|
||||
|
||||
function searchCipher(cipher) {
|
||||
var searchTerm = $scope.searchText.toLowerCase();
|
||||
if (cipher.name && cipher.name.toLowerCase().indexOf(searchTerm) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (cipher.subTitle && cipher.subTitle.toLowerCase().indexOf(searchTerm) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (cipher.login && cipher.login.uri && cipher.login.uri.toLowerCase().indexOf(searchTerm) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$scope.addCipher = function () {
|
||||
storeState();
|
||||
$state.go('addCipher', {
|
||||
animation: 'in-slide-up',
|
||||
from: 'grouping',
|
||||
folderId: state.folderId
|
||||
});
|
||||
};
|
||||
|
||||
$scope.viewCipher = function (cipher) {
|
||||
var canLaunch = cipher.login && cipher.login.uri &&
|
||||
(cipher.login.uri.startsWith('http://') || cipher.login.uri.startsWith('https://'));
|
||||
if (canLaunch && cipher.clicked) {
|
||||
cipher.cancelClick = true;
|
||||
cipher.clicked = false;
|
||||
$scope.launchWebsite(cipher);
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.clicked = true;
|
||||
cipher.cancelClick = false;
|
||||
|
||||
$timeout(function () {
|
||||
if (cipher.cancelClick) {
|
||||
cipher.cancelClick = false;
|
||||
cipher.clicked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
storeState();
|
||||
$state.go('viewCipher', {
|
||||
cipherId: cipher.id,
|
||||
animation: 'in-slide-up',
|
||||
from: 'grouping'
|
||||
});
|
||||
|
||||
// clean up
|
||||
cipher.cancelClick = false;
|
||||
cipher.clicked = false;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
$scope.launchWebsite = function (cipher) {
|
||||
if (cipher.login && cipher.login.uri) {
|
||||
$analytics.eventTrack('Launched Website');
|
||||
BrowserApi.createNewTab(cipher.login.uri);
|
||||
}
|
||||
};
|
||||
|
||||
function storeState() {
|
||||
angular.extend(state, {
|
||||
scrollY: getScrollY(),
|
||||
searchText: $scope.searchText
|
||||
});
|
||||
|
||||
stateService.saveState(stateKey, state);
|
||||
}
|
||||
|
||||
function getScrollY() {
|
||||
var content = document.getElementsByClassName('content')[0];
|
||||
return content.scrollTop;
|
||||
}
|
||||
|
||||
function setScrollY() {
|
||||
if (state.scrollY) {
|
||||
var content = document.getElementsByClassName('content')[0];
|
||||
content.scrollTop = state.scrollY;
|
||||
}
|
||||
}
|
||||
});
|
@ -1,128 +0,0 @@
|
||||
<div class="header header-search">
|
||||
<pop-out ng-if="showPopout" class="left"></pop-out>
|
||||
<div class="search" ng-style="{'visibility': disableSearch ? 'hidden' : 'visible'}">
|
||||
<input type="search" placeholder="{{::i18n.searchVault}}" id="search" ng-model="searchText" />
|
||||
<i class="fa fa-search"></i>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a href="" ng-click="addCipher()" title="{{::i18n.addItem}}"><i class="fa fa-plus fa-lg"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content content-tabs">
|
||||
<!-- Grouping List -->
|
||||
<div ng-if="vaultCiphers.length && (!searchText || searchText.length < 2)">
|
||||
<div class="list">
|
||||
<div class="list-section" ng-if="favoriteCiphers.length">
|
||||
<div class="list-section-header">
|
||||
{{::i18n.favorites}}
|
||||
<span>{{favoriteCiphers.length}}</span>
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<a href="#" stop-click ng-click="viewCipher(cipher)" class="list-section-item condensed"
|
||||
title="{{::i18n.view}}"
|
||||
ng-repeat="cipher in favoriteCiphers track by $index">
|
||||
<action-buttons cipher="cipher"></action-buttons>
|
||||
<icon cipher="cipher"></icon>
|
||||
<span class="text">
|
||||
{{cipher.name}}
|
||||
<i class="fa fa-share-alt text-muted" ng-if="cipher.organizationId" title="{{::i18n.shared}}"></i>
|
||||
<i class="fa fa-paperclip text-muted" ng-if="cipher.attachments" title="{{::i18n.attachments}}"></i>
|
||||
</span>
|
||||
<span class="detail">{{cipher.subTitle}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="vaultFolders.length">
|
||||
<div class="list-section-header">
|
||||
{{::i18n.folders}}
|
||||
<span>{{vaultFolders.length}}</span>
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<a href="#" stop-click ng-click="viewGrouping(folder, true)" class="list-section-item"
|
||||
ng-repeat="folder in vaultFolders track by $index">
|
||||
<div class="pull-right">
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
<span class="item-sub-label" ng-if="showGroupingCounts">{{folder.itemCount}}</span>
|
||||
</div>
|
||||
<div class="icon single-line">
|
||||
<i class="fa fa-fw fa-lg"
|
||||
ng-class="{'fa-folder-open': folder.id, 'fa-folder-open-o': !folder.id}"></i>
|
||||
</div>
|
||||
<span class="text">
|
||||
{{folder.name}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="vaultCollections.length">
|
||||
<div class="list-section-header">
|
||||
{{::i18n.collections}}
|
||||
<span>{{vaultCollections.length}}</span>
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<a href="#" stop-click ng-click="viewGrouping(collection, false)" class="list-section-item"
|
||||
ng-repeat="collection in vaultCollections track by $index">
|
||||
<div class="pull-right">
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
<span class="item-sub-label" ng-if="showGroupingCounts">{{collection.itemCount}}</span>
|
||||
</div>
|
||||
<div class="icon single-line">
|
||||
<i class="fa fa-fw fa-lg fa-cube"></i>
|
||||
</div>
|
||||
<span class="text">
|
||||
{{collection.name}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="noFolderCiphers && noFolderCiphers.length">
|
||||
<div class="list-section-header">
|
||||
{{::i18n.noneFolder}}
|
||||
<span>{{noFolderCiphers.length}}</span>
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<a href="#" stop-click ng-click="viewCipher(cipher)" class="list-section-item condensed"
|
||||
title="{{::i18n.view}}"
|
||||
ng-repeat="cipher in noFolderCiphers track by $index">
|
||||
<action-buttons cipher="cipher"></action-buttons>
|
||||
<icon cipher="cipher"></icon>
|
||||
<span class="text">
|
||||
{{cipher.name}}
|
||||
<i class="fa fa-share-alt text-muted" ng-if="cipher.organizationId" title="{{::i18n.shared}}"></i>
|
||||
<i class="fa fa-paperclip text-muted" ng-if="cipher.attachments" title="{{::i18n.attachments}}"></i>
|
||||
</span>
|
||||
<span class="detail">{{cipher.subTitle}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Search Results List -->
|
||||
<div ng-if="vaultCiphers.length && searchText && searchText.length >= 2">
|
||||
<div class="list">
|
||||
<div class="list-section" style="padding-top: 0; padding-bottom: 0;">
|
||||
<a href="#" stop-click ng-click="viewCipher(cipher)"
|
||||
class="list-section-item condensed" title="{{::i18n.view}}"
|
||||
ng-repeat="cipher in searchResults = (vaultCiphers | filter: searchCiphers()) track by $index">
|
||||
<action-buttons cipher="cipher"></action-buttons>
|
||||
<icon cipher="cipher"></icon>
|
||||
<span class="text">
|
||||
{{cipher.name}}
|
||||
<i class="fa fa-share-alt text-muted" ng-if="cipher.organizationId" title="{{::i18n.shared}}"></i>
|
||||
<i class="fa fa-paperclip text-muted" ng-if="cipher.attachments" title="{{::i18n.attachments}}"></i>
|
||||
</span>
|
||||
<span class="detail">{{cipher.subTitle}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="centered-message" ng-if="loaded && !vaultCiphers.length">
|
||||
<p>
|
||||
{{i18n.noItemsInList}}
|
||||
<button ng-click="addCipher()" style="margin-top: 20px;" class="btn btn-link btn-block">{{::i18n.addItem}}</button>
|
||||
</p>
|
||||
</div>
|
||||
<div class="page-loading" ng-if="!loaded">
|
||||
<i class="fa fa-lg fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
@ -1,306 +0,0 @@
|
||||
<form name="theForm" ng-submit="save()" bit-form="savePromise" autocomplete="off">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ng-click="close()" href="">{{i18n.cancel}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.save}}</button>
|
||||
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||
</div>
|
||||
<div class="title">{{i18n.addItem}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{i18n.itemInformation}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="type" class="item-label">{{i18n.type}}</label>
|
||||
<select id="type" name="Type" ng-model="selectedType" ng-change="typeChanged()">
|
||||
<option value="{{constants.cipherType.login}}">{{i18n.typeLogin}}</option>
|
||||
<option value="{{constants.cipherType.card}}">{{i18n.typeCard}}</option>
|
||||
<option value="{{constants.cipherType.identity}}">{{i18n.typeIdentity}}</option>
|
||||
<option value="{{constants.cipherType.secureNote}}">{{i18n.typeSecureNote}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="name" class="item-label">{{i18n.name}}</label>
|
||||
<input id="name" type="text" name="Name" ng-model="cipher.name">
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-item">
|
||||
<label for="loginUsername" class="item-label">{{i18n.username}}</label>
|
||||
<input id="loginUsername" type="text" name="Login.Username" ng-model="cipher.login.username">
|
||||
</div>
|
||||
<div class="list-section-item flex">
|
||||
<div class="flex-grow">
|
||||
<label for="loginPassword" class="item-label">{{i18n.password}}</label>
|
||||
<input id="loginPassword" type="{{showPassword ? 'text' : 'password'}}" name="Login.Password" ng-model="cipher.login.password">
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.checkPassword}}" ng-click="checkPassword()">
|
||||
<i class="fa fa-lg fa-check-circle"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.togglePassword}}" ng-click="togglePassword()">
|
||||
<i class="fa fa-lg" ng-class="[{'fa-eye': !showPassword}, {'fa-eye-slash': showPassword}]"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.generatePassword}}" ng-click="generatePassword()">
|
||||
<i class="fa fa-lg fa-refresh"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="loginTotp" class="item-label">{{i18n.authenticatorKeyTotp}}</label>
|
||||
<input id="loginTotp" type="text" name="Login.Totp" ng-model="cipher.login.totp">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.card">
|
||||
<div class="list-section-item">
|
||||
<label for="cardCardholderName" class="item-label">{{i18n.cardholderName}}</label>
|
||||
<input id="cardCardholderName" type="text" name="Card.CardholderName"
|
||||
ng-model="cipher.card.cardholderName">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardNumber" class="item-label">{{i18n.number}}</label>
|
||||
<input id="cardNumber" type="text" name="Card.Number" ng-model="cipher.card.number">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardBrand" class="item-label">{{i18n.brand}}</label>
|
||||
<select id="cardBrand" name="Card.Brand" ng-model="cipher.card.brand">
|
||||
<option value="">-- {{i18n.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">{{i18n.other}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardExpMonth" class="item-label">{{i18n.expirationMonth}}</label>
|
||||
<select id="cardExpMonth" name="Card.ExpMonth" ng-model="cipher.card.expMonth">
|
||||
<option value="">-- {{i18n.select}} --</option>
|
||||
<option value="1">01 - {{i18n.january}}</option>
|
||||
<option value="2">02 - {{i18n.february}}</option>
|
||||
<option value="3">03 - {{i18n.march}}</option>
|
||||
<option value="4">04 - {{i18n.april}}</option>
|
||||
<option value="5">05 - {{i18n.may}}</option>
|
||||
<option value="6">06 - {{i18n.june}}</option>
|
||||
<option value="7">07 - {{i18n.july}}</option>
|
||||
<option value="8">08 - {{i18n.august}}</option>
|
||||
<option value="9">09 - {{i18n.september}}</option>
|
||||
<option value="10">10 - {{i18n.october}}</option>
|
||||
<option value="11">11 - {{i18n.november}}</option>
|
||||
<option value="12">12 - {{i18n.december}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardExpYear" class="item-label">{{i18n.expirationYear}}</label>
|
||||
<input id="cardExpYear" type="text" name="Card.ExpYear" ng-model="cipher.card.expYear"
|
||||
placeholder="{{i18n.ex}} 2019">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardCode" class="item-label">{{i18n.securityCode}}</label>
|
||||
<input id="cardCode" type="text" name="Card.Code" ng-model="cipher.card.code">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.identity">
|
||||
<div class="list-section-item">
|
||||
<label for="identityTitle" class="item-label">{{i18n.title}}</label>
|
||||
<select id="identityTitle" name="Identity.Title" ng-model="cipher.identity.title">
|
||||
<option value="">-- {{i18n.select}} --</option>
|
||||
<option value="{{i18n.mr}}">{{i18n.mr}}</option>
|
||||
<option value="{{i18n.mrs}}">{{i18n.mrs}}</option>
|
||||
<option value="{{i18n.ms}}">{{i18n.ms}}</option>
|
||||
<option value="{{i18n.dr}}">{{i18n.dr}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityFirstName" class="item-label">{{i18n.firstName}}</label>
|
||||
<input id="identityFirstName" type="text" name="Identity.FirstName"
|
||||
ng-model="cipher.identity.firstName">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityMiddleName" class="item-label">{{i18n.middleName}}</label>
|
||||
<input id="identityMiddleName" type="text" name="Identity.MiddleName"
|
||||
ng-model="cipher.identity.middleName">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityLastName" class="item-label">{{i18n.lastName}}</label>
|
||||
<input id="identityLastName" type="text" name="Identity.LastName"
|
||||
ng-model="cipher.identity.lastName">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityUsername" class="item-label">{{i18n.username}}</label>
|
||||
<input id="identityUsername" type="text" name="Identity.Username"
|
||||
ng-model="cipher.identity.username">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityCompany" class="item-label">{{i18n.company}}</label>
|
||||
<input id="identityCompany" type="text" name="Identity.Company" ng-model="cipher.identity.company">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identitySsn" class="item-label">{{i18n.ssn}}</label>
|
||||
<input id="identitySsn" type="text" name="Identity.SSN" ng-model="cipher.identity.ssn">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityPassportNumber" class="item-label">{{i18n.passportNumber}}</label>
|
||||
<input id="identityPassportNumber" type="text" name="Identity.PassportNumber"
|
||||
ng-model="cipher.identity.passportNumber">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityLicenseNumber" class="item-label">{{i18n.licenseNumber}}</label>
|
||||
<input id="identityLicenseNumber" type="text" name="Identity.LicenseNumber"
|
||||
ng-model="cipher.identity.licenseNumber">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityEmail" class="item-label">{{i18n.email}}</label>
|
||||
<input id="identityEmail" type="text" name="Identity.Email" ng-model="cipher.identity.email">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityPhone" class="item-label">{{i18n.phone}}</label>
|
||||
<input id="identityPhone" type="text" name="Identity.Phone" ng-model="cipher.identity.phone">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityAddress1" class="item-label">{{i18n.address1}}</label>
|
||||
<input id="identityAddress1" type="text" name="Identity.Address1" ng-model="cipher.identity.address1">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityAddress2" class="item-label">{{i18n.address2}}</label>
|
||||
<input id="identityAddress2" type="text" name="Identity.Address2" ng-model="cipher.identity.address2">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityAddress3" class="item-label">{{i18n.address3}}</label>
|
||||
<input id="identityAddress3" type="text" name="Identity.Address3" ng-model="cipher.identity.address3">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityCity" class="item-label">{{i18n.cityTown}}</label>
|
||||
<input id="identityCity" type="text" name="Identity.City" ng-model="cipher.identity.city">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityState" class="item-label">{{i18n.stateProvince}}</label>
|
||||
<input id="identityState" type="text" name="Identity.State" ng-model="cipher.identity.state">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityPostalCode" class="item-label">{{i18n.zipPostalCode}}</label>
|
||||
<input id="identityPostalCode" type="text" name="Identity.PostalCode"
|
||||
ng-model="cipher.identity.postalCode">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityCountry" class="item-label">{{i18n.country}}</label>
|
||||
<input id="identityCountry" type="text" name="Identity.Country" ng-model="cipher.identity.country">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.secureNote">
|
||||
<!-- Nothing for now -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-table"
|
||||
ng-if="cipher.login.uris && cipher.login.uris.length" ng-repeat="u in cipher.login.uris">
|
||||
<a href="#" stop-click ng-click="removeUri(u)" class="action-button text-danger">
|
||||
<i class="fa fa-minus-circle fa-lg"></i>
|
||||
</a>
|
||||
<div class="action-button-content">
|
||||
<label for="loginUri{{$index}}" class="item-label">{{i18n.uri}} {{$index + 1}}</label>
|
||||
<input id="loginUri{{$index}}" type="text" name="Login.Uris[{{$index}}].Uri"
|
||||
ng-model="u.uri" placeholder="{{i18n.ex}} https://google.com">
|
||||
<label for="loginUriMatch{{$index}}" class="sr-only">
|
||||
{{i18n.matchDetection}} {{$index + 1}}
|
||||
</label>
|
||||
<select id="loginUriMatch{{$index}}" name="Login.Uris[{{$index}}].Match"
|
||||
ng-hide="u.showOptions === false || (u.showOptions == null && u.match == null)"
|
||||
ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
|
||||
<option value="">{{i18n.defaultMatchDetection}}</option>
|
||||
<option value="0">{{i18n.baseDomain}}</option>
|
||||
<option value="1">{{i18n.host}}</option>
|
||||
<option value="2">{{i18n.startsWith}}</option>
|
||||
<option value="4">{{i18n.regEx}}</option>
|
||||
<option value="3">{{i18n.exact}}</option>
|
||||
<option value="5">{{i18n.never}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<a href="#" stop-click ng-click="toggleUriOptions(u)" class="action-button"
|
||||
title="{{i18n.toggleOptions}}">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
<a class="list-section-item text-primary" href="#" stop-click ng-click="addUri()">
|
||||
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{i18n.newUri}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="folder" class="item-label">{{i18n.folder}}</label>
|
||||
<select id="folder" name="FolderId" ng-model="cipher.folderId">
|
||||
<option ng-repeat="folder in folders track by $index" value="{{folder.id}}">
|
||||
{{folder.name}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="favorite">{{i18n.favorite}}</label>
|
||||
<input id="favorite" name="Favorite" type="checkbox" ng-model="cipher.favorite">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
<label for="notes">{{i18n.notes}}</label>
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<textarea id="notes" name="Notes" rows="5" ng-model="cipher.notes"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{i18n.customFields}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-table"
|
||||
ng-if="cipher.fields && cipher.fields.length" ng-repeat="field in cipher.fields"
|
||||
ng-class="{'list-section-item-checkbox' : field.type === constants.fieldType.boolean}">
|
||||
<a href="#" stop-click ng-click="removeField(field)" class="action-button text-danger">
|
||||
<i class="fa fa-minus-circle fa-lg"></i>
|
||||
</a>
|
||||
<div class="action-button-content">
|
||||
<input id="field_name{{$index}}" type="text" name="Field.Name{{$index}}"
|
||||
ng-model="field.name" class="item-label"
|
||||
placeholder="{{i18n.name}}">
|
||||
<input id="field_value{{$index}}" type="text" name="Field.Value{{$index}}"
|
||||
ng-model="field.value" ng-if="field.type === constants.fieldType.text"
|
||||
placeholder="{{i18n.value}}">
|
||||
<input id="field_value{{$index}}" type="password" name="Field.Value{{$index}}"
|
||||
ng-model="field.value" ng-if="field.type === constants.fieldType.hidden"
|
||||
placeholder="{{i18n.value}}">
|
||||
<input id="field_value{{$index}}" name="Field.Value{{$index}}" type="checkbox"
|
||||
ng-model="field.value" data-ng-true-value="'true'"
|
||||
ng-if="field.type === constants.fieldType.boolean">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<a href="#" stop-click ng-click="addField(addFieldType)">
|
||||
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{i18n.newCustomField}}
|
||||
</a>
|
||||
<select ng-model="addFieldType" class="field-type">
|
||||
<option value="0">{{i18n.cfTypeText}}</option>
|
||||
<option value="1">{{i18n.cfTypeHidden}}</option>
|
||||
<option value="2">{{i18n.cfTypeBoolean}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,47 +0,0 @@
|
||||
<form name="theForm" ng-submit="submit()" bit-form="submitPromise" autocomplete="off">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a href="" ng-click="close()">{{i18n.close}}</a>
|
||||
</div>
|
||||
<div class="right" ng-if="isPremium">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.submit}}</button>
|
||||
<i class="fa fa-spinner fa-lg fa-spin no-animation" ng-show="theForm.$loading"></i>
|
||||
</div>
|
||||
<div class="title">{{i18n.attachments}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list list-no-selection">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item" ng-if="!cipher.attachments.length">
|
||||
{{i18n.noAttachments}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-repeat="attachment in cipher.attachments">
|
||||
<div class="action-buttons">
|
||||
<span class="btn-list no-padding" stop-prop stop-click title="{{i18n.deleteAttachment}}"
|
||||
ng-click="delete(attachment)">
|
||||
<i class="fa fa-lg fa-trash"></i>
|
||||
</span>
|
||||
</div>
|
||||
<small class="item-sub-label">{{attachment.sizeName}}</small>
|
||||
<span class="text">{{attachment.fileName}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="isPremium">
|
||||
<div class="list-section-header">
|
||||
{{i18n.newAttachment}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="file" class="item-label">{{i18n.file}}</label>
|
||||
<input type="file" id="file" name="file" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
{{i18n.maxFileSize}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,310 +0,0 @@
|
||||
<form name="theForm" ng-submit="save()" bit-form="savePromise" autocomplete="off">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ng-click="close()" href="">{{i18n.cancel}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.save}}</button>
|
||||
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||
</div>
|
||||
<div class="title">{{i18n.editItem}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{i18n.itemInformation}}
|
||||
<i class="fa fa-share-alt fa-lg pull-right" ng-if="cipher.organizationId" title="{{i18n.shared}}"></i>
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="name" class="item-label">{{i18n.name}}</label>
|
||||
<input id="name" type="text" name="Name" ng-model="cipher.name">
|
||||
</div>
|
||||
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-item">
|
||||
<label for="loginUsername" class="item-label">{{i18n.username}}</label>
|
||||
<input id="loginUsername" type="text" name="Login.Username" ng-model="cipher.login.username">
|
||||
</div>
|
||||
<div class="list-section-item flex">
|
||||
<div class="flex-grow">
|
||||
<label for="loginPassword" class="item-label">{{i18n.password}}</label>
|
||||
<input id="loginPassword" type="{{showPassword ? 'text' : 'password'}}" name="Login.Password" ng-model="cipher.login.password">
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.checkPassword}}" ng-click="checkPassword()">
|
||||
<i class="fa fa-lg fa-check-circle"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.togglePassword}}" ng-click="togglePassword()">
|
||||
<i class="fa fa-lg" ng-class="[{'fa-eye': !showPassword}, {'fa-eye-slash': showPassword}]"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.generatePassword}}" ng-click="generatePassword()">
|
||||
<i class="fa fa-lg fa-refresh"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="loginTotp" class="item-label">{{i18n.authenticatorKeyTotp}}</label>
|
||||
<input id="loginTotp" type="text" name="Login.Totp" ng-model="cipher.login.totp">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.card">
|
||||
<div class="list-section-item">
|
||||
<label for="cardCardholderName" class="item-label">{{i18n.cardholderName}}</label>
|
||||
<input id="cardCardholderName" type="text" name="Card.CardholderName"
|
||||
ng-model="cipher.card.cardholderName">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardNumber" class="item-label">{{i18n.number}}</label>
|
||||
<input id="cardNumber" type="text" name="Card.Number" ng-model="cipher.card.number">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardBrand" class="item-label">{{i18n.brand}}</label>
|
||||
<select id="cardBrand" name="Card.Brand" ng-model="cipher.card.brand">
|
||||
<option value="">-- {{i18n.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">{{i18n.other}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardExpMonth" class="item-label">{{i18n.expirationMonth}}</label>
|
||||
<select id="cardExpMonth" name="Card.ExpMonth" ng-model="cipher.card.expMonth">
|
||||
<option value="">-- {{i18n.select}} --</option>
|
||||
<option value="1">01 - {{i18n.january}}</option>
|
||||
<option value="2">02 - {{i18n.february}}</option>
|
||||
<option value="3">03 - {{i18n.march}}</option>
|
||||
<option value="4">04 - {{i18n.april}}</option>
|
||||
<option value="5">05 - {{i18n.may}}</option>
|
||||
<option value="6">06 - {{i18n.june}}</option>
|
||||
<option value="7">07 - {{i18n.july}}</option>
|
||||
<option value="8">08 - {{i18n.august}}</option>
|
||||
<option value="9">09 - {{i18n.september}}</option>
|
||||
<option value="10">10 - {{i18n.october}}</option>
|
||||
<option value="11">11 - {{i18n.november}}</option>
|
||||
<option value="12">12 - {{i18n.december}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardExpYear" class="item-label">{{i18n.expirationYear}}</label>
|
||||
<input id="cardExpYear" type="text" name="Card.ExpYear" ng-model="cipher.card.expYear"
|
||||
placeholder="{{i18n.ex}} 2019">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="cardCode" class="item-label">{{i18n.securityCode}}</label>
|
||||
<input id="cardCode" type="text" name="Card.Code" ng-model="cipher.card.code">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.identity">
|
||||
<div class="list-section-item">
|
||||
<label for="identityTitle" class="item-label">{{i18n.title}}</label>
|
||||
<select id="identityTitle" name="Identity.Title" ng-model="cipher.identity.title">
|
||||
<option value="">-- {{i18n.select}} --</option>
|
||||
<option value="{{i18n.mr}}">{{i18n.mr}}</option>
|
||||
<option value="{{i18n.mrs}}">{{i18n.mrs}}</option>
|
||||
<option value="{{i18n.ms}}">{{i18n.ms}}</option>
|
||||
<option value="{{i18n.dr}}">{{i18n.dr}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityFirstName" class="item-label">{{i18n.firstName}}</label>
|
||||
<input id="identityFirstName" type="text" name="Identity.FirstName"
|
||||
ng-model="cipher.identity.firstName">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityMiddleName" class="item-label">{{i18n.middleName}}</label>
|
||||
<input id="identityMiddleName" type="text" name="Identity.MiddleName"
|
||||
ng-model="cipher.identity.middleName">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityLastName" class="item-label">{{i18n.lastName}}</label>
|
||||
<input id="identityLastName" type="text" name="Identity.LastName"
|
||||
ng-model="cipher.identity.lastName">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityUsername" class="item-label">{{i18n.username}}</label>
|
||||
<input id="identityUsername" type="text" name="Identity.Username"
|
||||
ng-model="cipher.identity.username">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityCompany" class="item-label">{{i18n.company}}</label>
|
||||
<input id="identityCompany" type="text" name="Identity.Company" ng-model="cipher.identity.company">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identitySsn" class="item-label">{{i18n.ssn}}</label>
|
||||
<input id="identitySsn" type="text" name="Identity.SSN" ng-model="cipher.identity.ssn">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityPassportNumber" class="item-label">{{i18n.passportNumber}}</label>
|
||||
<input id="identityPassportNumber" type="text" name="Identity.PassportNumber"
|
||||
ng-model="cipher.identity.passportNumber">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityLicenseNumber" class="item-label">{{i18n.licenseNumber}}</label>
|
||||
<input id="identityLicenseNumber" type="text" name="Identity.LicenseNumber"
|
||||
ng-model="cipher.identity.licenseNumber">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityEmail" class="item-label">{{i18n.email}}</label>
|
||||
<input id="identityEmail" type="text" name="Identity.Email" ng-model="cipher.identity.email">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityPhone" class="item-label">{{i18n.phone}}</label>
|
||||
<input id="identityPhone" type="text" name="Identity.Phone" ng-model="cipher.identity.phone">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityAddress1" class="item-label">{{i18n.address1}}</label>
|
||||
<input id="identityAddress1" type="text" name="Identity.Address1" ng-model="cipher.identity.address1">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityAddress2" class="item-label">{{i18n.address2}}</label>
|
||||
<input id="identityAddress2" type="text" name="Identity.Address2" ng-model="cipher.identity.address2">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityAddress3" class="item-label">{{i18n.address3}}</label>
|
||||
<input id="identityAddress3" type="text" name="Identity.Address3" ng-model="cipher.identity.address3">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityCity" class="item-label">{{i18n.cityTown}}</label>
|
||||
<input id="identityCity" type="text" name="Identity.City" ng-model="cipher.identity.city">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityState" class="item-label">{{i18n.stateProvince}}</label>
|
||||
<input id="identityState" type="text" name="Identity.State" ng-model="cipher.identity.state">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityPostalCode" class="item-label">{{i18n.zipPostalCode}}</label>
|
||||
<input id="identityPostalCode" type="text" name="Identity.PostalCode"
|
||||
ng-model="cipher.identity.postalCode">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="identityCountry" class="item-label">{{i18n.country}}</label>
|
||||
<input id="identityCountry" type="text" name="Identity.Country" ng-model="cipher.identity.country">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.secureNote">
|
||||
<!-- Nothing for now -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-table"
|
||||
ng-if="cipher.login.uris && cipher.login.uris.length" ng-repeat="u in cipher.login.uris">
|
||||
<a href="#" stop-click ng-click="removeUri(u)" class="action-button text-danger">
|
||||
<i class="fa fa-minus-circle fa-lg"></i>
|
||||
</a>
|
||||
<div class="action-button-content">
|
||||
<label for="loginUri{{$index}}" class="item-label">{{i18n.uri}} {{$index + 1}}</label>
|
||||
<input id="loginUri{{$index}}" type="text" name="Login.Uris[{{$index}}].Uri"
|
||||
ng-model="u.uri" placeholder="{{i18n.ex}} https://google.com">
|
||||
<label for="loginUriMatch{{$index}}" class="sr-only">
|
||||
{{i18n.matchDetection}} {{$index + 1}}
|
||||
</label>
|
||||
<select id="loginUriMatch{{$index}}" name="Login.Uris[{{$index}}].Match"
|
||||
ng-hide="u.showOptions === false || (u.showOptions == null && u.match == null)"
|
||||
ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
|
||||
<option value="">{{i18n.defaultMatchDetection}}</option>
|
||||
<option value="0">{{i18n.baseDomain}}</option>
|
||||
<option value="1">{{i18n.host}}</option>
|
||||
<option value="2">{{i18n.startsWith}}</option>
|
||||
<option value="4">{{i18n.regEx}}</option>
|
||||
<option value="3">{{i18n.exact}}</option>
|
||||
<option value="5">{{i18n.never}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<a href="#" stop-click ng-click="toggleUriOptions(u)" class="action-button"
|
||||
title="{{i18n.toggleOptions}}">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
<a class="list-section-item text-primary" href="#" stop-click ng-click="addUri()">
|
||||
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{i18n.newUri}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="folder" class="item-label">{{i18n.folder}}</label>
|
||||
<select id="folder" name="FolderId" ng-model="cipher.folderId">
|
||||
<option ng-repeat="folder in folders track by $index" value="{{folder.id}}">
|
||||
{{folder.name}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-section-item list-section-item-checkbox">
|
||||
<label for="favorite">{{i18n.favorite}}</label>
|
||||
<input id="favorite" name="Favorite" type="checkbox" ng-model="cipher.favorite">
|
||||
</div>
|
||||
<a class="list-section-item" href="#" stop-click ng-click="attachments()" ng-if="showAttachments">
|
||||
{{i18n.attachments}}
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
<label for="notes">{{i18n.notes}}</label>
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<textarea id="notes" name="Notes" rows="5" ng-model="cipher.notes"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{i18n.customFields}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-table"
|
||||
ng-if="cipher.fields && cipher.fields.length" ng-repeat="field in cipher.fields"
|
||||
ng-class="{'list-section-item-checkbox' : field.type === constants.fieldType.boolean}">
|
||||
<a href="#" stop-click ng-click="removeField(field)" class="action-button text-danger">
|
||||
<i class="fa fa-minus-circle fa-lg"></i>
|
||||
</a>
|
||||
<div class="action-button-content">
|
||||
<input id="field_name{{$index}}" type="text" name="Field.Name{{$index}}"
|
||||
ng-model="field.name" class="item-label"
|
||||
placeholder="{{i18n.name}}">
|
||||
<input id="field_value{{$index}}" type="text" name="Field.Value{{$index}}"
|
||||
ng-model="field.value" ng-if="field.type === constants.fieldType.text"
|
||||
placeholder="{{i18n.value}}">
|
||||
<input id="field_value{{$index}}" type="password" name="Field.Value{{$index}}"
|
||||
ng-model="field.value" ng-if="field.type === constants.fieldType.hidden"
|
||||
placeholder="{{i18n.value}}">
|
||||
<input id="field_value{{$index}}" name="Field.Value{{$index}}" type="checkbox"
|
||||
ng-model="field.value" data-ng-true-value="'true'"
|
||||
ng-if="field.type === constants.fieldType.boolean">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<a href="#" stop-click ng-click="addField(addFieldType)">
|
||||
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{i18n.newCustomField}}
|
||||
</a>
|
||||
<select ng-model="addFieldType" class="field-type">
|
||||
<option value="0">{{i18n.cfTypeText}}</option>
|
||||
<option value="1">{{i18n.cfTypeHidden}}</option>
|
||||
<option value="2">{{i18n.cfTypeBoolean}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<a href="" ng-click="delete()" class="list-section-item text-danger">
|
||||
<i class="fa fa-trash fa-fw fa-lg"></i> {{i18n.deleteItem}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,251 +0,0 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a href="" ng-click="close()">{{i18n.close}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a href="" ng-click="edit(cipher)">{{i18n.edit}}</a>
|
||||
</div>
|
||||
<div class="title">{{i18n.viewItem}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list list-no-selection">
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{i18n.itemInformation}}
|
||||
<i class="fa fa-share-alt fa-lg pull-right" ng-if="cipher.organizationId" title="{{i18n.shared}}"></i>
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item wrap">
|
||||
<span class="item-label">{{i18n.name}}</span>
|
||||
{{cipher.name}}
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-item wrap" ng-if="cipher.login.username">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.copyUsername}}"
|
||||
ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
ngclipboard-success="clipboardSuccess(e, i18n.username, 'Username')"
|
||||
data-clipboard-text="{{cipher.login.username}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="item-label">{{i18n.username}}</span>
|
||||
<span id="username">{{cipher.login.username}}</span>
|
||||
</div>
|
||||
<div class="list-section-item wrap" ng-if="cipher.login.password">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.checkPassword}}" ng-click="checkPassword()">
|
||||
<i class="fa fa-lg fa-check-circle"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.togglePassword}}" ng-click="togglePassword()">
|
||||
<i class="fa fa-lg" ng-class="[{'fa-eye': !showPassword}, {'fa-eye-slash': showPassword}]"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.copyPassword}}"
|
||||
ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
ngclipboard-success="clipboardSuccess(e, i18n.password, 'Password')"
|
||||
data-clipboard-text="{{cipher.login.password}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="item-label">{{i18n.password}}</span>
|
||||
<span ng-show="!showPassword" class="monospaced">{{maskValue(cipher.login.password)}}</span>
|
||||
<span id="password" ng-show="showPassword" class="monospaced">{{cipher.login.password}}</span>
|
||||
</div>
|
||||
<div class="list-section-item totp" ng-class="{'low': totpLow}" ng-if="cipher.login.totp && totpCode">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.copyVerificationCode}}"
|
||||
ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
ngclipboard-success="clipboardSuccess(e, i18n.verificationCodeTotp, 'TOTP')"
|
||||
data-clipboard-text="{{totpCode}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="totp-countdown">
|
||||
<span class="totp-sec">{{totpSec}}</span>
|
||||
<svg>
|
||||
<g>
|
||||
<circle class="totp-circle inner" r="12.6" cy="16" cx="16"
|
||||
style="stroke-dashoffset: {{totpDash}}px;"></circle>
|
||||
<circle class="totp-circle outer" r="14" cy="16" cx="16"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="item-label">{{i18n.verificationCodeTotp}}</span>
|
||||
<span id="totp" class="totp-code">{{totpCodeFormatted}}</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.card">
|
||||
<div class="list-section-item" ng-if="cipher.card.cardholderName">
|
||||
<span class="item-label">{{i18n.cardholderName}}</span>
|
||||
{{cipher.card.cardholderName}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.card.number">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.copyNumber}}"
|
||||
ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
ngclipboard-success="clipboardSuccess(e, i18n.number, 'Number')"
|
||||
data-clipboard-text="{{cipher.card.number}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="item-label">{{i18n.number}}</span>
|
||||
{{cipher.card.number}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.card.brand">
|
||||
<span class="item-label">{{i18n.brand}}</span>
|
||||
{{cipher.card.brand}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.card.expMonth || cipher.card.expYear">
|
||||
<span class="item-label">{{i18n.expiration}}</span>
|
||||
{{cipher.card.expMonth ? ('0' + cipher.card.expMonth).slice(-2) : '__'}}
|
||||
/
|
||||
{{cipher.card.expYear ? formatYear(cipher.card.expYear) : '____'}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.card.code">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.copySecurityCode}}"
|
||||
ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
ngclipboard-success="clipboardSuccess(e, i18n.securityCode, 'Security Code')"
|
||||
data-clipboard-text="{{cipher.card.code}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="item-label">{{i18n.securityCode}}</span>
|
||||
{{cipher.card.code}}
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.identity">
|
||||
<div class="list-section-item" ng-if="cipher.identity.firstName || cipher.identity.lastName">
|
||||
<span class="item-label">{{i18n.identityName}}</span>
|
||||
{{cipher.identity.title}}
|
||||
{{cipher.identity.firstName}}
|
||||
{{cipher.identity.middleName}}
|
||||
{{cipher.identity.lastName}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.identity.username">
|
||||
<span class="item-label">{{i18n.username}}</span>
|
||||
{{cipher.identity.username}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.identity.company">
|
||||
<span class="item-label">{{i18n.company}}</span>
|
||||
{{cipher.identity.company}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.identity.ssn">
|
||||
<span class="item-label">{{i18n.ssn}}</span>
|
||||
{{cipher.identity.ssn}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.identity.passportNumber">
|
||||
<span class="item-label">{{i18n.passportNumber}}</span>
|
||||
{{cipher.identity.passportNumber}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.identity.licenseNumber">
|
||||
<span class="item-label">{{i18n.licenseNumber}}</span>
|
||||
{{cipher.identity.licenseNumber}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.identity.email">
|
||||
<span class="item-label">{{i18n.email}}</span>
|
||||
{{cipher.identity.email}}
|
||||
</div>
|
||||
<div class="list-section-item" ng-if="cipher.identity.phone">
|
||||
<span class="item-label">{{i18n.phone}}</span>
|
||||
{{cipher.identity.phone}}
|
||||
</div>
|
||||
<div class="list-section-item"
|
||||
ng-if="cipher.identity.address1 || cipher.identity.city || cipher.identity.country">
|
||||
<span class="item-label">{{i18n.address}}</span>
|
||||
<div ng-if="cipher.identity.address1">{{cipher.identity.address1}}</div>
|
||||
<div ng-if="cipher.identity.address2">{{cipher.identity.address2}}</div>
|
||||
<div ng-if="cipher.identity.address3">{{cipher.identity.address3}}</div>
|
||||
<div ng-if="cipher.identity.city || cipher.identity.state || cipher.identity.postalCode">
|
||||
{{cipher.identity.city || '-'}},
|
||||
{{cipher.identity.state || '-'}},
|
||||
{{cipher.identity.postalCode || '-'}}
|
||||
</div>
|
||||
<div ng-if="cipher.identity.country">{{cipher.identity.country}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.secureNote">
|
||||
<!-- Nothing for now -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section"
|
||||
ng-if="cipher.type === constants.cipherType.login && cipher.login.uris && cipher.login.uris.length">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item wrap" title="{{u.uri}}" ng-repeat="u in cipher.login.uris">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.launchWebsite}}" ng-click="launch(u)"
|
||||
ng-show="u.canLaunch">
|
||||
<i class="fa fa-lg fa-share-square-o"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.copyUri}}"
|
||||
ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
ngclipboard-success="clipboardSuccess(e, i18n.uri, 'URI')"
|
||||
data-clipboard-text="{{u.uri}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="item-label" ng-if="u.isWebsite">{{i18n.website}}</span>
|
||||
<span class="item-label" ng-if="!u.isWebsite">{{i18n.uri}}</span>
|
||||
{{u.domainOrUri}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="cipher.notes">
|
||||
<div class="list-section-header">
|
||||
{{i18n.notes}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item pre">{{cipher.notes}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="cipher.fields && cipher.fields.length">
|
||||
<div class="list-section-header">
|
||||
{{i18n.customFields}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item wrap" ng-repeat="field in cipher.fields">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.toggleValue}}" ng-click="toggleFieldValue(field)"
|
||||
ng-if="field.type === constants.fieldType.hidden">
|
||||
<i class="fa fa-lg" ng-class="[{'fa-eye': !field.showValue}, {'fa-eye-slash': field.showValue}]"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.copyValue}}" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
ngclipboard-success="clipboardSuccess(e, i18n.value, 'Field')" data-clipboard-text="{{field.value}}"
|
||||
ng-if="field.type !== constants.fieldType.boolean && field.value">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="item-label">{{field.name}}</span>
|
||||
<div ng-if="field.type === constants.fieldType.text">
|
||||
{{field.value || ' '}}
|
||||
</div>
|
||||
<div ng-if="field.type === constants.fieldType.hidden">
|
||||
<span ng-show="!field.showValue" class="monospaced">{{maskValue(field.value)}}</span>
|
||||
<span ng-show="field.showValue" class="monospaced">{{field.value}}</span>
|
||||
</div>
|
||||
<div ng-if="field.type === constants.fieldType.boolean">
|
||||
<i class="fa fa-check-square-o" ng-if="field.value === 'true'"></i>
|
||||
<i class="fa fa-square-o" ng-if="field.value !== 'true'"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="showAttachments && isPremium && cipher.attachments && cipher.attachments.length">
|
||||
<div class="list-section-header">
|
||||
{{i18n.attachments}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<a class="list-section-item list-allow-selection wrap" href="#" stop-click
|
||||
ng-repeat="attachment in cipher.attachments"
|
||||
ng-click="download(attachment)">
|
||||
<i class="fa fa-download right-icon fa-fw no-animation" ng-if="!attachment.downloading"></i>
|
||||
<i class="fa fa-spin fa-spinner right-icon fa-fw no-animation" ng-if="attachment.downloading"></i>
|
||||
<small class="item-sub-label">{{attachment.sizeName}}</small>
|
||||
<span class="text">{{attachment.fileName}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,50 +0,0 @@
|
||||
<div class="header header-search">
|
||||
<div class="left">
|
||||
<a ui-sref="tabs.vault({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
|
||||
</div>
|
||||
<div class="search">
|
||||
<input type="search" placeholder="{{collectionGrouping ? i18n.searchCollection : i18n.searchFolder}}"
|
||||
ng-model="searchText" ng-change="searchCiphers()" id="search" />
|
||||
<i class="fa fa-search"></i>
|
||||
</div>
|
||||
<div class="right" ng-if="folderGrouping">
|
||||
<a href="" ng-click="addCipher()"><i class="fa fa-plus fa-lg"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div ng-if="vaultCiphers.length" infinite-scroll="loadMore()" infinite-scroll-distance="1" infinite-scroll-parent="true"
|
||||
infinite-scroll-immediate-check="true">
|
||||
<div class="list">
|
||||
<div class="list-section" style="padding-bottom: 0;">
|
||||
<div class="list-section-header">
|
||||
{{grouping.name}}
|
||||
<span>{{vaultCiphers.length}}</span>
|
||||
</div>
|
||||
<a href="#" stop-click ng-click="viewCipher(cipher)"
|
||||
class="list-section-item condensed" title="{{i18n.edit}} {{cipher.name}}"
|
||||
ng-repeat="cipher in pagedVaultCiphers track by $index">
|
||||
<action-buttons cipher="cipher"></action-buttons>
|
||||
<icon cipher="cipher"></icon>
|
||||
<span class="text">
|
||||
{{cipher.name}}
|
||||
<i class="fa fa-share-alt text-muted" ng-if="cipher.organizationId" title="{{i18n.shared}}"></i>
|
||||
<i class="fa fa-paperclip text-muted" ng-if="cipher.attachments" title="{{i18n.attachments}}"></i>
|
||||
</span>
|
||||
<span class="detail">{{cipher.subTitle}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="centered-message" ng-if="loaded && !vaultCiphers.length && !searchText">
|
||||
<p>
|
||||
{{i18n.noItemsInList}}
|
||||
<button ng-click="addCipher()" style="margin-top: 20px;" class="btn btn-link btn-block"
|
||||
ng-show="folderGrouping">
|
||||
{{i18n.addItem}}
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
<div class="page-loading" ng-if="!loaded">
|
||||
<i class="fa fa-lg fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 100% 100%">
|
||||
<text fill="%23333333" x="50%" y="50%" font-family="\'Open Sans\', \'Helvetica Neue\', Helvetica, Arial, sans-serif"
|
||||
font-size="18" text-anchor="middle">
|
||||
Loading...
|
||||
</text>
|
||||
</svg>
|
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 174 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user