diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 96fb60102d..a9e3794b65 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -16,6 +16,7 @@ import { TwoFactorComponent } from './accounts/two-factor.component';
import { ExportComponent } from './tools/export.component';
import { ImportComponent } from './tools/import.component';
+import { PasswordGeneratorComponent } from './tools/password-generator.component';
import { ToolsComponent } from './tools/tools.component';
import { VaultComponent } from './vault/vault.component';
@@ -45,9 +46,10 @@ const routes: Routes = [
path: 'tools',
component: ToolsComponent,
children: [
- { path: '', pathMatch: 'full', redirectTo: 'import' },
+ { path: '', pathMatch: 'full', redirectTo: 'generator' },
{ path: 'import', component: ImportComponent, canActivate: [AuthGuardService] },
{ path: 'export', component: ExportComponent, canActivate: [AuthGuardService] },
+ { path: 'generator', component: PasswordGeneratorComponent, canActivate: [AuthGuardService] },
],
},
],
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index ec927de868..eb6ed514f9 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -31,6 +31,8 @@ import { TwoFactorComponent } from './accounts/two-factor.component';
import { ExportComponent } from './tools/export.component';
import { ImportComponent } from './tools/import.component';
+import { PasswordGeneratorHistoryComponent } from './tools/password-generator-history.component';
+import { PasswordGeneratorComponent } from './tools/password-generator.component';
import { ToolsComponent } from './tools/tools.component';
import { AddEditComponent } from './vault/add-edit.component';
@@ -60,7 +62,6 @@ import { TrueFalseValueDirective } from 'jslib/angular/directives/true-false-val
import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
-import { Folder } from 'jslib/models/domain';
@NgModule({
imports: [
@@ -106,6 +107,8 @@ import { Folder } from 'jslib/models/domain';
NavbarComponent,
OrganizationsComponent,
OrganizationLayoutComponent,
+ PasswordGeneratorComponent,
+ PasswordGeneratorHistoryComponent,
RegisterComponent,
SearchCiphersPipe,
ShareComponent,
@@ -127,6 +130,7 @@ import { Folder } from 'jslib/models/domain';
CollectionsComponent,
FolderAddEditComponent,
ModalComponent,
+ PasswordGeneratorHistoryComponent,
ShareComponent,
TwoFactorOptionsComponent,
],
diff --git a/src/app/tools/password-generator-history.component.html b/src/app/tools/password-generator-history.component.html
new file mode 100644
index 0000000000..5eed695725
--- /dev/null
+++ b/src/app/tools/password-generator-history.component.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+ -
+
+
{{h.password}}
+
{{h.date | date:'medium'}}
+
+
+
+
+
+
+
+ {{'noPasswordsInList' | i18n}}
+
+
+
+
+
diff --git a/src/app/tools/password-generator-history.component.ts b/src/app/tools/password-generator-history.component.ts
new file mode 100644
index 0000000000..e1d47a41f8
--- /dev/null
+++ b/src/app/tools/password-generator-history.component.ts
@@ -0,0 +1,24 @@
+import { ToasterService } from 'angular2-toaster';
+import { Angulartics2 } from 'angulartics2';
+
+import { Component } from '@angular/core';
+
+import { I18nService } from 'jslib/abstractions/i18n.service';
+import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
+import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
+
+import {
+ PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent,
+} from 'jslib/angular/components/password-generator-history.component';
+
+@Component({
+ selector: 'app-password-generator-history',
+ templateUrl: 'password-generator-history.component.html',
+})
+export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
+ constructor(passwordGenerationService: PasswordGenerationService, analytics: Angulartics2,
+ platformUtilsService: PlatformUtilsService, i18nService: I18nService,
+ toasterService: ToasterService) {
+ super(passwordGenerationService, analytics, platformUtilsService, i18nService, toasterService, window);
+ }
+}
diff --git a/src/app/tools/password-generator.component.html b/src/app/tools/password-generator.component.html
new file mode 100644
index 0000000000..0658ee902d
--- /dev/null
+++ b/src/app/tools/password-generator.component.html
@@ -0,0 +1,63 @@
+
+
+{{'options' | i18n}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/tools/password-generator.component.ts b/src/app/tools/password-generator.component.ts
new file mode 100644
index 0000000000..c541eabd2c
--- /dev/null
+++ b/src/app/tools/password-generator.component.ts
@@ -0,0 +1,50 @@
+import { ToasterService } from 'angular2-toaster';
+import { Angulartics2 } from 'angulartics2';
+
+import {
+ Component,
+ ComponentFactoryResolver,
+ ViewChild,
+ ViewContainerRef,
+} from '@angular/core';
+
+import { I18nService } from 'jslib/abstractions/i18n.service';
+import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
+import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
+
+import {
+ PasswordGeneratorComponent as BasePasswordGeneratorComponent,
+} from 'jslib/angular/components/password-generator.component';
+
+import { ModalComponent } from '../modal.component';
+import { PasswordGeneratorHistoryComponent } from './password-generator-history.component';
+
+@Component({
+ selector: 'app-password-generator',
+ templateUrl: 'password-generator.component.html',
+})
+export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
+ @ViewChild('historyTemplate', { read: ViewContainerRef }) historyModalRef: ViewContainerRef;
+
+ private modal: ModalComponent = null;
+
+ constructor(passwordGenerationService: PasswordGenerationService, analytics: Angulartics2,
+ platformUtilsService: PlatformUtilsService, i18nService: I18nService,
+ toasterService: ToasterService, private componentFactoryResolver: ComponentFactoryResolver) {
+ super(passwordGenerationService, analytics, platformUtilsService, i18nService, toasterService, window);
+ }
+
+ history() {
+ if (this.modal != null) {
+ this.modal.close();
+ }
+
+ const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
+ this.modal = this.historyModalRef.createComponent(factory).instance;
+ this.modal.show(PasswordGeneratorHistoryComponent, this.historyModalRef);
+
+ this.modal.onClosed.subscribe(async () => {
+ this.modal = null;
+ });
+ }
+}
diff --git a/src/app/tools/tools.component.html b/src/app/tools/tools.component.html
index a3789f8bff..6d765e620d 100644
--- a/src/app/tools/tools.component.html
+++ b/src/app/tools/tools.component.html
@@ -4,11 +4,15 @@
diff --git a/src/app/vault/add-edit.component.ts b/src/app/vault/add-edit.component.ts
index b1b96ae415..b82d845a77 100644
--- a/src/app/vault/add-edit.component.ts
+++ b/src/app/vault/add-edit.component.ts
@@ -12,6 +12,7 @@ import { AuditService } from 'jslib/abstractions/audit.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
+import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { TokenService } from 'jslib/abstractions/token.service';
@@ -38,7 +39,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit {
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
analytics: Angulartics2, toasterService: ToasterService,
auditService: AuditService, stateService: StateService,
- private tokenService: TokenService, private totpService: TotpService) {
+ private tokenService: TokenService, private totpService: TotpService,
+ private passwordGenerationService: PasswordGenerationService) {
super(cipherService, folderService, i18nService, platformUtilsService, analytics,
toasterService, auditService, stateService);
}
@@ -83,6 +85,15 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit {
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
}
+ async generatePassword(): Promise {
+ const confirmed = await super.generatePassword();
+ if (confirmed) {
+ const options = await this.passwordGenerationService.getOptions();
+ this.cipher.login.password = await this.passwordGenerationService.generatePassword(options);
+ }
+ return confirmed;
+ }
+
private cleanUp() {
if (this.totpInterval) {
window.clearInterval(this.totpInterval);
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
index 5b4fb67361..46327efb50 100644
--- a/src/locales/en/messages.json
+++ b/src/locales/en/messages.json
@@ -732,5 +732,36 @@
},
"exportSuccess": {
"message": "Your vault data has been exported."
+ },
+ "passwordGenerator": {
+ "message": "Password Generator"
+ },
+ "minNumbers": {
+ "message": "Minimum Numbers"
+ },
+ "minSpecial": {
+ "message": "Minimum Special",
+ "description": "Minimum Special Characters"
+ },
+ "ambiguous": {
+ "message": "Avoid Ambiguous Characters"
+ },
+ "regeneratePassword": {
+ "message": "Regenerate Password"
+ },
+ "length": {
+ "message": "Length"
+ },
+ "options": {
+ "message": "Options"
+ },
+ "passwordHistory": {
+ "message": "Password History"
+ },
+ "noPasswordsInList": {
+ "message": "There are no passwords to list."
+ },
+ "clear": {
+ "message": "Clear"
}
}
diff --git a/src/scss/styles.scss b/src/scss/styles.scss
index fc439ed3f7..ab54c33bd4 100644
--- a/src/scss/styles.scss
+++ b/src/scss/styles.scss
@@ -159,7 +159,7 @@ body {
@include border-radius($modal-content-border-radius);
}
-form label:not(.form-check-label) {
+label:not(.form-check-label) {
font-weight: bold;
}
@@ -296,6 +296,33 @@ app-login {
}
}
+app-password-generator {
+ #lengthRange {
+ width: 100%;
+ }
+
+ .card-password {
+ span {
+ word-break: break-all;
+ display: block;
+ text-align: center;
+ font-size: $font-size-lg;
+ font-family: $font-family-monospace;
+ }
+ }
+}
+
+app-password-generator-history {
+ .list-group-item {
+ line-height: 1;
+
+ .password {
+ word-break: break-all;
+ font-family: $font-family-monospace;
+ }
+ }
+}
+
#duo-frame {
background: url('../images/loading.svg') 0 0 no-repeat;
height: 330px;
diff --git a/src/services/htmlStorage.service.ts b/src/services/htmlStorage.service.ts
index 684de60f0d..973207cb7d 100644
--- a/src/services/htmlStorage.service.ts
+++ b/src/services/htmlStorage.service.ts
@@ -2,9 +2,9 @@ import { StorageService } from 'jslib/abstractions/storage.service';
import { ConstantsService } from 'jslib/services';
export class HtmlStorageService implements StorageService {
- private localStorageKeys = new Set(['appId', 'anonymousAppId', 'rememberedEmail',
- ConstantsService.disableFaviconKey, ConstantsService.lockOptionKey, ConstantsService.localeKey,
- ConstantsService.lockOptionKey]);
+ private localStorageKeys = new Set(['appId', 'anonymousAppId', 'rememberedEmail', 'passwordGenerationOptions',
+ ConstantsService.disableFaviconKey, ConstantsService.lockOptionKey,
+ ConstantsService.localeKey, ConstantsService.lockOptionKey]);
private localStorageStartsWithKeys = ['twoFactorToken_'];
get(key: string): Promise {