mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-23 11:56:00 +01:00
History of generated passwords (#310)
* Save last 5 passwords. * Move password history to seperate page. * Use the util helpers for accessing the local storage. * Change close to back for password history. Remove unused html. * Change orderBy to use the date instead of magic array. * Move historyService to background. * Add passwords generated from shortcut and contextmenu to history. * Fix return to edit/add not working in password generator history. * Change password icon to clipboard. * Change link to password history to use on-click. * Clear password generator history on logout. * Code style fix. * Add new .wrap class for wrapping long text. Fix password icon.
This commit is contained in:
parent
358fb9b277
commit
f1262147a3
@ -945,5 +945,14 @@
|
||||
},
|
||||
"typeIdentity": {
|
||||
"message": "Identity"
|
||||
},
|
||||
"viewPasswordHistory": {
|
||||
"message": "View Password History"
|
||||
},
|
||||
"generatePasswordHistory": {
|
||||
"message": "Generated Passwords History"
|
||||
},
|
||||
"back": {
|
||||
"message": "Back"
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ var bg_isBackground = true,
|
||||
setIcon, refreshBadgeAndMenu);
|
||||
bg_syncService = new SyncService(bg_cipherService, bg_folderService, bg_userService, bg_apiService, bg_settingsService,
|
||||
bg_cryptoService, logout);
|
||||
bg_passwordGenerationService = new PasswordGenerationService();
|
||||
bg_passwordGenerationService = new PasswordGenerationService(bg_constantsService, bg_utilsService, bg_cryptoService);
|
||||
bg_totpService = new TotpService(bg_constantsService);
|
||||
bg_autofillService = new AutofillService(bg_utilsService, bg_totpService, bg_tokenService, bg_cipherService);
|
||||
|
||||
@ -59,6 +59,7 @@ var bg_isBackground = true,
|
||||
bg_passwordGenerationService.getOptions().then(function (options) {
|
||||
var password = bg_passwordGenerationService.generatePassword(options);
|
||||
bg_utilsService.copyToClipboard(password);
|
||||
bg_passwordGenerationService.addHistory(password);
|
||||
});
|
||||
}
|
||||
else if (command === 'autofill_login') {
|
||||
@ -193,6 +194,7 @@ var bg_isBackground = true,
|
||||
bg_passwordGenerationService.getOptions().then(function (options) {
|
||||
var password = bg_passwordGenerationService.generatePassword(options);
|
||||
bg_utilsService.copyToClipboard(password);
|
||||
bg_passwordGenerationService.addHistory(password);
|
||||
});
|
||||
}
|
||||
else if (info.parentMenuItemId === 'autofill' || info.parentMenuItemId === 'copy-username' ||
|
||||
@ -817,7 +819,8 @@ var bg_isBackground = true,
|
||||
bg_userService.clear(),
|
||||
bg_settingsService.clear(userId),
|
||||
bg_cipherService.clear(userId),
|
||||
bg_folderService.clear(userId)
|
||||
bg_folderService.clear(userId),
|
||||
bg_passwordGenerationService.clear()
|
||||
]).then(function () {
|
||||
chrome.runtime.sendMessage({
|
||||
command: 'doneLoggingOut', expired: expired
|
||||
|
@ -171,6 +171,13 @@
|
||||
data: { authorize: true },
|
||||
params: { animation: null, addState: null, editState: null }
|
||||
})
|
||||
.state('passwordGeneratorHistory', {
|
||||
url: '/history',
|
||||
templateUrl: 'app/tools/views/toolsPasswordGeneratorHistory.html',
|
||||
controller: 'toolsPasswordGeneratorHistoryController',
|
||||
data: { authorize: true },
|
||||
params: { animation: null, addState: null, editState: null }
|
||||
})
|
||||
.state('export', {
|
||||
url: '/export',
|
||||
templateUrl: 'app/tools/views/toolsExport.html',
|
||||
|
@ -57,6 +57,7 @@
|
||||
};
|
||||
|
||||
$scope.clipboardSuccess = function (e) {
|
||||
passwordGenerationService.addHistory(e.text);
|
||||
$analytics.eventTrack('Copied Generated Password');
|
||||
e.clearSelection();
|
||||
toastr.info(i18nService.passwordCopied);
|
||||
@ -79,6 +80,14 @@
|
||||
dismiss();
|
||||
};
|
||||
|
||||
$scope.goHistory = function () {
|
||||
$state.go('^.passwordGeneratorHistory', {
|
||||
animation: 'in-slide-left',
|
||||
addState: $stateParams.addState,
|
||||
editState: $stateParams.editState
|
||||
});
|
||||
};
|
||||
|
||||
function dismiss() {
|
||||
if (addState) {
|
||||
$state.go('addCipher', {
|
||||
|
@ -0,0 +1,31 @@
|
||||
angular
|
||||
.module('bit.tools')
|
||||
|
||||
.controller('toolsPasswordGeneratorHistoryController', function (
|
||||
$scope, $state, $stateParams, toastr, $analytics, i18nService, passwordGenerationService) {
|
||||
$scope.i18n = i18nService;
|
||||
|
||||
$scope.passwords = passwordGenerationService.getHistory();
|
||||
|
||||
$scope.clipboardError = function (e, password) {
|
||||
toastr.info(i18n.browserNotSupportClipboard);
|
||||
};
|
||||
|
||||
$scope.clipboardSuccess = function (e) {
|
||||
$analytics.eventTrack('Copied Generated Password');
|
||||
e.clearSelection();
|
||||
toastr.info(i18nService.passwordCopied);
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
dismiss();
|
||||
};
|
||||
|
||||
function dismiss() {
|
||||
$state.go('^.passwordGenerator', {
|
||||
animation: 'out-slide-right',
|
||||
addState: $stateParams.addState,
|
||||
editState: $stateParams.editState
|
||||
});
|
||||
}
|
||||
});
|
@ -21,6 +21,10 @@
|
||||
ngclipboard-success="clipboardSuccess(e)" data-clipboard-text="{{password}}">
|
||||
{{i18n.copyPassword}}
|
||||
</a>
|
||||
<a class="list-section-item text-primary" href="" ng-click="goHistory()">
|
||||
{{i18n.viewPasswordHistory}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
|
27
src/popup/app/tools/views/toolsPasswordGeneratorHistory.html
Normal file
27
src/popup/app/tools/views/toolsPasswordGeneratorHistory.html
Normal file
@ -0,0 +1,27 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ng-click="close()" href="">{{i18n.back}}</a>
|
||||
</div>
|
||||
<div class="title">{{i18n.generatePasswordHistory}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list" style="margin-top: 0;">
|
||||
<div class="list-section" ng-if="passwords.length !== 0">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-checkbox condensed wrap" ng-repeat="password in passwords | orderBy: 'date':true">
|
||||
<div class="action-buttons">
|
||||
<span class="btn-list" stop-prop stop-click title="{{i18n.copyPassword}}" ngclipboard
|
||||
ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.password)"
|
||||
data-clipboard-text="{{password.password}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</span>
|
||||
</div>
|
||||
<span class="text monospaced">
|
||||
{{password.password}}
|
||||
</span>
|
||||
<span class="detail">{{password.date | date}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -111,6 +111,7 @@
|
||||
<script src="app/tools/toolsModule.js"></script>
|
||||
<script src="app/tools/toolsController.js"></script>
|
||||
<script src="app/tools/toolsPasswordGeneratorController.js"></script>
|
||||
<script src="app/tools/toolsPasswordGeneratorHistoryController.js"></script>
|
||||
<script src="app/tools/toolsExportController.js"></script>
|
||||
|
||||
<script src="app/lock/lockModule.js"></script>
|
||||
|
@ -428,6 +428,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.wrap {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]), select, textarea {
|
||||
border: none;
|
||||
width: 100%;
|
||||
|
@ -9,6 +9,7 @@ function ConstantsService(i18nService) {
|
||||
enableAutoFillOnPageLoadKey: 'enableAutoFillOnPageLoad',
|
||||
lockOptionKey: 'lockOption',
|
||||
lastActiveKey: 'lastActive',
|
||||
generatedPasswordHistory: 'generatedPasswordHistory',
|
||||
encType: {
|
||||
AesCbc256_B64: 0,
|
||||
AesCbc128_HmacSha256_B64: 1,
|
||||
|
@ -1,10 +1,14 @@
|
||||
function PasswordGenerationService() {
|
||||
function PasswordGenerationService(constantsService, utilsService, cryptoService) {
|
||||
this.optionsCache = null;
|
||||
this.constantsService = constantsService;
|
||||
this.utilsService = utilsService;
|
||||
this.cryptoService = cryptoService;
|
||||
this.history = [];
|
||||
|
||||
initPasswordGenerationService();
|
||||
initPasswordGenerationService(this);
|
||||
}
|
||||
|
||||
function initPasswordGenerationService() {
|
||||
function initPasswordGenerationService(self) {
|
||||
var optionsKey = 'passwordGenerationOptions';
|
||||
var defaultOptions = {
|
||||
length: 10,
|
||||
@ -181,4 +185,85 @@ function initPasswordGenerationService() {
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
// History
|
||||
var key = self.constantsService.generatedPasswordHistory;
|
||||
var MAX_PASSWORDS_IN_HISTORY = 10;
|
||||
|
||||
self.utilsService
|
||||
.getObjFromStorage(key)
|
||||
.then(function(encrypted) {
|
||||
return decrypt(encrypted);
|
||||
}).then(function(history) {
|
||||
history.forEach(function(item) {
|
||||
self.history.push(item);
|
||||
});
|
||||
});
|
||||
|
||||
PasswordGenerationService.prototype.getHistory = function () {
|
||||
return self.history;
|
||||
};
|
||||
|
||||
PasswordGenerationService.prototype.addHistory = function (password) {
|
||||
// Prevent duplicates
|
||||
if (matchesPrevious(password)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.history.push({
|
||||
password: password,
|
||||
date: Date.now()
|
||||
});
|
||||
|
||||
// Remove old items.
|
||||
if (self.history.length > MAX_PASSWORDS_IN_HISTORY) {
|
||||
self.history.shift();
|
||||
}
|
||||
|
||||
save();
|
||||
};
|
||||
|
||||
PasswordGenerationService.prototype.clear = function () {
|
||||
self.history = [];
|
||||
self.utilsService.removeFromStorage(key);
|
||||
};
|
||||
|
||||
function save() {
|
||||
return encryptHistory()
|
||||
.then(function(history) {
|
||||
return self.utilsService.saveObjToStorage(key, history);
|
||||
});
|
||||
}
|
||||
|
||||
function encryptHistory() {
|
||||
var promises = self.history.map(function(historyItem) {
|
||||
return self.cryptoService.encrypt(historyItem.password).then(function(encrypted) {
|
||||
return {
|
||||
password: encrypted.encryptedString,
|
||||
date: historyItem.date
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
}
|
||||
|
||||
function decrypt(history) {
|
||||
var promises = history.map(function(item) {
|
||||
return self.cryptoService.decrypt(new CipherString(item.password)).then(function(decrypted) {
|
||||
return {
|
||||
password: decrypted,
|
||||
date: item.date
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
}
|
||||
|
||||
function matchesPrevious(password) {
|
||||
var len = self.history.length;
|
||||
return len !== 0 && self.history[len-1].password === password;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user