delete old popup

This commit is contained in:
Kyle Spearrin 2018-04-13 22:31:05 -04:00
parent 9d81843643
commit 9ec9a6cf88
109 changed files with 0 additions and 7983 deletions

View File

@ -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);
});
});
}
});

View File

@ -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
});
}
});
};
});

View File

@ -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);
}
});

View File

@ -1,2 +0,0 @@
angular
.module('bit.accounts', ['toastr', 'ngAnimate']);

View File

@ -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;
}
});

View File

@ -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]);
}
}
}
});

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1 +0,0 @@
declare module '*.html';

View File

@ -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']);
});

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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;

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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');
}
});
});
});

View File

@ -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>

View File

@ -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,
};

View File

@ -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;

View File

@ -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);
});
}
}

View File

@ -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;

View File

@ -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;
});
};
}

View File

@ -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();
});
};
}

View File

@ -1,7 +0,0 @@
export function StopPropDirective() {
return (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => {
element[0].addEventListener('click', (e) => {
e.stopPropagation();
});
};
}

View File

@ -1,7 +0,0 @@
export class BaseController implements ng.IController {
constructor($scope: any, i18nService: any) {
$scope.i18n = i18nService;
}
}
BaseController.$inject = ['$scope', 'i18nService'];

View File

@ -1 +0,0 @@
<div ui-view></div>

View File

@ -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;

View File

@ -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>&nbsp;{{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>

View File

@ -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'];

View File

@ -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'];

View File

@ -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>

View File

@ -1,5 +0,0 @@
<div class="content">
<div class="splash-page">
<img src="../../images/logo@3x.png" alt="bitwarden" />
</div>
</div>

View File

@ -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'];

View File

@ -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>

View File

@ -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>

View File

@ -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,
};

View File

@ -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;

View File

@ -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');

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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'];

View File

@ -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'];

View File

@ -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 />
&copy; 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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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;

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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,
};

View File

@ -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;

View File

@ -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
}
});
};
});

View File

@ -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;
};
});

View File

@ -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;
}
}
});

View File

@ -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() : '';
}
}
}
});

View File

@ -1,2 +0,0 @@
angular
.module('bit.vault', ['ngAnimate', 'toastr', 'ngclipboard', 'oitozero.ngSweetAlert', 'infinite-scroll']);

View File

@ -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();
}
});
}
});

View File

@ -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;
}
}
});

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 || '&nbsp;'}}
</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>

View File

@ -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>

View File

@ -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