From 0ebf30b8b6ed2e56e381a722b85b26f7a5e0d9ac Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 11 Dec 2018 14:47:41 -0500 Subject: [PATCH] reused passwords report --- src/app/app-routing.module.ts | 6 ++ src/app/app.module.ts | 2 + .../reused-passwords-report.component.html | 39 +++++++++ .../reused-passwords-report.component.ts | 86 +++++++++++++++++++ src/app/tools/tools.component.html | 3 + src/locales/en/messages.json | 30 +++++++ 6 files changed, 166 insertions(+) create mode 100644 src/app/tools/reused-passwords-report.component.html create mode 100644 src/app/tools/reused-passwords-report.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 66b8eaad9a..cbeac5bad0 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -52,6 +52,7 @@ import { BreachReportComponent } from './tools/breach-report.component'; import { ExportComponent } from './tools/export.component'; import { ImportComponent } from './tools/import.component'; import { PasswordGeneratorComponent } from './tools/password-generator.component'; +import { ReusedPasswordsReportComponent } from './tools/reused-passwords-report.component'; import { ToolsComponent } from './tools/tools.component'; import { VaultComponent } from './vault/vault.component'; @@ -148,6 +149,11 @@ const routes: Routes = [ data: { titleId: 'passwordGenerator' }, }, { path: 'breach-report', component: BreachReportComponent, data: { titleId: 'dataBreachReport' } }, + { + path: 'reused-passwords-report', + component: ReusedPasswordsReportComponent, + data: { titleId: 'reusedPasswordsReport' }, + }, ], }, ], diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7b0a529e67..fed89906ff 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -108,6 +108,7 @@ 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 { ReusedPasswordsReportComponent } from './tools/reused-passwords-report.component'; import { ToolsComponent } from './tools/tools.component'; import { AddEditComponent } from './vault/add-edit.component'; @@ -270,6 +271,7 @@ registerLocaleData(localeZhCn, 'zh-CN'); RecoverDeleteComponent, RecoverTwoFactorComponent, RegisterComponent, + ReusedPasswordsReportComponent, SearchCiphersPipe, SearchPipe, SettingsComponent, diff --git a/src/app/tools/reused-passwords-report.component.html b/src/app/tools/reused-passwords-report.component.html new file mode 100644 index 0000000000..6b7a251340 --- /dev/null +++ b/src/app/tools/reused-passwords-report.component.html @@ -0,0 +1,39 @@ + +

{{'reusedPasswordsReportDesc' | i18n}}

+
+ +
+
+ + {{'noReusedPasswords'}} + + + + {{'reusedPasswordsFoundDesc' | i18n : ciphers.length}} + + + + + + + + + +
+ + + {{c.name}} + + +
+ {{c.subTitle}} +
+ + {{'reusedXTimes' | i18n : passwordUseMap.get(c.login.password)}} + +
+
+
+ diff --git a/src/app/tools/reused-passwords-report.component.ts b/src/app/tools/reused-passwords-report.component.ts new file mode 100644 index 0000000000..4fd3985072 --- /dev/null +++ b/src/app/tools/reused-passwords-report.component.ts @@ -0,0 +1,86 @@ +import { + Component, + ComponentFactoryResolver, + OnInit, + ViewChild, + ViewContainerRef, +} from '@angular/core'; + +import { CipherService } from 'jslib/abstractions/cipher.service'; + +import { CipherView } from 'jslib/models/view/cipherView'; + +import { CipherType } from 'jslib/enums/cipherType'; + +import { ModalComponent } from '../modal.component'; +import { AddEditComponent } from '../vault/add-edit.component'; + +@Component({ + selector: 'app-reused-passwords-report', + templateUrl: 'reused-passwords-report.component.html', +}) +export class ReusedPasswordsReportComponent implements OnInit { + @ViewChild('cipherAddEdit', { read: ViewContainerRef }) cipherAddEditModalRef: ViewContainerRef; + + loading = true; + hasLoaded = false; + ciphers: CipherView[] = []; + passwordUseMap: Map; + + private modal: ModalComponent = null; + + constructor(private ciphersService: CipherService, private componentFactoryResolver: ComponentFactoryResolver) { } + + async ngOnInit() { + this.load(); + this.hasLoaded = true; + } + + async load() { + this.loading = true; + const allCiphers = await this.ciphersService.getAllDecrypted(); + const ciphersWithPasswords: CipherView[] = []; + this.passwordUseMap = new Map(); + allCiphers.forEach((c) => { + if (c.type !== CipherType.Login || c.login.password == null || c.login.password === '') { + return; + } + ciphersWithPasswords.push(c); + if (this.passwordUseMap.has(c.login.password)) { + this.passwordUseMap.set(c.login.password, this.passwordUseMap.get(c.login.password) + 1); + } else { + this.passwordUseMap.set(c.login.password, 1); + } + }); + this.ciphers = ciphersWithPasswords.filter((c) => + this.passwordUseMap.has(c.login.password) && this.passwordUseMap.get(c.login.password) > 1); + this.loading = false; + } + + selectCipher(cipher: CipherView) { + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.cipherAddEditModalRef.createComponent(factory).instance; + const childComponent = this.modal.show( + AddEditComponent, this.cipherAddEditModalRef); + + childComponent.cipherId = cipher == null ? null : cipher.id; + childComponent.onSavedCipher.subscribe(async (c: CipherView) => { + this.modal.close(); + await this.load(); + }); + childComponent.onDeletedCipher.subscribe(async (c: CipherView) => { + this.modal.close(); + await this.load(); + }); + + this.modal.onClosed.subscribe(() => { + this.modal = null; + }); + + return childComponent; + } +} diff --git a/src/app/tools/tools.component.html b/src/app/tools/tools.component.html index d04ea0b9ff..5be28feed2 100644 --- a/src/app/tools/tools.component.html +++ b/src/app/tools/tools.component.html @@ -21,6 +21,9 @@ {{'dataBreachReport' | i18n}} + + {{'reusedPasswordsReport' | i18n}} + diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index db28804a77..1c49ac048a 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -1287,6 +1287,36 @@ "reports": { "message": "Reports" }, + "reusedPasswordsReport": { + "message": "Reused Passwords Report" + }, + "reusedPasswordsReportDesc": { + "message": "If a service that you use is compromised, reusing the same password elsewhere can allow hackers to easily gain access to more of your online accounts. You should use a unique password for every account or service." + }, + "reusedPasswordsFound": { + "message": "Reused Passwords Found" + }, + "reusedPasswordsFoundDesc": { + "message": "We found $COUNT$ passwords that are being reused in your vault. You should change them.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noReusedPasswords": { + "message": "No logins in your vault have passwords that are being reused." + }, + "reusedXTimes": { + "message": "Reused $COUNT$ times", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, "dataBreachReport": { "message": "Data Breach Report" },