mirror of
https://github.com/bitwarden/browser
synced 2025-12-29 06:33:40 +00:00
Apply Prettier (#1347)
This commit is contained in:
@@ -1,57 +1,64 @@
|
||||
<div class="page-header">
|
||||
<h1>{{'dataBreachReport' | i18n}}</h1>
|
||||
<h1>{{ "dataBreachReport" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{'breachDesc' | i18n}}</p>
|
||||
<p>{{ "breachDesc" | i18n }}</p>
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="username">{{'username' | i18n}}</label>
|
||||
<input id="username" type="text" name="Username" class="form-control" [(ngModel)]="username" required>
|
||||
<small class="form-text text-muted">{{'breachCheckUsernameEmail' | i18n}}</small>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="username">{{ "username" | i18n }}</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
name="Username"
|
||||
class="form-control"
|
||||
[(ngModel)]="username"
|
||||
required
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "breachCheckUsernameEmail" | i18n }}</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'checkBreaches' | i18n}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "checkBreaches" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
<div class="mt-4" *ngIf="!form.loading && checkedUsername">
|
||||
<p *ngIf="error">{{'reportError' | i18n}}...</p>
|
||||
<ng-container *ngIf="!error">
|
||||
<app-callout type="success" title="{{'goodNews' | i18n}}" *ngIf="!breachedAccounts.length">
|
||||
{{'breachUsernameNotFound' | i18n : checkedUsername}}
|
||||
</app-callout>
|
||||
<app-callout type="danger" title="{{'breachFound' | i18n}}" *ngIf="breachedAccounts.length">
|
||||
{{'breachUsernameFound' | i18n : checkedUsername : breachedAccounts.length}}
|
||||
</app-callout>
|
||||
<ul class="list-group list-group-breach" *ngIf="breachedAccounts.length">
|
||||
<li *ngFor="let a of breachedAccounts" class="list-group-item min-height-fix">
|
||||
<div class="row">
|
||||
<div class="col-2 text-center">
|
||||
<img [src]="a.logoPath" alt="" class="img-fluid">
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<h3 class="text-lg">{{a.title}}</h3>
|
||||
<p [innerHTML]="a.description"></p>
|
||||
<p class="mb-1">{{'compromisedData' | i18n}}:</p>
|
||||
<ul>
|
||||
<li *ngFor="let d of a.dataClasses">{{d}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<dl>
|
||||
<dt>{{'website' | i18n}}</dt>
|
||||
<dd>{{a.domain}}</dd>
|
||||
<dt>{{'affectedUsers' | i18n}}</dt>
|
||||
<dd>{{a.pwnCount | number}}</dd>
|
||||
<dt>{{'breachOccurred' | i18n}}</dt>
|
||||
<dd>{{a.breachDate | date: 'mediumDate'}}</dd>
|
||||
<dt>{{'breachReported' | i18n}}</dt>
|
||||
<dd>{{a.addedDate | date: 'mediumDate'}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
<p *ngIf="error">{{ "reportError" | i18n }}...</p>
|
||||
<ng-container *ngIf="!error">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!breachedAccounts.length">
|
||||
{{ "breachUsernameNotFound" | i18n: checkedUsername }}
|
||||
</app-callout>
|
||||
<app-callout type="danger" title="{{ 'breachFound' | i18n }}" *ngIf="breachedAccounts.length">
|
||||
{{ "breachUsernameFound" | i18n: checkedUsername:breachedAccounts.length }}
|
||||
</app-callout>
|
||||
<ul class="list-group list-group-breach" *ngIf="breachedAccounts.length">
|
||||
<li *ngFor="let a of breachedAccounts" class="list-group-item min-height-fix">
|
||||
<div class="row">
|
||||
<div class="col-2 text-center">
|
||||
<img [src]="a.logoPath" alt="" class="img-fluid" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<h3 class="text-lg">{{ a.title }}</h3>
|
||||
<p [innerHTML]="a.description"></p>
|
||||
<p class="mb-1">{{ "compromisedData" | i18n }}:</p>
|
||||
<ul>
|
||||
<li *ngFor="let d of a.dataClasses">{{ d }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<dl>
|
||||
<dt>{{ "website" | i18n }}</dt>
|
||||
<dd>{{ a.domain }}</dd>
|
||||
<dt>{{ "affectedUsers" | i18n }}</dt>
|
||||
<dd>{{ a.pwnCount | number }}</dd>
|
||||
<dt>{{ "breachOccurred" | i18n }}</dt>
|
||||
<dd>{{ a.breachDate | date: "mediumDate" }}</dd>
|
||||
<dt>{{ "breachReported" | i18n }}</dt>
|
||||
<dd>{{ a.addedDate | date: "mediumDate" }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,38 +1,35 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { BreachAccountResponse } from 'jslib-common/models/response/breachAccountResponse';
|
||||
import { AuditService } from "jslib-common/abstractions/audit.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { BreachAccountResponse } from "jslib-common/models/response/breachAccountResponse";
|
||||
|
||||
@Component({
|
||||
selector: 'app-breach-report',
|
||||
templateUrl: 'breach-report.component.html',
|
||||
selector: "app-breach-report",
|
||||
templateUrl: "breach-report.component.html",
|
||||
})
|
||||
export class BreachReportComponent implements OnInit {
|
||||
error = false;
|
||||
username: string;
|
||||
checkedUsername: string;
|
||||
breachedAccounts: BreachAccountResponse[] = [];
|
||||
formPromise: Promise<BreachAccountResponse[]>;
|
||||
error = false;
|
||||
username: string;
|
||||
checkedUsername: string;
|
||||
breachedAccounts: BreachAccountResponse[] = [];
|
||||
formPromise: Promise<BreachAccountResponse[]>;
|
||||
|
||||
constructor(private auditService: AuditService, private stateService: StateService) { }
|
||||
constructor(private auditService: AuditService, private stateService: StateService) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.username = await this.stateService.getEmail();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.error = false;
|
||||
this.username = this.username.toLowerCase();
|
||||
try {
|
||||
this.formPromise = this.auditService.breachedAccounts(this.username);
|
||||
this.breachedAccounts = await this.formPromise;
|
||||
} catch {
|
||||
this.error = true;
|
||||
}
|
||||
this.checkedUsername = this.username;
|
||||
async ngOnInit() {
|
||||
this.username = await this.stateService.getEmail();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.error = false;
|
||||
this.username = this.username.toLowerCase();
|
||||
try {
|
||||
this.formPromise = this.auditService.breachedAccounts(this.username);
|
||||
this.breachedAccounts = await this.formPromise;
|
||||
} catch {
|
||||
this.error = true;
|
||||
}
|
||||
this.checkedUsername = this.username;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +1,107 @@
|
||||
import {
|
||||
Directive,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
import { Organization } from 'jslib-common/models/domain/organization';
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
import { AddEditComponent as OrgAddEditComponent } from '../organizations/vault/add-edit.component';
|
||||
import { AddEditComponent } from '../vault/add-edit.component';
|
||||
import { AddEditComponent as OrgAddEditComponent } from "../organizations/vault/add-edit.component";
|
||||
import { AddEditComponent } from "../vault/add-edit.component";
|
||||
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
||||
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
|
||||
@Directive()
|
||||
export class CipherReportComponent {
|
||||
@ViewChild('cipherAddEdit', { read: ViewContainerRef, static: true }) cipherAddEditModalRef: ViewContainerRef;
|
||||
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
|
||||
cipherAddEditModalRef: ViewContainerRef;
|
||||
|
||||
loading = false;
|
||||
hasLoaded = false;
|
||||
ciphers: CipherView[] = [];
|
||||
organization: Organization;
|
||||
loading = false;
|
||||
hasLoaded = false;
|
||||
ciphers: CipherView[] = [];
|
||||
organization: Organization;
|
||||
|
||||
constructor(private modalService: ModalService, protected messagingService: MessagingService,
|
||||
public requiresPaid: boolean, private stateService: StateService,
|
||||
protected passwordRepromptService: PasswordRepromptService) { }
|
||||
constructor(
|
||||
private modalService: ModalService,
|
||||
protected messagingService: MessagingService,
|
||||
public requiresPaid: boolean,
|
||||
private stateService: StateService,
|
||||
protected passwordRepromptService: PasswordRepromptService
|
||||
) {}
|
||||
|
||||
async load() {
|
||||
this.loading = true;
|
||||
await this.setCiphers();
|
||||
this.loading = false;
|
||||
this.hasLoaded = true;
|
||||
async load() {
|
||||
this.loading = true;
|
||||
await this.setCiphers();
|
||||
this.loading = false;
|
||||
this.hasLoaded = true;
|
||||
}
|
||||
|
||||
async selectCipher(cipher: CipherView) {
|
||||
if (!(await this.repromptCipher(cipher))) {
|
||||
return;
|
||||
}
|
||||
|
||||
async selectCipher(cipher: CipherView) {
|
||||
if (!await this.repromptCipher(cipher)) {
|
||||
return;
|
||||
}
|
||||
const type = this.organization != null ? OrgAddEditComponent : AddEditComponent;
|
||||
|
||||
const type = this.organization != null ? OrgAddEditComponent : AddEditComponent;
|
||||
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(type, this.cipherAddEditModalRef, (comp: OrgAddEditComponent | AddEditComponent) => {
|
||||
if (this.organization != null) {
|
||||
(comp as OrgAddEditComponent).organization = this.organization;
|
||||
comp.organizationId = this.organization.id;
|
||||
}
|
||||
|
||||
comp.cipherId = cipher == null ? null : cipher.id;
|
||||
comp.onSavedCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
comp.onDeletedCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
comp.onRestoredCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return childComponent;
|
||||
}
|
||||
|
||||
protected async checkAccess(): Promise<boolean> {
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
type,
|
||||
this.cipherAddEditModalRef,
|
||||
(comp: OrgAddEditComponent | AddEditComponent) => {
|
||||
if (this.organization != null) {
|
||||
// TODO: Maybe we want to just make sure they are not on a free plan? Just compare useTotp for now
|
||||
// since all paid plans include useTotp
|
||||
if (this.requiresPaid && !this.organization.useTotp) {
|
||||
this.messagingService.send('upgradeOrganization', { organizationId: this.organization.id });
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const accessPremium = await this.stateService.getCanAccessPremium();
|
||||
if (this.requiresPaid && !accessPremium) {
|
||||
this.messagingService.send('premiumRequired');
|
||||
this.loading = false;
|
||||
return false;
|
||||
}
|
||||
(comp as OrgAddEditComponent).organization = this.organization;
|
||||
comp.organizationId = this.organization.id;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async setCiphers() {
|
||||
this.ciphers = [];
|
||||
}
|
||||
comp.cipherId = cipher == null ? null : cipher.id;
|
||||
comp.onSavedCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
comp.onDeletedCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
comp.onRestoredCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
protected async repromptCipher(c: CipherView) {
|
||||
return c.reprompt === CipherRepromptType.None || await this.passwordRepromptService.showPasswordPrompt();
|
||||
return childComponent;
|
||||
}
|
||||
|
||||
protected async checkAccess(): Promise<boolean> {
|
||||
if (this.organization != null) {
|
||||
// TODO: Maybe we want to just make sure they are not on a free plan? Just compare useTotp for now
|
||||
// since all paid plans include useTotp
|
||||
if (this.requiresPaid && !this.organization.useTotp) {
|
||||
this.messagingService.send("upgradeOrganization", { organizationId: this.organization.id });
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const accessPremium = await this.stateService.getCanAccessPremium();
|
||||
if (this.requiresPaid && !accessPremium) {
|
||||
this.messagingService.send("premiumRequired");
|
||||
this.loading = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async setCiphers() {
|
||||
this.ciphers = [];
|
||||
}
|
||||
|
||||
protected async repromptCipher(c: CipherView) {
|
||||
return (
|
||||
c.reprompt === CipherRepromptType.None ||
|
||||
(await this.passwordRepromptService.showPasswordPrompt())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,39 @@
|
||||
<form #form (ngSubmit)="submit()" ngNativeValidate [appApiAction]="formPromise" [formGroup]="exportForm">
|
||||
<div class="page-header">
|
||||
<h1>{{'exportVault' | i18n}}</h1>
|
||||
</div>
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
ngNativeValidate
|
||||
[appApiAction]="formPromise"
|
||||
[formGroup]="exportForm"
|
||||
>
|
||||
<div class="page-header">
|
||||
<h1>{{ "exportVault" | i18n }}</h1>
|
||||
</div>
|
||||
|
||||
<app-callout type="error" title="{{'vaultExportDisabled' | i18n}}" *ngIf="disabledByPolicy">
|
||||
{{'personalVaultExportPolicyInEffect' | i18n}}
|
||||
</app-callout>
|
||||
<app-callout type="error" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
|
||||
{{ "personalVaultExportPolicyInEffect" | i18n }}
|
||||
</app-callout>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="format">{{'fileFormat' | i18n}}</label>
|
||||
<select class="form-control" id="format" name="Format" formControlName="format">
|
||||
<option *ngFor="let f of formatOptions" [value]="f.value">{{f.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="format">{{ "fileFormat" | i18n }}</label>
|
||||
<select class="form-control" id="format" name="Format" formControlName="format">
|
||||
<option *ngFor="let f of formatOptions" [value]="f.value">{{ f.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<app-verify-master-password ngDefaultControl formControlName="secret" name="secret">
|
||||
</app-verify-master-password>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<app-verify-master-password ngDefaultControl formControlName="secret" name="secret">
|
||||
</app-verify-master-password>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" [disabled]="form.loading || exportForm.disabled">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="form.loading"></i>
|
||||
<span *ngIf="!form.loading">{{'exportVault' | i18n}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" [disabled]="form.loading || exportForm.disabled">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
*ngIf="form.loading"
|
||||
></i>
|
||||
<span *ngIf="!form.loading">{{ "exportVault" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -1,34 +1,51 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
||||
import { ExportService } from 'jslib-common/abstractions/export.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { EventService } from "jslib-common/abstractions/event.service";
|
||||
import { ExportService } from "jslib-common/abstractions/export.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
|
||||
|
||||
import { ExportComponent as BaseExportComponent } from 'jslib-angular/components/export.component';
|
||||
import { ExportComponent as BaseExportComponent } from "jslib-angular/components/export.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-export',
|
||||
templateUrl: 'export.component.html',
|
||||
selector: "app-export",
|
||||
templateUrl: "export.component.html",
|
||||
})
|
||||
export class ExportComponent extends BaseExportComponent {
|
||||
organizationId: string;
|
||||
organizationId: string;
|
||||
|
||||
constructor(cryptoService: CryptoService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, exportService: ExportService,
|
||||
eventService: EventService, policyService: PolicyService, logService: LogService,
|
||||
userVerificationService: UserVerificationService, fb: FormBuilder) {
|
||||
super(cryptoService, i18nService, platformUtilsService, exportService, eventService,
|
||||
policyService, window, logService, userVerificationService, fb);
|
||||
}
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
exportService: ExportService,
|
||||
eventService: EventService,
|
||||
policyService: PolicyService,
|
||||
logService: LogService,
|
||||
userVerificationService: UserVerificationService,
|
||||
fb: FormBuilder
|
||||
) {
|
||||
super(
|
||||
cryptoService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
exportService,
|
||||
eventService,
|
||||
policyService,
|
||||
window,
|
||||
logService,
|
||||
userVerificationService,
|
||||
fb
|
||||
);
|
||||
}
|
||||
|
||||
protected saved() {
|
||||
super.saved();
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('exportSuccess'));
|
||||
}
|
||||
protected saved() {
|
||||
super.saved();
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("exportSuccess"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,63 @@
|
||||
<div class="page-header">
|
||||
<h1>{{'exposedPasswordsReport' | i18n}}</h1>
|
||||
<h1>{{ "exposedPasswordsReport" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{'exposedPasswordsReportDesc' | i18n}}</p>
|
||||
<p>{{ "exposedPasswordsReportDesc" | i18n }}</p>
|
||||
<button type="button" class="btn btn-primary btn-submit" [disabled]="loading" (click)="load()">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'checkExposedPasswords' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "checkExposedPasswords" | i18n }}</span>
|
||||
</button>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{'goodNews' | i18n}}" *ngIf="!ciphers.length">
|
||||
{{'noExposedPasswords' | i18n}}
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noExposedPasswords" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'exposedPasswordsFound' | i18n }}" [useAlertRole]="true">
|
||||
{{ "exposedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{'exposedPasswordsFound' | i18n}}" [useAlertRole]="true">
|
||||
{{'exposedPasswordsFoundDesc' | 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">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c) ; else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{c.name}}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-cube" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i class="fa fa-paperclip" appStopProp title="{{'attachments' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'attachments' | i18n}}</span>
|
||||
</ng-container>
|
||||
<br>
|
||||
<small>{{c.subTitle}}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="badge badge-warning">
|
||||
{{'exposedXTimes' | i18n : (exposedPasswordMap.get(c.id) | number)}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
<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">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{ c.name }}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="fa fa-cube"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="fa fa-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="badge badge-warning">
|
||||
{{ "exposedXTimes" | i18n: (exposedPasswordMap.get(c.id) | number) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
|
||||
@@ -1,71 +1,78 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { AuditService } from "jslib-common/abstractions/audit.service";
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
|
||||
import { CipherReportComponent } from './cipher-report.component';
|
||||
import { CipherReportComponent } from "./cipher-report.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-exposed-passwords-report',
|
||||
templateUrl: 'exposed-passwords-report.component.html',
|
||||
selector: "app-exposed-passwords-report",
|
||||
templateUrl: "exposed-passwords-report.component.html",
|
||||
})
|
||||
export class ExposedPasswordsReportComponent extends CipherReportComponent implements OnInit {
|
||||
exposedPasswordMap = new Map<string, number>();
|
||||
exposedPasswordMap = new Map<string, number>();
|
||||
|
||||
constructor(protected cipherService: CipherService, protected auditService: AuditService,
|
||||
modalService: ModalService, messagingService: MessagingService,
|
||||
stateService: StateService, passwordRepromptService: PasswordRepromptService) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
constructor(
|
||||
protected cipherService: CipherService,
|
||||
protected auditService: AuditService,
|
||||
modalService: ModalService,
|
||||
messagingService: MessagingService,
|
||||
stateService: StateService,
|
||||
passwordRepromptService: PasswordRepromptService
|
||||
) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.checkAccess();
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (await this.checkAccess()) {
|
||||
super.load();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.checkAccess();
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (await this.checkAccess()) {
|
||||
super.load();
|
||||
async setCiphers() {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const exposedPasswordCiphers: CipherView[] = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
allCiphers.forEach((c) => {
|
||||
if (
|
||||
c.type !== CipherType.Login ||
|
||||
c.login.password == null ||
|
||||
c.login.password === "" ||
|
||||
c.isDeleted
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const promise = this.auditService.passwordLeaked(c.login.password).then((exposedCount) => {
|
||||
if (exposedCount > 0) {
|
||||
exposedPasswordCiphers.push(c);
|
||||
this.exposedPasswordMap.set(c.id, exposedCount);
|
||||
}
|
||||
}
|
||||
});
|
||||
promises.push(promise);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
this.ciphers = exposedPasswordCiphers;
|
||||
}
|
||||
|
||||
async setCiphers() {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const exposedPasswordCiphers: CipherView[] = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
allCiphers.forEach(c => {
|
||||
if (c.type !== CipherType.Login || c.login.password == null || c.login.password === '' || c.isDeleted) {
|
||||
return;
|
||||
}
|
||||
const promise = this.auditService.passwordLeaked(c.login.password).then(exposedCount => {
|
||||
if (exposedCount > 0) {
|
||||
exposedPasswordCiphers.push(c);
|
||||
this.exposedPasswordMap.set(c.id, exposedCount);
|
||||
}
|
||||
});
|
||||
promises.push(promise);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
this.ciphers = exposedPasswordCiphers;
|
||||
}
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
protected canManageCipher(c: CipherView): boolean {
|
||||
// this will only ever be false from the org view;
|
||||
return true;
|
||||
}
|
||||
protected canManageCipher(c: CipherView): boolean {
|
||||
// this will only ever be false from the org view;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,266 +1,329 @@
|
||||
<div class="page-header">
|
||||
<h1>{{'importData' | i18n}}</h1>
|
||||
<h1>{{ "importData" | i18n }}</h1>
|
||||
</div>
|
||||
<app-callout type="info" *ngIf="importBlockedByPolicy">
|
||||
{{'personalOwnershipPolicyInEffectImports' | i18n}}
|
||||
{{ "personalOwnershipPolicyInEffectImports" | i18n }}
|
||||
</app-callout>
|
||||
<form #form (ngSubmit)="submit()" ngNativeValidate>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="type">1. {{'selectFormat' | i18n}}</label>
|
||||
<select id="type" name="Format" [(ngModel)]="format" class="form-control"
|
||||
[disabled]="importBlockedByPolicy" required>
|
||||
<option *ngFor="let o of featuredImportOptions" [ngValue]="o.id">{{o.name}}</option>
|
||||
<ng-container *ngIf="importOptions && importOptions.length">
|
||||
<option value="-" disabled></option>
|
||||
<option *ngFor="let o of importOptions" [ngValue]="o.id">{{o.name}}</option>
|
||||
</ng-container>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="type">1. {{ "selectFormat" | i18n }}</label>
|
||||
<select
|
||||
id="type"
|
||||
name="Format"
|
||||
[(ngModel)]="format"
|
||||
class="form-control"
|
||||
[disabled]="importBlockedByPolicy"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of featuredImportOptions" [ngValue]="o.id">{{ o.name }}</option>
|
||||
<ng-container *ngIf="importOptions && importOptions.length">
|
||||
<option value="-" disabled></option>
|
||||
<option *ngFor="let o of importOptions" [ngValue]="o.id">{{ o.name }}</option>
|
||||
</ng-container>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<app-callout type="info" title="{{getFormatInstructionTitle()}}" *ngIf="format">
|
||||
<ng-container *ngIf="format === 'bitwardencsv' || format === 'bitwardenjson'">
|
||||
See detailed instructions on our help site at
|
||||
<a target="_blank" rel="noopener" href="https://help.bitwarden.com/article/export-your-data/">
|
||||
https://help.bitwarden.com/article/export-your-data/</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'lastpasscsv'">
|
||||
See detailed instructions on our help site at
|
||||
<a target="_blank" rel="noopener" href="https://help.bitwarden.com/article/import-from-lastpass/">
|
||||
https://help.bitwarden.com/article/import-from-lastpass/</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'keepassxcsv'">
|
||||
Using the KeePassX desktop application, navigate to "Database" → "Export to CSV file" and save the CSV
|
||||
file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'aviracsv'">
|
||||
In the Avira web vault, go to "Settings" → "My Data" → "Export data" and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'blurcsv'">
|
||||
In the Blur web vault, click your username at the top and go to "Settings" → "Export Data", then click
|
||||
"Export CSV"
|
||||
for your "Accounts".
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'safeincloudxml'">
|
||||
Using the SaveInCloud desktop application, navigate to "File" → "Export" → "As XML" and save the
|
||||
XML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'padlockcsv'">
|
||||
Using the Padlock desktop application, click the hamburger icon in the top left corner and navigate to
|
||||
"Settings" →
|
||||
"Export" button and save the file "As CSV".
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'keepass2xml'">
|
||||
Using the KeePass 2 desktop application, navigate to "File" → "Export" and select the "KeePass XML
|
||||
(2.x)" option.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'upmcsv'">
|
||||
Using the Universal Password Manager desktop application, navigate to "Database" → "Export" and save
|
||||
the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'saferpasscsv'">
|
||||
Using the SaferPass browser extension, click the hamburger icon in the top left corner and navigate to
|
||||
"Settings". Click the "Export accounts" button to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'meldiumcsv'">
|
||||
Using the Meldium web vault, navigate to "Settings". Locate the "Export data" function and click "Show me my
|
||||
data" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'keepercsv'">
|
||||
Log into the Keeper web vault (keepersecurity.com/vault). Navigate to "Backup" (top right) and find the
|
||||
"Export to .csv File" option. Click "Export Now" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'chromecsv' || format === 'operacsv' || format === 'vivaldicsv'">
|
||||
<span *ngIf="format !== 'chromecsv'">
|
||||
The process is exactly the same as importing from Google Chrome.
|
||||
</span>
|
||||
See detailed instructions on our help site at
|
||||
<a target="_blank" rel="noopener" href="https://help.bitwarden.com/article/import-from-chrome/">
|
||||
https://help.bitwarden.com/article/import-from-chrome/</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'firefoxcsv'">
|
||||
See detailed instructions on our help site at
|
||||
<a target="_blank" rel="noopener" href="https://bitwarden.com/help/article/import-from-firefox/">
|
||||
https://bitwarden.com/help/article/import-from-firefox/</a>.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'safaricsv'">
|
||||
See detailed instructions on our help site at
|
||||
<a target="_blank" rel="noopener" href="https://bitwarden.com/help/article/import-from-safari/">
|
||||
https://bitwarden.com/help/article/import-from-safari/</a>.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === '1password1pif' || format === '1passwordwincsv' || format === '1passwordmaccsv'">
|
||||
See detailed instructions on our help site at
|
||||
<a target="_blank" rel="noopener" href="https://help.bitwarden.com/article/import-from-1password/">
|
||||
https://help.bitwarden.com/article/import-from-1password/</a>.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passworddragonxml'">
|
||||
Using the Password Dragon desktop application, navigate to "File" → "Export" → "To XML". In the
|
||||
dialog that pops up select "All Rows" and check all fields. Click the "Export" button and save the XML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'enpasscsv'">
|
||||
Using the Enpass desktop application, navigate to "File" → "Export" → "As CSV". Select "OK" to the
|
||||
warning alert and save the CSV file. Note that the importer only supports files exported while Enpass is set
|
||||
to the English language, so adjust your settings accordingly.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'enpassjson'">
|
||||
Using the Enpass 6 desktop application, click the menu button and navigate to "File" → "Export".
|
||||
Select the ".json" file format option and save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'pwsafexml'">
|
||||
Using the Password Safe desktop application, navigate to "File" → "Export To" → "XML format..."
|
||||
and save the XML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'dashlanejson'">
|
||||
Using the Dashlane desktop application, navigate to "File" → "Export" → "Unsecured archive
|
||||
(readable) in JSON format" and save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'msecurecsv'">
|
||||
Using the mSecure desktop application, navigate to "File" → "Export" → "CSV File..." and save the
|
||||
CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'stickypasswordxml'">
|
||||
Using the Sticky Password desktop application, navigate to "Menu" (top right) → "Export" → "Export
|
||||
all". Select the unencrypted format XML option and save the XML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'truekeycsv'">
|
||||
Using the True Key desktop application, click the gear icon (top right) and then navigate to "App Settings".
|
||||
Click the "Export" button, enter your password and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'clipperzhtml'">
|
||||
Log into the Clipperz web application (clipperz.is/app). Click the hamburger menu icon in the top right to
|
||||
expand the navigation bar. Navigate to "Data" → "Export". Click the "download HTML+JSON" button to save
|
||||
the HTML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'roboformcsv'">
|
||||
Using the RoboForm Editor desktop application, navigate to "RoboForm" (top left) → "Options" →
|
||||
"Account & Data" and click the "Export" button. Select all of your data, change the "Format" to "CSV
|
||||
file" and then click the "Export" button to save the CSV file. Note: RoboForm only allows you to export
|
||||
Logins. Other items will not be exported.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passboltcsv'">
|
||||
Log into the Passbolt web vault and navigate to the "Passwords" listing. Select all of the passwords you
|
||||
would like to export and click the "Export" button at the top of the listing. Choose the "csv (lastpass)"
|
||||
export format and click the "Export" button.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'ascendocsv'">
|
||||
Using the Ascendo DataVault desktop application, navigate to "Tools" → "Export". In the dialog that
|
||||
pops up, select the "All Items (DVX, CSV)" option. Click the "Ok" button to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passwordbossjson'">
|
||||
Using the Password Boss desktop application, navigate to "File" → "Export data" → "Password Boss
|
||||
JSON - not encrypted" and save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'zohovaultcsv'">
|
||||
Log into the Zoho web vault (vault.zoho.com). Navigate to "Tools" → "Export Secrets". Select "All
|
||||
Secrets" and click the "Zoho Vault Format CSV" button. Highlight and copy the data from the textarea. Open a
|
||||
text editor like Notepad and paste the data. Save the data from the text editor as
|
||||
<code>zoho_export.csv</code>.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'splashidcsv'">
|
||||
Using the SplashID Safe desktop application, click on the SplashID blue lock logo in the top right corner.
|
||||
Navigate to "Export" → "Export as CSV" and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passkeepcsv'">
|
||||
Using the PassKeep mobile app, navigate to "Backup/Restore". Locate the "CSV Backup/Restore" section and
|
||||
click "Backup to CSV" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'gnomejson'">
|
||||
Make sure you have python-keyring and python-gnomekeyring installed. Save the
|
||||
<a target="_blank" rel="noopener" href="https://bit.ly/2GpOMTg">GNOME Keyring Import/Export</a> python
|
||||
script to your desktop as <code>pw_helper.py</code>. Open terminal and run
|
||||
<code>chmod +rx Desktop/pw_helper.py</code> and then
|
||||
<code>python Desktop/pw_helper.py export Desktop/my_passwords.json</code>. Then upload the resulting
|
||||
<code>my_passwords.json</code> file here to Bitwarden.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passwordagentcsv'">
|
||||
Using the Password Agent desktop application navigate to "File" → "Export", select the "Fields to
|
||||
export" button and check all of the fields, change the "Output format" to "CSV", and then click the "Start"
|
||||
button to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passpackcsv'">
|
||||
Log into the Passpack website vault and navigate to "Settings" → "Export", then click the "Download"
|
||||
button to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passmanjson'">
|
||||
Open your Passman vault and click on "Settings" in the bottom left corner. In the "Settings" window switch
|
||||
to the "Export credentials" tab and choose "JSON" as the export type. Enter your vault's passphrase and
|
||||
click the "Export" button to save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'avastcsv'">
|
||||
Open the Avast Passwords desktop application and navigate to "Settings" → "Import/export data". Select
|
||||
the "Export" button for the "Export to CSV file" option to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'avastjson'">
|
||||
Open the Avast Passwords desktop application and navigate to "Settings" → "Import/export data". Select
|
||||
the "Export" button for the "Export to JSON file" option to save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'fsecurefsk'">
|
||||
Open the F-Secure KEY desktop application and navigate to "Settings" → "Export Passwords". Select the
|
||||
"Export" button, enter your master password, and save the FSK file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'kasperskytxt'">
|
||||
Open the Kaspersky Password Manager desktop application and navigate to "Settings" → "Import/Export".
|
||||
Locate the "Export to text file" section and select the "Export" button to save the TXT file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'remembearcsv'">
|
||||
Open the RememBear desktop application and navigate to "Settings" → "Account" → "Export".
|
||||
Enter your master password and select the "Export Anyway" button to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passwordwallettxt'">
|
||||
Open the PasswordWallet desktop application and navigate to "File" → "Export" →
|
||||
"Visible entries to text file". Enter your password and select the "Ok" button to save the TXT file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'mykicsv'">
|
||||
Open the Myki desktop browser extension and navigate to "Advanced" → "Export Accounts" and then scan
|
||||
the QR code with your mobile device. Various CSV files will then be saved to your computer's
|
||||
downloads folder.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'securesafecsv'">
|
||||
Export your SecureSafe password safe to a CSV file with a comma delimiter.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'logmeoncecsv'">
|
||||
Open the LogMeOnce browser extension, then navigate to "Open Menu" → "Export To" and
|
||||
select "CSV File" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'blackberrycsv'">
|
||||
Open the BlackBerry Password Keeper application, then navigate to "Settings" → "Import/Export".
|
||||
Select "Export Passwords" and follow the instructions on screen to save the unencrypted CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'buttercupcsv'">
|
||||
Open the Buttercup desktop application and unlock your vault. Right click on your vault's icon and
|
||||
select "Export" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'codebookcsv'">
|
||||
Open the Codebook desktop application and log in. Navigate to "File" → "Export all", then click
|
||||
"Yes" on the dialog and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'encryptrcsv'">
|
||||
Open the newest version of the Encryptr desktop application and allow all of your data to sync.
|
||||
Once syncing of your data is complete, the download icon in the top right corner will turn pink. Click
|
||||
the download icon and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'yoticsv'">
|
||||
From the Yoti browser extension, click on "Settings", then "Export Saved Logins" and save the CSV file.
|
||||
</ng-container>
|
||||
</app-callout>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="file">2. {{'selectImportFile' | i18n}}</label>
|
||||
<input type="file" id="file" class="form-control-file" name="file" [disabled]="importBlockedByPolicy">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<app-callout type="info" title="{{ getFormatInstructionTitle() }}" *ngIf="format">
|
||||
<ng-container *ngIf="format === 'bitwardencsv' || format === 'bitwardenjson'">
|
||||
See detailed instructions on our help site at
|
||||
<a target="_blank" rel="noopener" href="https://help.bitwarden.com/article/export-your-data/">
|
||||
https://help.bitwarden.com/article/export-your-data/</a
|
||||
>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'lastpasscsv'">
|
||||
See detailed instructions on our help site at
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
href="https://help.bitwarden.com/article/import-from-lastpass/"
|
||||
>
|
||||
https://help.bitwarden.com/article/import-from-lastpass/</a
|
||||
>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'keepassxcsv'">
|
||||
Using the KeePassX desktop application, navigate to "Database" → "Export to CSV file" and
|
||||
save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'aviracsv'">
|
||||
In the Avira web vault, go to "Settings" → "My Data" → "Export data" and save the
|
||||
CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'blurcsv'">
|
||||
In the Blur web vault, click your username at the top and go to "Settings" → "Export
|
||||
Data", then click "Export CSV" for your "Accounts".
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'safeincloudxml'">
|
||||
Using the SaveInCloud desktop application, navigate to "File" → "Export" → "As XML"
|
||||
and save the XML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'padlockcsv'">
|
||||
Using the Padlock desktop application, click the hamburger icon in the top left corner and
|
||||
navigate to "Settings" → "Export" button and save the file "As CSV".
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'keepass2xml'">
|
||||
Using the KeePass 2 desktop application, navigate to "File" → "Export" and select the
|
||||
"KeePass XML (2.x)" option.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'upmcsv'">
|
||||
Using the Universal Password Manager desktop application, navigate to "Database" →
|
||||
"Export" and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'saferpasscsv'">
|
||||
Using the SaferPass browser extension, click the hamburger icon in the top left corner and
|
||||
navigate to "Settings". Click the "Export accounts" button to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'meldiumcsv'">
|
||||
Using the Meldium web vault, navigate to "Settings". Locate the "Export data" function and
|
||||
click "Show me my data" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'keepercsv'">
|
||||
Log into the Keeper web vault (keepersecurity.com/vault). Navigate to "Backup" (top right) and
|
||||
find the "Export to .csv File" option. Click "Export Now" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="format === 'chromecsv' || format === 'operacsv' || format === 'vivaldicsv'"
|
||||
>
|
||||
<span *ngIf="format !== 'chromecsv'">
|
||||
The process is exactly the same as importing from Google Chrome.
|
||||
</span>
|
||||
See detailed instructions on our help site at
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
href="https://help.bitwarden.com/article/import-from-chrome/"
|
||||
>
|
||||
https://help.bitwarden.com/article/import-from-chrome/</a
|
||||
>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'firefoxcsv'">
|
||||
See detailed instructions on our help site at
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
href="https://bitwarden.com/help/article/import-from-firefox/"
|
||||
>
|
||||
https://bitwarden.com/help/article/import-from-firefox/</a
|
||||
>.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'safaricsv'">
|
||||
See detailed instructions on our help site at
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
href="https://bitwarden.com/help/article/import-from-safari/"
|
||||
>
|
||||
https://bitwarden.com/help/article/import-from-safari/</a
|
||||
>.
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
format === '1password1pif' || format === '1passwordwincsv' || format === '1passwordmaccsv'
|
||||
"
|
||||
>
|
||||
See detailed instructions on our help site at
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
href="https://help.bitwarden.com/article/import-from-1password/"
|
||||
>
|
||||
https://help.bitwarden.com/article/import-from-1password/</a
|
||||
>.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passworddragonxml'">
|
||||
Using the Password Dragon desktop application, navigate to "File" → "Export" → "To
|
||||
XML". In the dialog that pops up select "All Rows" and check all fields. Click the "Export"
|
||||
button and save the XML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'enpasscsv'">
|
||||
Using the Enpass desktop application, navigate to "File" → "Export" → "As CSV".
|
||||
Select "OK" to the warning alert and save the CSV file. Note that the importer only supports
|
||||
files exported while Enpass is set to the English language, so adjust your settings
|
||||
accordingly.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'enpassjson'">
|
||||
Using the Enpass 6 desktop application, click the menu button and navigate to "File" →
|
||||
"Export". Select the ".json" file format option and save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'pwsafexml'">
|
||||
Using the Password Safe desktop application, navigate to "File" → "Export To" → "XML
|
||||
format..." and save the XML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'dashlanejson'">
|
||||
Using the Dashlane desktop application, navigate to "File" → "Export" → "Unsecured
|
||||
archive (readable) in JSON format" and save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'msecurecsv'">
|
||||
Using the mSecure desktop application, navigate to "File" → "Export" → "CSV File..."
|
||||
and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'stickypasswordxml'">
|
||||
Using the Sticky Password desktop application, navigate to "Menu" (top right) → "Export"
|
||||
→ "Export all". Select the unencrypted format XML option and save the XML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'truekeycsv'">
|
||||
Using the True Key desktop application, click the gear icon (top right) and then navigate to
|
||||
"App Settings". Click the "Export" button, enter your password and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'clipperzhtml'">
|
||||
Log into the Clipperz web application (clipperz.is/app). Click the hamburger menu icon in the
|
||||
top right to expand the navigation bar. Navigate to "Data" → "Export". Click the
|
||||
"download HTML+JSON" button to save the HTML file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'roboformcsv'">
|
||||
Using the RoboForm Editor desktop application, navigate to "RoboForm" (top left) →
|
||||
"Options" → "Account & Data" and click the "Export" button. Select all of your data,
|
||||
change the "Format" to "CSV file" and then click the "Export" button to save the CSV file.
|
||||
Note: RoboForm only allows you to export Logins. Other items will not be exported.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passboltcsv'">
|
||||
Log into the Passbolt web vault and navigate to the "Passwords" listing. Select all of the
|
||||
passwords you would like to export and click the "Export" button at the top of the listing.
|
||||
Choose the "csv (lastpass)" export format and click the "Export" button.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'ascendocsv'">
|
||||
Using the Ascendo DataVault desktop application, navigate to "Tools" → "Export". In the
|
||||
dialog that pops up, select the "All Items (DVX, CSV)" option. Click the "Ok" button to save
|
||||
the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passwordbossjson'">
|
||||
Using the Password Boss desktop application, navigate to "File" → "Export data" →
|
||||
"Password Boss JSON - not encrypted" and save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'zohovaultcsv'">
|
||||
Log into the Zoho web vault (vault.zoho.com). Navigate to "Tools" → "Export Secrets".
|
||||
Select "All Secrets" and click the "Zoho Vault Format CSV" button. Highlight and copy the data
|
||||
from the textarea. Open a text editor like Notepad and paste the data. Save the data from the
|
||||
text editor as
|
||||
<code>zoho_export.csv</code>.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'splashidcsv'">
|
||||
Using the SplashID Safe desktop application, click on the SplashID blue lock logo in the top
|
||||
right corner. Navigate to "Export" → "Export as CSV" and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passkeepcsv'">
|
||||
Using the PassKeep mobile app, navigate to "Backup/Restore". Locate the "CSV Backup/Restore"
|
||||
section and click "Backup to CSV" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'gnomejson'">
|
||||
Make sure you have python-keyring and python-gnomekeyring installed. Save the
|
||||
<a target="_blank" rel="noopener" href="https://bit.ly/2GpOMTg"
|
||||
>GNOME Keyring Import/Export</a
|
||||
>
|
||||
python script to your desktop as <code>pw_helper.py</code>. Open terminal and run
|
||||
<code>chmod +rx Desktop/pw_helper.py</code> and then
|
||||
<code>python Desktop/pw_helper.py export Desktop/my_passwords.json</code>. Then upload the
|
||||
resulting <code>my_passwords.json</code> file here to Bitwarden.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passwordagentcsv'">
|
||||
Using the Password Agent desktop application navigate to "File" → "Export", select the
|
||||
"Fields to export" button and check all of the fields, change the "Output format" to "CSV",
|
||||
and then click the "Start" button to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passpackcsv'">
|
||||
Log into the Passpack website vault and navigate to "Settings" → "Export", then click the
|
||||
"Download" button to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passmanjson'">
|
||||
Open your Passman vault and click on "Settings" in the bottom left corner. In the "Settings"
|
||||
window switch to the "Export credentials" tab and choose "JSON" as the export type. Enter your
|
||||
vault's passphrase and click the "Export" button to save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'avastcsv'">
|
||||
Open the Avast Passwords desktop application and navigate to "Settings" → "Import/export
|
||||
data". Select the "Export" button for the "Export to CSV file" option to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'avastjson'">
|
||||
Open the Avast Passwords desktop application and navigate to "Settings" → "Import/export
|
||||
data". Select the "Export" button for the "Export to JSON file" option to save the JSON file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'fsecurefsk'">
|
||||
Open the F-Secure KEY desktop application and navigate to "Settings" → "Export
|
||||
Passwords". Select the "Export" button, enter your master password, and save the FSK file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'kasperskytxt'">
|
||||
Open the Kaspersky Password Manager desktop application and navigate to "Settings" →
|
||||
"Import/Export". Locate the "Export to text file" section and select the "Export" button to
|
||||
save the TXT file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'remembearcsv'">
|
||||
Open the RememBear desktop application and navigate to "Settings" → "Account" →
|
||||
"Export". Enter your master password and select the "Export Anyway" button to save the CSV
|
||||
file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'passwordwallettxt'">
|
||||
Open the PasswordWallet desktop application and navigate to "File" → "Export" →
|
||||
"Visible entries to text file". Enter your password and select the "Ok" button to save the TXT
|
||||
file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'mykicsv'">
|
||||
Open the Myki desktop browser extension and navigate to "Advanced" → "Export Accounts"
|
||||
and then scan the QR code with your mobile device. Various CSV files will then be saved to
|
||||
your computer's downloads folder.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'securesafecsv'">
|
||||
Export your SecureSafe password safe to a CSV file with a comma delimiter.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'logmeoncecsv'">
|
||||
Open the LogMeOnce browser extension, then navigate to "Open Menu" → "Export To" and
|
||||
select "CSV File" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'blackberrycsv'">
|
||||
Open the BlackBerry Password Keeper application, then navigate to "Settings" →
|
||||
"Import/Export". Select "Export Passwords" and follow the instructions on screen to save the
|
||||
unencrypted CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'buttercupcsv'">
|
||||
Open the Buttercup desktop application and unlock your vault. Right click on your vault's icon
|
||||
and select "Export" to save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'codebookcsv'">
|
||||
Open the Codebook desktop application and log in. Navigate to "File" → "Export all", then
|
||||
click "Yes" on the dialog and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'encryptrcsv'">
|
||||
Open the newest version of the Encryptr desktop application and allow all of your data to
|
||||
sync. Once syncing of your data is complete, the download icon in the top right corner will
|
||||
turn pink. Click the download icon and save the CSV file.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'yoticsv'">
|
||||
From the Yoti browser extension, click on "Settings", then "Export Saved Logins" and save the
|
||||
CSV file.
|
||||
</ng-container>
|
||||
</app-callout>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="file">2. {{ "selectImportFile" | i18n }}</label>
|
||||
<input
|
||||
type="file"
|
||||
id="file"
|
||||
class="form-control-file"
|
||||
name="file"
|
||||
[disabled]="importBlockedByPolicy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="fileContents">{{'orCopyPasteFileContents' | i18n}}</label>
|
||||
<textarea id="fileContents" class="form-control" name="FileContents" [(ngModel)]="fileContents"
|
||||
[disabled]="importBlockedByPolicy"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="loading || importBlockedByPolicy"
|
||||
[ngClass]="{manual:importBlockedByPolicy}">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'importData' | i18n}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="fileContents">{{ "orCopyPasteFileContents" | i18n }}</label>
|
||||
<textarea
|
||||
id="fileContents"
|
||||
class="form-control"
|
||||
name="FileContents"
|
||||
[(ngModel)]="fileContents"
|
||||
[disabled]="importBlockedByPolicy"
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit"
|
||||
[disabled]="loading || importBlockedByPolicy"
|
||||
[ngClass]="{ manual: importBlockedByPolicy }"
|
||||
>
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "importData" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -1,185 +1,209 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { ImportOption, ImportService } from 'jslib-common/abstractions/import.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { ImportOption, ImportService } from "jslib-common/abstractions/import.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
|
||||
import { PolicyType } from 'jslib-common/enums/policyType';
|
||||
import { PolicyType } from "jslib-common/enums/policyType";
|
||||
|
||||
import Swal, { SweetAlertIcon } from 'sweetalert2';
|
||||
import Swal, { SweetAlertIcon } from "sweetalert2";
|
||||
|
||||
@Component({
|
||||
selector: 'app-import',
|
||||
templateUrl: 'import.component.html',
|
||||
selector: "app-import",
|
||||
templateUrl: "import.component.html",
|
||||
})
|
||||
export class ImportComponent implements OnInit {
|
||||
featuredImportOptions: ImportOption[];
|
||||
importOptions: ImportOption[];
|
||||
format: string = null;
|
||||
fileContents: string;
|
||||
formPromise: Promise<Error>;
|
||||
loading: boolean = false;
|
||||
importBlockedByPolicy: boolean = false;
|
||||
featuredImportOptions: ImportOption[];
|
||||
importOptions: ImportOption[];
|
||||
format: string = null;
|
||||
fileContents: string;
|
||||
formPromise: Promise<Error>;
|
||||
loading: boolean = false;
|
||||
importBlockedByPolicy: boolean = false;
|
||||
|
||||
protected organizationId: string = null;
|
||||
protected successNavigate: any[] = ['vault'];
|
||||
protected organizationId: string = null;
|
||||
protected successNavigate: any[] = ["vault"];
|
||||
|
||||
constructor(protected i18nService: I18nService,
|
||||
protected importService: ImportService, protected router: Router,
|
||||
protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService,
|
||||
private logService: LogService) { }
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected importService: ImportService,
|
||||
protected router: Router,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected policyService: PolicyService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.setImportOptions();
|
||||
this.importOptions.sort((a, b) => {
|
||||
if (a.name == null && b.name != null) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name != null && b.name == null) {
|
||||
return 1;
|
||||
}
|
||||
if (a.name == null && b.name == null) {
|
||||
return 0;
|
||||
}
|
||||
async ngOnInit() {
|
||||
this.setImportOptions();
|
||||
this.importOptions.sort((a, b) => {
|
||||
if (a.name == null && b.name != null) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name != null && b.name == null) {
|
||||
return 1;
|
||||
}
|
||||
if (a.name == null && b.name == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.i18nService.collator ? this.i18nService.collator.compare(a.name, b.name) :
|
||||
a.name.localeCompare(b.name);
|
||||
});
|
||||
return this.i18nService.collator
|
||||
? this.i18nService.collator.compare(a.name, b.name)
|
||||
: a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
this.importBlockedByPolicy = await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership);
|
||||
this.importBlockedByPolicy = await this.policyService.policyAppliesToUser(
|
||||
PolicyType.PersonalOwnership
|
||||
);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.importBlockedByPolicy) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("personalOwnershipPolicyInEffectImports")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.importBlockedByPolicy) {
|
||||
this.platformUtilsService.showToast('error', null,
|
||||
this.i18nService.t('personalOwnershipPolicyInEffectImports'));
|
||||
return;
|
||||
this.loading = true;
|
||||
|
||||
const importer = this.importService.getImporter(this.format, this.organizationId);
|
||||
if (importer === null) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("selectFormat")
|
||||
);
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const fileEl = document.getElementById("file") as HTMLInputElement;
|
||||
const files = fileEl.files;
|
||||
if (
|
||||
(files == null || files.length === 0) &&
|
||||
(this.fileContents == null || this.fileContents === "")
|
||||
) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("selectFile")
|
||||
);
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let fileContents = this.fileContents;
|
||||
if (files != null && files.length > 0) {
|
||||
try {
|
||||
const content = await this.getFileContents(files[0]);
|
||||
if (content != null) {
|
||||
fileContents = content;
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const importer = this.importService.getImporter(this.format, this.organizationId);
|
||||
if (importer === null) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('selectFormat'));
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const fileEl = document.getElementById('file') as HTMLInputElement;
|
||||
const files = fileEl.files;
|
||||
if ((files == null || files.length === 0) && (this.fileContents == null || this.fileContents === '')) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('selectFile'));
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let fileContents = this.fileContents;
|
||||
if (files != null && files.length > 0) {
|
||||
try {
|
||||
const content = await this.getFileContents(files[0]);
|
||||
if (content != null) {
|
||||
fileContents = content;
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (fileContents == null || fileContents === '') {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('selectFile'));
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.importService.import(importer, fileContents, this.organizationId);
|
||||
const error = await this.formPromise;
|
||||
if (error != null) {
|
||||
this.error(error);
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('importSuccess'));
|
||||
this.router.navigate(this.successNavigate);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
if (fileContents == null || fileContents === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("selectFile")
|
||||
);
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.importService.import(importer, fileContents, this.organizationId);
|
||||
const error = await this.formPromise;
|
||||
if (error != null) {
|
||||
this.error(error);
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("importSuccess"));
|
||||
this.router.navigate(this.successNavigate);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
getFormatInstructionTitle() {
|
||||
if (this.format == null) {
|
||||
return null;
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
getFormatInstructionTitle() {
|
||||
if (this.format == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const results = this.featuredImportOptions
|
||||
.concat(this.importOptions)
|
||||
.filter((o) => o.id === this.format);
|
||||
if (results.length > 0) {
|
||||
return this.i18nService.t("instructionsFor", results[0].name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected setImportOptions() {
|
||||
this.featuredImportOptions = [
|
||||
{
|
||||
id: null,
|
||||
name: "-- " + this.i18nService.t("select") + " --",
|
||||
},
|
||||
...this.importService.featuredImportOptions,
|
||||
];
|
||||
this.importOptions = this.importService.regularImportOptions;
|
||||
}
|
||||
|
||||
private async error(error: Error) {
|
||||
await Swal.fire({
|
||||
heightAuto: false,
|
||||
buttonsStyling: false,
|
||||
icon: "error" as SweetAlertIcon,
|
||||
iconHtml: `<i class="swal-custom-icon fa fa-bolt text-danger"></i>`,
|
||||
input: "textarea",
|
||||
inputValue: error.message,
|
||||
inputAttributes: {
|
||||
readonly: "true",
|
||||
},
|
||||
titleText: this.i18nService.t("importError"),
|
||||
text: this.i18nService.t("importErrorDesc"),
|
||||
showConfirmButton: true,
|
||||
confirmButtonText: this.i18nService.t("ok"),
|
||||
onOpen: (popupEl) => {
|
||||
popupEl.querySelector(".swal2-textarea").scrollTo(0, 0);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private getFileContents(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file, "utf-8");
|
||||
reader.onload = (evt) => {
|
||||
if (this.format === "lastpasscsv" && file.type === "text/html") {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString((evt.target as any).result, "text/html");
|
||||
const pre = doc.querySelector("pre");
|
||||
if (pre != null) {
|
||||
resolve(pre.textContent);
|
||||
return;
|
||||
}
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
const results = this.featuredImportOptions.concat(this.importOptions).filter(o => o.id === this.format);
|
||||
if (results.length > 0) {
|
||||
return this.i18nService.t('instructionsFor', results[0].name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected setImportOptions() {
|
||||
this.featuredImportOptions = [{
|
||||
id: null,
|
||||
name: '-- ' + this.i18nService.t('select') + ' --',
|
||||
}, ...this.importService.featuredImportOptions];
|
||||
this.importOptions = this.importService.regularImportOptions;
|
||||
}
|
||||
|
||||
private async error(error: Error) {
|
||||
await Swal.fire({
|
||||
heightAuto: false,
|
||||
buttonsStyling: false,
|
||||
icon: 'error' as SweetAlertIcon,
|
||||
iconHtml: `<i class="swal-custom-icon fa fa-bolt text-danger"></i>`,
|
||||
input: 'textarea',
|
||||
inputValue: error.message,
|
||||
inputAttributes: {
|
||||
'readonly': 'true',
|
||||
},
|
||||
titleText: this.i18nService.t('importError'),
|
||||
text: this.i18nService.t('importErrorDesc'),
|
||||
showConfirmButton: true,
|
||||
confirmButtonText: this.i18nService.t('ok'),
|
||||
onOpen: popupEl => {
|
||||
popupEl.querySelector('.swal2-textarea').scrollTo(0, 0);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private getFileContents(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file, 'utf-8');
|
||||
reader.onload = evt => {
|
||||
if (this.format === 'lastpasscsv' && file.type === 'text/html') {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString((evt.target as any).result, 'text/html');
|
||||
const pre = doc.querySelector('pre');
|
||||
if (pre != null) {
|
||||
resolve(pre.textContent);
|
||||
return;
|
||||
}
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
resolve((evt.target as any).result);
|
||||
};
|
||||
reader.onerror = () => {
|
||||
reject();
|
||||
};
|
||||
});
|
||||
}
|
||||
resolve((evt.target as any).result);
|
||||
};
|
||||
reader.onerror = () => {
|
||||
reject();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,74 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{'inactive2faReport' | i18n}}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</small>
|
||||
</h1>
|
||||
<h1>
|
||||
{{ "inactive2faReport" | i18n }}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<p>{{'inactive2faReportDesc' | i18n}}</p>
|
||||
<p>{{ "inactive2faReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{'goodNews' | i18n}}" *ngIf="!ciphers.length">
|
||||
{{'noInactive2fa' | i18n}}
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noInactive2fa" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'inactive2faFound' | i18n }}">
|
||||
{{ "inactive2faFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</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>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-cube" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i class="fa fa-paperclip" appStopProp title="{{'attachments' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'attachments' | i18n}}</span>
|
||||
</ng-container>
|
||||
<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)">
|
||||
{{'instructions' | i18n}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
<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>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="fa fa-cube"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="fa fa-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<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)"
|
||||
>
|
||||
{{ "instructions" | i18n }}</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
|
||||
@@ -1,108 +1,114 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
|
||||
import { Utils } from 'jslib-common/misc/utils';
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
|
||||
import { CipherReportComponent } from './cipher-report.component';
|
||||
import { CipherReportComponent } from "./cipher-report.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-inactive-two-factor-report',
|
||||
templateUrl: 'inactive-two-factor-report.component.html',
|
||||
selector: "app-inactive-two-factor-report",
|
||||
templateUrl: "inactive-two-factor-report.component.html",
|
||||
})
|
||||
export class InactiveTwoFactorReportComponent extends CipherReportComponent implements OnInit {
|
||||
services = new Map<string, string>();
|
||||
cipherDocs = new Map<string, string>();
|
||||
services = new Map<string, string>();
|
||||
cipherDocs = new Map<string, string>();
|
||||
|
||||
constructor(protected cipherService: CipherService, modalService: ModalService,
|
||||
messagingService: MessagingService, stateService: StateService, private logService: LogService,
|
||||
passwordRepromptService: PasswordRepromptService) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
constructor(
|
||||
protected cipherService: CipherService,
|
||||
modalService: ModalService,
|
||||
messagingService: MessagingService,
|
||||
stateService: StateService,
|
||||
private logService: LogService,
|
||||
passwordRepromptService: PasswordRepromptService
|
||||
) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (await this.checkAccess()) {
|
||||
await super.load();
|
||||
}
|
||||
}
|
||||
|
||||
async setCiphers() {
|
||||
try {
|
||||
await this.load2fa();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (await this.checkAccess()) {
|
||||
await super.load();
|
||||
if (this.services.size > 0) {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const inactive2faCiphers: CipherView[] = [];
|
||||
const promises: 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 ||
|
||||
c.isDeleted
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async setCiphers() {
|
||||
try {
|
||||
await this.load2fa();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
if (this.services.size > 0) {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const inactive2faCiphers: CipherView[] = [];
|
||||
const promises: 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 ||
|
||||
c.isDeleted) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < c.login.uris.length; i++) {
|
||||
const u = c.login.uris[i];
|
||||
if (u.uri != null && u.uri !== '') {
|
||||
const uri = u.uri.replace('www.', '');
|
||||
const domain = Utils.getDomain(uri);
|
||||
if (domain != null && this.services.has(domain)) {
|
||||
if (this.services.get(domain) != null) {
|
||||
docs.set(c.id, this.services.get(domain));
|
||||
}
|
||||
inactive2faCiphers.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
await Promise.all(promises);
|
||||
this.ciphers = inactive2faCiphers;
|
||||
this.cipherDocs = docs;
|
||||
}
|
||||
}
|
||||
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
private async load2fa() {
|
||||
if (this.services.size > 0) {
|
||||
return;
|
||||
}
|
||||
const response = await fetch(new Request('https://2fa.directory/api/v3/totp.json'));
|
||||
if (response.status !== 200) {
|
||||
throw new Error();
|
||||
}
|
||||
const responseJson = await response.json();
|
||||
for (const service of responseJson) {
|
||||
const serviceData = service[1];
|
||||
if (serviceData.domain == null) {
|
||||
continue;
|
||||
for (let i = 0; i < c.login.uris.length; i++) {
|
||||
const u = c.login.uris[i];
|
||||
if (u.uri != null && u.uri !== "") {
|
||||
const uri = u.uri.replace("www.", "");
|
||||
const domain = Utils.getDomain(uri);
|
||||
if (domain != null && this.services.has(domain)) {
|
||||
if (this.services.get(domain) != null) {
|
||||
docs.set(c.id, this.services.get(domain));
|
||||
}
|
||||
inactive2faCiphers.push(c);
|
||||
}
|
||||
if (serviceData.documentation == null) {
|
||||
continue;
|
||||
}
|
||||
if (serviceData['additional-domains'] != null) {
|
||||
for (const additionalDomain of serviceData['additional-domains']) {
|
||||
this.services.set(additionalDomain, serviceData.documentation);
|
||||
}
|
||||
}
|
||||
this.services.set(serviceData.domain, serviceData.documentation);
|
||||
}
|
||||
}
|
||||
});
|
||||
await Promise.all(promises);
|
||||
this.ciphers = inactive2faCiphers;
|
||||
this.cipherDocs = docs;
|
||||
}
|
||||
}
|
||||
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
private async load2fa() {
|
||||
if (this.services.size > 0) {
|
||||
return;
|
||||
}
|
||||
const response = await fetch(new Request("https://2fa.directory/api/v3/totp.json"));
|
||||
if (response.status !== 200) {
|
||||
throw new Error();
|
||||
}
|
||||
const responseJson = await response.json();
|
||||
for (const service of responseJson) {
|
||||
const serviceData = service[1];
|
||||
if (serviceData.domain == null) {
|
||||
continue;
|
||||
}
|
||||
if (serviceData.documentation == null) {
|
||||
continue;
|
||||
}
|
||||
if (serviceData["additional-domains"] != null) {
|
||||
for (const additionalDomain of serviceData["additional-domains"]) {
|
||||
this.services.set(additionalDomain, serviceData.documentation);
|
||||
}
|
||||
}
|
||||
this.services.set(serviceData.domain, serviceData.documentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,58 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="passHistoryTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="passHistoryTitle">{{'passwordHistory' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="passHistoryTitle">{{ "passwordHistory" | i18n }}</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="history.length">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item d-flex" *ngFor="let h of history">
|
||||
<div class="password-row">
|
||||
<div
|
||||
class="text-monospace password-wrapper"
|
||||
[innerHTML]="h.password | colorPassword"
|
||||
appSelectCopy
|
||||
></div>
|
||||
<small class="text-muted">{{ h.date | date: "medium" }}</small>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="history.length">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item d-flex" *ngFor="let h of history">
|
||||
<div class="password-row">
|
||||
<div class="text-monospace password-wrapper" [innerHTML]="h.password | colorPassword"
|
||||
appSelectCopy></div>
|
||||
<small class="text-muted">{{h.date | date:'medium'}}</small>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<button class="btn btn-link" appA11yTitle="{{'copyPassword' | i18n}}"
|
||||
(click)="copy(h.password)">
|
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!history.length">
|
||||
{{'noPasswordsInList' | i18n}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{'close' | i18n}}
|
||||
</button>
|
||||
<div class="ml-auto">
|
||||
<button type="button" (click)="clear()" class="btn btn-outline-danger"
|
||||
appA11yTitle="{{'clear' | i18n}}">
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
class="btn btn-link"
|
||||
appA11yTitle="{{ 'copyPassword' | i18n }}"
|
||||
(click)="copy(h.password)"
|
||||
>
|
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!history.length">
|
||||
{{ "noPasswordsInList" | i18n }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
(click)="clear()"
|
||||
class="btn btn-outline-danger"
|
||||
appA11yTitle="{{ 'clear' | i18n }}"
|
||||
>
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import {
|
||||
PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent,
|
||||
} from 'jslib-angular/components/password-generator-history.component';
|
||||
import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-password-generator-history',
|
||||
templateUrl: 'password-generator-history.component.html',
|
||||
selector: "app-password-generator-history",
|
||||
templateUrl: "password-generator-history.component.html",
|
||||
})
|
||||
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
|
||||
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService) {
|
||||
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
||||
}
|
||||
constructor(
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService
|
||||
) {
|
||||
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +1,199 @@
|
||||
<div class="page-header">
|
||||
<h1>{{'passwordGenerator' | i18n}}</h1>
|
||||
<h1>{{ "passwordGenerator" | i18n }}</h1>
|
||||
</div>
|
||||
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
|
||||
{{'passwordGeneratorPolicyInEffect' | i18n}}
|
||||
{{ "passwordGeneratorPolicyInEffect" | i18n }}
|
||||
</app-callout>
|
||||
<div class="card card-password bg-light my-4">
|
||||
<div class="card-body">
|
||||
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check form-check-inline" *ngFor="let o of passTypeOptions">
|
||||
<input class="form-check-input" type="radio" [(ngModel)]="options.type" name="Type_{{o.value}}"
|
||||
id="type_{{o.value}}" [value]="o.value" (change)="saveOptions()" [checked]="options.type === o.value">
|
||||
<label class="form-check-label" for="type_{{o.value}}">
|
||||
{{o.name}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline" *ngFor="let o of passTypeOptions">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
[(ngModel)]="options.type"
|
||||
name="Type_{{ o.value }}"
|
||||
id="type_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveOptions()"
|
||||
[checked]="options.type === o.value"
|
||||
/>
|
||||
<label class="form-check-label" for="type_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="options.type === 'passphrase'">
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="num-words">{{'numWords' | i18n}}</label>
|
||||
<input id="num-words" class="form-control" type="number" min="3" max="20" [(ngModel)]="options.numWords"
|
||||
(blur)="saveOptions()">
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="word-separator">{{'wordSeparator' | i18n}}</label>
|
||||
<input id="word-separator" class="form-control" type="text" maxlength="1"
|
||||
[(ngModel)]="options.wordSeparator" (blur)="saveOptions()">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="num-words">{{ "numWords" | i18n }}</label>
|
||||
<input
|
||||
id="num-words"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="3"
|
||||
max="20"
|
||||
[(ngModel)]="options.numWords"
|
||||
(blur)="saveOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input id="capitalize" class="form-check-input" type="checkbox" (change)="saveOptions()"
|
||||
[(ngModel)]="options.capitalize" [disabled]="enforcedPolicyOptions?.capitalize">
|
||||
<label for="capitalize" class="form-check-label">{{'capitalize' | i18n}}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input id="include-number" class="form-check-input" type="checkbox" (change)="saveOptions()"
|
||||
[(ngModel)]="options.includeNumber" [disabled]="enforcedPolicyOptions?.includeNumber">
|
||||
<label for="include-number" class="form-check-label">{{'includeNumber' | i18n}}</label>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
|
||||
<input
|
||||
id="word-separator"
|
||||
class="form-control"
|
||||
type="text"
|
||||
maxlength="1"
|
||||
[(ngModel)]="options.wordSeparator"
|
||||
(blur)="saveOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="capitalize"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.capitalize"
|
||||
[disabled]="enforcedPolicyOptions?.capitalize"
|
||||
/>
|
||||
<label for="capitalize" class="form-check-label">{{ "capitalize" | i18n }}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="include-number"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.includeNumber"
|
||||
[disabled]="enforcedPolicyOptions?.includeNumber"
|
||||
/>
|
||||
<label for="include-number" class="form-check-label">{{ "includeNumber" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="options.type === 'password'">
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="length">{{'length' | i18n}}</label>
|
||||
<input id="length" class="form-control" type="number" min="5" max="128" [(ngModel)]="options.length"
|
||||
(blur)="saveOptions()" (change)="lengthChanged()">
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-number">{{'minNumbers' | i18n}}</label>
|
||||
<input id="min-number" class="form-control" type="number" min="0" max="9" (blur)="saveOptions()"
|
||||
[(ngModel)]="options.minNumber" (change)="minNumberChanged()">
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-special">{{'minSpecial' | i18n}}</label>
|
||||
<input id="min-special" class="form-control" type="number" min="0" max="9" (blur)="saveOptions()"
|
||||
[(ngModel)]="options.minSpecial" (change)="minSpecialChanged()">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="length">{{ "length" | i18n }}</label>
|
||||
<input
|
||||
id="length"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="5"
|
||||
max="128"
|
||||
[(ngModel)]="options.length"
|
||||
(blur)="saveOptions()"
|
||||
(change)="lengthChanged()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input id="uppercase" class="form-check-input" type="checkbox" (change)="saveOptions()"
|
||||
[(ngModel)]="options.uppercase" [disabled]="enforcedPolicyOptions?.useUppercase">
|
||||
<label for="uppercase" class="form-check-label">A-Z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input id="lowercase" class="form-check-input" type="checkbox" (change)="saveOptions()"
|
||||
[(ngModel)]="options.lowercase" [disabled]="enforcedPolicyOptions?.useLowercase">
|
||||
<label for="lowercase" class="form-check-label">a-z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input id="numbers" class="form-check-input" type="checkbox" (change)="saveOptions()"
|
||||
[(ngModel)]="options.number" [disabled]="enforcedPolicyOptions?.useNumbers">
|
||||
<label for="numbers" class="form-check-label">0-9</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input id="special" class="form-check-input" type="checkbox" (change)="saveOptions()"
|
||||
[(ngModel)]="options.special" [disabled]="enforcedPolicyOptions?.useSpecial">
|
||||
<label for="special" class="form-check-label">!@#$%^&*</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input id="ambiguous" class="form-check-input" type="checkbox" (change)="saveOptions()"
|
||||
[(ngModel)]="avoidAmbiguous">
|
||||
<label for="ambiguous" class="form-check-label">{{'ambiguous' | i18n}}</label>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-number">{{ "minNumbers" | i18n }}</label>
|
||||
<input
|
||||
id="min-number"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
(blur)="saveOptions()"
|
||||
[(ngModel)]="options.minNumber"
|
||||
(change)="minNumberChanged()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-special">{{ "minSpecial" | i18n }}</label>
|
||||
<input
|
||||
id="min-special"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
(blur)="saveOptions()"
|
||||
[(ngModel)]="options.minSpecial"
|
||||
(change)="minSpecialChanged()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="uppercase"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.uppercase"
|
||||
[disabled]="enforcedPolicyOptions?.useUppercase"
|
||||
/>
|
||||
<label for="uppercase" class="form-check-label">A-Z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="lowercase"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.lowercase"
|
||||
[disabled]="enforcedPolicyOptions?.useLowercase"
|
||||
/>
|
||||
<label for="lowercase" class="form-check-label">a-z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="numbers"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.number"
|
||||
[disabled]="enforcedPolicyOptions?.useNumbers"
|
||||
/>
|
||||
<label for="numbers" class="form-check-label">0-9</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="special"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.special"
|
||||
[disabled]="enforcedPolicyOptions?.useSpecial"
|
||||
/>
|
||||
<label for="special" class="form-check-label">!@#$%^&*</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="ambiguous"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="avoidAmbiguous"
|
||||
/>
|
||||
<label for="ambiguous" class="form-check-label">{{ "ambiguous" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="d-flex">
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="regenerate()">
|
||||
{{'regeneratePassword' | i18n}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
|
||||
{{'copyPassword' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="history()"
|
||||
appA11yTitle="{{'passwordHistory' | i18n}}">
|
||||
<i class="fa fa-clock-o fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="regenerate()">
|
||||
{{ "regeneratePassword" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
|
||||
{{ "copyPassword" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
(click)="history()"
|
||||
appA11yTitle="{{ 'passwordHistory' | i18n }}"
|
||||
>
|
||||
<i class="fa fa-clock-o fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #historyTemplate></ng-template>
|
||||
|
||||
@@ -1,45 +1,44 @@
|
||||
import {
|
||||
Component,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import {
|
||||
PasswordGeneratorComponent as BasePasswordGeneratorComponent,
|
||||
} from 'jslib-angular/components/password-generator.component';
|
||||
import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { PasswordGeneratorHistoryComponent } from './password-generator-history.component';
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
import { PasswordGeneratorHistoryComponent } from "./password-generator-history.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-password-generator',
|
||||
templateUrl: 'password-generator.component.html',
|
||||
selector: "app-password-generator",
|
||||
templateUrl: "password-generator.component.html",
|
||||
})
|
||||
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
|
||||
@ViewChild('historyTemplate', { read: ViewContainerRef, static: true }) historyModalRef: ViewContainerRef;
|
||||
@ViewChild("historyTemplate", { read: ViewContainerRef, static: true })
|
||||
historyModalRef: ViewContainerRef;
|
||||
|
||||
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService, private modalService: ModalService) {
|
||||
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
||||
}
|
||||
constructor(
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
private modalService: ModalService
|
||||
) {
|
||||
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
||||
}
|
||||
|
||||
async history() {
|
||||
await this.modalService.openViewRef(PasswordGeneratorHistoryComponent, this.historyModalRef);
|
||||
}
|
||||
async history() {
|
||||
await this.modalService.openViewRef(PasswordGeneratorHistoryComponent, this.historyModalRef);
|
||||
}
|
||||
|
||||
lengthChanged() {
|
||||
document.getElementById('length').focus();
|
||||
}
|
||||
lengthChanged() {
|
||||
document.getElementById("length").focus();
|
||||
}
|
||||
|
||||
minNumberChanged() {
|
||||
document.getElementById('min-number').focus();
|
||||
}
|
||||
minNumberChanged() {
|
||||
document.getElementById("min-number").focus();
|
||||
}
|
||||
|
||||
minSpecialChanged() {
|
||||
document.getElementById('min-special').focus();
|
||||
}
|
||||
minSpecialChanged() {
|
||||
document.getElementById("min-special").focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,73 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{'reusedPasswordsReport' | i18n}}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</small>
|
||||
</h1>
|
||||
<h1>
|
||||
{{ "reusedPasswordsReport" | i18n }}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<p>{{'reusedPasswordsReportDesc' | i18n}}</p>
|
||||
<p>{{ "reusedPasswordsReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{'goodNews' | i18n}}" *ngIf="!ciphers.length">
|
||||
{{'noReusedPasswords' | i18n}}
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noReusedPasswords" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'reusedPasswordsFound' | i18n }}">
|
||||
{{ "reusedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{'reusedPasswordsFound' | i18n}}">
|
||||
{{'reusedPasswordsFoundDesc' | 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">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c) ; else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{c.name}}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-cube" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i class="fa fa-paperclip" appStopProp title="{{'attachments' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'attachments' | i18n}}</span>
|
||||
</ng-container>
|
||||
<br>
|
||||
<small>{{c.subTitle}}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="badge badge-warning">
|
||||
{{'reusedXTimes' | i18n : passwordUseMap.get(c.login.password)}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
<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">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{ c.name }}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="fa fa-cube"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="fa fa-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="badge badge-warning">
|
||||
{{ "reusedXTimes" | i18n: passwordUseMap.get(c.login.password) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
|
||||
@@ -1,66 +1,74 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
|
||||
import { CipherReportComponent } from './cipher-report.component';
|
||||
import { CipherReportComponent } from "./cipher-report.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-reused-passwords-report',
|
||||
templateUrl: 'reused-passwords-report.component.html',
|
||||
selector: "app-reused-passwords-report",
|
||||
templateUrl: "reused-passwords-report.component.html",
|
||||
})
|
||||
export class ReusedPasswordsReportComponent extends CipherReportComponent implements OnInit {
|
||||
passwordUseMap: Map<string, number>;
|
||||
passwordUseMap: Map<string, number>;
|
||||
|
||||
constructor(protected cipherService: CipherService, modalService: ModalService,
|
||||
messagingService: MessagingService, stateService: StateService,
|
||||
passwordRepromptService: PasswordRepromptService) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
}
|
||||
constructor(
|
||||
protected cipherService: CipherService,
|
||||
modalService: ModalService,
|
||||
messagingService: MessagingService,
|
||||
stateService: StateService,
|
||||
passwordRepromptService: PasswordRepromptService
|
||||
) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (await this.checkAccess()) {
|
||||
await super.load();
|
||||
}
|
||||
async ngOnInit() {
|
||||
if (await this.checkAccess()) {
|
||||
await super.load();
|
||||
}
|
||||
}
|
||||
|
||||
async setCiphers() {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const ciphersWithPasswords: CipherView[] = [];
|
||||
this.passwordUseMap = new Map<string, number>();
|
||||
allCiphers.forEach(c => {
|
||||
if (c.type !== CipherType.Login || c.login.password == null || c.login.password === '' || c.isDeleted) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
const reusedPasswordCiphers = ciphersWithPasswords.filter(c =>
|
||||
this.passwordUseMap.has(c.login.password) && this.passwordUseMap.get(c.login.password) > 1);
|
||||
this.ciphers = reusedPasswordCiphers;
|
||||
}
|
||||
async setCiphers() {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const ciphersWithPasswords: CipherView[] = [];
|
||||
this.passwordUseMap = new Map<string, number>();
|
||||
allCiphers.forEach((c) => {
|
||||
if (
|
||||
c.type !== CipherType.Login ||
|
||||
c.login.password == null ||
|
||||
c.login.password === "" ||
|
||||
c.isDeleted
|
||||
) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
const reusedPasswordCiphers = ciphersWithPasswords.filter(
|
||||
(c) =>
|
||||
this.passwordUseMap.has(c.login.password) && this.passwordUseMap.get(c.login.password) > 1
|
||||
);
|
||||
this.ciphers = reusedPasswordCiphers;
|
||||
}
|
||||
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
protected canManageCipher(c: CipherView): boolean {
|
||||
// this will only ever be false from an organization view
|
||||
return true;
|
||||
}
|
||||
protected canManageCipher(c: CipherView): boolean {
|
||||
// this will only ever be false from an organization view
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,76 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">{{'tools' | i18n}}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="generator" class="list-group-item" routerLinkActive="active">
|
||||
{{'passwordGenerator' | i18n}}
|
||||
</a>
|
||||
<a routerLink="import" class="list-group-item" routerLinkActive="active">
|
||||
{{'importData' | i18n}}
|
||||
</a>
|
||||
<a routerLink="export" class="list-group-item" routerLinkActive="active">
|
||||
{{'exportVault' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header d-flex">
|
||||
{{'reports' | i18n}}
|
||||
<div class="ml-auto">
|
||||
<a href="#" appStopClick class="badge badge-primary" *ngIf="!canAccessPremium"
|
||||
(click)="premiumRequired()">
|
||||
{{'premium' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="exposed-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'exposedPasswordsReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="reused-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'reusedPasswordsReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="weak-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'weakPasswordsReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="unsecured-websites-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'unsecuredWebsitesReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="inactive-two-factor-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'inactive2faReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="breach-report" class="list-group-item d-flex" routerLinkActive="active">
|
||||
{{'dataBreachReport' | i18n}}
|
||||
<div class="ml-auto">
|
||||
<span class="badge badge-success" *ngIf="!canAccessPremium">
|
||||
{{'free' | i18n | uppercase}}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">{{ "tools" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="generator" class="list-group-item" routerLinkActive="active">
|
||||
{{ "passwordGenerator" | i18n }}
|
||||
</a>
|
||||
<a routerLink="import" class="list-group-item" routerLinkActive="active">
|
||||
{{ "importData" | i18n }}
|
||||
</a>
|
||||
<a routerLink="export" class="list-group-item" routerLinkActive="active">
|
||||
{{ "exportVault" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header d-flex">
|
||||
{{ "reports" | i18n }}
|
||||
<div class="ml-auto">
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
class="badge badge-primary"
|
||||
*ngIf="!canAccessPremium"
|
||||
(click)="premiumRequired()"
|
||||
>
|
||||
{{ "premium" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a
|
||||
routerLink="exposed-passwords-report"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ "exposedPasswordsReport" | i18n }}
|
||||
</a>
|
||||
<a routerLink="reused-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{ "reusedPasswordsReport" | i18n }}
|
||||
</a>
|
||||
<a routerLink="weak-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{ "weakPasswordsReport" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="unsecured-websites-report"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ "unsecuredWebsitesReport" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="inactive-two-factor-report"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ "inactive2faReport" | i18n }}
|
||||
</a>
|
||||
<a routerLink="breach-report" class="list-group-item d-flex" routerLinkActive="active">
|
||||
{{ "dataBreachReport" | i18n }}
|
||||
<div class="ml-auto">
|
||||
<span class="badge badge-success" *ngIf="!canAccessPremium">
|
||||
{{ "free" | i18n | uppercase }}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-tools',
|
||||
templateUrl: 'tools.component.html',
|
||||
selector: "app-tools",
|
||||
templateUrl: "tools.component.html",
|
||||
})
|
||||
export class ToolsComponent implements OnInit {
|
||||
canAccessPremium = false;
|
||||
canAccessPremium = false;
|
||||
|
||||
constructor(private stateService: StateService, private messagingService: MessagingService) { }
|
||||
constructor(private stateService: StateService, private messagingService: MessagingService) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
}
|
||||
|
||||
premiumRequired() {
|
||||
if (!this.canAccessPremium) {
|
||||
this.messagingService.send('premiumRequired');
|
||||
return;
|
||||
}
|
||||
async ngOnInit() {
|
||||
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
}
|
||||
|
||||
premiumRequired() {
|
||||
if (!this.canAccessPremium) {
|
||||
this.messagingService.send("premiumRequired");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,63 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{'unsecuredWebsitesReport' | i18n}}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</small>
|
||||
</h1>
|
||||
<h1>
|
||||
{{ "unsecuredWebsitesReport" | i18n }}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<p>{{'unsecuredWebsitesReportDesc' | i18n}}</p>
|
||||
<p>{{ "unsecuredWebsitesReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{'goodNews' | i18n}}" *ngIf="!ciphers.length">
|
||||
{{'noUnsecuredWebsites' | i18n}}
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noUnsecuredWebsites" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'unsecuredWebsitesFound' | i18n }}">
|
||||
{{ "unsecuredWebsitesFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{'unsecuredWebsitesFound' | i18n}}">
|
||||
{{'unsecuredWebsitesFoundDesc' | 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>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-cube" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i class="fa fa-paperclip" appStopProp title="{{'attachments' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'attachments' | i18n}}</span>
|
||||
</ng-container>
|
||||
<br>
|
||||
<small>{{c.subTitle}}</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
<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>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="fa fa-cube"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="fa fa-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
|
||||
@@ -1,50 +1,51 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
import { CipherReportComponent } from './cipher-report.component';
|
||||
import { CipherReportComponent } from "./cipher-report.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-unsecured-websites-report',
|
||||
templateUrl: 'unsecured-websites-report.component.html',
|
||||
selector: "app-unsecured-websites-report",
|
||||
templateUrl: "unsecured-websites-report.component.html",
|
||||
})
|
||||
export class UnsecuredWebsitesReportComponent extends CipherReportComponent implements OnInit {
|
||||
constructor(protected cipherService: CipherService, modalService: ModalService,
|
||||
messagingService: MessagingService, stateService: StateService,
|
||||
passwordRepromptService: PasswordRepromptService) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
}
|
||||
constructor(
|
||||
protected cipherService: CipherService,
|
||||
modalService: ModalService,
|
||||
messagingService: MessagingService,
|
||||
stateService: StateService,
|
||||
passwordRepromptService: PasswordRepromptService
|
||||
) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (await this.checkAccess()) {
|
||||
await super.load();
|
||||
}
|
||||
async ngOnInit() {
|
||||
if (await this.checkAccess()) {
|
||||
await super.load();
|
||||
}
|
||||
}
|
||||
|
||||
async setCiphers() {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const unsecuredCiphers = allCiphers.filter(c => {
|
||||
if (c.type !== CipherType.Login || !c.login.hasUris || c.isDeleted) {
|
||||
return false;
|
||||
}
|
||||
return c.login.uris.some(u => u.uri != null && u.uri.indexOf('http://') === 0);
|
||||
});
|
||||
this.ciphers = unsecuredCiphers;
|
||||
}
|
||||
async setCiphers() {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const unsecuredCiphers = allCiphers.filter((c) => {
|
||||
if (c.type !== CipherType.Login || !c.login.hasUris || c.isDeleted) {
|
||||
return false;
|
||||
}
|
||||
return c.login.uris.some((u) => u.uri != null && u.uri.indexOf("http://") === 0);
|
||||
});
|
||||
this.ciphers = unsecuredCiphers;
|
||||
}
|
||||
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,73 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{'weakPasswordsReport' | i18n}}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</small>
|
||||
</h1>
|
||||
<h1>
|
||||
{{ "weakPasswordsReport" | i18n }}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<p>{{'weakPasswordsReportDesc' | i18n}}</p>
|
||||
<p>{{ "weakPasswordsReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{'goodNews' | i18n}}" *ngIf="!ciphers.length">
|
||||
{{'noWeakPasswords' | i18n}}
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noWeakPasswords" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'weakPasswordsFound' | i18n }}">
|
||||
{{ "weakPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{'weakPasswordsFound' | i18n}}">
|
||||
{{'weakPasswordsFoundDesc' | 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">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c) ; else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{c.name}}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-cube" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i class="fa fa-paperclip" appStopProp title="{{'attachments' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'attachments' | i18n}}</span>
|
||||
</ng-container>
|
||||
<br>
|
||||
<small>{{c.subTitle}}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="badge badge-{{passwordStrengthMap.get(c.id)[1]}}">
|
||||
{{passwordStrengthMap.get(c.id)[0] | i18n}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
<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">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{ c.name }}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="fa fa-cube"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="fa fa-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="badge badge-{{ passwordStrengthMap.get(c.id)[1] }}">
|
||||
{{ passwordStrengthMap.get(c.id)[0] | i18n }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
|
||||
@@ -1,109 +1,128 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
|
||||
import { CipherReportComponent } from './cipher-report.component';
|
||||
import { CipherReportComponent } from "./cipher-report.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-weak-passwords-report',
|
||||
templateUrl: 'weak-passwords-report.component.html',
|
||||
selector: "app-weak-passwords-report",
|
||||
templateUrl: "weak-passwords-report.component.html",
|
||||
})
|
||||
export class WeakPasswordsReportComponent extends CipherReportComponent implements OnInit {
|
||||
passwordStrengthMap = new Map<string, [string, string]>();
|
||||
|
||||
passwordStrengthMap = new Map<string, [string, string]>();
|
||||
private passwordStrengthCache = new Map<string, number>();
|
||||
|
||||
private passwordStrengthCache = new Map<string, number>();
|
||||
constructor(
|
||||
protected cipherService: CipherService,
|
||||
protected passwordGenerationService: PasswordGenerationService,
|
||||
modalService: ModalService,
|
||||
messagingService: MessagingService,
|
||||
stateService: StateService,
|
||||
passwordRepromptService: PasswordRepromptService
|
||||
) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
}
|
||||
|
||||
constructor(protected cipherService: CipherService, protected passwordGenerationService: PasswordGenerationService,
|
||||
modalService: ModalService, messagingService: MessagingService,
|
||||
stateService: StateService, passwordRepromptService: PasswordRepromptService) {
|
||||
super(modalService, messagingService, true, stateService, passwordRepromptService);
|
||||
async ngOnInit() {
|
||||
if (await this.checkAccess()) {
|
||||
await super.load();
|
||||
}
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (await this.checkAccess()) {
|
||||
await super.load();
|
||||
async setCiphers() {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const weakPasswordCiphers: CipherView[] = [];
|
||||
const isUserNameNotEmpty = (c: CipherView): boolean => {
|
||||
return c.login.username != null && c.login.username.trim() !== "";
|
||||
};
|
||||
const getCacheKey = (c: CipherView): string => {
|
||||
return c.login.password + "_____" + (isUserNameNotEmpty(c) ? c.login.username : "");
|
||||
};
|
||||
|
||||
allCiphers.forEach((c) => {
|
||||
if (
|
||||
c.type !== CipherType.Login ||
|
||||
c.login.password == null ||
|
||||
c.login.password === "" ||
|
||||
c.isDeleted
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const hasUserName = isUserNameNotEmpty(c);
|
||||
const cacheKey = getCacheKey(c);
|
||||
if (!this.passwordStrengthCache.has(cacheKey)) {
|
||||
let userInput: string[] = [];
|
||||
if (hasUserName) {
|
||||
const atPosition = c.login.username.indexOf("@");
|
||||
if (atPosition > -1) {
|
||||
userInput = userInput
|
||||
.concat(
|
||||
c.login.username
|
||||
.substr(0, atPosition)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split(/[^A-Za-z0-9]/)
|
||||
)
|
||||
.filter((i) => i.length >= 3);
|
||||
} else {
|
||||
userInput = c.login.username
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split(/[^A-Za-z0-9]/)
|
||||
.filter((i) => i.length >= 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = this.passwordGenerationService.passwordStrength(
|
||||
c.login.password,
|
||||
userInput.length > 0 ? userInput : null
|
||||
);
|
||||
this.passwordStrengthCache.set(cacheKey, result.score);
|
||||
}
|
||||
const score = this.passwordStrengthCache.get(cacheKey);
|
||||
if (score != null && score <= 2) {
|
||||
this.passwordStrengthMap.set(c.id, this.scoreKey(score));
|
||||
weakPasswordCiphers.push(c);
|
||||
}
|
||||
});
|
||||
weakPasswordCiphers.sort((a, b) => {
|
||||
return (
|
||||
this.passwordStrengthCache.get(getCacheKey(a)) -
|
||||
this.passwordStrengthCache.get(getCacheKey(b))
|
||||
);
|
||||
});
|
||||
this.ciphers = weakPasswordCiphers;
|
||||
}
|
||||
|
||||
async setCiphers() {
|
||||
const allCiphers = await this.getAllCiphers();
|
||||
const weakPasswordCiphers: CipherView[] = [];
|
||||
const isUserNameNotEmpty = (c: CipherView): boolean => {
|
||||
return c.login.username != null && c.login.username.trim() !== '';
|
||||
};
|
||||
const getCacheKey = (c: CipherView): string => {
|
||||
return c.login.password + '_____' + (isUserNameNotEmpty(c) ? c.login.username : '');
|
||||
};
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
allCiphers.forEach(c => {
|
||||
if (c.type !== CipherType.Login || c.login.password == null || c.login.password === '' || c.isDeleted) {
|
||||
return;
|
||||
}
|
||||
const hasUserName = isUserNameNotEmpty(c);
|
||||
const cacheKey = getCacheKey(c);
|
||||
if (!this.passwordStrengthCache.has(cacheKey)) {
|
||||
let userInput: string[] = [];
|
||||
if (hasUserName) {
|
||||
const atPosition = c.login.username.indexOf('@');
|
||||
if (atPosition > -1) {
|
||||
userInput = userInput.concat(
|
||||
c.login.username.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/))
|
||||
.filter(i => i.length >= 3);
|
||||
} else {
|
||||
userInput = c.login.username.trim().toLowerCase().split(/[^A-Za-z0-9]/)
|
||||
.filter(i => i.length >= 3);
|
||||
}
|
||||
}
|
||||
const result = this.passwordGenerationService.passwordStrength(c.login.password,
|
||||
userInput.length > 0 ? userInput : null);
|
||||
this.passwordStrengthCache.set(cacheKey, result.score);
|
||||
}
|
||||
const score = this.passwordStrengthCache.get(cacheKey);
|
||||
if (score != null && score <= 2) {
|
||||
this.passwordStrengthMap.set(c.id, this.scoreKey(score));
|
||||
weakPasswordCiphers.push(c);
|
||||
}
|
||||
});
|
||||
weakPasswordCiphers.sort((a, b) => {
|
||||
return this.passwordStrengthCache.get(getCacheKey(a)) -
|
||||
this.passwordStrengthCache.get(getCacheKey(b));
|
||||
});
|
||||
this.ciphers = weakPasswordCiphers;
|
||||
}
|
||||
protected canManageCipher(c: CipherView): boolean {
|
||||
// this will only ever be false from the org view;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
protected canManageCipher(c: CipherView): boolean {
|
||||
// this will only ever be false from the org view;
|
||||
return true;
|
||||
}
|
||||
|
||||
private scoreKey(score: number): [string, string] {
|
||||
switch (score) {
|
||||
case 4:
|
||||
return ['strong', 'success'];
|
||||
case 3:
|
||||
return ['good', 'primary'];
|
||||
case 2:
|
||||
return ['weak', 'warning'];
|
||||
default:
|
||||
return ['veryWeak', 'danger'];
|
||||
}
|
||||
private scoreKey(score: number): [string, string] {
|
||||
switch (score) {
|
||||
case 4:
|
||||
return ["strong", "success"];
|
||||
case 3:
|
||||
return ["good", "primary"];
|
||||
case 2:
|
||||
return ["weak", "warning"];
|
||||
default:
|
||||
return ["veryWeak", "danger"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user