mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-24 12:06:15 +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": {
|
"typeIdentity": {
|
||||||
"message": "Identity"
|
"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);
|
setIcon, refreshBadgeAndMenu);
|
||||||
bg_syncService = new SyncService(bg_cipherService, bg_folderService, bg_userService, bg_apiService, bg_settingsService,
|
bg_syncService = new SyncService(bg_cipherService, bg_folderService, bg_userService, bg_apiService, bg_settingsService,
|
||||||
bg_cryptoService, logout);
|
bg_cryptoService, logout);
|
||||||
bg_passwordGenerationService = new PasswordGenerationService();
|
bg_passwordGenerationService = new PasswordGenerationService(bg_constantsService, bg_utilsService, bg_cryptoService);
|
||||||
bg_totpService = new TotpService(bg_constantsService);
|
bg_totpService = new TotpService(bg_constantsService);
|
||||||
bg_autofillService = new AutofillService(bg_utilsService, bg_totpService, bg_tokenService, bg_cipherService);
|
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) {
|
bg_passwordGenerationService.getOptions().then(function (options) {
|
||||||
var password = bg_passwordGenerationService.generatePassword(options);
|
var password = bg_passwordGenerationService.generatePassword(options);
|
||||||
bg_utilsService.copyToClipboard(password);
|
bg_utilsService.copyToClipboard(password);
|
||||||
|
bg_passwordGenerationService.addHistory(password);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (command === 'autofill_login') {
|
else if (command === 'autofill_login') {
|
||||||
@ -193,6 +194,7 @@ var bg_isBackground = true,
|
|||||||
bg_passwordGenerationService.getOptions().then(function (options) {
|
bg_passwordGenerationService.getOptions().then(function (options) {
|
||||||
var password = bg_passwordGenerationService.generatePassword(options);
|
var password = bg_passwordGenerationService.generatePassword(options);
|
||||||
bg_utilsService.copyToClipboard(password);
|
bg_utilsService.copyToClipboard(password);
|
||||||
|
bg_passwordGenerationService.addHistory(password);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (info.parentMenuItemId === 'autofill' || info.parentMenuItemId === 'copy-username' ||
|
else if (info.parentMenuItemId === 'autofill' || info.parentMenuItemId === 'copy-username' ||
|
||||||
@ -817,7 +819,8 @@ var bg_isBackground = true,
|
|||||||
bg_userService.clear(),
|
bg_userService.clear(),
|
||||||
bg_settingsService.clear(userId),
|
bg_settingsService.clear(userId),
|
||||||
bg_cipherService.clear(userId),
|
bg_cipherService.clear(userId),
|
||||||
bg_folderService.clear(userId)
|
bg_folderService.clear(userId),
|
||||||
|
bg_passwordGenerationService.clear()
|
||||||
]).then(function () {
|
]).then(function () {
|
||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
command: 'doneLoggingOut', expired: expired
|
command: 'doneLoggingOut', expired: expired
|
||||||
|
@ -171,6 +171,13 @@
|
|||||||
data: { authorize: true },
|
data: { authorize: true },
|
||||||
params: { animation: null, addState: null, editState: null }
|
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', {
|
.state('export', {
|
||||||
url: '/export',
|
url: '/export',
|
||||||
templateUrl: 'app/tools/views/toolsExport.html',
|
templateUrl: 'app/tools/views/toolsExport.html',
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.clipboardSuccess = function (e) {
|
$scope.clipboardSuccess = function (e) {
|
||||||
|
passwordGenerationService.addHistory(e.text);
|
||||||
$analytics.eventTrack('Copied Generated Password');
|
$analytics.eventTrack('Copied Generated Password');
|
||||||
e.clearSelection();
|
e.clearSelection();
|
||||||
toastr.info(i18nService.passwordCopied);
|
toastr.info(i18nService.passwordCopied);
|
||||||
@ -79,6 +80,14 @@
|
|||||||
dismiss();
|
dismiss();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.goHistory = function () {
|
||||||
|
$state.go('^.passwordGeneratorHistory', {
|
||||||
|
animation: 'in-slide-left',
|
||||||
|
addState: $stateParams.addState,
|
||||||
|
editState: $stateParams.editState
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function dismiss() {
|
function dismiss() {
|
||||||
if (addState) {
|
if (addState) {
|
||||||
$state.go('addCipher', {
|
$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}}">
|
ngclipboard-success="clipboardSuccess(e)" data-clipboard-text="{{password}}">
|
||||||
{{i18n.copyPassword}}
|
{{i18n.copyPassword}}
|
||||||
</a>
|
</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>
|
</div>
|
||||||
<div class="list-section">
|
<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/toolsModule.js"></script>
|
||||||
<script src="app/tools/toolsController.js"></script>
|
<script src="app/tools/toolsController.js"></script>
|
||||||
<script src="app/tools/toolsPasswordGeneratorController.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/tools/toolsExportController.js"></script>
|
||||||
|
|
||||||
<script src="app/lock/lockModule.js"></script>
|
<script src="app/lock/lockModule.js"></script>
|
||||||
|
@ -428,6 +428,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.wrap {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
input:not([type="checkbox"]), select, textarea {
|
input:not([type="checkbox"]), select, textarea {
|
||||||
border: none;
|
border: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -9,6 +9,7 @@ function ConstantsService(i18nService) {
|
|||||||
enableAutoFillOnPageLoadKey: 'enableAutoFillOnPageLoad',
|
enableAutoFillOnPageLoadKey: 'enableAutoFillOnPageLoad',
|
||||||
lockOptionKey: 'lockOption',
|
lockOptionKey: 'lockOption',
|
||||||
lastActiveKey: 'lastActive',
|
lastActiveKey: 'lastActive',
|
||||||
|
generatedPasswordHistory: 'generatedPasswordHistory',
|
||||||
encType: {
|
encType: {
|
||||||
AesCbc256_B64: 0,
|
AesCbc256_B64: 0,
|
||||||
AesCbc128_HmacSha256_B64: 1,
|
AesCbc128_HmacSha256_B64: 1,
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
function PasswordGenerationService() {
|
function PasswordGenerationService(constantsService, utilsService, cryptoService) {
|
||||||
this.optionsCache = null;
|
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 optionsKey = 'passwordGenerationOptions';
|
||||||
var defaultOptions = {
|
var defaultOptions = {
|
||||||
length: 10,
|
length: 10,
|
||||||
@ -181,4 +185,85 @@ function initPasswordGenerationService() {
|
|||||||
|
|
||||||
return deferred.promise;
|
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