inactive 2fa report

This commit is contained in:
Kyle Spearrin 2018-12-11 23:25:05 -05:00
parent b44eee8d81
commit ea032bf551
6 changed files with 210 additions and 1 deletions

View File

@ -52,6 +52,7 @@ import { BreachReportComponent } from './tools/breach-report.component';
import { ExportComponent } from './tools/export.component';
import { ExposedPasswordsReportComponent } from './tools/exposed-passwords-report.component';
import { ImportComponent } from './tools/import.component';
import { InactiveTwoFactorReportComponent } from './tools/inactive-two-factor-report.component';
import { PasswordGeneratorComponent } from './tools/password-generator.component';
import { ReusedPasswordsReportComponent } from './tools/reused-passwords-report.component';
import { ToolsComponent } from './tools/tools.component';
@ -172,6 +173,11 @@ const routes: Routes = [
component: ExposedPasswordsReportComponent,
data: { titleId: 'exposedPasswordsReport' },
},
{
path: 'inactive-two-factor-report',
component: InactiveTwoFactorReportComponent,
data: { titleId: 'exposedPasswordsReport' },
},
],
},
],

View File

@ -107,6 +107,7 @@ import { BreachReportComponent } from './tools/breach-report.component';
import { ExportComponent } from './tools/export.component';
import { ExposedPasswordsReportComponent } from './tools/exposed-passwords-report.component';
import { ImportComponent } from './tools/import.component';
import { InactiveTwoFactorReportComponent } from './tools/inactive-two-factor-report.component';
import { PasswordGeneratorHistoryComponent } from './tools/password-generator-history.component';
import { PasswordGeneratorComponent } from './tools/password-generator.component';
import { ReusedPasswordsReportComponent } from './tools/reused-passwords-report.component';
@ -230,9 +231,10 @@ registerLocaleData(localeZhCn, 'zh-CN');
FrontendLayoutComponent,
GroupingsComponent,
HintComponent,
IconComponent,
I18nPipe,
IconComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
InputVerbatimDirective,
LockComponent,
LoginComponent,

View File

@ -0,0 +1,44 @@
<div class="page-header">
<h1>
{{'inactive2faReport' | i18n}}
<small *ngIf="hasLoaded && loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
</small>
</h1>
</div>
<p>{{'inactive2faReportDesc' | i18n}}</p>
<div *ngIf="!hasLoaded && loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
</div>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{'goodNews' | i18n}}" *ngIf="!ciphers.length">
{{'noInactive2fa'}}
</app-callout>
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{'inactive2faFound' | i18n}}">
{{'inactive2faFoundDesc' | i18n : (ciphers.length | number)}}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
<i class="fa fa-share-alt" *ngIf="!organization && c.organizationId" title="{{'shared' | i18n}}"></i>
<i class="fa fa-paperclip" title="{{'attachments' | i18n}}" *ngIf="c.hasAttachments"></i>
<br>
<small>{{c.subTitle}}</small>
</td>
<td class="text-right">
<a class="badge badge-primary" href="{{cipherDocs.get(c.id)}}" target="_blank" rel="noopener"
*ngIf="cipherDocs.has(c.id)">
{{'2faInstructions' | i18n}}</a>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>

View File

@ -0,0 +1,130 @@
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';
import { Utils } from 'jslib/misc/utils';
@Component({
selector: 'app-inactive-two-factor-report',
templateUrl: 'inactive-two-factor-report.component.html',
})
export class InactiveTwoFactorReportComponent implements OnInit {
@ViewChild('cipherAddEdit', { read: ViewContainerRef }) cipherAddEditModalRef: ViewContainerRef;
loading = false;
hasLoaded = false;
services = new Map<string, string>();
cipherDocs = new Map<string, string>();
ciphers: CipherView[] = [];
private modal: ModalComponent = null;
constructor(private ciphersService: CipherService, private componentFactoryResolver: ComponentFactoryResolver) { }
async ngOnInit() {
await this.load();
}
async load() {
this.loading = true;
try {
await this.load2fa();
} catch { }
if (this.services.size > 0) {
const allCiphers = await this.ciphersService.getAllDecrypted();
const inactive2faCiphers: CipherView[] = [];
const promises: Array<Promise<void>> = [];
const docs = new Map<string, string>();
allCiphers.forEach((c) => {
if (c.type !== CipherType.Login || (c.login.totp != null && c.login.totp !== '') || !c.login.hasUris) {
return;
}
for (let i = 0; i < c.login.uris.length; i++) {
const u = c.login.uris[i];
if (u.uri != null && u.uri !== '') {
const hostname = Utils.getHostname(u.uri);
if (hostname != null && this.services.has(hostname)) {
if (this.services.get(hostname) != null) {
docs.set(c.id, this.services.get(hostname));
}
inactive2faCiphers.push(c);
break;
}
}
}
});
await Promise.all(promises);
this.ciphers = inactive2faCiphers;
this.cipherDocs = docs;
}
this.loading = false;
this.hasLoaded = true;
}
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>(
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;
}
private async load2fa() {
if (this.services.size > 0) {
return;
}
const response = await fetch(new Request('https://twofactorauth.org/data.json'));
if (response.status !== 200) {
throw new Error();
}
const responseJson = await response.json();
for (const categoryName in responseJson) {
if (responseJson.hasOwnProperty(categoryName)) {
const category = responseJson[categoryName];
for (const serviceName in category) {
if (category.hasOwnProperty(serviceName)) {
const service = category[serviceName];
if (service.tfa && service.url != null) {
const hostname = Utils.getHostname(service.url);
if (hostname != null) {
this.services.set(hostname, service.doc);
}
}
}
}
}
}
}
}

View File

@ -33,6 +33,9 @@
<a routerLink="exposed-passwords-report" class="list-group-item" routerLinkActive="active">
{{'exposedPasswordsReport' | i18n}}
</a>
<a routerLink="inactive-two-factor-report" class="list-group-item" routerLinkActive="active">
{{'inactive2faReport' | i18n}}
</a>
</div>
</div>
</div>

View File

@ -1308,6 +1308,30 @@
"noUnsecuredWebsites": {
"message": "No items in your vault have unsecured URIs."
},
"inactive2faReport": {
"message": "Inactive 2FA Report"
},
"inactive2faReportDesc": {
"message": "Two-factor authentication (2FA) is an important security setting that helps secure your accounts. If the website offers it, you should always enable two-factor authentication."
},
"inactive2faFound": {
"message": "Logins Without 2FA Found"
},
"inactive2faFoundDesc": {
"message": "We found $COUNT$ website(s) in your vault that may not be configured with two-factor authentication (according to twofactorauth.org). To further protect your accounts, you should always use two-factor authentication.",
"placeholders": {
"count": {
"content": "$1",
"example": "8"
}
}
},
"noInactive2fa": {
"message": "No websites were found in your vault with a missing two-factor authentication configuration."
},
"2faInstructions": {
"message": "2FA Instructions"
},
"exposedPasswordsReport": {
"message": "Exposed Passwords Report"
},