1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

Apply Prettier (#581)

This commit is contained in:
Oscar Hinton
2021-12-16 13:36:21 +01:00
committed by GitHub
parent 8b2dfc6cdc
commit 193434461d
589 changed files with 46650 additions and 41924 deletions

View File

@@ -1,124 +1,120 @@
import {
Directive,
Input,
OnChanges,
SimpleChanges,
} from '@angular/core';
import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core";
import {
CdkDragDrop,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop";
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { FieldView } from 'jslib-common/models/view/fieldView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { FieldView } from "jslib-common/models/view/fieldView";
import { CipherType } from 'jslib-common/enums/cipherType';
import { EventType } from 'jslib-common/enums/eventType';
import { FieldType } from 'jslib-common/enums/fieldType';
import { CipherType } from "jslib-common/enums/cipherType";
import { EventType } from "jslib-common/enums/eventType";
import { FieldType } from "jslib-common/enums/fieldType";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive()
export class AddEditCustomFieldsComponent implements OnChanges {
@Input() cipher: CipherView;
@Input() thisCipherType: CipherType;
@Input() editMode: boolean;
@Input() cipher: CipherView;
@Input() thisCipherType: CipherType;
@Input() editMode: boolean;
addFieldType: FieldType = FieldType.Text;
addFieldTypeOptions: any[];
addFieldLinkedTypeOption: any;
linkedFieldOptions: any[] = [];
addFieldType: FieldType = FieldType.Text;
addFieldTypeOptions: any[];
addFieldLinkedTypeOption: any;
linkedFieldOptions: any[] = [];
cipherType = CipherType;
fieldType = FieldType;
eventType = EventType;
cipherType = CipherType;
fieldType = FieldType;
eventType = EventType;
constructor(private i18nService: I18nService, private eventService: EventService) {
this.addFieldTypeOptions = [
{ name: i18nService.t('cfTypeText'), value: FieldType.Text },
{ name: i18nService.t('cfTypeHidden'), value: FieldType.Hidden },
{ name: i18nService.t('cfTypeBoolean'), value: FieldType.Boolean },
];
this.addFieldLinkedTypeOption = { name: this.i18nService.t('cfTypeLinked'), value: FieldType.Linked };
constructor(private i18nService: I18nService, private eventService: EventService) {
this.addFieldTypeOptions = [
{ name: i18nService.t("cfTypeText"), value: FieldType.Text },
{ name: i18nService.t("cfTypeHidden"), value: FieldType.Hidden },
{ name: i18nService.t("cfTypeBoolean"), value: FieldType.Boolean },
];
this.addFieldLinkedTypeOption = {
name: this.i18nService.t("cfTypeLinked"),
value: FieldType.Linked,
};
}
ngOnChanges(changes: SimpleChanges) {
if (changes.thisCipherType != null) {
this.setLinkedFieldOptions();
if (!changes.thisCipherType.firstChange) {
this.resetCipherLinkedFields();
}
}
}
addField() {
if (this.cipher.fields == null) {
this.cipher.fields = [];
}
ngOnChanges(changes: SimpleChanges) {
if (changes.thisCipherType != null) {
this.setLinkedFieldOptions();
const f = new FieldView();
f.type = this.addFieldType;
f.newField = true;
if (!changes.thisCipherType.firstChange) {
this.resetCipherLinkedFields();
}
}
if (f.type === FieldType.Linked) {
f.linkedId = this.linkedFieldOptions[0].value;
}
addField() {
if (this.cipher.fields == null) {
this.cipher.fields = [];
}
this.cipher.fields.push(f);
}
const f = new FieldView();
f.type = this.addFieldType;
f.newField = true;
removeField(field: FieldView) {
const i = this.cipher.fields.indexOf(field);
if (i > -1) {
this.cipher.fields.splice(i, 1);
}
}
if (f.type === FieldType.Linked) {
f.linkedId = this.linkedFieldOptions[0].value;
}
toggleFieldValue(field: FieldView) {
const f = field as any;
f.showValue = !f.showValue;
if (this.editMode && f.showValue) {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
}
}
this.cipher.fields.push(f);
trackByFunction(index: number, item: any) {
return index;
}
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex);
}
private setLinkedFieldOptions() {
if (this.cipher.linkedFieldOptions == null) {
return;
}
removeField(field: FieldView) {
const i = this.cipher.fields.indexOf(field);
if (i > -1) {
this.cipher.fields.splice(i, 1);
}
const options: any = [];
this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) =>
options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id })
);
this.linkedFieldOptions = options.sort(Utils.getSortFunction(this.i18nService, "name"));
}
private resetCipherLinkedFields() {
if (this.cipher.fields == null || this.cipher.fields.length === 0) {
return;
}
toggleFieldValue(field: FieldView) {
const f = (field as any);
f.showValue = !f.showValue;
if (this.editMode && f.showValue) {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
}
// Delete any Linked custom fields if the item type does not support them
if (this.cipher.linkedFieldOptions == null) {
this.cipher.fields = this.cipher.fields.filter((f) => f.type !== FieldType.Linked);
return;
}
trackByFunction(index: number, item: any) {
return index;
}
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex);
}
private setLinkedFieldOptions() {
if (this.cipher.linkedFieldOptions == null) {
return;
}
const options: any = [];
this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) =>
options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id }));
this.linkedFieldOptions = options.sort(Utils.getSortFunction(this.i18nService, 'name'));
}
private resetCipherLinkedFields() {
if (this.cipher.fields == null || this.cipher.fields.length === 0) {
return;
}
// Delete any Linked custom fields if the item type does not support them
if (this.cipher.linkedFieldOptions == null) {
this.cipher.fields = this.cipher.fields.filter(f => f.type !== FieldType.Linked);
return;
}
this.cipher.fields
.filter(f => f.type === FieldType.Linked)
.forEach(f => f.linkedId = this.linkedFieldOptions[0].value);
}
this.cipher.fields
.filter((f) => f.type === FieldType.Linked)
.forEach((f) => (f.linkedId = this.linkedFieldOptions[0].value));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,250 +1,291 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.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 { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.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 { StateService } from "jslib-common/abstractions/state.service";
import { Cipher } from 'jslib-common/models/domain/cipher';
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { Cipher } from "jslib-common/models/domain/cipher";
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { AttachmentView } from 'jslib-common/models/view/attachmentView';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { AttachmentView } from "jslib-common/models/view/attachmentView";
import { CipherView } from "jslib-common/models/view/cipherView";
@Directive()
export class AttachmentsComponent implements OnInit {
@Input() cipherId: string;
@Output() onUploadedAttachment = new EventEmitter();
@Output() onDeletedAttachment = new EventEmitter();
@Output() onReuploadedAttachment = new EventEmitter();
@Input() cipherId: string;
@Output() onUploadedAttachment = new EventEmitter();
@Output() onDeletedAttachment = new EventEmitter();
@Output() onReuploadedAttachment = new EventEmitter();
cipher: CipherView;
cipherDomain: Cipher;
hasUpdatedKey: boolean;
canAccessAttachments: boolean;
formPromise: Promise<any>;
deletePromises: { [id: string]: Promise<any>; } = {};
reuploadPromises: { [id: string]: Promise<any>; } = {};
emergencyAccessId?: string = null;
cipher: CipherView;
cipherDomain: Cipher;
hasUpdatedKey: boolean;
canAccessAttachments: boolean;
formPromise: Promise<any>;
deletePromises: { [id: string]: Promise<any> } = {};
reuploadPromises: { [id: string]: Promise<any> } = {};
emergencyAccessId?: string = null;
constructor(protected cipherService: CipherService, protected i18nService: I18nService,
protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService, protected win: Window,
protected logService: LogService, protected stateService: StateService) { }
constructor(
protected cipherService: CipherService,
protected i18nService: I18nService,
protected cryptoService: CryptoService,
protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService,
protected win: Window,
protected logService: LogService,
protected stateService: StateService
) {}
async ngOnInit() {
await this.init();
async ngOnInit() {
await this.init();
}
async submit() {
if (!this.hasUpdatedKey) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("updateKey")
);
return;
}
async submit() {
if (!this.hasUpdatedKey) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('updateKey'));
return;
}
const fileEl = document.getElementById('file') as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectFile'));
return;
}
if (files[0].size > 524288000) { // 500 MB
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('maxFileSize'));
return;
}
try {
this.formPromise = this.saveCipherAttachment(files[0]);
this.cipherDomain = await this.formPromise;
this.cipher = await this.cipherDomain.decrypt();
this.platformUtilsService.showToast('success', null, this.i18nService.t('attachmentSaved'));
this.onUploadedAttachment.emit();
} catch (e) {
this.logService.error(e);
}
// reset file input
// ref: https://stackoverflow.com/a/20552042
fileEl.type = '';
fileEl.type = 'file';
fileEl.value = '';
const fileEl = document.getElementById("file") as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectFile")
);
return;
}
async delete(attachment: AttachmentView) {
if (this.deletePromises[attachment.id] != null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteAttachmentConfirmation'), this.i18nService.t('deleteAttachment'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return;
}
try {
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
await this.deletePromises[attachment.id];
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedAttachment'));
const i = this.cipher.attachments.indexOf(attachment);
if (i > -1) {
this.cipher.attachments.splice(i, 1);
}
} catch (e) {
this.logService.error(e);
}
this.deletePromises[attachment.id] = null;
this.onDeletedAttachment.emit();
if (files[0].size > 524288000) {
// 500 MB
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("maxFileSize")
);
return;
}
async download(attachment: AttachmentView) {
const a = (attachment as any);
if (a.downloading) {
return;
}
try {
this.formPromise = this.saveCipherAttachment(files[0]);
this.cipherDomain = await this.formPromise;
this.cipher = await this.cipherDomain.decrypt();
this.platformUtilsService.showToast("success", null, this.i18nService.t("attachmentSaved"));
this.onUploadedAttachment.emit();
} catch (e) {
this.logService.error(e);
}
if (!this.canAccessAttachments) {
this.platformUtilsService.showToast('error', this.i18nService.t('premiumRequired'),
this.i18nService.t('premiumRequiredDesc'));
return;
}
// reset file input
// ref: https://stackoverflow.com/a/20552042
fileEl.type = "";
fileEl.type = "file";
fileEl.value = "";
}
let url: string;
try {
const attachmentDownloadResponse = await this.apiService.getAttachmentData(this.cipher.id, attachment.id,
this.emergencyAccessId);
url = attachmentDownloadResponse.url;
} catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
url = attachment.url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
}
}
async delete(attachment: AttachmentView) {
if (this.deletePromises[attachment.id] != null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteAttachmentConfirmation"),
this.i18nService.t("deleteAttachment"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return;
}
try {
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
await this.deletePromises[attachment.id];
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedAttachment"));
const i = this.cipher.attachments.indexOf(attachment);
if (i > -1) {
this.cipher.attachments.splice(i, 1);
}
} catch (e) {
this.logService.error(e);
}
this.deletePromises[attachment.id] = null;
this.onDeletedAttachment.emit();
}
async download(attachment: AttachmentView) {
const a = attachment as any;
if (a.downloading) {
return;
}
if (!this.canAccessAttachments) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("premiumRequired"),
this.i18nService.t("premiumRequiredDesc")
);
return;
}
let url: string;
try {
const attachmentDownloadResponse = await this.apiService.getAttachmentData(
this.cipher.id,
attachment.id,
this.emergencyAccessId
);
url = attachmentDownloadResponse.url;
} catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
url = attachment.url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
}
}
a.downloading = true;
const response = await fetch(new Request(url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
a.downloading = false;
return;
}
try {
const buf = await response.arrayBuffer();
const key =
attachment.key != null
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
a.downloading = false;
}
protected async init() {
this.cipherDomain = await this.loadCipher();
this.cipher = await this.cipherDomain.decrypt();
this.hasUpdatedKey = await this.cryptoService.hasEncKey();
const canAccessPremium = await this.stateService.getCanAccessPremium();
this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null;
if (!this.canAccessAttachments) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t("premiumRequired"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase");
}
} else if (!this.hasUpdatedKey) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("updateKey"),
this.i18nService.t("featureUnavailable"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel"),
"warning"
);
if (confirmed) {
this.platformUtilsService.launchUri(
"https://help.bitwarden.com/article/update-encryption-key/"
);
}
}
}
protected async reuploadCipherAttachment(attachment: AttachmentView, admin: boolean) {
const a = attachment as any;
if (attachment.key != null || a.downloading || this.reuploadPromises[attachment.id] != null) {
return;
}
try {
this.reuploadPromises[attachment.id] = Promise.resolve().then(async () => {
// 1. Download
a.downloading = true;
const response = await fetch(new Request(url, { cache: 'no-store' }));
const response = await fetch(new Request(attachment.url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
a.downloading = false;
return;
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
a.downloading = false;
return;
}
try {
const buf = await response.arrayBuffer();
const key = attachment.key != null ? attachment.key :
await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
// 2. Resave
const buf = await response.arrayBuffer();
const key =
attachment.key != null
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer(
this.cipherDomain,
attachment.fileName,
decBuf,
admin
);
this.cipher = await this.cipherDomain.decrypt();
// 3. Delete old
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
await this.deletePromises[attachment.id];
const foundAttachment = this.cipher.attachments.filter((a2) => a2.id === attachment.id);
if (foundAttachment.length > 0) {
const i = this.cipher.attachments.indexOf(foundAttachment[0]);
if (i > -1) {
this.cipher.attachments.splice(i, 1);
}
}
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("attachmentSaved")
);
this.onReuploadedAttachment.emit();
} catch (e) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
a.downloading = false;
});
await this.reuploadPromises[attachment.id];
} catch (e) {
this.logService.error(e);
}
}
protected async init() {
this.cipherDomain = await this.loadCipher();
this.cipher = await this.cipherDomain.decrypt();
protected loadCipher() {
return this.cipherService.get(this.cipherId);
}
this.hasUpdatedKey = await this.cryptoService.hasEncKey();
const canAccessPremium = await this.stateService.getCanAccessPremium();
this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null;
protected saveCipherAttachment(file: File) {
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file);
}
if (!this.canAccessAttachments) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'),
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
if (confirmed) {
this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=purchase');
}
} else if (!this.hasUpdatedKey) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('updateKey'), this.i18nService.t('featureUnavailable'),
this.i18nService.t('learnMore'), this.i18nService.t('cancel'), 'warning');
if (confirmed) {
this.platformUtilsService.launchUri('https://help.bitwarden.com/article/update-encryption-key/');
}
}
}
protected async reuploadCipherAttachment(attachment: AttachmentView, admin: boolean) {
const a = (attachment as any);
if (attachment.key != null || a.downloading || this.reuploadPromises[attachment.id] != null) {
return;
}
try {
this.reuploadPromises[attachment.id] = Promise.resolve().then(async () => {
// 1. Download
a.downloading = true;
const response = await fetch(new Request(attachment.url, { cache: 'no-store' }));
if (response.status !== 200) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
a.downloading = false;
return;
}
try {
// 2. Resave
const buf = await response.arrayBuffer();
const key = attachment.key != null ? attachment.key :
await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer(
this.cipherDomain, attachment.fileName, decBuf, admin);
this.cipher = await this.cipherDomain.decrypt();
// 3. Delete old
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
await this.deletePromises[attachment.id];
const foundAttachment = this.cipher.attachments.filter(a2 => a2.id === attachment.id);
if (foundAttachment.length > 0) {
const i = this.cipher.attachments.indexOf(foundAttachment[0]);
if (i > -1) {
this.cipher.attachments.splice(i, 1);
}
}
this.platformUtilsService.showToast('success', null, this.i18nService.t('attachmentSaved'));
this.onReuploadedAttachment.emit();
} catch (e) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
}
a.downloading = false;
});
await this.reuploadPromises[attachment.id];
} catch (e) {
this.logService.error(e);
}
}
protected loadCipher() {
return this.cipherService.get(this.cipherId);
}
protected saveCipherAttachment(file: File) {
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file);
}
protected deleteCipherAttachment(attachmentId: string) {
return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId);
}
protected deleteCipherAttachment(attachmentId: string) {
return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId);
}
}

View File

@@ -1,138 +1,143 @@
import {
Component,
Input,
OnChanges,
OnInit,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { Component, Input, OnChanges, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Component({
selector: 'app-avatar',
template: '<img [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
'[ngClass]="{\'rounded-circle\': circle}">',
selector: "app-avatar",
template:
'<img [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
"[ngClass]=\"{'rounded-circle': circle}\">",
})
export class AvatarComponent implements OnChanges, OnInit {
@Input() data: string;
@Input() email: string;
@Input() size = 45;
@Input() charCount = 2;
@Input() textColor = '#ffffff';
@Input() fontSize = 20;
@Input() fontWeight = 300;
@Input() dynamic = false;
@Input() circle = false;
@Input() data: string;
@Input() email: string;
@Input() size = 45;
@Input() charCount = 2;
@Input() textColor = "#ffffff";
@Input() fontSize = 20;
@Input() fontWeight = 300;
@Input() dynamic = false;
@Input() circle = false;
src: string;
src: string;
constructor(public sanitizer: DomSanitizer, private cryptoFunctionService: CryptoFunctionService,
private stateService: StateService) { }
constructor(
public sanitizer: DomSanitizer,
private cryptoFunctionService: CryptoFunctionService,
private stateService: StateService
) {}
ngOnInit() {
if (!this.dynamic) {
this.generate();
}
ngOnInit() {
if (!this.dynamic) {
this.generate();
}
}
ngOnChanges() {
if (this.dynamic) {
this.generate();
}
ngOnChanges() {
if (this.dynamic) {
this.generate();
}
}
private async generate() {
const enableGravatars = await this.stateService.getEnableGravitars();
if (enableGravatars && this.email != null) {
const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5');
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
this.src = 'https://www.gravatar.com/avatar/' + hash + '?s=' + this.size + '&r=pg&d=retro';
} else {
let chars: string = null;
const upperData = this.data.toUpperCase();
private async generate() {
const enableGravatars = await this.stateService.getEnableGravitars();
if (enableGravatars && this.email != null) {
const hashBytes = await this.cryptoFunctionService.hash(
this.email.toLowerCase().trim(),
"md5"
);
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
this.src = "https://www.gravatar.com/avatar/" + hash + "?s=" + this.size + "&r=pg&d=retro";
} else {
let chars: string = null;
const upperData = this.data.toUpperCase();
if (this.charCount > 1) {
chars = this.getFirstLetters(upperData, this.charCount);
}
if (chars == null) {
chars = this.unicodeSafeSubstring(upperData, this.charCount);
}
if (this.charCount > 1) {
chars = this.getFirstLetters(upperData, this.charCount);
}
if (chars == null) {
chars = this.unicodeSafeSubstring(upperData, this.charCount);
}
// If the chars contain an emoji, only show it.
if (chars.match(Utils.regexpEmojiPresentation)) {
chars = chars.match(Utils.regexpEmojiPresentation)[0];
}
// If the chars contain an emoji, only show it.
if (chars.match(Utils.regexpEmojiPresentation)) {
chars = chars.match(Utils.regexpEmojiPresentation)[0];
}
const charObj = this.getCharText(chars);
const color = this.stringToColor(upperData);
const svg = this.getSvg(this.size, color);
svg.appendChild(charObj);
const html = window.document.createElement('div').appendChild(svg).outerHTML;
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
this.src = 'data:image/svg+xml;base64,' + svgHtml;
}
const charObj = this.getCharText(chars);
const color = this.stringToColor(upperData);
const svg = this.getSvg(this.size, color);
svg.appendChild(charObj);
const html = window.document.createElement("div").appendChild(svg).outerHTML;
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
this.src = "data:image/svg+xml;base64," + svgHtml;
}
}
private stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
// tslint:disable-next-line
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = '#';
for (let i = 0; i < 3; i++) {
// tslint:disable-next-line
const value = (hash >> (i * 8)) & 0xFF;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
private stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
// tslint:disable-next-line
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = "#";
for (let i = 0; i < 3; i++) {
// tslint:disable-next-line
const value = (hash >> (i * 8)) & 0xff;
color += ("00" + value.toString(16)).substr(-2);
}
return color;
}
private getFirstLetters(data: string, count: number): string {
const parts = data.split(' ');
if (parts.length > 1) {
let text = '';
for (let i = 0; i < count; i++) {
text += this.unicodeSafeSubstring(parts[i], 1);
}
return text;
}
return null;
private getFirstLetters(data: string, count: number): string {
const parts = data.split(" ");
if (parts.length > 1) {
let text = "";
for (let i = 0; i < count; i++) {
text += this.unicodeSafeSubstring(parts[i], 1);
}
return text;
}
return null;
}
private getSvg(size: number, color: string): HTMLElement {
const svgTag = window.document.createElement('svg');
svgTag.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgTag.setAttribute('pointer-events', 'none');
svgTag.setAttribute('width', size.toString());
svgTag.setAttribute('height', size.toString());
svgTag.style.backgroundColor = color;
svgTag.style.width = size + 'px';
svgTag.style.height = size + 'px';
return svgTag;
}
private getSvg(size: number, color: string): HTMLElement {
const svgTag = window.document.createElement("svg");
svgTag.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svgTag.setAttribute("pointer-events", "none");
svgTag.setAttribute("width", size.toString());
svgTag.setAttribute("height", size.toString());
svgTag.style.backgroundColor = color;
svgTag.style.width = size + "px";
svgTag.style.height = size + "px";
return svgTag;
}
private getCharText(character: string): HTMLElement {
const textTag = window.document.createElement('text');
textTag.setAttribute('text-anchor', 'middle');
textTag.setAttribute('y', '50%');
textTag.setAttribute('x', '50%');
textTag.setAttribute('dy', '0.35em');
textTag.setAttribute('pointer-events', 'auto');
textTag.setAttribute('fill', this.textColor);
textTag.setAttribute('font-family', '"Open Sans","Helvetica Neue",Helvetica,Arial,' +
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"');
textTag.textContent = character;
textTag.style.fontWeight = this.fontWeight.toString();
textTag.style.fontSize = this.fontSize + 'px';
return textTag;
}
private getCharText(character: string): HTMLElement {
const textTag = window.document.createElement("text");
textTag.setAttribute("text-anchor", "middle");
textTag.setAttribute("y", "50%");
textTag.setAttribute("x", "50%");
textTag.setAttribute("dy", "0.35em");
textTag.setAttribute("pointer-events", "auto");
textTag.setAttribute("fill", this.textColor);
textTag.setAttribute(
"font-family",
'"Open Sans","Helvetica Neue",Helvetica,Arial,' +
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'
);
textTag.textContent = character;
textTag.style.fontWeight = this.fontWeight.toString();
textTag.style.fontSize = this.fontSize + "px";
return textTag;
}
private unicodeSafeSubstring(str: string, count: number) {
const characters = str.match(/./ug);
return characters != null ? characters.slice(0, count).join('') : '';
}
private unicodeSafeSubstring(str: string, count: number) {
const characters = str.match(/./gu);
return characters != null ? characters.slice(0, count).join("") : "";
}
}

View File

@@ -1,27 +1,35 @@
<div #callout class="callout callout-{{calloutStyle}}" [ngClass]="{'clickable': clickable}"
[attr.role]="useAlertRole ? 'alert' : null">
<h3 class="callout-heading" *ngIf="title">
<i class="fa {{icon}}" *ngIf="icon" aria-hidden="true"></i>
{{title}}
</h3>
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
{{enforcedPolicyMessage}}
<ul>
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
{{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}}
</li>
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
</li>
<li *ngIf="enforcedPolicyOptions?.requireUpper">
{{'policyInEffectUppercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireLower">
{{'policyInEffectLowercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
{{'policyInEffectNumbers' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}</li>
</ul>
</div>
<ng-content></ng-content>
<div
#callout
class="callout callout-{{ calloutStyle }}"
[ngClass]="{ clickable: clickable }"
[attr.role]="useAlertRole ? 'alert' : null"
>
<h3 class="callout-heading" *ngIf="title">
<i class="fa {{ icon }}" *ngIf="icon" aria-hidden="true"></i>
{{ title }}
</h3>
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
{{ enforcedPolicyMessage }}
<ul>
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
{{ "policyInEffectMinComplexity" | i18n: getPasswordScoreAlertDisplay() }}
</li>
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
{{ "policyInEffectMinLength" | i18n: enforcedPolicyOptions?.minLength.toString() }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireUpper">
{{ "policyInEffectUppercase" | i18n }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireLower">
{{ "policyInEffectLowercase" | i18n }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
{{ "policyInEffectNumbers" | i18n }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
{{ "policyInEffectSpecial" | i18n: "!@#$%^&*" }}
</li>
</ul>
</div>
<ng-content></ng-content>
</div>

View File

@@ -1,83 +1,79 @@
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
@Component({
selector: 'app-callout',
templateUrl: 'callout.component.html',
selector: "app-callout",
templateUrl: "callout.component.html",
})
export class CalloutComponent implements OnInit {
@Input() type = 'info';
@Input() icon: string;
@Input() title: string;
@Input() clickable: boolean;
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
@Input() enforcedPolicyMessage: string;
@Input() useAlertRole = false;
@Input() type = "info";
@Input() icon: string;
@Input() title: string;
@Input() clickable: boolean;
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
@Input() enforcedPolicyMessage: string;
@Input() useAlertRole = false;
calloutStyle: string;
calloutStyle: string;
constructor(private i18nService: I18nService) { }
constructor(private i18nService: I18nService) {}
ngOnInit() {
this.calloutStyle = this.type;
ngOnInit() {
this.calloutStyle = this.type;
if (this.enforcedPolicyMessage === undefined) {
this.enforcedPolicyMessage = this.i18nService.t('masterPasswordPolicyInEffect');
}
if (this.type === 'warning' || this.type === 'danger') {
if (this.type === 'danger') {
this.calloutStyle = 'danger';
}
if (this.title === undefined) {
this.title = this.i18nService.t('warning');
}
if (this.icon === undefined) {
this.icon = 'fa-warning';
}
} else if (this.type === 'error') {
this.calloutStyle = 'danger';
if (this.title === undefined) {
this.title = this.i18nService.t('error');
}
if (this.icon === undefined) {
this.icon = 'fa-bolt';
}
} else if (this.type === 'tip') {
this.calloutStyle = 'success';
if (this.title === undefined) {
this.title = this.i18nService.t('tip');
}
if (this.icon === undefined) {
this.icon = 'fa-lightbulb-o';
}
}
if (this.enforcedPolicyMessage === undefined) {
this.enforcedPolicyMessage = this.i18nService.t("masterPasswordPolicyInEffect");
}
getPasswordScoreAlertDisplay() {
if (this.enforcedPolicyOptions == null) {
return '';
}
let str: string;
switch (this.enforcedPolicyOptions.minComplexity) {
case 4:
str = this.i18nService.t('strong');
break;
case 3:
str = this.i18nService.t('good');
break;
default:
str = this.i18nService.t('weak');
break;
}
return str + ' (' + this.enforcedPolicyOptions.minComplexity + ')';
if (this.type === "warning" || this.type === "danger") {
if (this.type === "danger") {
this.calloutStyle = "danger";
}
if (this.title === undefined) {
this.title = this.i18nService.t("warning");
}
if (this.icon === undefined) {
this.icon = "fa-warning";
}
} else if (this.type === "error") {
this.calloutStyle = "danger";
if (this.title === undefined) {
this.title = this.i18nService.t("error");
}
if (this.icon === undefined) {
this.icon = "fa-bolt";
}
} else if (this.type === "tip") {
this.calloutStyle = "success";
if (this.title === undefined) {
this.title = this.i18nService.t("tip");
}
if (this.icon === undefined) {
this.icon = "fa-lightbulb-o";
}
}
}
getPasswordScoreAlertDisplay() {
if (this.enforcedPolicyOptions == null) {
return "";
}
let str: string;
switch (this.enforcedPolicyOptions.minComplexity) {
case 4:
str = this.i18nService.t("strong");
break;
case 3:
str = this.i18nService.t("good");
break;
default:
str = this.i18nService.t("weak");
break;
}
return str + " (" + this.enforcedPolicyOptions.minComplexity + ")";
}
}

View File

@@ -1,47 +1,55 @@
import { Directive, Input } from '@angular/core';
import { Directive, Input } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { CaptchaIFrame } from 'jslib-common/misc/captcha_iframe';
import { CaptchaIFrame } from "jslib-common/misc/captcha_iframe";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive()
export abstract class CaptchaProtectedComponent {
@Input() captchaSiteKey: string = null;
captchaToken: string = null;
captcha: CaptchaIFrame;
@Input() captchaSiteKey: string = null;
captchaToken: string = null;
captcha: CaptchaIFrame;
constructor(protected environmentService: EnvironmentService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService) { }
constructor(
protected environmentService: EnvironmentService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService
) {}
async setupCaptcha() {
const webVaultUrl = this.environmentService.getWebVaultUrl();
async setupCaptcha() {
const webVaultUrl = this.environmentService.getWebVaultUrl();
this.captcha = new CaptchaIFrame(window, webVaultUrl,
this.i18nService, (token: string) => {
this.captchaToken = token;
}, (error: string) => {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error);
}, (info: string) => {
this.platformUtilsService.showToast('info', this.i18nService.t('info'), info);
}
);
this.captcha = new CaptchaIFrame(
window,
webVaultUrl,
this.i18nService,
(token: string) => {
this.captchaToken = token;
},
(error: string) => {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error);
},
(info: string) => {
this.platformUtilsService.showToast("info", this.i18nService.t("info"), info);
}
);
}
showCaptcha() {
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
}
protected handleCaptchaRequired(response: { captchaSiteKey: string }): boolean {
if (Utils.isNullOrWhitespace(response.captchaSiteKey)) {
return false;
}
showCaptcha() {
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
}
protected handleCaptchaRequired(response: { captchaSiteKey: string; }): boolean {
if (Utils.isNullOrWhitespace(response.captchaSiteKey)) {
return false;
}
this.captchaSiteKey = response.captchaSiteKey;
this.captcha.init(response.captchaSiteKey);
return true;
}
this.captchaSiteKey = response.captchaSiteKey;
this.captcha.init(response.captchaSiteKey);
return true;
}
}

View File

@@ -1,152 +1,197 @@
import { Directive, OnInit } from '@angular/core';
import { Directive, OnInit } from "@angular/core";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { EncString } from 'jslib-common/models/domain/encString';
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { EncString } from "jslib-common/models/domain/encString";
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { KdfType } from 'jslib-common/enums/kdfType';
import { KdfType } from "jslib-common/enums/kdfType";
@Directive()
export class ChangePasswordComponent implements OnInit {
masterPassword: string;
masterPasswordRetype: string;
formPromise: Promise<any>;
masterPasswordScore: number;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
masterPassword: string;
masterPasswordRetype: string;
formPromise: Promise<any>;
masterPasswordScore: number;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
protected email: string;
protected kdf: KdfType;
protected kdfIterations: number;
protected email: string;
protected kdf: KdfType;
protected kdfIterations: number;
private masterPasswordStrengthTimeout: any;
private masterPasswordStrengthTimeout: any;
constructor(protected i18nService: I18nService, protected cryptoService: CryptoService,
protected messagingService: MessagingService, protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService,
protected stateService: StateService) { }
constructor(
protected i18nService: I18nService,
protected cryptoService: CryptoService,
protected messagingService: MessagingService,
protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService,
protected policyService: PolicyService,
protected stateService: StateService
) {}
async ngOnInit() {
this.email = await this.stateService.getEmail();
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
async ngOnInit() {
this.email = await this.stateService.getEmail();
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
}
async submit() {
if (!(await this.strongPassword())) {
return;
}
async submit() {
if (!await this.strongPassword()) {
return;
}
if (!await this.setupSubmitActions()) {
return;
}
const email = await this.stateService.getEmail();
if (this.kdf == null) {
this.kdf = await this.stateService.getKdfType();
}
if (this.kdfIterations == null) {
this.kdfIterations = await this.stateService.getKdfIterations();
}
const key = await this.cryptoService.makeKey(this.masterPassword, email.trim().toLowerCase(),
this.kdf, this.kdfIterations);
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
let encKey: [SymmetricCryptoKey, EncString] = null;
const existingEncKey = await this.cryptoService.getEncKey();
if (existingEncKey == null) {
encKey = await this.cryptoService.makeEncKey(key);
} else {
encKey = await this.cryptoService.remakeEncKey(key);
}
await this.performSubmitActions(masterPasswordHash, key, encKey);
if (!(await this.setupSubmitActions())) {
return;
}
async setupSubmitActions(): Promise<boolean> {
// Override in sub-class
// Can be used for additional validation and/or other processes the should occur before changing passwords
return true;
const email = await this.stateService.getEmail();
if (this.kdf == null) {
this.kdf = await this.stateService.getKdfType();
}
if (this.kdfIterations == null) {
this.kdfIterations = await this.stateService.getKdfIterations();
}
const key = await this.cryptoService.makeKey(
this.masterPassword,
email.trim().toLowerCase(),
this.kdf,
this.kdfIterations
);
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
let encKey: [SymmetricCryptoKey, EncString] = null;
const existingEncKey = await this.cryptoService.getEncKey();
if (existingEncKey == null) {
encKey = await this.cryptoService.makeEncKey(key);
} else {
encKey = await this.cryptoService.remakeEncKey(key);
}
async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]) {
// Override in sub-class
await this.performSubmitActions(masterPasswordHash, key, encKey);
}
async setupSubmitActions(): Promise<boolean> {
// Override in sub-class
// Can be used for additional validation and/or other processes the should occur before changing passwords
return true;
}
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
) {
// Override in sub-class
}
async strongPassword(): Promise<boolean> {
if (this.masterPassword == null || this.masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return false;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassLength")
);
return false;
}
if (this.masterPassword !== this.masterPasswordRetype) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassDoesntMatch")
);
return false;
}
async strongPassword(): Promise<boolean> {
if (this.masterPassword == null || this.masterPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return false;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassLength'));
return false;
}
if (this.masterPassword !== this.masterPasswordRetype) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassDoesntMatch'));
return false;
}
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
if (this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
strengthResult.score,
this.masterPassword,
this.enforcedPolicyOptions)) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
return false;
}
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
'warning');
if (!result) {
return false;
}
}
return true;
if (
this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
strengthResult.score,
this.masterPassword,
this.enforcedPolicyOptions
)
) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
);
return false;
}
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!result) {
return false;
}
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'),
this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel'));
if (confirmed) {
this.messagingService.send('logout');
}
}
return true;
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf('@');
if (atPosition > -1) {
userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/));
}
return userInput;
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("logOutConfirmation"),
this.i18nService.t("logOut"),
this.i18nService.t("logOut"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.messagingService.send("logout");
}
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
this.email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
return userInput;
}
}

View File

@@ -1,95 +1,94 @@
import {
Directive,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, Output } from "@angular/core";
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SearchService } from "jslib-common/abstractions/search.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
@Directive()
export class CiphersComponent {
@Input() activeCipherId: string = null;
@Output() onCipherClicked = new EventEmitter<CipherView>();
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
@Output() onAddCipher = new EventEmitter();
@Output() onAddCipherOptions = new EventEmitter();
@Input() activeCipherId: string = null;
@Output() onCipherClicked = new EventEmitter<CipherView>();
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
@Output() onAddCipher = new EventEmitter();
@Output() onAddCipherOptions = new EventEmitter();
loaded: boolean = false;
ciphers: CipherView[] = [];
searchText: string;
searchPlaceholder: string = null;
filter: (cipher: CipherView) => boolean = null;
deleted: boolean = false;
loaded: boolean = false;
ciphers: CipherView[] = [];
searchText: string;
searchPlaceholder: string = null;
filter: (cipher: CipherView) => boolean = null;
deleted: boolean = false;
protected searchPending = false;
protected searchPending = false;
private searchTimeout: any = null;
private searchTimeout: any = null;
constructor(protected searchService: SearchService) { }
constructor(protected searchService: SearchService) {}
async load(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
this.deleted = deleted || false;
await this.applyFilter(filter);
this.loaded = true;
async load(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
this.deleted = deleted || false;
await this.applyFilter(filter);
this.loaded = true;
}
async reload(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
this.loaded = false;
this.ciphers = [];
await this.load(filter, deleted);
}
async refresh() {
await this.reload(this.filter, this.deleted);
}
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
this.filter = filter;
await this.search(null);
}
async search(timeout: number = null, indexedCiphers?: CipherView[]) {
this.searchPending = false;
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
async reload(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
this.loaded = false;
this.ciphers = [];
await this.load(filter, deleted);
if (timeout == null) {
await this.doSearch(indexedCiphers);
return;
}
this.searchPending = true;
this.searchTimeout = setTimeout(async () => {
await this.doSearch(indexedCiphers);
this.searchPending = false;
}, timeout);
}
async refresh() {
await this.reload(this.filter, this.deleted);
}
selectCipher(cipher: CipherView) {
this.onCipherClicked.emit(cipher);
}
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
this.filter = filter;
await this.search(null);
}
rightClickCipher(cipher: CipherView) {
this.onCipherRightClicked.emit(cipher);
}
async search(timeout: number = null, indexedCiphers?: CipherView[]) {
this.searchPending = false;
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
if (timeout == null) {
await this.doSearch(indexedCiphers);
return;
}
this.searchPending = true;
this.searchTimeout = setTimeout(async () => {
await this.doSearch(indexedCiphers);
this.searchPending = false;
}, timeout);
}
addCipher() {
this.onAddCipher.emit();
}
selectCipher(cipher: CipherView) {
this.onCipherClicked.emit(cipher);
}
addCipherOptions() {
this.onAddCipherOptions.emit();
}
rightClickCipher(cipher: CipherView) {
this.onCipherRightClicked.emit(cipher);
}
isSearching() {
return !this.searchPending && this.searchService.isSearchable(this.searchText);
}
addCipher() {
this.onAddCipher.emit();
}
protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted;
addCipherOptions() {
this.onAddCipherOptions.emit();
}
isSearching() {
return !this.searchPending && this.searchService.isSearchable(this.searchText);
}
protected deletedFilter: (cipher: CipherView) => boolean = c => c.isDeleted === this.deleted;
protected async doSearch(indexedCiphers?: CipherView[]) {
this.ciphers = await this.searchService.searchCiphers(this.searchText, [this.filter, this.deletedFilter], indexedCiphers);
}
protected async doSearch(indexedCiphers?: CipherView[]) {
this.ciphers = await this.searchService.searchCiphers(
this.searchText,
[this.filter, this.deletedFilter],
indexedCiphers
);
}
}

View File

@@ -1,90 +1,94 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.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 { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.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 { CipherView } from 'jslib-common/models/view/cipherView';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { Cipher } from 'jslib-common/models/domain/cipher';
import { Cipher } from "jslib-common/models/domain/cipher";
@Directive()
export class CollectionsComponent implements OnInit {
@Input() cipherId: string;
@Input() allowSelectNone = false;
@Output() onSavedCollections = new EventEmitter();
@Input() cipherId: string;
@Input() allowSelectNone = false;
@Output() onSavedCollections = new EventEmitter();
formPromise: Promise<any>;
cipher: CipherView;
collectionIds: string[];
collections: CollectionView[] = [];
formPromise: Promise<any>;
cipher: CipherView;
collectionIds: string[];
collections: CollectionView[] = [];
protected cipherDomain: Cipher;
protected cipherDomain: Cipher;
constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService, protected cipherService: CipherService, private logService: LogService) { }
constructor(
protected collectionService: CollectionService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected cipherService: CipherService,
private logService: LogService
) {}
async ngOnInit() {
await this.load();
async ngOnInit() {
await this.load();
}
async load() {
this.cipherDomain = await this.loadCipher();
this.collectionIds = this.loadCipherCollections();
this.cipher = await this.cipherDomain.decrypt();
this.collections = await this.loadCollections();
this.collections.forEach((c) => ((c as any).checked = false));
if (this.collectionIds != null) {
this.collections.forEach((c) => {
(c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1;
});
}
}
async load() {
this.cipherDomain = await this.loadCipher();
this.collectionIds = this.loadCipherCollections();
this.cipher = await this.cipherDomain.decrypt();
this.collections = await this.loadCollections();
this.collections.forEach(c => (c as any).checked = false);
if (this.collectionIds != null) {
this.collections.forEach(c => {
(c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1;
});
}
async submit() {
const selectedCollectionIds = this.collections
.filter((c) => !!(c as any).checked)
.map((c) => c.id);
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectOneCollection")
);
return;
}
async submit() {
const selectedCollectionIds = this.collections
.filter(c => !!(c as any).checked)
.map(c => c.id);
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectOneCollection'));
return;
}
this.cipherDomain.collectionIds = selectedCollectionIds;
try {
this.formPromise = this.saveCollections();
await this.formPromise;
this.onSavedCollections.emit();
this.platformUtilsService.showToast('success', null, this.i18nService.t('editedItem'));
} catch (e) {
this.logService.error(e);
}
this.cipherDomain.collectionIds = selectedCollectionIds;
try {
this.formPromise = this.saveCollections();
await this.formPromise;
this.onSavedCollections.emit();
this.platformUtilsService.showToast("success", null, this.i18nService.t("editedItem"));
} catch (e) {
this.logService.error(e);
}
}
protected loadCipher() {
return this.cipherService.get(this.cipherId);
}
protected loadCipher() {
return this.cipherService.get(this.cipherId);
}
protected loadCipherCollections() {
return this.cipherDomain.collectionIds;
}
protected loadCipherCollections() {
return this.cipherDomain.collectionIds;
}
protected async loadCollections() {
const allCollections = await this.collectionService.getAllDecrypted();
return allCollections.filter(c => !c.readOnly && c.organizationId === this.cipher.organizationId);
}
protected async loadCollections() {
const allCollections = await this.collectionService.getAllDecrypted();
return allCollections.filter(
(c) => !c.readOnly && c.organizationId === this.cipher.organizationId
);
}
protected saveCollections() {
return this.cipherService.saveCollectionsWithServer(this.cipherDomain);
}
protected saveCollections() {
return this.cipherService.saveCollectionsWithServer(this.cipherDomain);
}
}

View File

@@ -1,65 +1,63 @@
import {
Directive,
EventEmitter,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Output } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Directive()
export class EnvironmentComponent {
@Output() onSaved = new EventEmitter();
@Output() onSaved = new EventEmitter();
iconsUrl: string;
identityUrl: string;
apiUrl: string;
webVaultUrl: string;
notificationsUrl: string;
baseUrl: string;
showCustom = false;
iconsUrl: string;
identityUrl: string;
apiUrl: string;
webVaultUrl: string;
notificationsUrl: string;
baseUrl: string;
showCustom = false;
constructor(protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService,
protected i18nService: I18nService) {
constructor(
protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService,
protected i18nService: I18nService
) {
const urls = this.environmentService.getUrls();
const urls = this.environmentService.getUrls();
this.baseUrl = urls.base || "";
this.webVaultUrl = urls.webVault || "";
this.apiUrl = urls.api || "";
this.identityUrl = urls.identity || "";
this.iconsUrl = urls.icons || "";
this.notificationsUrl = urls.notifications || "";
}
this.baseUrl = urls.base || '';
this.webVaultUrl = urls.webVault || '';
this.apiUrl = urls.api || '';
this.identityUrl = urls.identity || '';
this.iconsUrl = urls.icons || '';
this.notificationsUrl = urls.notifications || '';
}
async submit() {
const resUrls = await this.environmentService.setUrls({
base: this.baseUrl,
api: this.apiUrl,
identity: this.identityUrl,
webVault: this.webVaultUrl,
icons: this.iconsUrl,
notifications: this.notificationsUrl,
});
async submit() {
const resUrls = await this.environmentService.setUrls({
base: this.baseUrl,
api: this.apiUrl,
identity: this.identityUrl,
webVault: this.webVaultUrl,
icons: this.iconsUrl,
notifications: this.notificationsUrl,
});
// re-set urls since service can change them, ex: prefixing https://
this.baseUrl = resUrls.base;
this.apiUrl = resUrls.api;
this.identityUrl = resUrls.identity;
this.webVaultUrl = resUrls.webVault;
this.iconsUrl = resUrls.icons;
this.notificationsUrl = resUrls.notifications;
// re-set urls since service can change them, ex: prefixing https://
this.baseUrl = resUrls.base;
this.apiUrl = resUrls.api;
this.identityUrl = resUrls.identity;
this.webVaultUrl = resUrls.webVault;
this.iconsUrl = resUrls.icons;
this.notificationsUrl = resUrls.notifications;
this.platformUtilsService.showToast("success", null, this.i18nService.t("environmentSaved"));
this.saved();
}
this.platformUtilsService.showToast('success', null, this.i18nService.t('environmentSaved'));
this.saved();
}
toggleCustom() {
this.showCustom = !this.showCustom;
}
toggleCustom() {
this.showCustom = !this.showCustom;
}
protected saved() {
this.onSaved.emit();
}
protected saved() {
this.onSaved.emit();
}
}

View File

@@ -1,140 +1,156 @@
import {
Directive,
EventEmitter,
OnInit,
Output,
} from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Directive, EventEmitter, OnInit, Output } 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 { EventType } from 'jslib-common/enums/eventType';
import { PolicyType } from 'jslib-common/enums/policyType';
import { EventType } from "jslib-common/enums/eventType";
import { PolicyType } from "jslib-common/enums/policyType";
@Directive()
export class ExportComponent implements OnInit {
@Output() onSaved = new EventEmitter();
@Output() onSaved = new EventEmitter();
formPromise: Promise<string>;
disabledByPolicy: boolean = false;
formPromise: Promise<string>;
disabledByPolicy: boolean = false;
exportForm = this.fb.group({
format: ['json'],
secret: [''],
});
exportForm = this.fb.group({
format: ["json"],
secret: [""],
});
formatOptions = [
{ name: '.json', value: 'json' },
{ name: '.csv', value: 'csv' },
{ name: '.json (Encrypted)', value: 'encrypted_json' },
];
formatOptions = [
{ name: ".json", value: "json" },
{ name: ".csv", value: "csv" },
{ name: ".json (Encrypted)", value: "encrypted_json" },
];
constructor(protected cryptoService: CryptoService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected exportService: ExportService,
protected eventService: EventService, private policyService: PolicyService, protected win: Window,
private logService: LogService, private userVerificationService: UserVerificationService,
private fb: FormBuilder) { }
constructor(
protected cryptoService: CryptoService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected exportService: ExportService,
protected eventService: EventService,
private policyService: PolicyService,
protected win: Window,
private logService: LogService,
private userVerificationService: UserVerificationService,
private fb: FormBuilder
) {}
async ngOnInit() {
await this.checkExportDisabled();
async ngOnInit() {
await this.checkExportDisabled();
}
async checkExportDisabled() {
this.disabledByPolicy = await this.policyService.policyAppliesToUser(
PolicyType.DisablePersonalVaultExport
);
if (this.disabledByPolicy) {
this.exportForm.disable();
}
}
get encryptedFormat() {
return this.format === "encrypted_json";
}
async submit() {
if (this.disabledByPolicy) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("personalVaultExportPolicyInEffect")
);
return;
}
async checkExportDisabled() {
this.disabledByPolicy = await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport);
if (this.disabledByPolicy) {
this.exportForm.disable();
}
const acceptedWarning = await this.warningDialog();
if (!acceptedWarning) {
return;
}
get encryptedFormat() {
return this.format === 'encrypted_json';
const secret = this.exportForm.get("secret").value;
try {
await this.userVerificationService.verifyUser(secret);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
return;
}
async submit() {
if (this.disabledByPolicy) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('personalVaultExportPolicyInEffect'));
return;
}
const acceptedWarning = await this.warningDialog();
if (!acceptedWarning) {
return;
}
const secret = this.exportForm.get('secret').value;
try {
await this.userVerificationService.verifyUser(secret);
} catch (e) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e.message);
return;
}
try {
this.formPromise = this.getExportData();
const data = await this.formPromise;
this.downloadFile(data);
this.saved();
await this.collectEvent();
this.exportForm.get('secret').setValue('');
} catch (e) {
this.logService.error(e);
}
try {
this.formPromise = this.getExportData();
const data = await this.formPromise;
this.downloadFile(data);
this.saved();
await this.collectEvent();
this.exportForm.get("secret").setValue("");
} catch (e) {
this.logService.error(e);
}
}
async warningDialog() {
if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog(
'<p>' + this.i18nService.t('encExportKeyWarningDesc') +
'<p>' + this.i18nService.t('encExportAccountWarningDesc'),
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
this.i18nService.t('cancel'), 'warning',
true);
} else {
return await this.platformUtilsService.showDialog(
this.i18nService.t('exportWarningDesc'),
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
this.i18nService.t('cancel'), 'warning');
}
async warningDialog() {
if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog(
"<p>" +
this.i18nService.t("encExportKeyWarningDesc") +
"<p>" +
this.i18nService.t("encExportAccountWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning",
true
);
} else {
return await this.platformUtilsService.showDialog(
this.i18nService.t("exportWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning"
);
}
}
protected saved() {
this.onSaved.emit();
}
protected saved() {
this.onSaved.emit();
}
protected getExportData() {
return this.exportService.getExport(this.format);
}
protected getExportData() {
return this.exportService.getExport(this.format);
}
protected getFileName(prefix?: string) {
let extension = this.format;
if (this.format === 'encrypted_json') {
if (prefix == null) {
prefix = 'encrypted';
} else {
prefix = 'encrypted_' + prefix;
}
extension = 'json';
}
return this.exportService.getFileName(prefix, extension);
protected getFileName(prefix?: string) {
let extension = this.format;
if (this.format === "encrypted_json") {
if (prefix == null) {
prefix = "encrypted";
} else {
prefix = "encrypted_" + prefix;
}
extension = "json";
}
return this.exportService.getFileName(prefix, extension);
}
protected async collectEvent(): Promise<any> {
await this.eventService.collect(EventType.User_ClientExportedVault);
}
protected async collectEvent(): Promise<any> {
await this.eventService.collect(EventType.User_ClientExportedVault);
}
get format() {
return this.exportForm.get('format').value;
}
get format() {
return this.exportForm.get("format").value;
}
private downloadFile(csv: string): void {
const fileName = this.getFileName();
this.platformUtilsService.saveFile(this.win, csv, { type: 'text/plain' }, fileName);
}
private downloadFile(csv: string): void {
const fileName = this.getFileName();
this.platformUtilsService.saveFile(this.win, csv, { type: "text/plain" }, fileName);
}
}

View File

@@ -1,89 +1,97 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { FolderService } from 'jslib-common/abstractions/folder.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 { FolderService } from "jslib-common/abstractions/folder.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 { FolderView } from 'jslib-common/models/view/folderView';
import { FolderView } from "jslib-common/models/view/folderView";
@Directive()
export class FolderAddEditComponent implements OnInit {
@Input() folderId: string;
@Output() onSavedFolder = new EventEmitter<FolderView>();
@Output() onDeletedFolder = new EventEmitter<FolderView>();
@Input() folderId: string;
@Output() onSavedFolder = new EventEmitter<FolderView>();
@Output() onDeletedFolder = new EventEmitter<FolderView>();
editMode: boolean = false;
folder: FolderView = new FolderView();
title: string;
formPromise: Promise<any>;
deletePromise: Promise<any>;
editMode: boolean = false;
folder: FolderView = new FolderView();
title: string;
formPromise: Promise<any>;
deletePromise: Promise<any>;
constructor(protected folderService: FolderService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, private logService: LogService) { }
constructor(
protected folderService: FolderService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() {
await this.init();
async ngOnInit() {
await this.init();
}
async submit(): Promise<boolean> {
if (this.folder.name == null || this.folder.name === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nameRequired")
);
return false;
}
async submit(): Promise<boolean> {
if (this.folder.name == null || this.folder.name === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nameRequired'));
return false;
}
try {
const folder = await this.folderService.encrypt(this.folder);
this.formPromise = this.folderService.saveWithServer(folder);
await this.formPromise;
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.editMode ? 'editedFolder' : 'addedFolder'));
this.onSavedFolder.emit(this.folder);
return true;
} catch (e) {
this.logService.error(e);
}
return false;
try {
const folder = await this.folderService.encrypt(this.folder);
this.formPromise = this.folderService.saveWithServer(folder);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder")
);
this.onSavedFolder.emit(this.folder);
return true;
} catch (e) {
this.logService.error(e);
}
async delete(): Promise<boolean> {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteFolderConfirmation'), this.i18nService.t('deleteFolder'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
return false;
}
try {
this.deletePromise = this.folderService.deleteWithServer(this.folder.id);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedFolder'));
this.onDeletedFolder.emit(this.folder);
} catch (e) {
this.logService.error(e);
}
return true;
async delete(): Promise<boolean> {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteFolderConfirmation"),
this.i18nService.t("deleteFolder"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
protected async init() {
this.editMode = this.folderId != null;
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t('editFolder');
const folder = await this.folderService.get(this.folderId);
this.folder = await folder.decrypt();
} else {
this.title = this.i18nService.t('addFolder');
}
try {
this.deletePromise = this.folderService.deleteWithServer(this.folder.id);
await this.deletePromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder"));
this.onDeletedFolder.emit(this.folder);
} catch (e) {
this.logService.error(e);
}
return true;
}
protected async init() {
this.editMode = this.folderId != null;
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t("editFolder");
const folder = await this.folderService.get(this.folderId);
this.folder = await folder.decrypt();
} else {
this.title = this.i18nService.t("addFolder");
}
}
}

View File

@@ -1,162 +1,160 @@
import {
Directive,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, Output } from "@angular/core";
import { CipherType } from 'jslib-common/enums/cipherType';
import { CipherType } from "jslib-common/enums/cipherType";
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { FolderView } from 'jslib-common/models/view/folderView';
import { CollectionView } from "jslib-common/models/view/collectionView";
import { FolderView } from "jslib-common/models/view/folderView";
import { TreeNode } from 'jslib-common/models/domain/treeNode';
import { TreeNode } from "jslib-common/models/domain/treeNode";
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { StateService } from "jslib-common/abstractions/state.service";
@Directive()
export class GroupingsComponent {
@Input() showFolders = true;
@Input() showCollections = true;
@Input() showFavorites = true;
@Input() showTrash = true;
@Input() showFolders = true;
@Input() showCollections = true;
@Input() showFavorites = true;
@Input() showTrash = true;
@Output() onAllClicked = new EventEmitter();
@Output() onFavoritesClicked = new EventEmitter();
@Output() onTrashClicked = new EventEmitter();
@Output() onCipherTypeClicked = new EventEmitter<CipherType>();
@Output() onFolderClicked = new EventEmitter<FolderView>();
@Output() onAddFolder = new EventEmitter();
@Output() onEditFolder = new EventEmitter<FolderView>();
@Output() onCollectionClicked = new EventEmitter<CollectionView>();
@Output() onAllClicked = new EventEmitter();
@Output() onFavoritesClicked = new EventEmitter();
@Output() onTrashClicked = new EventEmitter();
@Output() onCipherTypeClicked = new EventEmitter<CipherType>();
@Output() onFolderClicked = new EventEmitter<FolderView>();
@Output() onAddFolder = new EventEmitter();
@Output() onEditFolder = new EventEmitter<FolderView>();
@Output() onCollectionClicked = new EventEmitter<CollectionView>();
folders: FolderView[];
nestedFolders: TreeNode<FolderView>[];
collections: CollectionView[];
nestedCollections: TreeNode<CollectionView>[];
loaded: boolean = false;
cipherType = CipherType;
selectedAll: boolean = false;
selectedFavorites: boolean = false;
selectedTrash: boolean = false;
selectedType: CipherType = null;
selectedFolder: boolean = false;
selectedFolderId: string = null;
selectedCollectionId: string = null;
folders: FolderView[];
nestedFolders: TreeNode<FolderView>[];
collections: CollectionView[];
nestedCollections: TreeNode<CollectionView>[];
loaded: boolean = false;
cipherType = CipherType;
selectedAll: boolean = false;
selectedFavorites: boolean = false;
selectedTrash: boolean = false;
selectedType: CipherType = null;
selectedFolder: boolean = false;
selectedFolderId: string = null;
selectedCollectionId: string = null;
private collapsedGroupings: Set<string>;
private collapsedGroupings: Set<string>;
constructor(protected collectionService: CollectionService, protected folderService: FolderService,
protected stateService: StateService) { }
constructor(
protected collectionService: CollectionService,
protected folderService: FolderService,
protected stateService: StateService
) {}
async load(setLoaded = true) {
const collapsedGroupings = await this.stateService.getCollapsedGroupings();
if (collapsedGroupings == null) {
this.collapsedGroupings = new Set<string>();
} else {
this.collapsedGroupings = new Set(collapsedGroupings);
}
await this.loadFolders();
await this.loadCollections();
if (setLoaded) {
this.loaded = true;
}
async load(setLoaded = true) {
const collapsedGroupings = await this.stateService.getCollapsedGroupings();
if (collapsedGroupings == null) {
this.collapsedGroupings = new Set<string>();
} else {
this.collapsedGroupings = new Set(collapsedGroupings);
}
async loadCollections(organizationId?: string) {
if (!this.showCollections) {
return;
}
const collections = await this.collectionService.getAllDecrypted();
if (organizationId != null) {
this.collections = collections.filter(c => c.organizationId === organizationId);
} else {
this.collections = collections;
}
this.nestedCollections = await this.collectionService.getAllNested(this.collections);
}
await this.loadFolders();
await this.loadCollections();
async loadFolders() {
if (!this.showFolders) {
return;
}
this.folders = await this.folderService.getAllDecrypted();
this.nestedFolders = await this.folderService.getAllNested();
if (setLoaded) {
this.loaded = true;
}
}
selectAll() {
this.clearSelections();
this.selectedAll = true;
this.onAllClicked.emit();
async loadCollections(organizationId?: string) {
if (!this.showCollections) {
return;
}
const collections = await this.collectionService.getAllDecrypted();
if (organizationId != null) {
this.collections = collections.filter((c) => c.organizationId === organizationId);
} else {
this.collections = collections;
}
this.nestedCollections = await this.collectionService.getAllNested(this.collections);
}
selectFavorites() {
this.clearSelections();
this.selectedFavorites = true;
this.onFavoritesClicked.emit();
async loadFolders() {
if (!this.showFolders) {
return;
}
this.folders = await this.folderService.getAllDecrypted();
this.nestedFolders = await this.folderService.getAllNested();
}
selectTrash() {
this.clearSelections();
this.selectedTrash = true;
this.onTrashClicked.emit();
}
selectAll() {
this.clearSelections();
this.selectedAll = true;
this.onAllClicked.emit();
}
selectType(type: CipherType) {
this.clearSelections();
this.selectedType = type;
this.onCipherTypeClicked.emit(type);
}
selectFavorites() {
this.clearSelections();
this.selectedFavorites = true;
this.onFavoritesClicked.emit();
}
selectFolder(folder: FolderView) {
this.clearSelections();
this.selectedFolder = true;
this.selectedFolderId = folder.id;
this.onFolderClicked.emit(folder);
}
selectTrash() {
this.clearSelections();
this.selectedTrash = true;
this.onTrashClicked.emit();
}
addFolder() {
this.onAddFolder.emit();
}
selectType(type: CipherType) {
this.clearSelections();
this.selectedType = type;
this.onCipherTypeClicked.emit(type);
}
editFolder(folder: FolderView) {
this.onEditFolder.emit(folder);
}
selectFolder(folder: FolderView) {
this.clearSelections();
this.selectedFolder = true;
this.selectedFolderId = folder.id;
this.onFolderClicked.emit(folder);
}
selectCollection(collection: CollectionView) {
this.clearSelections();
this.selectedCollectionId = collection.id;
this.onCollectionClicked.emit(collection);
}
addFolder() {
this.onAddFolder.emit();
}
clearSelections() {
this.selectedAll = false;
this.selectedFavorites = false;
this.selectedTrash = false;
this.selectedType = null;
this.selectedFolder = false;
this.selectedFolderId = null;
this.selectedCollectionId = null;
}
editFolder(folder: FolderView) {
this.onEditFolder.emit(folder);
}
async collapse(grouping: FolderView | CollectionView, idPrefix = '') {
if (grouping.id == null) {
return;
}
const id = idPrefix + grouping.id;
if (this.isCollapsed(grouping, idPrefix)) {
this.collapsedGroupings.delete(id);
} else {
this.collapsedGroupings.add(id);
}
await this.stateService.setCollapsedGroupings(this.collapsedGroupings);
}
selectCollection(collection: CollectionView) {
this.clearSelections();
this.selectedCollectionId = collection.id;
this.onCollectionClicked.emit(collection);
}
isCollapsed(grouping: FolderView | CollectionView, idPrefix = '') {
return this.collapsedGroupings.has(idPrefix + grouping.id);
clearSelections() {
this.selectedAll = false;
this.selectedFavorites = false;
this.selectedTrash = false;
this.selectedType = null;
this.selectedFolder = false;
this.selectedFolderId = null;
this.selectedCollectionId = null;
}
async collapse(grouping: FolderView | CollectionView, idPrefix = "") {
if (grouping.id == null) {
return;
}
const id = idPrefix + grouping.id;
if (this.isCollapsed(grouping, idPrefix)) {
this.collapsedGroupings.delete(id);
} else {
this.collapsedGroupings.add(id);
}
await this.stateService.setCollapsedGroupings(this.collapsedGroupings);
}
isCollapsed(grouping: FolderView | CollectionView, idPrefix = "") {
return this.collapsedGroupings.has(idPrefix + grouping.id);
}
}

View File

@@ -1,46 +1,56 @@
import { Router } from '@angular/router';
import { Router } from "@angular/router";
import { PasswordHintRequest } from 'jslib-common/models/request/passwordHintRequest';
import { PasswordHintRequest } from "jslib-common/models/request/passwordHintRequest";
import { ApiService } from 'jslib-common/abstractions/api.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 { ApiService } from "jslib-common/abstractions/api.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";
export class HintComponent {
email: string = '';
formPromise: Promise<any>;
email: string = "";
formPromise: Promise<any>;
protected successRoute = 'login';
protected onSuccessfulSubmit: () => void;
protected successRoute = "login";
protected onSuccessfulSubmit: () => void;
constructor(protected router: Router, protected i18nService: I18nService,
protected apiService: ApiService, protected platformUtilsService: PlatformUtilsService,
private logService: LogService) { }
constructor(
protected router: Router,
protected i18nService: I18nService,
protected apiService: ApiService,
protected platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async submit() {
if (this.email == null || this.email === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('emailRequired'));
return;
}
if (this.email.indexOf('@') === -1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidEmail'));
return;
}
try {
this.formPromise = this.apiService.postPasswordHint(new PasswordHintRequest(this.email));
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('masterPassSent'));
if (this.onSuccessfulSubmit != null) {
this.onSuccessfulSubmit();
} else if (this.router != null) {
this.router.navigate([this.successRoute]);
}
} catch (e) {
this.logService.error(e);
}
async submit() {
if (this.email == null || this.email === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("emailRequired")
);
return;
}
if (this.email.indexOf("@") === -1) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidEmail")
);
return;
}
try {
this.formPromise = this.apiService.postPasswordHint(new PasswordHintRequest(this.email));
await this.formPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("masterPassSent"));
if (this.onSuccessfulSubmit != null) {
this.onSuccessfulSubmit();
} else if (this.router != null) {
this.router.navigate([this.successRoute]);
}
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,4 +1,4 @@
<div class="icon" aria-hidden="true">
<img [src]="image" appFallbackSrc="{{fallbackImage}}" *ngIf="imageEnabled && image" alt="" />
<i class="fa fa-fw fa-lg {{icon}}" *ngIf="!imageEnabled || !image"></i>
<img [src]="image" appFallbackSrc="{{ fallbackImage }}" *ngIf="imageEnabled && image" alt="" />
<i class="fa fa-fw fa-lg {{ icon }}" *ngIf="!imageEnabled || !image"></i>
</div>

View File

@@ -1,105 +1,105 @@
import {
Component,
Input,
OnChanges,
} from '@angular/core';
import { Component, Input, OnChanges } from "@angular/core";
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 { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
const IconMap: any = {
'fa-globe': String.fromCharCode(0xf0ac),
'fa-sticky-note-o': String.fromCharCode(0xf24a),
'fa-id-card-o': String.fromCharCode(0xf2c3),
'fa-credit-card': String.fromCharCode(0xf09d),
'fa-android': String.fromCharCode(0xf17b),
'fa-apple': String.fromCharCode(0xf179),
"fa-globe": String.fromCharCode(0xf0ac),
"fa-sticky-note-o": String.fromCharCode(0xf24a),
"fa-id-card-o": String.fromCharCode(0xf2c3),
"fa-credit-card": String.fromCharCode(0xf09d),
"fa-android": String.fromCharCode(0xf17b),
"fa-apple": String.fromCharCode(0xf179),
};
@Component({
selector: 'app-vault-icon',
templateUrl: 'icon.component.html',
selector: "app-vault-icon",
templateUrl: "icon.component.html",
})
export class IconComponent implements OnChanges {
@Input() cipher: CipherView;
icon: string;
image: string;
fallbackImage: string;
imageEnabled: boolean;
@Input() cipher: CipherView;
icon: string;
image: string;
fallbackImage: string;
imageEnabled: boolean;
private iconsUrl: string;
private iconsUrl: string;
constructor(environmentService: EnvironmentService, private stateService: StateService) {
this.iconsUrl = environmentService.getIconsUrl();
constructor(environmentService: EnvironmentService, private stateService: StateService) {
this.iconsUrl = environmentService.getIconsUrl();
}
async ngOnChanges() {
// Components may be re-used when using cdk-virtual-scroll. Which puts the component in a weird state,
// to avoid this we reset all state variables.
this.image = null;
this.fallbackImage = null;
this.imageEnabled = !(await this.stateService.getDisableFavicon());
this.load();
}
get iconCode(): string {
return IconMap[this.icon];
}
protected load() {
switch (this.cipher.type) {
case CipherType.Login:
this.icon = "fa-globe";
this.setLoginIcon();
break;
case CipherType.SecureNote:
this.icon = "fa-sticky-note-o";
break;
case CipherType.Card:
this.icon = "fa-credit-card";
break;
case CipherType.Identity:
this.icon = "fa-id-card-o";
break;
default:
break;
}
}
async ngOnChanges() {
// Components may be re-used when using cdk-virtual-scroll. Which puts the component in a weird state,
// to avoid this we reset all state variables.
private setLoginIcon() {
if (this.cipher.login.uri) {
let hostnameUri = this.cipher.login.uri;
let isWebsite = false;
if (hostnameUri.indexOf("androidapp://") === 0) {
this.icon = "fa-android";
this.image = null;
this.fallbackImage = null;
this.imageEnabled = !(await this.stateService.getDisableFavicon());
this.load();
}
} else if (hostnameUri.indexOf("iosapp://") === 0) {
this.icon = "fa-apple";
this.image = null;
} else if (
this.imageEnabled &&
hostnameUri.indexOf("://") === -1 &&
hostnameUri.indexOf(".") > -1
) {
hostnameUri = "http://" + hostnameUri;
isWebsite = true;
} else if (this.imageEnabled) {
isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1;
}
get iconCode(): string {
return IconMap[this.icon];
}
protected load() {
switch (this.cipher.type) {
case CipherType.Login:
this.icon = 'fa-globe';
this.setLoginIcon();
break;
case CipherType.SecureNote:
this.icon = 'fa-sticky-note-o';
break;
case CipherType.Card:
this.icon = 'fa-credit-card';
break;
case CipherType.Identity:
this.icon = 'fa-id-card-o';
break;
default:
break;
}
}
private setLoginIcon() {
if (this.cipher.login.uri) {
let hostnameUri = this.cipher.login.uri;
let isWebsite = false;
if (hostnameUri.indexOf('androidapp://') === 0) {
this.icon = 'fa-android';
this.image = null;
} else if (hostnameUri.indexOf('iosapp://') === 0) {
this.icon = 'fa-apple';
this.image = null;
} else if (this.imageEnabled && hostnameUri.indexOf('://') === -1 && hostnameUri.indexOf('.') > -1) {
hostnameUri = 'http://' + hostnameUri;
isWebsite = true;
} else if (this.imageEnabled) {
isWebsite = hostnameUri.indexOf('http') === 0 && hostnameUri.indexOf('.') > -1;
}
if (this.imageEnabled && isWebsite) {
try {
this.image = this.iconsUrl + '/' + Utils.getHostname(hostnameUri) + '/icon.png';
this.fallbackImage = 'images/fa-globe.png';
} catch (e) {
// Ignore error since the fallback icon will be shown if image is null.
}
}
} else {
this.image = null;
if (this.imageEnabled && isWebsite) {
try {
this.image = this.iconsUrl + "/" + Utils.getHostname(hostnameUri) + "/icon.png";
this.fallbackImage = "images/fa-globe.png";
} catch (e) {
// Ignore error since the fallback icon will be shown if image is null.
}
}
} else {
this.image = null;
}
}
}

View File

@@ -1,216 +1,279 @@
import { Directive, NgZone, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { take } from 'rxjs/operators';
import { Directive, NgZone, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { take } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { EncString } from 'jslib-common/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { EncString } from "jslib-common/models/domain/encString";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { SecretVerificationRequest } from 'jslib-common/models/request/secretVerificationRequest';
import { SecretVerificationRequest } from "jslib-common/models/request/secretVerificationRequest";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { HashPurpose } from 'jslib-common/enums/hashPurpose';
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
import { HashPurpose } from "jslib-common/enums/hashPurpose";
import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions";
@Directive()
export class LockComponent implements OnInit {
masterPassword: string = '';
pin: string = '';
showPassword: boolean = false;
email: string;
pinLock: boolean = false;
webVaultHostname: string = '';
formPromise: Promise<any>;
supportsBiometric: boolean;
biometricLock: boolean;
biometricText: string;
hideInput: boolean;
masterPassword: string = "";
pin: string = "";
showPassword: boolean = false;
email: string;
pinLock: boolean = false;
webVaultHostname: string = "";
formPromise: Promise<any>;
supportsBiometric: boolean;
biometricLock: boolean;
biometricText: string;
hideInput: boolean;
protected successRoute: string = 'vault';
protected onSuccessfulSubmit: () => Promise<void>;
protected successRoute: string = "vault";
protected onSuccessfulSubmit: () => Promise<void>;
private invalidPinAttempts = 0;
private pinSet: [boolean, boolean];
private invalidPinAttempts = 0;
private pinSet: [boolean, boolean];
constructor(protected router: Router, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService,
protected cryptoService: CryptoService, protected vaultTimeoutService: VaultTimeoutService,
protected environmentService: EnvironmentService, protected stateService: StateService,
protected apiService: ApiService, private logService: LogService,
private keyConnectorService: KeyConnectorService, protected ngZone: NgZone) { }
constructor(
protected router: Router,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected messagingService: MessagingService,
protected cryptoService: CryptoService,
protected vaultTimeoutService: VaultTimeoutService,
protected environmentService: EnvironmentService,
protected stateService: StateService,
protected apiService: ApiService,
private logService: LogService,
private keyConnectorService: KeyConnectorService,
protected ngZone: NgZone
) {}
async ngOnInit() {
this.stateService.activeAccount.subscribe(async _userId => {
await this.load();
});
async ngOnInit() {
this.stateService.activeAccount.subscribe(async (_userId) => {
await this.load();
});
}
async submit() {
if (this.pinLock && (this.pin == null || this.pin === "")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("pinRequired")
);
return;
}
if (!this.pinLock && (this.masterPassword == null || this.masterPassword === "")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return;
}
async submit() {
if (this.pinLock && (this.pin == null || this.pin === '')) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('pinRequired'));
return;
}
if (!this.pinLock && (this.masterPassword == null || this.masterPassword === '')) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return;
}
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
if (this.pinLock) {
let failed = true;
try {
if (this.pinSet[0]) {
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations,
await this.stateService.getDecryptedPinProtected());
const encKey = await this.cryptoService.getEncKey(key);
const protectedPin = await this.stateService.getProtectedPin();
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
failed = decPin !== this.pin;
if (!failed) {
await this.setKeyAndContinue(key);
}
} else {
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations);
failed = false;
await this.setKeyAndContinue(key);
}
} catch {
failed = true;
}
if (failed) {
this.invalidPinAttempts++;
if (this.invalidPinAttempts >= 5) {
this.messagingService.send('logout');
return;
}
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidPin'));
}
if (this.pinLock) {
let failed = true;
try {
if (this.pinSet[0]) {
const key = await this.cryptoService.makeKeyFromPin(
this.pin,
this.email,
kdf,
kdfIterations,
await this.stateService.getDecryptedPinProtected()
);
const encKey = await this.cryptoService.getEncKey(key);
const protectedPin = await this.stateService.getProtectedPin();
const decPin = await this.cryptoService.decryptToUtf8(
new EncString(protectedPin),
encKey
);
failed = decPin !== this.pin;
if (!failed) {
await this.setKeyAndContinue(key);
}
} else {
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfIterations);
const storedKeyHash = await this.cryptoService.getKeyHash();
let passwordValid = false;
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, key);
} else {
const request = new SecretVerificationRequest();
const serverKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key,
HashPurpose.ServerAuthorization);
request.masterPasswordHash = serverKeyHash;
try {
this.formPromise = this.apiService.postAccountVerifyPassword(request);
await this.formPromise;
passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key,
HashPurpose.LocalAuthorization);
await this.cryptoService.setKeyHash(localKeyHash);
} catch (e) {
this.logService.error(e);
}
}
if (passwordValid) {
if (this.pinSet[0]) {
const protectedPin = await this.stateService.getProtectedPin();
const encKey = await this.cryptoService.getEncKey(key);
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfIterations);
await this.stateService.setDecryptedPinProtected(await this.cryptoService.encrypt(key.key, pinKey));
}
await this.setKeyAndContinue(key);
} else {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidMasterPassword'));
}
const key = await this.cryptoService.makeKeyFromPin(
this.pin,
this.email,
kdf,
kdfIterations
);
failed = false;
await this.setKeyAndContinue(key);
}
} catch {
failed = true;
}
if (failed) {
this.invalidPinAttempts++;
if (this.invalidPinAttempts >= 5) {
this.messagingService.send("logout");
return;
}
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidPin")
);
}
} else {
const key = await this.cryptoService.makeKey(
this.masterPassword,
this.email,
kdf,
kdfIterations
);
const storedKeyHash = await this.cryptoService.getKeyHash();
let passwordValid = false;
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, key);
} else {
const request = new SecretVerificationRequest();
const serverKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
key,
HashPurpose.ServerAuthorization
);
request.masterPasswordHash = serverKeyHash;
try {
this.formPromise = this.apiService.postAccountVerifyPassword(request);
await this.formPromise;
passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
key,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash);
} catch (e) {
this.logService.error(e);
}
}
if (passwordValid) {
if (this.pinSet[0]) {
const protectedPin = await this.stateService.getProtectedPin();
const encKey = await this.cryptoService.getEncKey(key);
const decPin = await this.cryptoService.decryptToUtf8(
new EncString(protectedPin),
encKey
);
const pinKey = await this.cryptoService.makePinKey(
decPin,
this.email,
kdf,
kdfIterations
);
await this.stateService.setDecryptedPinProtected(
await this.cryptoService.encrypt(key.key, pinKey)
);
}
await this.setKeyAndContinue(key);
} else {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidMasterPassword")
);
}
}
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("logOutConfirmation"),
this.i18nService.t("logOut"),
this.i18nService.t("logOut"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.messagingService.send("logout");
}
}
async unlockBiometric(): Promise<boolean> {
if (!this.biometricLock) {
return;
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'),
this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel'));
if (confirmed) {
this.messagingService.send('logout');
}
const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
if (success) {
await this.doContinue();
}
async unlockBiometric(): Promise<boolean> {
if (!this.biometricLock) {
return;
}
return success;
}
const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
togglePassword() {
this.showPassword = !this.showPassword;
const input = document.getElementById(this.pinLock ? "pin" : "masterPassword");
if (this.ngZone.isStable) {
input.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => input.focus());
}
}
if (success) {
await this.doContinue();
}
private async setKeyAndContinue(key: SymmetricCryptoKey) {
await this.cryptoService.setKey(key);
await this.doContinue();
}
return success;
private async doContinue() {
await this.stateService.setBiometricLocked(false);
await this.stateService.setEverBeenUnlocked(true);
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
this.messagingService.send("unlocked");
if (this.onSuccessfulSubmit != null) {
await this.onSuccessfulSubmit();
} else if (this.router != null) {
this.router.navigate([this.successRoute]);
}
}
private async load() {
this.pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pinLock =
(this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) ||
this.pinSet[1];
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometricLock =
(await this.vaultTimeoutService.isBiometricLockSet()) &&
((await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric)) ||
!this.platformUtilsService.supportsSecureStorage());
this.biometricText = await this.stateService.getBiometricText();
this.email = await this.stateService.getEmail();
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.hideInput = usesKeyConnector && !this.pinLock;
// Users with key connector and without biometric or pin has no MP to unlock using
if (usesKeyConnector && !(this.biometricLock || this.pinLock)) {
await this.vaultTimeoutService.logOut();
}
togglePassword() {
this.showPassword = !this.showPassword;
const input = document.getElementById(this.pinLock ? 'pin' : 'masterPassword');
if (this.ngZone.isStable) {
input.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => input.focus());
}
}
private async setKeyAndContinue(key: SymmetricCryptoKey) {
await this.cryptoService.setKey(key);
await this.doContinue();
}
private async doContinue() {
await this.stateService.setBiometricLocked(false);
await this.stateService.setEverBeenUnlocked(true);
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
this.messagingService.send('unlocked');
if (this.onSuccessfulSubmit != null) {
await this.onSuccessfulSubmit();
} else if (this.router != null) {
this.router.navigate([this.successRoute]);
}
}
private async load() {
this.pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pinLock = (this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || this.pinSet[1];
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometricLock = await this.vaultTimeoutService.isBiometricLockSet() &&
(await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric) || !this.platformUtilsService.supportsSecureStorage());
this.biometricText = await this.stateService.getBiometricText();
this.email = await this.stateService.getEmail();
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.hideInput = usesKeyConnector && !this.pinLock;
// Users with key connector and without biometric or pin has no MP to unlock using
if (usesKeyConnector && !(this.biometricLock || this.pinLock)) {
await this.vaultTimeoutService.logOut();
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
const vaultUrl = webVaultUrl === 'https://vault.bitwarden.com' ? 'https://bitwarden.com' : webVaultUrl;
this.webVaultHostname = Utils.getHostname(vaultUrl);
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
const vaultUrl =
webVaultUrl === "https://vault.bitwarden.com" ? "https://bitwarden.com" : webVaultUrl;
this.webVaultHostname = Utils.getHostname(vaultUrl);
}
}

View File

@@ -1,164 +1,186 @@
import {
Directive,
Input,
NgZone,
OnInit,
} from '@angular/core';
import { Directive, Input, NgZone, OnInit } from "@angular/core";
import { Router } from '@angular/router';
import { Router } from "@angular/router";
import { take } from 'rxjs/operators';
import { take } from "rxjs/operators";
import { AuthResult } from 'jslib-common/models/domain/authResult';
import { AuthResult } from "jslib-common/models/domain/authResult";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { CaptchaProtectedComponent } from './captchaProtected.component';
import { CaptchaProtectedComponent } from "./captchaProtected.component";
@Directive()
export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
@Input() email: string = '';
@Input() rememberEmail = true;
@Input() email: string = "";
@Input() rememberEmail = true;
masterPassword: string = '';
showPassword: boolean = false;
formPromise: Promise<AuthResult>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
masterPassword: string = "";
showPassword: boolean = false;
formPromise: Promise<AuthResult>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
protected twoFactorRoute = '2fa';
protected successRoute = 'vault';
protected forcePasswordResetRoute = 'update-temp-password';
protected twoFactorRoute = "2fa";
protected successRoute = "vault";
protected forcePasswordResetRoute = "update-temp-password";
constructor(protected authService: AuthService, protected router: Router,
platformUtilsService: PlatformUtilsService, i18nService: I18nService,
protected stateService: StateService, environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService, protected logService: LogService,
protected ngZone: NgZone) {
super(environmentService, i18nService, platformUtilsService);
constructor(
protected authService: AuthService,
protected router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
protected stateService: StateService,
environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService,
protected logService: LogService,
protected ngZone: NgZone
) {
super(environmentService, i18nService, platformUtilsService);
}
async ngOnInit() {
if (this.email == null || this.email === "") {
this.email = await this.stateService.getRememberedEmail();
if (this.email == null) {
this.email = "";
}
}
this.rememberEmail = (await this.stateService.getRememberedEmail()) != null;
if (Utils.isBrowser && !Utils.isNode) {
this.focusInput();
}
}
async submit() {
await this.setupCaptcha();
if (this.email == null || this.email === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("emailRequired")
);
return;
}
if (this.email.indexOf("@") === -1) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidEmail")
);
return;
}
if (this.masterPassword == null || this.masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return;
}
async ngOnInit() {
if (this.email == null || this.email === '') {
this.email = await this.stateService.getRememberedEmail();
if (this.email == null) {
this.email = '';
}
}
this.rememberEmail = await this.stateService.getRememberedEmail() != null;
if (Utils.isBrowser && !Utils.isNode) {
this.focusInput();
}
}
async submit() {
await this.setupCaptcha();
if (this.email == null || this.email === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('emailRequired'));
return;
}
if (this.email.indexOf('@') === -1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidEmail'));
return;
}
if (this.masterPassword == null || this.masterPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return;
}
try {
this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken);
const response = await this.formPromise;
if (this.rememberEmail) {
await this.stateService.setRememberedEmail(this.email);
} else {
await this.stateService.setRememberedEmail(null);
}
if (this.handleCaptchaRequired(response)) {
return;
} else if (response.twoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.router.navigate([this.twoFactorRoute]);
}
} else if (response.forcePasswordReset) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute]);
}
}
} catch (e) {
this.logService.error(e);
}
}
togglePassword() {
this.showPassword = !this.showPassword;
if (this.ngZone.isStable) {
document.getElementById('masterPassword').focus();
try {
this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken);
const response = await this.formPromise;
if (this.rememberEmail) {
await this.stateService.setRememberedEmail(this.email);
} else {
await this.stateService.setRememberedEmail(null);
}
if (this.handleCaptchaRequired(response)) {
return;
} else if (response.twoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => document.getElementById('masterPassword').focus());
this.router.navigate([this.twoFactorRoute]);
}
} else if (response.forcePasswordReset) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute]);
}
}
} catch (e) {
this.logService.error(e);
}
}
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
// Generate necessary sso params
const passwordOptions: any = {
type: 'password',
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state = await this.passwordGenerationService.generatePassword(passwordOptions);
const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, 'sha256');
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
// Save sso params
await this.stateService.setSsoState(state);
await this.stateService.setSsoCodeVerifier(ssoCodeVerifier);
// Build URI
const webUrl = this.environmentService.getWebVaultUrl();
// Launch browser
this.platformUtilsService.launchUri(webUrl + '/#/sso?clientId=' + clientId +
'&redirectUri=' + encodeURIComponent(ssoRedirectUri) +
'&state=' + state + '&codeChallenge=' + codeChallenge);
togglePassword() {
this.showPassword = !this.showPassword;
if (this.ngZone.isStable) {
document.getElementById("masterPassword").focus();
} else {
this.ngZone.onStable
.pipe(take(1))
.subscribe(() => document.getElementById("masterPassword").focus());
}
}
protected focusInput() {
document.getElementById(this.email == null || this.email === '' ? 'email' : 'masterPassword').focus();
}
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
// Generate necessary sso params
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state = await this.passwordGenerationService.generatePassword(passwordOptions);
const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
// Save sso params
await this.stateService.setSsoState(state);
await this.stateService.setSsoCodeVerifier(ssoCodeVerifier);
// Build URI
const webUrl = this.environmentService.getWebVaultUrl();
// Launch browser
this.platformUtilsService.launchUri(
webUrl +
"/#/sso?clientId=" +
clientId +
"&redirectUri=" +
encodeURIComponent(ssoRedirectUri) +
"&state=" +
state +
"&codeChallenge=" +
codeChallenge
);
}
protected focusInput() {
document
.getElementById(this.email == null || this.email === "" ? "email" : "masterPassword")
.focus();
}
}

View File

@@ -1,76 +1,80 @@
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ComponentRef,
ElementRef,
OnDestroy,
Type,
ViewChild,
ViewContainerRef
} from '@angular/core';
AfterViewInit,
ChangeDetectorRef,
Component,
ComponentRef,
ElementRef,
OnDestroy,
Type,
ViewChild,
ViewContainerRef,
} from "@angular/core";
import {
ConfigurableFocusTrap,
ConfigurableFocusTrapFactory,
} from '@angular/cdk/a11y';
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from "@angular/cdk/a11y";
import { ModalService } from '../../services/modal.service';
import { ModalService } from "../../services/modal.service";
import { ModalRef } from './modal.ref';
import { ModalRef } from "./modal.ref";
@Component({
selector: 'app-modal',
template: '<ng-template #modalContent></ng-template>',
selector: "app-modal",
template: "<ng-template #modalContent></ng-template>",
})
export class DynamicModalComponent implements AfterViewInit, OnDestroy {
componentRef: ComponentRef<any>;
componentRef: ComponentRef<any>;
@ViewChild('modalContent', { read: ViewContainerRef, static: true }) modalContentRef: ViewContainerRef;
@ViewChild("modalContent", { read: ViewContainerRef, static: true })
modalContentRef: ViewContainerRef;
childComponentType: Type<any>;
setComponentParameters: (component: any) => void;
childComponentType: Type<any>;
setComponentParameters: (component: any) => void;
private focusTrap: ConfigurableFocusTrap;
private focusTrap: ConfigurableFocusTrap;
constructor(private modalService: ModalService, private cd: ChangeDetectorRef,
private el: ElementRef<HTMLElement>, private focusTrapFactory: ConfigurableFocusTrapFactory,
public modalRef: ModalRef) { }
constructor(
private modalService: ModalService,
private cd: ChangeDetectorRef,
private el: ElementRef<HTMLElement>,
private focusTrapFactory: ConfigurableFocusTrapFactory,
public modalRef: ModalRef
) {}
ngAfterViewInit() {
this.loadChildComponent(this.childComponentType);
if (this.setComponentParameters != null) {
this.setComponentParameters(this.componentRef.instance);
}
this.cd.detectChanges();
this.modalRef.created(this.el.nativeElement);
this.focusTrap = this.focusTrapFactory.create(this.el.nativeElement.querySelector('.modal-dialog'));
if (this.el.nativeElement.querySelector('[appAutoFocus]') == null) {
this.focusTrap.focusFirstTabbableElementWhenReady();
}
ngAfterViewInit() {
this.loadChildComponent(this.childComponentType);
if (this.setComponentParameters != null) {
this.setComponentParameters(this.componentRef.instance);
}
this.cd.detectChanges();
loadChildComponent(componentType: Type<any>) {
const componentFactory = this.modalService.resolveComponentFactory(componentType);
this.modalContentRef.clear();
this.componentRef = this.modalContentRef.createComponent(componentFactory);
this.modalRef.created(this.el.nativeElement);
this.focusTrap = this.focusTrapFactory.create(
this.el.nativeElement.querySelector(".modal-dialog")
);
if (this.el.nativeElement.querySelector("[appAutoFocus]") == null) {
this.focusTrap.focusFirstTabbableElementWhenReady();
}
}
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
}
this.focusTrap.destroy();
}
loadChildComponent(componentType: Type<any>) {
const componentFactory = this.modalService.resolveComponentFactory(componentType);
close() {
this.modalRef.close();
}
this.modalContentRef.clear();
this.componentRef = this.modalContentRef.createComponent(componentFactory);
}
getFocus() {
const autoFocusEl = this.el.nativeElement.querySelector('[appAutoFocus]') as HTMLElement;
autoFocusEl?.focus();
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
}
this.focusTrap.destroy();
}
close() {
this.modalRef.close();
}
getFocus() {
const autoFocusEl = this.el.nativeElement.querySelector("[appAutoFocus]") as HTMLElement;
autoFocusEl?.focus();
}
}

View File

@@ -1,15 +1,10 @@
import {
InjectFlags,
InjectionToken,
Injector,
Type
} from '@angular/core';
import { InjectFlags, InjectionToken, Injector, Type } from "@angular/core";
export class ModalInjector implements Injector {
constructor(private _parentInjector: Injector, private _additionalTokens: WeakMap<any, any>) {}
constructor(private _parentInjector: Injector, private _additionalTokens: WeakMap<any, any>) {}
get<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
get(token: any, notFoundValue?: any, flags?: any) {
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
}
get<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
get(token: any, notFoundValue?: any, flags?: any) {
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
}
}

View File

@@ -1,51 +1,50 @@
import { Observable, Subject } from 'rxjs';
import { first } from 'rxjs/operators';
import { Observable, Subject } from "rxjs";
import { first } from "rxjs/operators";
export class ModalRef {
onCreated: Observable<HTMLElement>; // Modal added to the DOM.
onClose: Observable<any>; // Initiated close.
onClosed: Observable<any>; // Modal was closed (Remove element from DOM)
onShow: Observable<void>; // Start showing modal
onShown: Observable<void>; // Modal is fully visible
onCreated: Observable<HTMLElement>; // Modal added to the DOM.
onClose: Observable<any>; // Initiated close.
onClosed: Observable<any>; // Modal was closed (Remove element from DOM)
onShow: Observable<void>; // Start showing modal
onShown: Observable<void>; // Modal is fully visible
private readonly _onCreated = new Subject<HTMLElement>();
private readonly _onClose = new Subject<any>();
private readonly _onClosed = new Subject<any>();
private readonly _onShow = new Subject<void>();
private readonly _onShown = new Subject<void>();
private lastResult: any;
private readonly _onCreated = new Subject<HTMLElement>();
private readonly _onClose = new Subject<any>();
private readonly _onClosed = new Subject<any>();
private readonly _onShow = new Subject<void>();
private readonly _onShown = new Subject<void>();
private lastResult: any;
constructor() {
this.onCreated = this._onCreated.asObservable();
this.onClose = this._onClose.asObservable();
this.onClosed = this._onClosed.asObservable();
this.onShow = this._onShow.asObservable();
this.onShown = this._onShow.asObservable();
}
constructor() {
this.onCreated = this._onCreated.asObservable();
this.onClose = this._onClose.asObservable();
this.onClosed = this._onClosed.asObservable();
this.onShow = this._onShow.asObservable();
this.onShown = this._onShow.asObservable();
}
show() {
this._onShow.next();
}
show() {
this._onShow.next();
}
shown() {
this._onShown.next();
}
shown() {
this._onShown.next();
}
close(result?: any) {
this.lastResult = result;
this._onClose.next(result);
}
close(result?: any) {
this.lastResult = result;
this._onClose.next(result);
}
closed() {
this._onClosed.next(this.lastResult);
}
closed() {
this._onClosed.next(this.lastResult);
}
created(el: HTMLElement) {
this._onCreated.next(el);
}
created(el: HTMLElement) {
this._onCreated.next(el);
}
onClosedPromise(): Promise<any> {
return this.onClosed.pipe(first()).toPromise();
}
onClosedPromise(): Promise<any> {
return this.onClosed.pipe(first()).toPromise();
}
}

View File

@@ -1,32 +1,38 @@
import { Directive, OnInit } from '@angular/core';
import { Directive, OnInit } 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 { GeneratedPasswordHistory } from 'jslib-common/models/domain/generatedPasswordHistory';
import { GeneratedPasswordHistory } from "jslib-common/models/domain/generatedPasswordHistory";
@Directive()
export class PasswordGeneratorHistoryComponent implements OnInit {
history: GeneratedPasswordHistory[] = [];
history: GeneratedPasswordHistory[] = [];
constructor(protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
private win: Window) { }
constructor(
protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
private win: Window
) {}
async ngOnInit() {
this.history = await this.passwordGenerationService.getHistory();
}
async ngOnInit() {
this.history = await this.passwordGenerationService.getHistory();
}
clear() {
this.history = [];
this.passwordGenerationService.clear();
}
clear() {
this.history = [];
this.passwordGenerationService.clear();
}
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t('password')));
}
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t("password"))
);
}
}

View File

@@ -1,101 +1,106 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } 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 { PasswordGeneratorPolicyOptions } from 'jslib-common/models/domain/passwordGeneratorPolicyOptions';
import { PasswordGeneratorPolicyOptions } from "jslib-common/models/domain/passwordGeneratorPolicyOptions";
@Directive()
export class PasswordGeneratorComponent implements OnInit {
@Input() showSelect: boolean = false;
@Output() onSelected = new EventEmitter<string>();
@Input() showSelect: boolean = false;
@Output() onSelected = new EventEmitter<string>();
passTypeOptions: any[];
options: any = {};
password: string = '-';
showOptions = false;
avoidAmbiguous = false;
enforcedPolicyOptions: PasswordGeneratorPolicyOptions;
passTypeOptions: any[];
options: any = {};
password: string = "-";
showOptions = false;
avoidAmbiguous = false;
enforcedPolicyOptions: PasswordGeneratorPolicyOptions;
constructor(protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
private win: Window) {
this.passTypeOptions = [
{ name: i18nService.t('password'), value: 'password' },
{ name: i18nService.t('passphrase'), value: 'passphrase' },
];
}
constructor(
protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
private win: Window
) {
this.passTypeOptions = [
{ name: i18nService.t("password"), value: "password" },
{ name: i18nService.t("passphrase"), value: "passphrase" },
];
}
async ngOnInit() {
const optionsResponse = await this.passwordGenerationService.getOptions();
this.options = optionsResponse[0];
this.enforcedPolicyOptions = optionsResponse[1];
this.avoidAmbiguous = !this.options.ambiguous;
this.options.type = this.options.type === 'passphrase' ? 'passphrase' : 'password';
this.password = await this.passwordGenerationService.generatePassword(this.options);
await this.passwordGenerationService.addHistory(this.password);
async ngOnInit() {
const optionsResponse = await this.passwordGenerationService.getOptions();
this.options = optionsResponse[0];
this.enforcedPolicyOptions = optionsResponse[1];
this.avoidAmbiguous = !this.options.ambiguous;
this.options.type = this.options.type === "passphrase" ? "passphrase" : "password";
this.password = await this.passwordGenerationService.generatePassword(this.options);
await this.passwordGenerationService.addHistory(this.password);
}
async sliderChanged() {
this.saveOptions(false);
await this.passwordGenerationService.addHistory(this.password);
}
async sliderInput() {
this.normalizeOptions();
this.password = await this.passwordGenerationService.generatePassword(this.options);
}
async saveOptions(regenerate: boolean = true) {
this.normalizeOptions();
await this.passwordGenerationService.saveOptions(this.options);
if (regenerate) {
await this.regenerate();
}
}
async sliderChanged() {
this.saveOptions(false);
await this.passwordGenerationService.addHistory(this.password);
}
async regenerate() {
this.password = await this.passwordGenerationService.generatePassword(this.options);
await this.passwordGenerationService.addHistory(this.password);
}
async sliderInput() {
this.normalizeOptions();
this.password = await this.passwordGenerationService.generatePassword(this.options);
}
copy() {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(this.password, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t("password"))
);
}
async saveOptions(regenerate: boolean = true) {
this.normalizeOptions();
await this.passwordGenerationService.saveOptions(this.options);
select() {
this.onSelected.emit(this.password);
}
if (regenerate) {
await this.regenerate();
toggleOptions() {
this.showOptions = !this.showOptions;
}
private normalizeOptions() {
// Application level normalize options depedent on class variables
this.options.ambiguous = !this.avoidAmbiguous;
if (
!this.options.uppercase &&
!this.options.lowercase &&
!this.options.number &&
!this.options.special
) {
this.options.lowercase = true;
if (this.win != null) {
const lowercase = this.win.document.querySelector("#lowercase") as HTMLInputElement;
if (lowercase) {
lowercase.checked = true;
}
}
}
async regenerate() {
this.password = await this.passwordGenerationService.generatePassword(this.options);
await this.passwordGenerationService.addHistory(this.password);
}
copy() {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(this.password, copyOptions);
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t('password')));
}
select() {
this.onSelected.emit(this.password);
}
toggleOptions() {
this.showOptions = !this.showOptions;
}
private normalizeOptions() {
// Application level normalize options depedent on class variables
this.options.ambiguous = !this.avoidAmbiguous;
if (!this.options.uppercase && !this.options.lowercase && !this.options.number && !this.options.special) {
this.options.lowercase = true;
if (this.win != null) {
const lowercase = this.win.document.querySelector('#lowercase') as HTMLInputElement;
if (lowercase) {
lowercase.checked = true;
}
}
}
this.passwordGenerationService.normalizeOptions(this.options, this.enforcedPolicyOptions);
}
this.passwordGenerationService.normalizeOptions(this.options, this.enforcedPolicyOptions);
}
}

View File

@@ -1,33 +1,40 @@
import { Directive, OnInit } from '@angular/core';
import { Directive, OnInit } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PasswordHistoryView } from 'jslib-common/models/view/passwordHistoryView';
import { PasswordHistoryView } from "jslib-common/models/view/passwordHistoryView";
@Directive()
export class PasswordHistoryComponent implements OnInit {
cipherId: string;
history: PasswordHistoryView[] = [];
cipherId: string;
history: PasswordHistoryView[] = [];
constructor(protected cipherService: CipherService, protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService, private win: Window) { }
constructor(
protected cipherService: CipherService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
private win: Window
) {}
async ngOnInit() {
await this.init();
}
async ngOnInit() {
await this.init();
}
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t('password')));
}
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t("password"))
);
}
protected async init() {
const cipher = await this.cipherService.get(this.cipherId);
const decCipher = await cipher.decrypt();
this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
}
protected async init() {
const cipher = await this.cipherService.get(this.cipherId);
const decCipher = await cipher.decrypt();
this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
}
}

View File

@@ -1,30 +1,36 @@
import { Directive } from '@angular/core';
import { Directive } from "@angular/core";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ModalRef } from './modal/modal.ref';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ModalRef } from "./modal/modal.ref";
@Directive()
export class PasswordRepromptComponent {
showPassword = false;
masterPassword = "";
showPassword = false;
masterPassword = '';
constructor(
private modalRef: ModalRef,
private cryptoService: CryptoService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {}
constructor(private modalRef: ModalRef, private cryptoService: CryptoService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService) {}
togglePassword() {
this.showPassword = !this.showPassword;
}
togglePassword() {
this.showPassword = !this.showPassword;
async submit() {
if (!(await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, null))) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidMasterPassword")
);
return;
}
async submit() {
if (!await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, null)) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidMasterPassword'));
return;
}
this.modalRef.close(true);
}
this.modalRef.close(true);
}
}

View File

@@ -1,49 +1,61 @@
import { Directive, OnInit } from '@angular/core';
import { Directive, OnInit } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.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 { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.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 { StateService } from "jslib-common/abstractions/state.service";
@Directive()
export class PremiumComponent implements OnInit {
isPremium: boolean = false;
price: number = 10;
refreshPromise: Promise<any>;
isPremium: boolean = false;
price: number = 10;
refreshPromise: Promise<any>;
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService, private logService: LogService,
protected stateService: StateService) { }
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService,
private logService: LogService,
protected stateService: StateService
) {}
async ngOnInit() {
this.isPremium = await this.stateService.getCanAccessPremium();
async ngOnInit() {
this.isPremium = await this.stateService.getCanAccessPremium();
}
async refresh() {
try {
this.refreshPromise = this.apiService.refreshIdentityToken();
await this.refreshPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("refreshComplete"));
this.isPremium = await this.stateService.getCanAccessPremium();
} catch (e) {
this.logService.error(e);
}
}
async refresh() {
try {
this.refreshPromise = this.apiService.refreshIdentityToken();
await this.refreshPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('refreshComplete'));
this.isPremium = await this.stateService.getCanAccessPremium();
} catch (e) {
this.logService.error(e);
}
async purchase() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumPurchaseAlert"),
this.i18nService.t("premiumPurchase"),
this.i18nService.t("yes"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase");
}
}
async purchase() {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('premiumPurchaseAlert'),
this.i18nService.t('premiumPurchase'), this.i18nService.t('yes'), this.i18nService.t('cancel'));
if (confirmed) {
this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=purchase');
}
}
async manage() {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('premiumManageAlert'),
this.i18nService.t('premiumManage'), this.i18nService.t('yes'), this.i18nService.t('cancel'));
if (confirmed) {
this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=manage');
}
async manage() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumManageAlert"),
this.i18nService.t("premiumManage"),
this.i18nService.t("yes"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=manage");
}
}
}

View File

@@ -1,195 +1,251 @@
import { Directive, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Directive, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { KeysRequest } from 'jslib-common/models/request/keysRequest';
import { ReferenceEventRequest } from 'jslib-common/models/request/referenceEventRequest';
import { RegisterRequest } from 'jslib-common/models/request/registerRequest';
import { KeysRequest } from "jslib-common/models/request/keysRequest";
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
import { RegisterRequest } from "jslib-common/models/request/registerRequest";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { KdfType } from 'jslib-common/enums/kdfType';
import { KdfType } from "jslib-common/enums/kdfType";
import { CaptchaProtectedComponent } from './captchaProtected.component';
import { CaptchaProtectedComponent } from "./captchaProtected.component";
@Directive()
export class RegisterComponent extends CaptchaProtectedComponent implements OnInit {
name: string = '';
email: string = '';
masterPassword: string = '';
confirmMasterPassword: string = '';
hint: string = '';
showPassword: boolean = false;
formPromise: Promise<any>;
masterPasswordScore: number;
referenceData: ReferenceEventRequest;
showTerms = true;
acceptPolicies: boolean = false;
name: string = "";
email: string = "";
masterPassword: string = "";
confirmMasterPassword: string = "";
hint: string = "";
showPassword: boolean = false;
formPromise: Promise<any>;
masterPasswordScore: number;
referenceData: ReferenceEventRequest;
showTerms = true;
acceptPolicies: boolean = false;
protected successRoute = 'login';
private masterPasswordStrengthTimeout: any;
protected successRoute = "login";
private masterPasswordStrengthTimeout: any;
constructor(protected authService: AuthService, protected router: Router,
i18nService: I18nService, protected cryptoService: CryptoService,
protected apiService: ApiService, protected stateService: StateService,
platformUtilsService: PlatformUtilsService,
protected passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService,
protected logService: LogService) {
super(environmentService, i18nService, platformUtilsService);
this.showTerms = !platformUtilsService.isSelfHost();
constructor(
protected authService: AuthService,
protected router: Router,
i18nService: I18nService,
protected cryptoService: CryptoService,
protected apiService: ApiService,
protected stateService: StateService,
platformUtilsService: PlatformUtilsService,
protected passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
protected logService: LogService
) {
super(environmentService, i18nService, platformUtilsService);
this.showTerms = !platformUtilsService.isSelfHost();
}
async ngOnInit() {
this.setupCaptcha();
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return "success";
case 3:
return "primary";
case 2:
return "warning";
default:
return "danger";
}
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t("strong");
case 3:
return this.i18nService.t("good");
case 2:
return this.i18nService.t("weak");
default:
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
}
}
async submit() {
if (!this.acceptPolicies && this.showTerms) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("acceptPoliciesError")
);
return;
}
async ngOnInit() {
this.setupCaptcha();
if (this.email == null || this.email === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("emailRequired")
);
return;
}
if (this.email.indexOf("@") === -1) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidEmail")
);
return;
}
if (this.masterPassword == null || this.masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassLength")
);
return;
}
if (this.masterPassword !== this.confirmMasterPassword) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassDoesntMatch")
);
return;
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!result) {
return;
}
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return 'success';
case 3:
return 'primary';
case 2:
return 'warning';
default:
return 'danger';
}
if (this.hint === this.masterPassword) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("hintEqualsPassword")
);
return;
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t('strong');
case 3:
return this.i18nService.t('good');
case 2:
return this.i18nService.t('weak');
default:
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
}
this.name = this.name === "" ? null : this.name;
this.email = this.email.trim().toLowerCase();
const kdf = KdfType.PBKDF2_SHA256;
const useLowerKdf = this.platformUtilsService.isIE();
const kdfIterations = useLowerKdf ? 10000 : 100000;
const key = await this.cryptoService.makeKey(
this.masterPassword,
this.email,
kdf,
kdfIterations
);
const encKey = await this.cryptoService.makeEncKey(key);
const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new RegisterRequest(
this.email,
this.name,
hashedPassword,
this.hint,
encKey[1].encryptedString,
kdf,
kdfIterations,
this.referenceData,
this.captchaToken
);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.getOrganizationInvitation();
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
request.token = orgInvite.token;
request.organizationUserId = orgInvite.organizationUserId;
}
async submit() {
if (!this.acceptPolicies && this.showTerms) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('acceptPoliciesError'));
return;
}
if (this.email == null || this.email === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('emailRequired'));
return;
}
if (this.email.indexOf('@') === -1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidEmail'));
return;
}
if (this.masterPassword == null || this.masterPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassLength'));
return;
}
if (this.masterPassword !== this.confirmMasterPassword) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassDoesntMatch'));
return;
}
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
'warning');
if (!result) {
return;
}
}
if (this.hint === this.masterPassword) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.i18nService.t('hintEqualsPassword'));
return;
}
this.name = this.name === '' ? null : this.name;
this.email = this.email.trim().toLowerCase();
const kdf = KdfType.PBKDF2_SHA256;
const useLowerKdf = this.platformUtilsService.isIE();
const kdfIterations = useLowerKdf ? 10000 : 100000;
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfIterations);
const encKey = await this.cryptoService.makeEncKey(key);
const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new RegisterRequest(this.email, this.name, hashedPassword,
this.hint, encKey[1].encryptedString, kdf, kdfIterations, this.referenceData, this.captchaToken);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.getOrganizationInvitation();
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
request.token = orgInvite.token;
request.organizationUserId = orgInvite.organizationUserId;
}
try {
this.formPromise = this.apiService.postRegister(request);
try {
await this.formPromise;
} catch (e) {
if (this.handleCaptchaRequired(e)) {
return;
} else {
throw e;
}
}
this.platformUtilsService.showToast('success', null, this.i18nService.t('newAccountCreated'));
this.router.navigate([this.successRoute], { queryParams: { email: this.email } });
} catch (e) {
this.logService.error(e);
try {
this.formPromise = this.apiService.postRegister(request);
try {
await this.formPromise;
} catch (e) {
if (this.handleCaptchaRequired(e)) {
return;
} else {
throw e;
}
}
this.platformUtilsService.showToast("success", null, this.i18nService.t("newAccountCreated"));
this.router.navigate([this.successRoute], { queryParams: { email: this.email } });
} catch (e) {
this.logService.error(e);
}
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus();
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf('@');
if (atPosition > -1) {
userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/));
}
if (this.name != null && this.name !== '') {
userInput = userInput.concat(this.name.trim().toLowerCase().split(' '));
}
return userInput;
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
this.email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
if (this.name != null && this.name !== "") {
userInput = userInput.concat(this.name.trim().toLowerCase().split(" "));
}
return userInput;
}
}

View File

@@ -1,74 +1,83 @@
import {
Directive,
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { Directive, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { Organization } from 'jslib-common/models/domain/organization';
import { Organization } from "jslib-common/models/domain/organization";
@Directive()
export class RemovePasswordComponent implements OnInit {
actionPromise: Promise<any>;
continuing: boolean = false;
leaving: boolean = false;
actionPromise: Promise<any>;
continuing: boolean = false;
leaving: boolean = false;
loading: boolean = true;
organization: Organization;
email: string;
loading: boolean = true;
organization: Organization;
email: string;
constructor(
private router: Router,
private stateService: StateService,
private apiService: ApiService,
private syncService: SyncService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private keyConnectorService: KeyConnectorService
) {}
constructor(private router: Router, private stateService: StateService,
private apiService: ApiService, private syncService: SyncService,
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private keyConnectorService: KeyConnectorService) { }
async ngOnInit() {
this.organization = await this.keyConnectorService.getManagingOrganization();
this.email = await this.stateService.getEmail();
await this.syncService.fullSync(false);
this.loading = false;
}
async ngOnInit() {
this.organization = await this.keyConnectorService.getManagingOrganization();
this.email = await this.stateService.getEmail();
await this.syncService.fullSync(false);
this.loading = false;
async convert() {
this.continuing = true;
this.actionPromise = this.keyConnectorService.migrateUser();
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removedMasterPassword")
);
await this.keyConnectorService.removeConvertAccountRequired();
this.router.navigate([""]);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
}
}
async leave() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("leaveOrganizationConfirmation"),
this.organization.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async convert() {
this.continuing = true;
this.actionPromise = this.keyConnectorService.migrateUser();
try {
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedMasterPassword'));
await this.keyConnectorService.removeConvertAccountRequired();
this.router.navigate(['']);
} catch (e) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e.message);
}
}
async leave() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('leaveOrganizationConfirmation'), this.organization.name,
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.leaving = true;
this.actionPromise = this.apiService.postLeaveOrganization(this.organization.id).then(() => {
return this.syncService.fullSync(true);
});
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('leftOrganization'));
await this.keyConnectorService.removeConvertAccountRequired();
this.router.navigate(['']);
} catch (e) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e);
}
try {
this.leaving = true;
this.actionPromise = this.apiService.postLeaveOrganization(this.organization.id).then(() => {
return this.syncService.fullSync(true);
});
await this.actionPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
await this.keyConnectorService.removeConvertAccountRequired();
this.router.navigate([""]);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e);
}
}
}

View File

@@ -1,274 +1,296 @@
import { DatePipe } from '@angular/common';
import {
Directive,
EventEmitter,
Input,
OnInit,
Output
} from '@angular/core';
import { DatePipe } from "@angular/common";
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { PolicyType } from 'jslib-common/enums/policyType';
import { SendType } from 'jslib-common/enums/sendType';
import { PolicyType } from "jslib-common/enums/policyType";
import { SendType } from "jslib-common/enums/sendType";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SendService } from "jslib-common/abstractions/send.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SendFileView } from 'jslib-common/models/view/sendFileView';
import { SendTextView } from 'jslib-common/models/view/sendTextView';
import { SendView } from 'jslib-common/models/view/sendView';
import { SendFileView } from "jslib-common/models/view/sendFileView";
import { SendTextView } from "jslib-common/models/view/sendTextView";
import { SendView } from "jslib-common/models/view/sendView";
import { EncArrayBuffer } from 'jslib-common/models/domain/encArrayBuffer';
import { Send } from 'jslib-common/models/domain/send';
import { EncArrayBuffer } from "jslib-common/models/domain/encArrayBuffer";
import { Send } from "jslib-common/models/domain/send";
@Directive()
export class AddEditComponent implements OnInit {
@Input() sendId: string;
@Input() type: SendType;
@Input() sendId: string;
@Input() type: SendType;
@Output() onSavedSend = new EventEmitter<SendView>();
@Output() onDeletedSend = new EventEmitter<SendView>();
@Output() onCancelled = new EventEmitter<SendView>();
@Output() onSavedSend = new EventEmitter<SendView>();
@Output() onDeletedSend = new EventEmitter<SendView>();
@Output() onCancelled = new EventEmitter<SendView>();
copyLink = false;
disableSend = false;
disableHideEmail = false;
send: SendView;
deletionDate: string;
expirationDate: string;
hasPassword: boolean;
password: string;
showPassword = false;
formPromise: Promise<any>;
deletePromise: Promise<any>;
sendType = SendType;
typeOptions: any[];
canAccessPremium = true;
emailVerified = true;
alertShown = false;
showOptions = false;
copyLink = false;
disableSend = false;
disableHideEmail = false;
send: SendView;
deletionDate: string;
expirationDate: string;
hasPassword: boolean;
password: string;
showPassword = false;
formPromise: Promise<any>;
deletePromise: Promise<any>;
sendType = SendType;
typeOptions: any[];
canAccessPremium = true;
emailVerified = true;
alertShown = false;
showOptions = false;
private sendLinkBaseUrl: string;
private sendLinkBaseUrl: string;
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService, protected datePipe: DatePipe,
protected sendService: SendService, protected messagingService: MessagingService,
protected policyService: PolicyService, private logService: LogService,
protected stateService: StateService) {
this.typeOptions = [
{ name: i18nService.t('sendTypeFile'), value: SendType.File },
{ name: i18nService.t('sendTypeText'), value: SendType.Text },
];
this.sendLinkBaseUrl = this.environmentService.getSendUrl();
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService,
protected datePipe: DatePipe,
protected sendService: SendService,
protected messagingService: MessagingService,
protected policyService: PolicyService,
private logService: LogService,
protected stateService: StateService
) {
this.typeOptions = [
{ name: i18nService.t("sendTypeFile"), value: SendType.File },
{ name: i18nService.t("sendTypeText"), value: SendType.Text },
];
this.sendLinkBaseUrl = this.environmentService.getSendUrl();
}
get link(): string {
if (this.send.id != null && this.send.accessId != null) {
return this.sendLinkBaseUrl + this.send.accessId + "/" + this.send.urlB64Key;
}
return null;
}
get isSafari() {
return this.platformUtilsService.isSafari();
}
get isDateTimeLocalSupported(): boolean {
return !(this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari());
}
async ngOnInit() {
await this.load();
}
get editMode(): boolean {
return this.sendId != null;
}
get title(): string {
return this.i18nService.t(this.editMode ? "editSend" : "createSend");
}
setDates(event: { deletionDate: string; expirationDate: string }) {
this.deletionDate = event.deletionDate;
this.expirationDate = event.expirationDate;
}
async load() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
this.disableHideEmail = await this.policyService.policyAppliesToUser(
PolicyType.SendOptions,
(p) => p.data.disableHideEmail
);
this.canAccessPremium = await this.stateService.getCanAccessPremium();
this.emailVerified = await this.stateService.getEmailVerified();
if (!this.canAccessPremium || !this.emailVerified) {
this.type = SendType.Text;
}
get link(): string {
if (this.send.id != null && this.send.accessId != null) {
return this.sendLinkBaseUrl + this.send.accessId + '/' + this.send.urlB64Key;
}
return null;
if (this.send == null) {
if (this.editMode) {
const send = await this.loadSend();
this.send = await send.decrypt();
} else {
this.send = new SendView();
this.send.type = this.type == null ? SendType.File : this.type;
this.send.file = new SendFileView();
this.send.text = new SendTextView();
this.send.deletionDate = new Date();
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
}
}
get isSafari() {
return this.platformUtilsService.isSafari();
this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
}
async submit(): Promise<boolean> {
if (this.disableSend) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("sendDisabledWarning")
);
return false;
}
get isDateTimeLocalSupported(): boolean {
return !(this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari());
if (this.send.name == null || this.send.name === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nameRequired")
);
return false;
}
async ngOnInit() {
await this.load();
}
get editMode(): boolean {
return this.sendId != null;
}
get title(): string {
return this.i18nService.t(
this.editMode ?
'editSend' :
'createSend'
let file: File = null;
if (this.send.type === SendType.File && !this.editMode) {
const fileEl = document.getElementById("file") as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectFile")
);
return;
}
file = files[0];
if (files[0].size > 524288000) {
// 500 MB
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("maxFileSize")
);
return;
}
}
setDates(event: {deletionDate: string, expirationDate: string}) {
this.deletionDate = event.deletionDate;
this.expirationDate = event.expirationDate;
if (this.password != null && this.password.trim() === "") {
this.password = null;
}
async load() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
this.disableHideEmail = await this.policyService.policyAppliesToUser(PolicyType.SendOptions,
p => p.data.disableHideEmail);
this.canAccessPremium = await this.stateService.getCanAccessPremium();
this.emailVerified = await this.stateService.getEmailVerified();
if (!this.canAccessPremium || !this.emailVerified) {
this.type = SendType.Text;
this.formPromise = this.encryptSend(file).then(async (encSend) => {
const uploadPromise = this.sendService.saveWithServer(encSend);
await uploadPromise;
if (this.send.id == null) {
this.send.id = encSend[0].id;
}
if (this.send.accessId == null) {
this.send.accessId = encSend[0].accessId;
}
this.onSavedSend.emit(this.send);
if (this.copyLink && this.link != null) {
const copySuccess = await this.copyLinkToClipboard(this.link);
if (copySuccess ?? true) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedSend" : "createdSend")
);
} else {
await this.platformUtilsService.showDialog(
this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
null,
this.i18nService.t("ok"),
null,
"success",
null
);
await this.copyLinkToClipboard(this.link);
}
}
});
try {
await this.formPromise;
return true;
} catch (e) {
this.logService.error(e);
}
return false;
}
if (this.send == null) {
if (this.editMode) {
const send = await this.loadSend();
this.send = await send.decrypt();
} else {
this.send = new SendView();
this.send.type = this.type == null ? SendType.File : this.type;
this.send.file = new SendFileView();
this.send.text = new SendTextView();
this.send.deletionDate = new Date();
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
}
}
async copyLinkToClipboard(link: string): Promise<void | boolean> {
return Promise.resolve(this.platformUtilsService.copyToClipboard(link));
}
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
async delete(): Promise<boolean> {
if (this.deletePromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteSendConfirmation"),
this.i18nService.t("deleteSend"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async submit(): Promise<boolean> {
if (this.disableSend) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('sendDisabledWarning'));
return false;
}
if (this.send.name == null || this.send.name === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nameRequired'));
return false;
}
let file: File = null;
if (this.send.type === SendType.File && !this.editMode) {
const fileEl = document.getElementById('file') as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectFile'));
return;
}
file = files[0];
if (files[0].size > 524288000) { // 500 MB
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('maxFileSize'));
return;
}
}
if (this.password != null && this.password.trim() === '') {
this.password = null;
}
this.formPromise = this.encryptSend(file)
.then(async encSend => {
const uploadPromise = this.sendService.saveWithServer(encSend);
await uploadPromise;
if (this.send.id == null) {
this.send.id = encSend[0].id;
}
if (this.send.accessId == null) {
this.send.accessId = encSend[0].accessId;
}
this.onSavedSend.emit(this.send);
if (this.copyLink && this.link != null) {
const copySuccess = await this.copyLinkToClipboard(this.link);
if (copySuccess ?? true) {
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.editMode ? 'editedSend' : 'createdSend'));
} else {
await this.platformUtilsService.showDialog(
this.i18nService.t(this.editMode ? 'editedSend' : 'createdSend'), null,
this.i18nService.t('ok'), null, 'success', null);
await this.copyLinkToClipboard(this.link);
}
}
});
try {
await this.formPromise;
return true;
} catch (e) {
this.logService.error(e);
}
return false;
try {
this.deletePromise = this.sendService.deleteWithServer(this.send.id);
await this.deletePromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend"));
await this.load();
this.onDeletedSend.emit(this.send);
return true;
} catch (e) {
this.logService.error(e);
}
async copyLinkToClipboard(link: string): Promise<void | boolean> {
return Promise.resolve(this.platformUtilsService.copyToClipboard(link));
return false;
}
typeChanged() {
if (this.send.type === SendType.File && !this.alertShown) {
if (!this.canAccessPremium) {
this.alertShown = true;
this.messagingService.send("premiumRequired");
} else if (!this.emailVerified) {
this.alertShown = true;
this.messagingService.send("emailVerificationRequired");
}
}
}
toggleOptions() {
this.showOptions = !this.showOptions;
}
protected async loadSend(): Promise<Send> {
return this.sendService.get(this.sendId);
}
protected async encryptSend(file: File): Promise<[Send, EncArrayBuffer]> {
const sendData = await this.sendService.encrypt(this.send, file, this.password, null);
// Parse dates
try {
sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
} catch {
sendData[0].deletionDate = null;
}
try {
sendData[0].expirationDate =
this.expirationDate == null ? null : new Date(this.expirationDate);
} catch {
sendData[0].expirationDate = null;
}
async delete(): Promise<boolean> {
if (this.deletePromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteSendConfirmation'),
this.i18nService.t('deleteSend'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
return sendData;
}
try {
this.deletePromise = this.sendService.deleteWithServer(this.send.id);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
await this.load();
this.onDeletedSend.emit(this.send);
return true;
} catch (e) {
this.logService.error(e);
}
return false;
}
typeChanged() {
if (this.send.type === SendType.File && !this.alertShown) {
if (!this.canAccessPremium) {
this.alertShown = true;
this.messagingService.send('premiumRequired');
} else if (!this.emailVerified) {
this.alertShown = true;
this.messagingService.send('emailVerificationRequired');
}
}
}
toggleOptions() {
this.showOptions = !this.showOptions;
}
protected async loadSend(): Promise<Send> {
return this.sendService.get(this.sendId);
}
protected async encryptSend(file: File): Promise<[Send, EncArrayBuffer]> {
const sendData = await this.sendService.encrypt(this.send, file, this.password, null);
// Parse dates
try {
sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
} catch {
sendData[0].deletionDate = null;
}
try {
sendData[0].expirationDate = this.expirationDate == null ? null : new Date(this.expirationDate);
} catch {
sendData[0].expirationDate = null;
}
return sendData;
}
protected togglePasswordVisible() {
this.showPassword = !this.showPassword;
document.getElementById('password').focus();
}
protected togglePasswordVisible() {
this.showPassword = !this.showPassword;
document.getElementById("password").focus();
}
}

View File

@@ -1,341 +1,354 @@
import { DatePipe } from '@angular/common';
import {
Directive,
EventEmitter,
Input,
OnInit,
Output
} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { DatePipe } from "@angular/common";
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
// Different BrowserPath = different controls.
enum BrowserPath {
// Native datetime-locale.
// We are happy.
Default = 'default',
// Native datetime-locale.
// We are happy.
Default = "default",
// Native date and time inputs, but no datetime-locale.
// We use individual date and time inputs and create a datetime programatically on submit.
Firefox = 'firefox',
// Native date and time inputs, but no datetime-locale.
// We use individual date and time inputs and create a datetime programatically on submit.
Firefox = "firefox",
// No native date, time, or datetime-locale inputs.
// We use a polyfill for dates and a dropdown for times.
Safari = 'safari',
// No native date, time, or datetime-locale inputs.
// We use a polyfill for dates and a dropdown for times.
Safari = "safari",
}
enum DateField {
DeletionDate = 'deletion',
ExpriationDate = 'expiration',
DeletionDate = "deletion",
ExpriationDate = "expiration",
}
// Value = hours
enum DatePreset {
OneHour = 1,
OneDay = 24,
TwoDays = 48,
ThreeDays = 72,
SevenDays = 168,
ThirtyDays = 720,
Custom = 0,
Never = null,
OneHour = 1,
OneDay = 24,
TwoDays = 48,
ThreeDays = 72,
SevenDays = 168,
ThirtyDays = 720,
Custom = 0,
Never = null,
}
// TimeOption is used for the dropdown implementation of custom times
// twelveHour = displayed time; twentyFourHour = time used in logic
interface TimeOption {
twelveHour: string;
twentyFourHour: string;
twelveHour: string;
twentyFourHour: string;
}
@Directive()
export class EffluxDatesComponent implements OnInit {
@Input() readonly initialDeletionDate: Date;
@Input() readonly initialExpirationDate: Date;
@Input() readonly editMode: boolean;
@Input() readonly disabled: boolean;
@Input() readonly initialDeletionDate: Date;
@Input() readonly initialExpirationDate: Date;
@Input() readonly editMode: boolean;
@Input() readonly disabled: boolean;
@Output() datesChanged = new EventEmitter<{deletionDate: string, expirationDate: string}>();
@Output() datesChanged = new EventEmitter<{ deletionDate: string; expirationDate: string }>();
get browserPath(): BrowserPath {
if (this.platformUtilsService.isFirefox()) {
return BrowserPath.Firefox;
} else if (this.platformUtilsService.isSafari()) {
return BrowserPath.Safari;
}
return BrowserPath.Default;
get browserPath(): BrowserPath {
if (this.platformUtilsService.isFirefox()) {
return BrowserPath.Firefox;
} else if (this.platformUtilsService.isSafari()) {
return BrowserPath.Safari;
}
return BrowserPath.Default;
}
datesForm = new FormGroup({
selectedDeletionDatePreset: new FormControl(),
selectedExpirationDatePreset: new FormControl(),
defaultDeletionDateTime: new FormControl(),
defaultExpirationDateTime: new FormControl(),
fallbackDeletionDate: new FormControl(),
fallbackDeletionTime: new FormControl(),
fallbackExpirationDate: new FormControl(),
fallbackExpirationTime: new FormControl(),
});
datesForm = new FormGroup({
selectedDeletionDatePreset: new FormControl(),
selectedExpirationDatePreset: new FormControl(),
defaultDeletionDateTime: new FormControl(),
defaultExpirationDateTime: new FormControl(),
fallbackDeletionDate: new FormControl(),
fallbackDeletionTime: new FormControl(),
fallbackExpirationDate: new FormControl(),
fallbackExpirationTime: new FormControl(),
});
deletionDatePresets: any[] = [
{ name: this.i18nService.t('oneHour'), value: DatePreset.OneHour },
{ name: this.i18nService.t('oneDay'), value: DatePreset.OneDay },
{ name: this.i18nService.t('days', '2'), value: DatePreset.TwoDays },
{ name: this.i18nService.t('days', '3'), value: DatePreset.ThreeDays },
{ name: this.i18nService.t('days', '7'), value: DatePreset.SevenDays },
{ name: this.i18nService.t('days', '30'), value: DatePreset.ThirtyDays },
{ name: this.i18nService.t('custom'), value: DatePreset.Custom },
];
deletionDatePresets: any[] = [
{ name: this.i18nService.t("oneHour"), value: DatePreset.OneHour },
{ name: this.i18nService.t("oneDay"), value: DatePreset.OneDay },
{ name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays },
{ name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays },
{ name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays },
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
{ name: this.i18nService.t("custom"), value: DatePreset.Custom },
];
expirationDatePresets: any[] = [
{ name: this.i18nService.t('never'), value: DatePreset.Never },
].concat([...this.deletionDatePresets]);
expirationDatePresets: any[] = [
{ name: this.i18nService.t("never"), value: DatePreset.Never },
].concat([...this.deletionDatePresets]);
get selectedDeletionDatePreset(): FormControl {
return this.datesForm.get('selectedDeletionDatePreset') as FormControl;
}
get selectedDeletionDatePreset(): FormControl {
return this.datesForm.get("selectedDeletionDatePreset") as FormControl;
}
get selectedExpirationDatePreset(): FormControl {
return this.datesForm.get('selectedExpirationDatePreset') as FormControl;
}
get selectedExpirationDatePreset(): FormControl {
return this.datesForm.get("selectedExpirationDatePreset") as FormControl;
}
get defaultDeletionDateTime(): FormControl {
return this.datesForm.get('defaultDeletionDateTime') as FormControl;
}
get defaultDeletionDateTime(): FormControl {
return this.datesForm.get("defaultDeletionDateTime") as FormControl;
}
get defaultExpirationDateTime(): FormControl {
return this.datesForm.get('defaultExpirationDateTime') as FormControl;
}
get defaultExpirationDateTime(): FormControl {
return this.datesForm.get("defaultExpirationDateTime") as FormControl;
}
get fallbackDeletionDate(): FormControl {
return this.datesForm.get('fallbackDeletionDate') as FormControl;
}
get fallbackDeletionDate(): FormControl {
return this.datesForm.get("fallbackDeletionDate") as FormControl;
}
get fallbackDeletionTime(): FormControl {
return this.datesForm.get('fallbackDeletionTime') as FormControl;
}
get fallbackDeletionTime(): FormControl {
return this.datesForm.get("fallbackDeletionTime") as FormControl;
}
get fallbackExpirationDate(): FormControl {
return this.datesForm.get('fallbackExpirationDate') as FormControl;
}
get fallbackExpirationDate(): FormControl {
return this.datesForm.get("fallbackExpirationDate") as FormControl;
}
get fallbackExpirationTime(): FormControl {
return this.datesForm.get('fallbackExpirationTime') as FormControl;
}
get fallbackExpirationTime(): FormControl {
return this.datesForm.get("fallbackExpirationTime") as FormControl;
}
// Should be able to call these at any time and compute a submitable value
get formattedDeletionDate(): string {
switch (this.selectedDeletionDatePreset.value as DatePreset) {
case DatePreset.Never:
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
return this.formattedDeletionDate;
case DatePreset.Custom:
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
return this.fallbackDeletionDate.value + 'T' + this.fallbackDeletionTime.value;
default:
return this.defaultDeletionDateTime.value;
}
default:
const now = new Date();
const miliseconds = now.setTime(now.getTime() +
(this.selectedDeletionDatePreset.value as number * 60 * 60 * 1000)) ;
return new Date(miliseconds).toString();
}
}
get formattedExpirationDate(): string {
switch (this.selectedExpirationDatePreset.value as DatePreset) {
case DatePreset.Never:
return null;
case DatePreset.Custom:
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
if ((!this.fallbackExpirationDate.value || !this.fallbackExpirationTime.value) &&
this.editMode) {
return null;
}
return this.fallbackExpirationDate.value + 'T' + this.fallbackExpirationTime.value;
default:
if (!this.defaultExpirationDateTime.value) {
return null;
}
return this.defaultExpirationDateTime.value;
}
default:
const now = new Date();
const miliseconds = now.setTime(now.getTime() +
(this.selectedExpirationDatePreset.value as number * 60 * 60 * 1000));
return new Date(miliseconds).toString();
}
}
//
get safariDeletionTimePresetOptions() {
return this.safariTimePresetOptions(DateField.DeletionDate);
}
get safariExpirationTimePresetOptions() {
return this.safariTimePresetOptions(DateField.ExpriationDate);
}
private get nextWeek(): Date {
const nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);
return nextWeek;
}
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected datePipe: DatePipe) {
}
ngOnInit(): void {
this.setInitialFormValues();
this.emitDates();
this.datesForm.valueChanges.subscribe(() => {
this.emitDates();
});
}
onDeletionDatePresetSelect(value: DatePreset) {
this.selectedDeletionDatePreset.setValue(value);
}
clearExpiration() {
// Should be able to call these at any time and compute a submitable value
get formattedDeletionDate(): string {
switch (this.selectedDeletionDatePreset.value as DatePreset) {
case DatePreset.Never:
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
return this.formattedDeletionDate;
case DatePreset.Custom:
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
this.fallbackExpirationDate.setValue(null);
this.fallbackExpirationTime.setValue(null);
break;
case BrowserPath.Default:
this.defaultExpirationDateTime.setValue(null);
break;
case BrowserPath.Safari:
case BrowserPath.Firefox:
return this.fallbackDeletionDate.value + "T" + this.fallbackDeletionTime.value;
default:
return this.defaultDeletionDateTime.value;
}
default:
const now = new Date();
const miliseconds = now.setTime(
now.getTime() + (this.selectedDeletionDatePreset.value as number) * 60 * 60 * 1000
);
return new Date(miliseconds).toString();
}
}
protected emitDates() {
this.datesChanged.emit({
deletionDate: this.formattedDeletionDate,
expirationDate: this.formattedExpirationDate,
});
}
protected setInitialFormValues() {
if (this.editMode) {
this.selectedDeletionDatePreset.setValue(DatePreset.Custom);
this.selectedExpirationDatePreset.setValue(DatePreset.Custom);
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
this.fallbackDeletionDate.setValue(this.initialDeletionDate.toISOString().slice(0, 10));
this.fallbackDeletionTime.setValue(this.initialDeletionDate.toTimeString().slice(0, 5));
if (this.initialExpirationDate != null) {
this.fallbackExpirationDate.setValue(this.initialExpirationDate.toISOString().slice(0, 10));
this.fallbackExpirationTime.setValue(this.initialExpirationDate.toTimeString().slice(0, 5));
}
break;
case BrowserPath.Default:
if (this.initialExpirationDate) {
this.defaultExpirationDateTime.setValue(
this.datePipe.transform(new Date(this.initialExpirationDate), 'yyyy-MM-ddTHH:mm'));
}
this.defaultDeletionDateTime.setValue(this.datePipe.transform(new Date(this.initialDeletionDate), 'yyyy-MM-ddTHH:mm'));
break;
get formattedExpirationDate(): string {
switch (this.selectedExpirationDatePreset.value as DatePreset) {
case DatePreset.Never:
return null;
case DatePreset.Custom:
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
if (
(!this.fallbackExpirationDate.value || !this.fallbackExpirationTime.value) &&
this.editMode
) {
return null;
}
return this.fallbackExpirationDate.value + "T" + this.fallbackExpirationTime.value;
default:
if (!this.defaultExpirationDateTime.value) {
return null;
}
return this.defaultExpirationDateTime.value;
}
default:
const now = new Date();
const miliseconds = now.setTime(
now.getTime() + (this.selectedExpirationDatePreset.value as number) * 60 * 60 * 1000
);
return new Date(miliseconds).toString();
}
}
//
get safariDeletionTimePresetOptions() {
return this.safariTimePresetOptions(DateField.DeletionDate);
}
get safariExpirationTimePresetOptions() {
return this.safariTimePresetOptions(DateField.ExpriationDate);
}
private get nextWeek(): Date {
const nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);
return nextWeek;
}
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected datePipe: DatePipe
) {}
ngOnInit(): void {
this.setInitialFormValues();
this.emitDates();
this.datesForm.valueChanges.subscribe(() => {
this.emitDates();
});
}
onDeletionDatePresetSelect(value: DatePreset) {
this.selectedDeletionDatePreset.setValue(value);
}
clearExpiration() {
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
this.fallbackExpirationDate.setValue(null);
this.fallbackExpirationTime.setValue(null);
break;
case BrowserPath.Default:
this.defaultExpirationDateTime.setValue(null);
break;
}
}
protected emitDates() {
this.datesChanged.emit({
deletionDate: this.formattedDeletionDate,
expirationDate: this.formattedExpirationDate,
});
}
protected setInitialFormValues() {
if (this.editMode) {
this.selectedDeletionDatePreset.setValue(DatePreset.Custom);
this.selectedExpirationDatePreset.setValue(DatePreset.Custom);
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
this.fallbackDeletionDate.setValue(this.initialDeletionDate.toISOString().slice(0, 10));
this.fallbackDeletionTime.setValue(this.initialDeletionDate.toTimeString().slice(0, 5));
if (this.initialExpirationDate != null) {
this.fallbackExpirationDate.setValue(
this.initialExpirationDate.toISOString().slice(0, 10)
);
this.fallbackExpirationTime.setValue(
this.initialExpirationDate.toTimeString().slice(0, 5)
);
}
break;
case BrowserPath.Default:
if (this.initialExpirationDate) {
this.defaultExpirationDateTime.setValue(
this.datePipe.transform(new Date(this.initialExpirationDate), "yyyy-MM-ddTHH:mm")
);
}
this.defaultDeletionDateTime.setValue(
this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm")
);
break;
}
} else {
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
this.selectedExpirationDatePreset.setValue(DatePreset.Never);
switch (this.browserPath) {
case BrowserPath.Safari:
this.fallbackDeletionDate.setValue(this.nextWeek.toISOString().slice(0, 10));
this.fallbackDeletionTime.setValue(
this.safariTimePresetOptions(DateField.DeletionDate)[1].twentyFourHour
);
break;
default:
break;
}
}
}
protected safariTimePresetOptions(field: DateField): TimeOption[] {
// init individual arrays for major sort groups
const noon: TimeOption[] = [];
const midnight: TimeOption[] = [];
const ams: TimeOption[] = [];
const pms: TimeOption[] = [];
// determine minute skip (5 min, 10 min, 15 min, etc.)
const minuteIncrementer = 15;
// loop through each hour on a 12 hour system
for (let h = 1; h <= 12; h++) {
// loop through each minute in the hour using the skip to incriment
for (let m = 0; m < 60; m += minuteIncrementer) {
// init the final strings that will be added to the lists
let hour = h.toString();
let minutes = m.toString();
// add prepending 0s to single digit hours/minutes
if (h < 10) {
hour = "0" + hour;
}
if (m < 10) {
minutes = "0" + minutes;
}
// build time strings and push to relevant sort groups
if (h === 12) {
const midnightOption: TimeOption = {
twelveHour: `${hour}:${minutes} AM`,
twentyFourHour: `00:${minutes}`,
};
midnight.push(midnightOption);
const noonOption: TimeOption = {
twelveHour: `${hour}:${minutes} PM`,
twentyFourHour: `${hour}:${minutes}`,
};
noon.push(noonOption);
} else {
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
this.selectedExpirationDatePreset.setValue(DatePreset.Never);
const amOption: TimeOption = {
twelveHour: `${hour}:${minutes} AM`,
twentyFourHour: `${hour}:${minutes}`,
};
ams.push(amOption);
switch (this.browserPath) {
case BrowserPath.Safari:
this.fallbackDeletionDate.setValue(this.nextWeek.toISOString().slice(0, 10));
this.fallbackDeletionTime.setValue(this.safariTimePresetOptions(DateField.DeletionDate)[1].twentyFourHour);
break;
default:
break;
}
const pmOption: TimeOption = {
twelveHour: `${hour}:${minutes} PM`,
twentyFourHour: `${h + 12}:${minutes}`,
};
pms.push(pmOption);
}
}
}
protected safariTimePresetOptions(field: DateField): TimeOption[] {
// init individual arrays for major sort groups
const noon: TimeOption[] = [];
const midnight: TimeOption[] = [];
const ams: TimeOption[] = [];
const pms: TimeOption[] = [];
// bring all the arrays together in the right order
const validTimes = [...midnight, ...ams, ...noon, ...pms];
// determine minute skip (5 min, 10 min, 15 min, etc.)
const minuteIncrementer = 15;
// loop through each hour on a 12 hour system
for (let h = 1; h <= 12; h++) {
// loop through each minute in the hour using the skip to incriment
for (let m = 0; m < 60; m += minuteIncrementer) {
// init the final strings that will be added to the lists
let hour = h.toString();
let minutes = m.toString();
// add prepending 0s to single digit hours/minutes
if (h < 10) {
hour = '0' + hour;
}
if (m < 10) {
minutes = '0' + minutes;
}
// build time strings and push to relevant sort groups
if (h === 12) {
const midnightOption: TimeOption = {
twelveHour: `${hour}:${minutes} AM`,
twentyFourHour: `00:${minutes}`,
};
midnight.push(midnightOption);
const noonOption: TimeOption = {
twelveHour: `${hour}:${minutes} PM`,
twentyFourHour: `${hour}:${minutes}`,
};
noon.push(noonOption);
} else {
const amOption: TimeOption = {
twelveHour: `${hour}:${minutes} AM`,
twentyFourHour: `${hour}:${minutes}`,
};
ams.push(amOption);
const pmOption: TimeOption = {
twelveHour: `${hour}:${minutes} PM`,
twentyFourHour: `${h + 12}:${minutes}`,
};
pms.push(pmOption);
}
}
}
// bring all the arrays together in the right order
const validTimes = [...midnight, ...ams, ...noon, ...pms];
// determine if an unsupported value already exists on the send & add that to the top of the option list
// example: if the Send was created with a different client
if (field === DateField.ExpriationDate && this.initialExpirationDate != null && this.editMode) {
const previousValue: TimeOption = {
twelveHour: this.datePipe.transform(this.initialExpirationDate, 'hh:mm a'),
twentyFourHour: this.datePipe.transform(this.initialExpirationDate, 'HH:mm'),
};
return [previousValue, { twelveHour: null, twentyFourHour: null }, ...validTimes];
} else if (field === DateField.DeletionDate && this.initialDeletionDate != null && this.editMode) {
const previousValue: TimeOption = {
twelveHour: this.datePipe.transform(this.initialDeletionDate, 'hh:mm a'),
twentyFourHour: this.datePipe.transform(this.initialDeletionDate, 'HH:mm'),
};
return [previousValue, ...validTimes];
} else {
return [{ twelveHour: null, twentyFourHour: null }, ...validTimes];
}
// determine if an unsupported value already exists on the send & add that to the top of the option list
// example: if the Send was created with a different client
if (field === DateField.ExpriationDate && this.initialExpirationDate != null && this.editMode) {
const previousValue: TimeOption = {
twelveHour: this.datePipe.transform(this.initialExpirationDate, "hh:mm a"),
twentyFourHour: this.datePipe.transform(this.initialExpirationDate, "HH:mm"),
};
return [previousValue, { twelveHour: null, twentyFourHour: null }, ...validTimes];
} else if (
field === DateField.DeletionDate &&
this.initialDeletionDate != null &&
this.editMode
) {
const previousValue: TimeOption = {
twelveHour: this.datePipe.transform(this.initialDeletionDate, "hh:mm a"),
twentyFourHour: this.datePipe.transform(this.initialDeletionDate, "HH:mm"),
};
return [previousValue, ...validTimes];
} else {
return [{ twelveHour: null, twentyFourHour: null }, ...validTimes];
}
}
}

View File

@@ -1,201 +1,212 @@
import {
Directive,
NgZone,
OnInit,
} from '@angular/core';
import { Directive, NgZone, OnInit } from "@angular/core";
import { PolicyType } from 'jslib-common/enums/policyType';
import { SendType } from 'jslib-common/enums/sendType';
import { PolicyType } from "jslib-common/enums/policyType";
import { SendType } from "jslib-common/enums/sendType";
import { SendView } from 'jslib-common/models/view/sendView';
import { SendView } from "jslib-common/models/view/sendView";
import { EnvironmentService } from 'jslib-common/abstractions/environment.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 { SearchService } from 'jslib-common/abstractions/search.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.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 { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from "jslib-common/abstractions/send.service";
@Directive()
export class SendComponent implements OnInit {
disableSend = false;
sendType = SendType;
loaded = false;
loading = true;
refreshing = false;
expired: boolean = false;
type: SendType = null;
sends: SendView[] = [];
filteredSends: SendView[] = [];
searchText: string;
selectedType: SendType;
selectedAll: boolean;
searchPlaceholder: string;
filter: (cipher: SendView) => boolean;
searchPending = false;
hasSearched = false; // search() function called - returns true if text qualifies for search
disableSend = false;
sendType = SendType;
loaded = false;
loading = true;
refreshing = false;
expired: boolean = false;
type: SendType = null;
sends: SendView[] = [];
filteredSends: SendView[] = [];
searchText: string;
selectedType: SendType;
selectedAll: boolean;
searchPlaceholder: string;
filter: (cipher: SendView) => boolean;
searchPending = false;
hasSearched = false; // search() function called - returns true if text qualifies for search
actionPromise: any;
onSuccessfulRemovePassword: () => Promise<any>;
onSuccessfulDelete: () => Promise<any>;
onSuccessfulLoad: () => Promise<any>;
actionPromise: any;
onSuccessfulRemovePassword: () => Promise<any>;
onSuccessfulDelete: () => Promise<any>;
onSuccessfulLoad: () => Promise<any>;
private searchTimeout: any;
private searchTimeout: any;
constructor(
protected sendService: SendService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService,
protected ngZone: NgZone,
protected searchService: SearchService,
protected policyService: PolicyService,
private logService: LogService
) {}
constructor(protected sendService: SendService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService,
protected ngZone: NgZone, protected searchService: SearchService,
protected policyService: PolicyService, private logService: LogService) { }
async ngOnInit() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
}
async ngOnInit() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
async load(filter: (send: SendView) => boolean = null) {
this.loading = true;
const sends = await this.sendService.getAllDecrypted();
this.sends = sends;
if (this.onSuccessfulLoad != null) {
await this.onSuccessfulLoad();
} else {
// Default action
this.selectAll();
}
this.loading = false;
this.loaded = true;
}
async reload(filter: (send: SendView) => boolean = null) {
this.loaded = false;
this.sends = [];
await this.load(filter);
}
async refresh() {
try {
this.refreshing = true;
await this.reload(this.filter);
} finally {
this.refreshing = false;
}
}
async applyFilter(filter: (send: SendView) => boolean = null) {
this.filter = filter;
await this.search(null);
}
async search(timeout: number = null) {
this.searchPending = false;
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
if (timeout == null) {
this.hasSearched = this.searchService.isSearchable(this.searchText);
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
this.applyTextSearch();
return;
}
this.searchPending = true;
this.searchTimeout = setTimeout(async () => {
this.hasSearched = this.searchService.isSearchable(this.searchText);
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
this.applyTextSearch();
this.searchPending = false;
}, timeout);
}
async removePassword(s: SendView): Promise<boolean> {
if (this.actionPromise != null || s.password == null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("removePasswordConfirmation"),
this.i18nService.t("removePassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async load(filter: (send: SendView) => boolean = null) {
this.loading = true;
const sends = await this.sendService.getAllDecrypted();
this.sends = sends;
if (this.onSuccessfulLoad != null) {
await this.onSuccessfulLoad();
} else {
// Default action
this.selectAll();
}
this.loading = false;
this.loaded = true;
try {
this.actionPromise = this.sendService.removePasswordWithServer(s.id);
await this.actionPromise;
if (this.onSuccessfulRemovePassword != null) {
this.onSuccessfulRemovePassword();
} else {
// Default actions
this.platformUtilsService.showToast("success", null, this.i18nService.t("removedPassword"));
await this.load();
}
} catch (e) {
this.logService.error(e);
}
this.actionPromise = null;
}
async delete(s: SendView): Promise<boolean> {
if (this.actionPromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteSendConfirmation"),
this.i18nService.t("deleteSend"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async reload(filter: (send: SendView) => boolean = null) {
this.loaded = false;
this.sends = [];
await this.load(filter);
try {
this.actionPromise = this.sendService.deleteWithServer(s.id);
await this.actionPromise;
if (this.onSuccessfulDelete != null) {
this.onSuccessfulDelete();
} else {
// Default actions
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend"));
await this.refresh();
}
} catch (e) {
this.logService.error(e);
}
this.actionPromise = null;
return true;
}
async refresh() {
try {
this.refreshing = true;
await this.reload(this.filter);
} finally {
this.refreshing = false;
}
}
async applyFilter(filter: (send: SendView) => boolean = null) {
this.filter = filter;
await this.search(null);
}
async search(timeout: number = null) {
this.searchPending = false;
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
if (timeout == null) {
this.hasSearched = this.searchService.isSearchable(this.searchText);
this.filteredSends = this.sends.filter(s => this.filter == null || this.filter(s));
this.applyTextSearch();
return;
}
this.searchPending = true;
this.searchTimeout = setTimeout(async () => {
this.hasSearched = this.searchService.isSearchable(this.searchText);
this.filteredSends = this.sends.filter(s => this.filter == null || this.filter(s));
this.applyTextSearch();
this.searchPending = false;
}, timeout);
}
async removePassword(s: SendView): Promise<boolean> {
if (this.actionPromise != null || s.password == null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('removePasswordConfirmation'),
this.i18nService.t('removePassword'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.sendService.removePasswordWithServer(s.id);
await this.actionPromise;
if (this.onSuccessfulRemovePassword != null) {
this.onSuccessfulRemovePassword();
} else {
// Default actions
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedPassword'));
await this.load();
}
} catch (e) {
this.logService.error(e);
}
this.actionPromise = null;
}
async delete(s: SendView): Promise<boolean> {
if (this.actionPromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteSendConfirmation'),
this.i18nService.t('deleteSend'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.sendService.deleteWithServer(s.id);
await this.actionPromise;
if (this.onSuccessfulDelete != null) {
this.onSuccessfulDelete();
} else {
// Default actions
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
await this.refresh();
}
} catch (e) {
this.logService.error(e);
}
this.actionPromise = null;
return true;
}
copy(s: SendView) {
const sendLinkBaseUrl = this.environmentService.getSendUrl();
const link = sendLinkBaseUrl + s.accessId + '/' + s.urlB64Key;
this.platformUtilsService.copyToClipboard(link);
this.platformUtilsService.showToast('success', null,
this.i18nService.t('valueCopied', this.i18nService.t('sendLink')));
}
searchTextChanged() {
this.search(200);
}
selectAll() {
this.clearSelections();
this.selectedAll = true;
this.applyFilter(null);
}
selectType(type: SendType) {
this.clearSelections();
this.selectedType = type;
this.applyFilter(s => s.type === type);
}
clearSelections() {
this.selectedAll = false;
this.selectedType = null;
}
private applyTextSearch() {
if (this.searchText != null) {
this.filteredSends = this.searchService.searchSends(this.filteredSends, this.searchText);
}
copy(s: SendView) {
const sendLinkBaseUrl = this.environmentService.getSendUrl();
const link = sendLinkBaseUrl + s.accessId + "/" + s.urlB64Key;
this.platformUtilsService.copyToClipboard(link);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("valueCopied", this.i18nService.t("sendLink"))
);
}
searchTextChanged() {
this.search(200);
}
selectAll() {
this.clearSelections();
this.selectedAll = true;
this.applyFilter(null);
}
selectType(type: SendType) {
this.clearSelections();
this.selectedType = type;
this.applyFilter((s) => s.type === type);
}
clearSelections() {
this.selectedAll = false;
this.selectedType = null;
}
private applyTextSearch() {
if (this.searchText != null) {
this.filteredSends = this.searchService.searchSends(this.filteredSends, this.searchText);
}
}
}

View File

@@ -1,152 +1,184 @@
import { Directive } from '@angular/core';
import {
ActivatedRoute,
Router
} from '@angular/router';
import { Directive } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { EncString } from 'jslib-common/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { EncString } from "jslib-common/models/domain/encString";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { KeysRequest } from 'jslib-common/models/request/keysRequest';
import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest';
import { SetPasswordRequest } from 'jslib-common/models/request/setPasswordRequest';
import { KeysRequest } from "jslib-common/models/request/keysRequest";
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
import { SetPasswordRequest } from "jslib-common/models/request/setPasswordRequest";
import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component';
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
import { HashPurpose } from 'jslib-common/enums/hashPurpose';
import { KdfType } from 'jslib-common/enums/kdfType';
import { HashPurpose } from "jslib-common/enums/hashPurpose";
import { KdfType } from "jslib-common/enums/kdfType";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive()
export class SetPasswordComponent extends BaseChangePasswordComponent {
syncLoading: boolean = true;
showPassword: boolean = false;
hint: string = '';
identifier: string = null;
orgId: string;
resetPasswordAutoEnroll = false;
syncLoading: boolean = true;
showPassword: boolean = false;
hint: string = "";
identifier: string = null;
orgId: string;
resetPasswordAutoEnroll = false;
onSuccessfulChangePassword: () => Promise<any>;
successRoute = 'vault';
onSuccessfulChangePassword: () => Promise<any>;
successRoute = "vault";
constructor(i18nService: I18nService, cryptoService: CryptoService,
messagingService: MessagingService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService,
protected router: Router, private apiService: ApiService,
private syncService: SyncService, private route: ActivatedRoute, stateService: StateService) {
super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, stateService);
constructor(
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
protected router: Router,
private apiService: ApiService,
private syncService: SyncService,
private route: ActivatedRoute,
stateService: StateService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
stateService
);
}
async ngOnInit() {
await this.syncService.fullSync(true);
this.syncLoading = false;
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
}
});
// Automatic Enrollment Detection
if (this.identifier != null) {
try {
const response = await this.apiService.getOrganizationAutoEnrollStatus(this.identifier);
this.orgId = response.id;
this.resetPasswordAutoEnroll = response.resetPasswordEnabled;
} catch {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
}
async ngOnInit() {
await this.syncService.fullSync(true);
this.syncLoading = false;
super.ngOnInit();
}
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
async setupSubmitActions() {
this.kdf = KdfType.PBKDF2_SHA256;
const useLowerKdf = this.platformUtilsService.isIE();
this.kdfIterations = useLowerKdf ? 10000 : 100000;
return true;
}
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
) {
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new SetPasswordRequest(
masterPasswordHash,
encKey[1].encryptedString,
this.hint,
this.kdf,
this.kdfIterations,
this.identifier,
new KeysRequest(keys[0], keys[1].encryptedString)
);
try {
if (this.resetPasswordAutoEnroll) {
this.formPromise = this.apiService
.setPassword(request)
.then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
return this.apiService.getOrganizationKeys(this.orgId);
})
.then(async (response) => {
if (response == null) {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
}
const userId = await this.stateService.getUserId();
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const userEncKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(
userEncKey.key,
publicKey.buffer
);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
return this.apiService.putOrganizationUserResetPasswordEnrollment(
this.orgId,
userId,
resetRequest
);
});
} else {
this.formPromise = this.apiService.setPassword(request).then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
});
}
// Automatic Enrollment Detection
if (this.identifier != null) {
try {
const response = await this.apiService.getOrganizationAutoEnrollStatus(this.identifier);
this.orgId = response.id;
this.resetPasswordAutoEnroll = response.resetPasswordEnabled;
} catch {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
}
}
await this.formPromise;
super.ngOnInit();
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
this.router.navigate([this.successRoute]);
}
} catch {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
}
async setupSubmitActions() {
this.kdf = KdfType.PBKDF2_SHA256;
const useLowerKdf = this.platformUtilsService.isIE();
this.kdfIterations = useLowerKdf ? 10000 : 100000;
return true;
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]) {
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new SetPasswordRequest(
masterPasswordHash,
encKey[1].encryptedString,
this.hint,
this.kdf,
this.kdfIterations,
this.identifier,
new KeysRequest(keys[0], keys[1].encryptedString)
);
try {
if (this.resetPasswordAutoEnroll) {
this.formPromise = this.apiService.setPassword(request).then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
return this.apiService.getOrganizationKeys(this.orgId);
}).then(async response => {
if (response == null) {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError'));
}
const userId = await this.stateService.getUserId();
const publicKey = Utils.fromB64ToArray(response.publicKey);
private async onSetPasswordSuccess(
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString],
keys: [string, EncString]
) {
await this.stateService.setKdfType(this.kdf);
await this.stateService.setKdfIterations(this.kdfIterations);
await this.cryptoService.setKey(key);
await this.cryptoService.setEncKey(encKey[1].encryptedString);
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);
// RSA Encrypt user's encKey.key with organization public key
const userEncKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(userEncKey.key, publicKey.buffer);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
return this.apiService.putOrganizationUserResetPasswordEnrollment(this.orgId, userId, resetRequest);
});
} else {
this.formPromise = this.apiService.setPassword(request).then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
});
}
await this.formPromise;
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
this.router.navigate([this.successRoute]);
}
} catch {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
}
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus();
}
private async onSetPasswordSuccess(key: SymmetricCryptoKey, encKey: [SymmetricCryptoKey, EncString], keys: [string, EncString]) {
await this.stateService.setKdfType(this.kdf);
await this.stateService.setKdfIterations(this.kdfIterations);
await this.cryptoService.setKey(key);
await this.cryptoService.setEncKey(encKey[1].encryptedString);
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);
const localKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key,
HashPurpose.LocalAuthorization);
await this.cryptoService.setKeyHash(localKeyHash);
}
const localKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
key,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash);
}
}

View File

@@ -1,53 +1,55 @@
import {
Directive,
OnInit
} from '@angular/core';
import { Directive, OnInit } from "@angular/core";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { ModalRef } from './modal/modal.ref';
import { ModalRef } from "./modal/modal.ref";
@Directive()
export class SetPinComponent implements OnInit {
pin = '';
showPin = false;
masterPassOnRestart = true;
showMasterPassOnRestart = true;
pin = "";
showPin = false;
masterPassOnRestart = true;
showMasterPassOnRestart = true;
constructor(private modalRef: ModalRef, private cryptoService: CryptoService,
private keyConnectorService: KeyConnectorService, private stateService: StateService) { }
constructor(
private modalRef: ModalRef,
private cryptoService: CryptoService,
private keyConnectorService: KeyConnectorService,
private stateService: StateService
) {}
async ngOnInit() {
this.showMasterPassOnRestart = this.masterPassOnRestart = !await this.keyConnectorService.getUsesKeyConnector();
async ngOnInit() {
this.showMasterPassOnRestart = this.masterPassOnRestart =
!(await this.keyConnectorService.getUsesKeyConnector());
}
toggleVisibility() {
this.showPin = !this.showPin;
}
async submit() {
if (Utils.isNullOrWhitespace(this.pin)) {
this.modalRef.close(false);
}
toggleVisibility() {
this.showPin = !this.showPin;
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
const email = await this.stateService.getEmail();
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations);
const key = await this.cryptoService.getKey();
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
if (this.masterPassOnRestart) {
const encPin = await this.cryptoService.encrypt(this.pin);
await this.stateService.setProtectedPin(encPin.encryptedString);
await this.stateService.setDecryptedPinProtected(pinProtectedKey);
} else {
await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString);
}
async submit() {
if (Utils.isNullOrWhitespace(this.pin)) {
this.modalRef.close(false);
}
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
const email = await this.stateService.getEmail();
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations);
const key = await this.cryptoService.getKey();
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
if (this.masterPassOnRestart) {
const encPin = await this.cryptoService.encrypt(this.pin);
await this.stateService.setProtectedPin(encPin.encryptedString);
await this.stateService.setDecryptedPinProtected(pinProtectedKey);
} else {
await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString);
}
this.modalRef.close(true);
}
this.modalRef.close(true);
}
}

View File

@@ -1,138 +1,140 @@
import { Directive, Input, OnInit } from "@angular/core";
import {
Directive,
Input,
OnInit,
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormBuilder,
ValidationErrors,
Validator
} from '@angular/forms';
AbstractControl,
ControlValueAccessor,
FormBuilder,
ValidationErrors,
Validator,
} from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { PolicyType } from 'jslib-common/enums/policyType';
import { Policy } from 'jslib-common/models/domain/policy';
import { PolicyType } from "jslib-common/enums/policyType";
import { Policy } from "jslib-common/models/domain/policy";
@Directive()
export class VaultTimeoutInputComponent implements ControlValueAccessor, Validator, OnInit {
get showCustom() {
return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE;
}
get showCustom() {
return this.form.get('vaultTimeout').value === VaultTimeoutInputComponent.CUSTOM_VALUE;
static CUSTOM_VALUE = -100;
form = this.fb.group({
vaultTimeout: [null],
custom: this.fb.group({
hours: [null],
minutes: [null],
}),
});
@Input() vaultTimeouts: { name: string; value: number }[];
vaultTimeoutPolicy: Policy;
vaultTimeoutPolicyHours: number;
vaultTimeoutPolicyMinutes: number;
private onChange: (vaultTimeout: number) => void;
private validatorChange: () => void;
constructor(
private fb: FormBuilder,
private policyService: PolicyService,
private i18nService: I18nService
) {}
async ngOnInit() {
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) {
const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout);
this.vaultTimeoutPolicy = vaultTimeoutPolicy[0];
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
this.vaultTimeouts = this.vaultTimeouts.filter(
(t) =>
t.value <= this.vaultTimeoutPolicy.data.minutes &&
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
t.value != null
);
this.validatorChange();
}
static CUSTOM_VALUE = -100;
form = this.fb.group({
vaultTimeout: [null],
custom: this.fb.group({
hours: [null],
minutes: [null],
}),
this.form.valueChanges.subscribe(async (value) => {
this.onChange(this.getVaultTimeout(value));
});
@Input() vaultTimeouts: { name: string; value: number; }[];
vaultTimeoutPolicy: Policy;
vaultTimeoutPolicyHours: number;
vaultTimeoutPolicyMinutes: number;
// Assign the previous value to the custom fields
this.form.get("vaultTimeout").valueChanges.subscribe((value) => {
if (value !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
return;
}
private onChange: (vaultTimeout: number) => void;
private validatorChange: () => void;
const current = Math.max(this.form.value.vaultTimeout, 0);
this.form.patchValue({
custom: {
hours: Math.floor(current / 60),
minutes: current % 60,
},
});
});
}
constructor(private fb: FormBuilder, private policyService: PolicyService, private i18nService: I18nService) {
ngOnChanges() {
this.vaultTimeouts.push({
name: this.i18nService.t("custom"),
value: VaultTimeoutInputComponent.CUSTOM_VALUE,
});
}
getVaultTimeout(value: any) {
if (value.vaultTimeout !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
return value.vaultTimeout;
}
async ngOnInit() {
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) {
const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout);
return value.custom.hours * 60 + value.custom.minutes;
}
this.vaultTimeoutPolicy = vaultTimeoutPolicy[0];
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
this.vaultTimeouts = this.vaultTimeouts.filter(t =>
t.value <= this.vaultTimeoutPolicy.data.minutes &&
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
t.value != null
);
this.validatorChange();
}
this.form.valueChanges.subscribe(async value => {
this.onChange(this.getVaultTimeout(value));
});
// Assign the previous value to the custom fields
this.form.get('vaultTimeout').valueChanges.subscribe(value => {
if (value !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
return;
}
const current = Math.max(this.form.value.vaultTimeout, 0);
this.form.patchValue({
custom: {
hours: Math.floor(current / 60),
minutes: current % 60,
},
});
});
writeValue(value: number): void {
if (value == null) {
return;
}
ngOnChanges() {
this.vaultTimeouts.push({ name: this.i18nService.t('custom'), value: VaultTimeoutInputComponent.CUSTOM_VALUE });
if (this.vaultTimeouts.every((p) => p.value !== value)) {
this.form.setValue({
vaultTimeout: VaultTimeoutInputComponent.CUSTOM_VALUE,
custom: {
hours: Math.floor(value / 60),
minutes: value % 60,
},
});
return;
}
getVaultTimeout(value: any) {
if (value.vaultTimeout !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
return value.vaultTimeout;
}
this.form.patchValue({
vaultTimeout: value,
});
}
return value.custom.hours * 60 + value.custom.minutes;
registerOnChange(onChange: any): void {
this.onChange = onChange;
}
// tslint:disable-next-line
registerOnTouched(onTouched: any): void {}
// tslint:disable-next-line
setDisabledState?(isDisabled: boolean): void {}
validate(control: AbstractControl): ValidationErrors {
if (this.vaultTimeoutPolicy && this.vaultTimeoutPolicy?.data?.minutes < control.value) {
return { policyError: true };
}
writeValue(value: number): void {
if (value == null) {
return;
}
return null;
}
if (this.vaultTimeouts.every(p => p.value !== value)) {
this.form.setValue({
vaultTimeout: VaultTimeoutInputComponent.CUSTOM_VALUE,
custom: {
hours: Math.floor(value / 60),
minutes: value % 60,
},
});
return;
}
this.form.patchValue({
vaultTimeout: value,
});
}
registerOnChange(onChange: any): void {
this.onChange = onChange;
}
// tslint:disable-next-line
registerOnTouched(onTouched: any): void {}
// tslint:disable-next-line
setDisabledState?(isDisabled: boolean): void { }
validate(control: AbstractControl): ValidationErrors {
if (this.vaultTimeoutPolicy && this.vaultTimeoutPolicy?.data?.minutes < control.value) {
return { policyError: true };
}
return null;
}
registerOnValidatorChange(fn: () => void): void {
this.validatorChange = fn;
}
registerOnValidatorChange(fn: () => void): void {
this.validatorChange = fn;
}
}

View File

@@ -1,108 +1,119 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { Organization } from 'jslib-common/models/domain/organization';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { Organization } from "jslib-common/models/domain/organization";
import { CipherView } from "jslib-common/models/view/cipherView";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive()
export class ShareComponent implements OnInit {
@Input() cipherId: string;
@Input() organizationId: string;
@Output() onSharedCipher = new EventEmitter();
@Input() cipherId: string;
@Input() organizationId: string;
@Output() onSharedCipher = new EventEmitter();
formPromise: Promise<any>;
cipher: CipherView;
collections: CollectionView[] = [];
organizations: Organization[] = [];
formPromise: Promise<any>;
cipher: CipherView;
collections: CollectionView[] = [];
organizations: Organization[] = [];
protected writeableCollections: CollectionView[] = [];
protected writeableCollections: CollectionView[] = [];
constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService, protected cipherService: CipherService,
private logService: LogService, protected organizationService: OrganizationService) { }
constructor(
protected collectionService: CollectionService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected cipherService: CipherService,
private logService: LogService,
protected organizationService: OrganizationService
) {}
async ngOnInit() {
await this.load();
async ngOnInit() {
await this.load();
}
async load() {
const allCollections = await this.collectionService.getAllDecrypted();
this.writeableCollections = allCollections.map((c) => c).filter((c) => !c.readOnly);
const orgs = await this.organizationService.getAll();
this.organizations = orgs
.sort(Utils.getSortFunction(this.i18nService, "name"))
.filter((o) => o.enabled && o.status === OrganizationUserStatusType.Confirmed);
const cipherDomain = await this.cipherService.get(this.cipherId);
this.cipher = await cipherDomain.decrypt();
if (this.organizationId == null && this.organizations.length > 0) {
this.organizationId = this.organizations[0].id;
}
this.filterCollections();
}
filterCollections() {
this.writeableCollections.forEach((c) => ((c as any).checked = false));
if (this.organizationId == null || this.writeableCollections.length === 0) {
this.collections = [];
} else {
this.collections = this.writeableCollections.filter(
(c) => c.organizationId === this.organizationId
);
}
}
async submit(): Promise<boolean> {
const selectedCollectionIds = this.collections
.filter((c) => !!(c as any).checked)
.map((c) => c.id);
if (selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectOneCollection")
);
return;
}
async load() {
const allCollections = await this.collectionService.getAllDecrypted();
this.writeableCollections = allCollections.map(c => c).filter(c => !c.readOnly);
const orgs = await this.organizationService.getAll();
this.organizations = orgs.sort(Utils.getSortFunction(this.i18nService, 'name'))
.filter(o => o.enabled && o.status === OrganizationUserStatusType.Confirmed);
const cipherDomain = await this.cipherService.get(this.cipherId);
const cipherView = await cipherDomain.decrypt();
const orgName =
this.organizations.find((o) => o.id === this.organizationId)?.name ??
this.i18nService.t("organization");
const cipherDomain = await this.cipherService.get(this.cipherId);
this.cipher = await cipherDomain.decrypt();
if (this.organizationId == null && this.organizations.length > 0) {
this.organizationId = this.organizations[0].id;
}
this.filterCollections();
try {
this.formPromise = this.cipherService
.shareWithServer(cipherView, this.organizationId, selectedCollectionIds)
.then(async () => {
this.onSharedCipher.emit();
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("movedItemToOrg", cipherView.name, orgName)
);
});
await this.formPromise;
return true;
} catch (e) {
this.logService.error(e);
}
return false;
}
filterCollections() {
this.writeableCollections.forEach(c => (c as any).checked = false);
if (this.organizationId == null || this.writeableCollections.length === 0) {
this.collections = [];
} else {
this.collections = this.writeableCollections.filter(c => c.organizationId === this.organizationId);
get canSave() {
if (this.collections != null) {
for (let i = 0; i < this.collections.length; i++) {
if ((this.collections[i] as any).checked) {
return true;
}
}
}
async submit(): Promise<boolean> {
const selectedCollectionIds = this.collections
.filter(c => !!(c as any).checked)
.map(c => c.id);
if (selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectOneCollection'));
return;
}
const cipherDomain = await this.cipherService.get(this.cipherId);
const cipherView = await cipherDomain.decrypt();
const orgName = this.organizations.find(o => o.id === this.organizationId)?.name ?? this.i18nService.t('organization');
try {
this.formPromise = this.cipherService.shareWithServer(cipherView, this.organizationId,
selectedCollectionIds).then(async () => {
this.onSharedCipher.emit();
this.platformUtilsService.showToast('success', null,
this.i18nService.t('movedItemToOrg', cipherView.name, orgName));
});
await this.formPromise;
return true;
} catch (e) {
this.logService.error(e);
}
return false;
}
get canSave() {
if (this.collections != null) {
for (let i = 0; i < this.collections.length; i++) {
if ((this.collections[i] as any).checked) {
return true;
}
}
}
return false;
}
return false;
}
}

View File

@@ -1,211 +1,254 @@
import { Directive } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Directive } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { AuthResult } from 'jslib-common/models/domain/authResult';
import { AuthResult } from "jslib-common/models/domain/authResult";
@Directive()
export class SsoComponent {
identifier: string;
loggingIn = false;
identifier: string;
loggingIn = false;
formPromise: Promise<AuthResult>;
initiateSsoFormPromise: Promise<any>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginChangePasswordNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
formPromise: Promise<AuthResult>;
initiateSsoFormPromise: Promise<any>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginChangePasswordNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
protected twoFactorRoute = '2fa';
protected successRoute = 'lock';
protected changePasswordRoute = 'set-password';
protected forcePasswordResetRoute = 'update-temp-password';
protected clientId: string;
protected redirectUri: string;
protected state: string;
protected codeChallenge: string;
protected twoFactorRoute = "2fa";
protected successRoute = "lock";
protected changePasswordRoute = "set-password";
protected forcePasswordResetRoute = "update-temp-password";
protected clientId: string;
protected redirectUri: string;
protected state: string;
protected codeChallenge: string;
constructor(protected authService: AuthService, protected router: Router,
protected i18nService: I18nService, protected route: ActivatedRoute,
protected stateService: StateService, protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService, protected cryptoFunctionService: CryptoFunctionService,
protected environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService,
protected logService: LogService) { }
constructor(
protected authService: AuthService,
protected router: Router,
protected i18nService: I18nService,
protected route: ActivatedRoute,
protected stateService: StateService,
protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService,
protected cryptoFunctionService: CryptoFunctionService,
protected environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected logService: LogService
) {}
async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.code != null && qParams.state != null) {
const codeVerifier = await this.stateService.getSsoCodeVerifier();
const state = await this.stateService.getSsoState();
await this.stateService.setSsoCodeVerifier(null);
await this.stateService.setSsoState(null);
if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) {
await this.logIn(qParams.code, codeVerifier, this.getOrgIdentifierFromState(qParams.state));
}
} else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null &&
qParams.codeChallenge != null) {
this.redirectUri = qParams.redirectUri;
this.state = qParams.state;
this.codeChallenge = qParams.codeChallenge;
this.clientId = qParams.clientId;
}
});
async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.code != null && qParams.state != null) {
const codeVerifier = await this.stateService.getSsoCodeVerifier();
const state = await this.stateService.getSsoState();
await this.stateService.setSsoCodeVerifier(null);
await this.stateService.setSsoState(null);
if (
qParams.code != null &&
codeVerifier != null &&
state != null &&
this.checkState(state, qParams.state)
) {
await this.logIn(
qParams.code,
codeVerifier,
this.getOrgIdentifierFromState(qParams.state)
);
}
} else if (
qParams.clientId != null &&
qParams.redirectUri != null &&
qParams.state != null &&
qParams.codeChallenge != null
) {
this.redirectUri = qParams.redirectUri;
this.state = qParams.state;
this.codeChallenge = qParams.codeChallenge;
this.clientId = qParams.clientId;
}
});
}
async submit(returnUri?: string, includeUserIdentifier?: boolean) {
this.initiateSsoFormPromise = this.preValidate();
if (await this.initiateSsoFormPromise) {
const authorizeUrl = await this.buildAuthorizeUrl(returnUri, includeUserIdentifier);
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
}
}
async preValidate(): Promise<boolean> {
if (this.identifier == null || this.identifier === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("ssoValidationFailed"),
this.i18nService.t("ssoIdentifierRequired")
);
return false;
}
return await this.apiService.preValidateSso(this.identifier);
}
protected async buildAuthorizeUrl(
returnUri?: string,
includeUserIdentifier?: boolean
): Promise<string> {
let codeChallenge = this.codeChallenge;
let state = this.state;
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
if (codeChallenge == null) {
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.stateService.setSsoCodeVerifier(codeVerifier);
}
async submit(returnUri?: string, includeUserIdentifier?: boolean) {
this.initiateSsoFormPromise = this.preValidate();
if (await this.initiateSsoFormPromise) {
const authorizeUrl = await this.buildAuthorizeUrl(returnUri, includeUserIdentifier);
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
}
if (state == null) {
state = await this.passwordGenerationService.generatePassword(passwordOptions);
if (returnUri) {
state += `_returnUri='${returnUri}'`;
}
}
async preValidate(): Promise<boolean> {
if (this.identifier == null || this.identifier === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('ssoValidationFailed'),
this.i18nService.t('ssoIdentifierRequired'));
return false;
}
return await this.apiService.preValidateSso(this.identifier);
// Add Organization Identifier to state
state += `_identifier=${this.identifier}`;
// Save state (regardless of new or existing)
await this.stateService.setSsoState(state);
let authorizeUrl =
this.environmentService.getIdentityUrl() +
"/connect/authorize?" +
"client_id=" +
this.clientId +
"&redirect_uri=" +
encodeURIComponent(this.redirectUri) +
"&" +
"response_type=code&scope=api offline_access&" +
"state=" +
state +
"&code_challenge=" +
codeChallenge +
"&" +
"code_challenge_method=S256&response_mode=query&" +
"domain_hint=" +
encodeURIComponent(this.identifier);
if (includeUserIdentifier) {
const userIdentifier = await this.apiService.getSsoUserIdentifier();
authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`;
}
protected async buildAuthorizeUrl(returnUri?: string, includeUserIdentifier?: boolean): Promise<string> {
let codeChallenge = this.codeChallenge;
let state = this.state;
return authorizeUrl;
}
const passwordOptions: any = {
type: 'password',
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
if (codeChallenge == null) {
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.stateService.setSsoCodeVerifier(codeVerifier);
private async logIn(code: string, codeVerifier: string, orgIdFromState: string) {
this.loggingIn = true;
try {
this.formPromise = this.authService.logInSso(
code,
codeVerifier,
this.redirectUri,
orgIdFromState
);
const response = await this.formPromise;
if (response.twoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.router.navigate([this.twoFactorRoute], {
queryParams: {
identifier: orgIdFromState,
sso: "true",
},
});
}
if (state == null) {
state = await this.passwordGenerationService.generatePassword(passwordOptions);
if (returnUri) {
state += `_returnUri='${returnUri}'`;
}
} else if (response.resetMasterPassword) {
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
this.onSuccessfulLoginChangePasswordNavigate();
} else {
this.router.navigate([this.changePasswordRoute], {
queryParams: {
identifier: orgIdFromState,
},
});
}
// Add Organization Identifier to state
state += `_identifier=${this.identifier}`;
// Save state (regardless of new or existing)
await this.stateService.setSsoState(state);
let authorizeUrl = this.environmentService.getIdentityUrl() + '/connect/authorize?' +
'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' +
'response_type=code&scope=api offline_access&' +
'state=' + state + '&code_challenge=' + codeChallenge + '&' +
'code_challenge_method=S256&response_mode=query&' +
'domain_hint=' + encodeURIComponent(this.identifier);
if (includeUserIdentifier) {
const userIdentifier = await this.apiService.getSsoUserIdentifier();
authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`;
} else if (response.forcePasswordReset) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute]);
}
}
} catch (e) {
this.logService.error(e);
if (e.message === "Unable to reach key connector") {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("ssoKeyConnectorUnavailable")
);
}
}
this.loggingIn = false;
}
return authorizeUrl;
private getOrgIdentifierFromState(state: string): string {
if (state === null || state === undefined) {
return null;
}
private async logIn(code: string, codeVerifier: string, orgIdFromState: string) {
this.loggingIn = true;
try {
this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri, orgIdFromState);
const response = await this.formPromise;
if (response.twoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.router.navigate([this.twoFactorRoute], {
queryParams: {
identifier: orgIdFromState,
sso: 'true',
},
});
}
} else if (response.resetMasterPassword) {
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
this.onSuccessfulLoginChangePasswordNavigate();
} else {
this.router.navigate([this.changePasswordRoute], {
queryParams: {
identifier: orgIdFromState,
},
});
}
} else if (response.forcePasswordReset) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute]);
}
}
} catch (e) {
this.logService.error(e);
if (e.message === 'Unable to reach key connector') {
this.platformUtilsService.showToast('error', null, this.i18nService.t('ssoKeyConnectorUnavailable'));
}
}
this.loggingIn = false;
const stateSplit = state.split("_identifier=");
return stateSplit.length > 1 ? stateSplit[1] : null;
}
private checkState(state: string, checkState: string): boolean {
if (state === null || state === undefined) {
return false;
}
if (checkState === null || checkState === undefined) {
return false;
}
private getOrgIdentifierFromState(state: string): string {
if (state === null || state === undefined) {
return null;
}
const stateSplit = state.split('_identifier=');
return stateSplit.length > 1 ? stateSplit[1] : null;
}
private checkState(state: string, checkState: string): boolean {
if (state === null || state === undefined) {
return false;
}
if (checkState === null || checkState === undefined) {
return false;
}
const stateSplit = state.split('_identifier=');
const checkStateSplit = checkState.split('_identifier=');
return stateSplit[0] === checkStateSplit[0];
}
const stateSplit = state.split("_identifier=");
const checkStateSplit = checkState.split("_identifier=");
return stateSplit[0] === checkStateSplit[0];
}
}

View File

@@ -1,85 +1,95 @@
import { animate, state, style, transition, trigger } from "@angular/animations";
import { CommonModule } from "@angular/common";
import { Component, ModuleWithProviders, NgModule } from "@angular/core";
import {
animate,
state,
style,
transition,
trigger
} from '@angular/animations';
import { CommonModule } from '@angular/common';
import { Component, ModuleWithProviders, NgModule } from '@angular/core';
import { DefaultNoComponentGlobalConfig, GlobalConfig, Toast as BaseToast, ToastPackage, ToastrService, TOAST_CONFIG } from 'ngx-toastr';
DefaultNoComponentGlobalConfig,
GlobalConfig,
Toast as BaseToast,
ToastPackage,
ToastrService,
TOAST_CONFIG,
} from "ngx-toastr";
@Component({
selector: '[toast-component2]',
template: `
<button *ngIf="options.closeButton" (click)="remove()" type="button" class="toast-close-button" aria-label="Close">
<span aria-hidden="true">&times;</span>
selector: "[toast-component2]",
template: `
<button
*ngIf="options.closeButton"
(click)="remove()"
type="button"
class="toast-close-button"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
<div class="icon">
<i></i>
<i></i>
</div>
<div>
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
</div>
<div *ngIf="message && options.enableHtml" role="alertdialog" aria-live="polite"
[class]="options.messageClass" [innerHTML]="message">
</div>
<div *ngIf="message && !options.enableHtml" role="alertdialog" aria-live="polite"
[class]="options.messageClass" [attr.aria-label]="message">
{{ message }}
</div>
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
</div>
<div
*ngIf="message && options.enableHtml"
role="alertdialog"
aria-live="polite"
[class]="options.messageClass"
[innerHTML]="message"
></div>
<div
*ngIf="message && !options.enableHtml"
role="alertdialog"
aria-live="polite"
[class]="options.messageClass"
[attr.aria-label]="message"
>
{{ message }}
</div>
</div>
<div *ngIf="options.progressBar">
<div class="toast-progress" [style.width]="width + '%'"></div>
<div class="toast-progress" [style.width]="width + '%'"></div>
</div>
`,
animations: [
trigger('flyInOut', [
state('inactive', style({ opacity: 0 })),
state('active', style({ opacity: 1 })),
state('removed', style({ opacity: 0 })),
transition(
'inactive => active',
animate('{{ easeTime }}ms {{ easing }}')
),
transition(
'active => removed',
animate('{{ easeTime }}ms {{ easing }}')
),
]),
],
preserveWhitespaces: false,
`,
animations: [
trigger("flyInOut", [
state("inactive", style({ opacity: 0 })),
state("active", style({ opacity: 1 })),
state("removed", style({ opacity: 0 })),
transition("inactive => active", animate("{{ easeTime }}ms {{ easing }}")),
transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")),
]),
],
preserveWhitespaces: false,
})
export class BitwardenToast extends BaseToast {
constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) {
super(toastrService, toastPackage);
}
constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) {
super(toastrService, toastPackage);
}
}
export const BitwardenToastGlobalConfig: GlobalConfig = {
...DefaultNoComponentGlobalConfig,
toastComponent: BitwardenToast,
};
...DefaultNoComponentGlobalConfig,
toastComponent: BitwardenToast,
};
@NgModule({
imports: [CommonModule],
declarations: [BitwardenToast],
exports: [BitwardenToast],
imports: [CommonModule],
declarations: [BitwardenToast],
exports: [BitwardenToast],
})
export class BitwardenToastModule {
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<BitwardenToastModule> {
return {
ngModule: BitwardenToastModule,
providers: [
{
provide: TOAST_CONFIG,
useValue: {
default: BitwardenToastGlobalConfig,
config: config,
},
},
],
};
}
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<BitwardenToastModule> {
return {
ngModule: BitwardenToastModule,
providers: [
{
provide: TOAST_CONFIG,
useValue: {
default: BitwardenToastGlobalConfig,
config: config,
},
},
],
};
}
}

View File

@@ -1,38 +1,37 @@
import {
Directive,
EventEmitter,
OnInit,
Output,
} from '@angular/core';
import { Router } from '@angular/router';
import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
import { Router } from "@angular/router";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Directive()
export class TwoFactorOptionsComponent implements OnInit {
@Output() onProviderSelected = new EventEmitter<TwoFactorProviderType>();
@Output() onRecoverSelected = new EventEmitter();
@Output() onProviderSelected = new EventEmitter<TwoFactorProviderType>();
@Output() onRecoverSelected = new EventEmitter();
providers: any[] = [];
providers: any[] = [];
constructor(protected authService: AuthService, protected router: Router,
protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected win: Window) { }
constructor(
protected authService: AuthService,
protected router: Router,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected win: Window
) {}
ngOnInit() {
this.providers = this.authService.getSupportedTwoFactorProviders(this.win);
}
ngOnInit() {
this.providers = this.authService.getSupportedTwoFactorProviders(this.win);
}
choose(p: any) {
this.onProviderSelected.emit(p.type);
}
choose(p: any) {
this.onProviderSelected.emit(p.type);
}
recover() {
this.platformUtilsService.launchUri('https://help.bitwarden.com/article/lost-two-step-device/');
this.onRecoverSelected.emit();
}
recover() {
this.platformUtilsService.launchUri("https://help.bitwarden.com/article/lost-two-step-device/");
this.onRecoverSelected.emit();
}
}

View File

@@ -1,251 +1,280 @@
import { Directive, OnDestroy, OnInit } from '@angular/core';
import { Directive, OnDestroy, OnInit } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { TwoFactorEmailRequest } from 'jslib-common/models/request/twoFactorEmailRequest';
import { TwoFactorEmailRequest } from "jslib-common/models/request/twoFactorEmailRequest";
import { AuthResult } from 'jslib-common/models/domain/authResult';
import { AuthResult } from "jslib-common/models/domain/authResult";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.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 { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.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 { StateService } from "jslib-common/abstractions/state.service";
import { TwoFactorProviders } from 'jslib-common/services/auth.service';
import { TwoFactorProviders } from "jslib-common/services/auth.service";
import * as DuoWebSDK from 'duo_web_sdk';
import { WebAuthnIFrame } from 'jslib-common/misc/webauthn_iframe';
import * as DuoWebSDK from "duo_web_sdk";
import { WebAuthnIFrame } from "jslib-common/misc/webauthn_iframe";
@Directive()
export class TwoFactorComponent implements OnInit, OnDestroy {
token: string = '';
remember: boolean = false;
webAuthnReady: boolean = false;
webAuthnNewTab: boolean = false;
providers = TwoFactorProviders;
providerType = TwoFactorProviderType;
selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
webAuthnSupported: boolean = false;
webAuthn: WebAuthnIFrame = null;
title: string = '';
twoFactorEmail: string = null;
formPromise: Promise<any>;
emailPromise: Promise<any>;
identifier: string = null;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
token: string = "";
remember: boolean = false;
webAuthnReady: boolean = false;
webAuthnNewTab: boolean = false;
providers = TwoFactorProviders;
providerType = TwoFactorProviderType;
selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
webAuthnSupported: boolean = false;
webAuthn: WebAuthnIFrame = null;
title: string = "";
twoFactorEmail: string = null;
formPromise: Promise<any>;
emailPromise: Promise<any>;
identifier: string = null;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
get webAuthnAllow(): string {
return `publickey-credentials-get ${this.environmentService.getWebVaultUrl()}`;
get webAuthnAllow(): string {
return `publickey-credentials-get ${this.environmentService.getWebVaultUrl()}`;
}
protected loginRoute = "login";
protected successRoute = "vault";
constructor(
protected authService: AuthService,
protected router: Router,
protected i18nService: I18nService,
protected apiService: ApiService,
protected platformUtilsService: PlatformUtilsService,
protected win: Window,
protected environmentService: EnvironmentService,
protected stateService: StateService,
protected route: ActivatedRoute,
protected logService: LogService
) {
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
}
async ngOnInit() {
if (!this.authing || this.authService.twoFactorProvidersData == null) {
this.router.navigate([this.loginRoute]);
return;
}
protected loginRoute = 'login';
protected successRoute = 'vault';
this.route.queryParams.pipe(first()).subscribe((qParams) => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
}
});
constructor(protected authService: AuthService, protected router: Router,
protected i18nService: I18nService, protected apiService: ApiService,
protected platformUtilsService: PlatformUtilsService, protected win: Window,
protected environmentService: EnvironmentService, protected stateService: StateService,
protected route: ActivatedRoute, protected logService: LogService) {
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
if (this.needsLock) {
this.successRoute = "lock";
}
async ngOnInit() {
if (!this.authing || this.authService.twoFactorProvidersData == null) {
this.router.navigate([this.loginRoute]);
return;
if (this.win != null && this.webAuthnSupported) {
const webVaultUrl = this.environmentService.getWebVaultUrl();
this.webAuthn = new WebAuthnIFrame(
this.win,
webVaultUrl,
this.webAuthnNewTab,
this.platformUtilsService,
this.i18nService,
(token: string) => {
this.token = token;
this.submit();
},
(error: string) => {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error);
},
(info: string) => {
if (info === "ready") {
this.webAuthnReady = true;
}
}
this.route.queryParams.pipe(first()).subscribe(qParams => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
}
});
if (this.needsLock) {
this.successRoute = 'lock';
}
if (this.win != null && this.webAuthnSupported) {
const webVaultUrl = this.environmentService.getWebVaultUrl();
this.webAuthn = new WebAuthnIFrame(this.win, webVaultUrl, this.webAuthnNewTab, this.platformUtilsService,
this.i18nService, (token: string) => {
this.token = token;
this.submit();
}, (error: string) => {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error);
}, (info: string) => {
if (info === 'ready') {
this.webAuthnReady = true;
}
}
);
}
this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(this.webAuthnSupported);
await this.init();
);
}
ngOnDestroy(): void {
this.cleanupWebAuthn();
this.webAuthn = null;
this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(
this.webAuthnSupported
);
await this.init();
}
ngOnDestroy(): void {
this.cleanupWebAuthn();
this.webAuthn = null;
}
async init() {
if (this.selectedProviderType == null) {
this.title = this.i18nService.t("loginUnavailable");
return;
}
async init() {
if (this.selectedProviderType == null) {
this.title = this.i18nService.t('loginUnavailable');
return;
this.cleanupWebAuthn();
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
switch (this.selectedProviderType) {
case TwoFactorProviderType.WebAuthn:
if (!this.webAuthnNewTab) {
setTimeout(() => {
this.authWebAuthn();
}, 500);
}
break;
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.OrganizationDuo:
setTimeout(() => {
DuoWebSDK.init({
iframe: undefined,
host: providerData.Host,
sig_request: providerData.Signature,
submit_callback: async (f: HTMLFormElement) => {
const sig = f.querySelector('input[name="sig_response"]') as HTMLInputElement;
if (sig != null) {
this.token = sig.value;
await this.submit();
}
},
});
}, 0);
break;
case TwoFactorProviderType.Email:
this.twoFactorEmail = providerData.Email;
if (this.authService.twoFactorProvidersData.size > 1) {
await this.sendEmail(false);
}
break;
default:
break;
}
}
this.cleanupWebAuthn();
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
switch (this.selectedProviderType) {
case TwoFactorProviderType.WebAuthn:
if (!this.webAuthnNewTab) {
setTimeout(() => {
this.authWebAuthn();
}, 500);
}
break;
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.OrganizationDuo:
setTimeout(() => {
DuoWebSDK.init({
iframe: undefined,
host: providerData.Host,
sig_request: providerData.Signature,
submit_callback: async (f: HTMLFormElement) => {
const sig = f.querySelector('input[name="sig_response"]') as HTMLInputElement;
if (sig != null) {
this.token = sig.value;
await this.submit();
}
},
});
}, 0);
break;
case TwoFactorProviderType.Email:
this.twoFactorEmail = providerData.Email;
if (this.authService.twoFactorProvidersData.size > 1) {
await this.sendEmail(false);
}
break;
default:
break;
}
async submit() {
if (this.token == null || this.token === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("verificationCodeRequired")
);
return;
}
async submit() {
if (this.token == null || this.token === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('verificationCodeRequired'));
return;
}
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) {
if (this.webAuthn != null) {
this.webAuthn.stop();
} else {
return;
}
} else if (this.selectedProviderType === TwoFactorProviderType.Email ||
this.selectedProviderType === TwoFactorProviderType.Authenticator) {
this.token = this.token.replace(' ', '').trim();
}
try {
await this.doSubmit();
} catch {
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) {
this.webAuthn.start();
}
}
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) {
if (this.webAuthn != null) {
this.webAuthn.stop();
} else {
return;
}
} else if (
this.selectedProviderType === TwoFactorProviderType.Email ||
this.selectedProviderType === TwoFactorProviderType.Authenticator
) {
this.token = this.token.replace(" ", "").trim();
}
async doSubmit() {
this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember);
const response: AuthResult = await this.formPromise;
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (response.resetMasterPassword) {
this.successRoute = 'set-password';
}
if (response.forcePasswordReset) {
this.successRoute = 'update-temp-password';
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute], {
queryParams: {
identifier: this.identifier,
},
});
}
try {
await this.doSubmit();
} catch {
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) {
this.webAuthn.start();
}
}
}
async doSubmit() {
this.formPromise = this.authService.logInTwoFactor(
this.selectedProviderType,
this.token,
this.remember
);
const response: AuthResult = await this.formPromise;
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (response.resetMasterPassword) {
this.successRoute = "set-password";
}
if (response.forcePasswordReset) {
this.successRoute = "update-temp-password";
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute], {
queryParams: {
identifier: this.identifier,
},
});
}
}
async sendEmail(doToast: boolean) {
if (this.selectedProviderType !== TwoFactorProviderType.Email) {
return;
}
async sendEmail(doToast: boolean) {
if (this.selectedProviderType !== TwoFactorProviderType.Email) {
return;
}
if (this.emailPromise != null) {
return;
}
try {
const request = new TwoFactorEmailRequest();
request.email = this.authService.email;
request.masterPasswordHash = this.authService.masterPasswordHash;
this.emailPromise = this.apiService.postTwoFactorEmail(request);
await this.emailPromise;
if (doToast) {
this.platformUtilsService.showToast('success', null,
this.i18nService.t('verificationCodeEmailSent', this.twoFactorEmail));
}
} catch (e) {
this.logService.error(e);
}
this.emailPromise = null;
if (this.emailPromise != null) {
return;
}
authWebAuthn() {
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
if (!this.webAuthnSupported || this.webAuthn == null) {
return;
}
this.webAuthn.init(providerData);
try {
const request = new TwoFactorEmailRequest();
request.email = this.authService.email;
request.masterPasswordHash = this.authService.masterPasswordHash;
this.emailPromise = this.apiService.postTwoFactorEmail(request);
await this.emailPromise;
if (doToast) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail)
);
}
} catch (e) {
this.logService.error(e);
}
private cleanupWebAuthn() {
if (this.webAuthn != null) {
this.webAuthn.stop();
this.webAuthn.cleanup();
}
this.emailPromise = null;
}
authWebAuthn() {
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
if (!this.webAuthnSupported || this.webAuthn == null) {
return;
}
get authing(): boolean {
return this.authService.authingWithPassword() || this.authService.authingWithSso() || this.authService.authingWithApiKey();
}
this.webAuthn.init(providerData);
}
get needsLock(): boolean {
return this.authService.authingWithSso() || this.authService.authingWithApiKey();
private cleanupWebAuthn() {
if (this.webAuthn != null) {
this.webAuthn.stop();
this.webAuthn.cleanup();
}
}
get authing(): boolean {
return (
this.authService.authingWithPassword() ||
this.authService.authingWithSso() ||
this.authService.authingWithApiKey()
);
}
get needsLock(): boolean {
return this.authService.authingWithSso() || this.authService.authingWithApiKey();
}
}

View File

@@ -1,109 +1,134 @@
import { Directive } from '@angular/core';
import { Directive } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component';
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
import { EncString } from 'jslib-common/models/domain/encString';
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { EncString } from "jslib-common/models/domain/encString";
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { UpdateTempPasswordRequest } from 'jslib-common/models/request/updateTempPasswordRequest';
import { UpdateTempPasswordRequest } from "jslib-common/models/request/updateTempPasswordRequest";
@Directive()
export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
hint: string;
key: string;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
showPassword: boolean = false;
hint: string;
key: string;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
showPassword: boolean = false;
onSuccessfulChangePassword: () => Promise<any>;
onSuccessfulChangePassword: () => Promise<any>;
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, messagingService: MessagingService,
private apiService: ApiService, stateService: StateService,
private syncService: SyncService, private logService: LogService) {
super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, stateService);
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
messagingService: MessagingService,
private apiService: ApiService,
stateService: StateService,
private syncService: SyncService,
private logService: LogService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
stateService
);
}
async ngOnInit() {
await this.syncService.fullSync(true);
super.ngOnInit();
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
async setupSubmitActions(): Promise<boolean> {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
this.email = await this.stateService.getEmail();
this.kdf = await this.stateService.getKdfType();
this.kdfIterations = await this.stateService.getKdfIterations();
return true;
}
async submit() {
// Validation
if (!(await this.strongPassword())) {
return;
}
async ngOnInit() {
await this.syncService.fullSync(true);
super.ngOnInit();
if (!(await this.setupSubmitActions())) {
return;
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus();
try {
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(
this.masterPassword,
this.email.trim().toLowerCase(),
this.kdf,
this.kdfIterations
);
const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey);
// Grab user's current enc key
const userEncKey = await this.cryptoService.getEncKey();
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
await this.performSubmitActions(newPasswordHash, newKey, newEncKey);
} catch (e) {
this.logService.error(e);
}
}
async setupSubmitActions(): Promise<boolean> {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
this.email = await this.stateService.getEmail();
this.kdf = await this.stateService.getKdfType();
this.kdfIterations = await this.stateService.getKdfIterations();
return true;
}
async submit() {
// Validation
if (!await this.strongPassword()) {
return;
}
if (!await this.setupSubmitActions()) {
return;
}
try {
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(this.masterPassword, this.email.trim().toLowerCase(),
this.kdf, this.kdfIterations);
const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey);
// Grab user's current enc key
const userEncKey = await this.cryptoService.getEncKey();
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
await this.performSubmitActions(newPasswordHash, newKey, newEncKey);
} catch (e) {
this.logService.error(e);
}
}
async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]) {
try {
// Create request
const request = new UpdateTempPasswordRequest();
request.key = encKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
// Update user's password
this.formPromise = this.apiService.putUpdateTempPassword(request);
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('updatedMasterPassword'));
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
this.messagingService.send('logout');
}
} catch (e) {
this.logService.error(e);
}
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
) {
try {
// Create request
const request = new UpdateTempPasswordRequest();
request.key = encKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
// Update user's password
this.formPromise = this.apiService.putUpdateTempPassword(request);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("updatedMasterPassword")
);
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
this.messagingService.send("logout");
}
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,25 +1,46 @@
<ng-container *ngIf="!usesKeyConnector">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
<small class="form-text text-muted">{{'confirmIdentity' | i18n}}</small>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
</ng-container>
<ng-container *ngIf="usesKeyConnector">
<div class="form-group">
<label class="d-block">{{'sendVerificationCode' | i18n}}</label>
<button type="button" class="btn btn-outline-secondary" (click)="requestOTP()" [disabled]="disableRequestOTP">
{{'sendCode' | i18n}}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{'codeSent' | i18n}}
</span>
</div>
<div class="form-group">
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button
type="button"
class="btn btn-outline-secondary"
(click)="requestOTP()"
[disabled]="disableRequestOTP"
>
{{ "sendCode" | i18n }}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{ "codeSent" | i18n }}
</span>
</div>
<div class="form-group">
<label for="verificationCode">{{'verificationCode' | i18n}}</label>
<input id="verificationCode" type="input" name="verificationCode" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
<small class="form-text text-muted">{{'confirmIdentity' | i18n}}</small>
</div>
<div class="form-group">
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input
id="verificationCode"
type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
</div>
</ng-container>

View File

@@ -1,105 +1,92 @@
import {
animate,
style,
transition,
trigger,
} from '@angular/animations';
import {
Component,
OnInit,
} from '@angular/core';
import {
ControlValueAccessor,
FormControl,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { animate, style, transition, trigger } from "@angular/animations";
import { Component, OnInit } from "@angular/core";
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from "@angular/forms";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
import { VerificationType } from 'jslib-common/enums/verificationType';
import { VerificationType } from "jslib-common/enums/verificationType";
import { Verification } from 'jslib-common/types/verification';
import { Verification } from "jslib-common/types/verification";
@Component({
selector: 'app-verify-master-password',
templateUrl: 'verify-master-password.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger('sent', [
transition(':enter', [
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
]),
]),
],
selector: "app-verify-master-password",
templateUrl: "verify-master-password.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger("sent", [
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
})
export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnInit {
usesKeyConnector: boolean = false;
disableRequestOTP: boolean = false;
sentCode: boolean = false;
usesKeyConnector: boolean = false;
disableRequestOTP: boolean = false;
sentCode: boolean = false;
secret = new FormControl('');
secret = new FormControl("");
private onChange: (value: Verification) => void;
private onChange: (value: Verification) => void;
constructor(private keyConnectorService: KeyConnectorService,
private userVerificationService: UserVerificationService) { }
constructor(
private keyConnectorService: KeyConnectorService,
private userVerificationService: UserVerificationService
) {}
async ngOnInit() {
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.processChanges(this.secret.value);
async ngOnInit() {
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.processChanges(this.secret.value);
this.secret.valueChanges.subscribe(secret => this.processChanges(secret));
this.secret.valueChanges.subscribe((secret) => this.processChanges(secret));
}
async requestOTP() {
if (this.usesKeyConnector) {
this.disableRequestOTP = true;
try {
await this.userVerificationService.requestOTP();
this.sentCode = true;
} finally {
this.disableRequestOTP = false;
}
}
}
writeValue(obj: any): void {
this.secret.setValue(obj);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
// Not implemented
}
setDisabledState?(isDisabled: boolean): void {
this.disableRequestOTP = isDisabled;
if (isDisabled) {
this.secret.disable();
} else {
this.secret.enable();
}
}
private processChanges(secret: string) {
if (this.onChange == null) {
return;
}
async requestOTP() {
if (this.usesKeyConnector) {
this.disableRequestOTP = true;
try {
await this.userVerificationService.requestOTP();
this.sentCode = true;
} finally {
this.disableRequestOTP = false;
}
}
}
writeValue(obj: any): void {
this.secret.setValue(obj);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
// Not implemented
}
setDisabledState?(isDisabled: boolean): void {
this.disableRequestOTP = isDisabled;
if (isDisabled) {
this.secret.disable();
} else {
this.secret.enable();
}
}
private processChanges(secret: string) {
if (this.onChange == null) {
return;
}
this.onChange({
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
secret: secret,
});
}
this.onChange({
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
secret: secret,
});
}
}

View File

@@ -1,35 +1,32 @@
import {
Directive,
Input,
} from '@angular/core';
import { Directive, Input } from "@angular/core";
import { EventType } from 'jslib-common/enums/eventType';
import { FieldType } from 'jslib-common/enums/fieldType';
import { EventType } from "jslib-common/enums/eventType";
import { FieldType } from "jslib-common/enums/fieldType";
import { EventService } from 'jslib-common/abstractions/event.service';
import { EventService } from "jslib-common/abstractions/event.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { FieldView } from 'jslib-common/models/view/fieldView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { FieldView } from "jslib-common/models/view/fieldView";
@Directive()
export class ViewCustomFieldsComponent {
@Input() cipher: CipherView;
@Input() promptPassword: () => Promise<boolean>;
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
@Input() cipher: CipherView;
@Input() promptPassword: () => Promise<boolean>;
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
fieldType = FieldType;
fieldType = FieldType;
constructor(private eventService: EventService) { }
constructor(private eventService: EventService) {}
async toggleFieldValue(field: FieldView) {
if (!await this.promptPassword()) {
return;
}
const f = (field as any);
f.showValue = !f.showValue;
if (f.showValue) {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
}
async toggleFieldValue(field: FieldView) {
if (!(await this.promptPassword())) {
return;
}
const f = field as any;
f.showValue = !f.showValue;
if (f.showValue) {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
}
}
}

View File

@@ -1,394 +1,447 @@
import {
ChangeDetectorRef,
Directive,
EventEmitter,
Input,
NgZone,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
ChangeDetectorRef,
Directive,
EventEmitter,
Input,
NgZone,
OnDestroy,
OnInit,
Output,
} from "@angular/core";
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
import { CipherType } from 'jslib-common/enums/cipherType';
import { EventType } from 'jslib-common/enums/eventType';
import { FieldType } from 'jslib-common/enums/fieldType';
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
import { CipherType } from "jslib-common/enums/cipherType";
import { EventType } from "jslib-common/enums/eventType";
import { FieldType } from "jslib-common/enums/fieldType";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { TotpService } from 'jslib-common/abstractions/totp.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuditService } from "jslib-common/abstractions/audit.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { TotpService } from "jslib-common/abstractions/totp.service";
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { AttachmentView } from 'jslib-common/models/view/attachmentView';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { LoginUriView } from 'jslib-common/models/view/loginUriView';
import { AttachmentView } from "jslib-common/models/view/attachmentView";
import { CipherView } from "jslib-common/models/view/cipherView";
import { LoginUriView } from "jslib-common/models/view/loginUriView";
const BroadcasterSubscriptionId = 'ViewComponent';
const BroadcasterSubscriptionId = "ViewComponent";
@Directive()
export class ViewComponent implements OnDestroy, OnInit {
@Input() cipherId: string;
@Output() onEditCipher = new EventEmitter<CipherView>();
@Output() onCloneCipher = new EventEmitter<CipherView>();
@Output() onShareCipher = new EventEmitter<CipherView>();
@Output() onDeletedCipher = new EventEmitter<CipherView>();
@Output() onRestoredCipher = new EventEmitter<CipherView>();
@Input() cipherId: string;
@Output() onEditCipher = new EventEmitter<CipherView>();
@Output() onCloneCipher = new EventEmitter<CipherView>();
@Output() onShareCipher = new EventEmitter<CipherView>();
@Output() onDeletedCipher = new EventEmitter<CipherView>();
@Output() onRestoredCipher = new EventEmitter<CipherView>();
cipher: CipherView;
showPassword: boolean;
showCardNumber: boolean;
showCardCode: boolean;
canAccessPremium: boolean;
totpCode: string;
totpCodeFormatted: string;
totpDash: number;
totpSec: number;
totpLow: boolean;
fieldType = FieldType;
checkPasswordPromise: Promise<number>;
cipher: CipherView;
showPassword: boolean;
showCardNumber: boolean;
showCardCode: boolean;
canAccessPremium: boolean;
totpCode: string;
totpCodeFormatted: string;
totpDash: number;
totpSec: number;
totpLow: boolean;
fieldType = FieldType;
checkPasswordPromise: Promise<number>;
private totpInterval: any;
private previousCipherId: string;
private passwordReprompted: boolean = false;
private totpInterval: any;
private previousCipherId: string;
private passwordReprompted: boolean = false;
constructor(protected cipherService: CipherService, protected totpService: TotpService,
protected tokenService: TokenService, protected i18nService: I18nService,
protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService,
protected auditService: AuditService, protected win: Window,
protected broadcasterService: BroadcasterService, protected ngZone: NgZone,
protected changeDetectorRef: ChangeDetectorRef, protected eventService: EventService,
protected apiService: ApiService, protected passwordRepromptService: PasswordRepromptService,
private logService: LogService, protected stateService: StateService) { }
constructor(
protected cipherService: CipherService,
protected totpService: TotpService,
protected tokenService: TokenService,
protected i18nService: I18nService,
protected cryptoService: CryptoService,
protected platformUtilsService: PlatformUtilsService,
protected auditService: AuditService,
protected win: Window,
protected broadcasterService: BroadcasterService,
protected ngZone: NgZone,
protected changeDetectorRef: ChangeDetectorRef,
protected eventService: EventService,
protected apiService: ApiService,
protected passwordRepromptService: PasswordRepromptService,
private logService: LogService,
protected stateService: StateService
) {}
ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'syncCompleted':
if (message.successfully) {
await this.load();
this.changeDetectorRef.detectChanges();
}
break;
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.cleanUp();
}
async load() {
this.cleanUp();
const cipher = await this.cipherService.get(this.cipherId);
this.cipher = await cipher.decrypt();
this.canAccessPremium = await this.stateService.getCanAccessPremium();
if (this.cipher.type === CipherType.Login && this.cipher.login.totp &&
(cipher.organizationUseTotp || this.canAccessPremium)) {
await this.totpUpdateCode();
const interval = this.totpService.getTimeInterval(this.cipher.login.totp);
await this.totpTick(interval);
this.totpInterval = setInterval(async () => {
await this.totpTick(interval);
}, 1000);
}
if (this.previousCipherId !== this.cipherId) {
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
}
this.previousCipherId = this.cipherId;
}
async edit() {
if (await this.promptPassword()) {
this.onEditCipher.emit(this.cipher);
return true;
}
return false;
}
async clone() {
if (await this.promptPassword()) {
this.onCloneCipher.emit(this.cipher);
return true;
}
return false;
}
async share() {
if (await this.promptPassword()) {
this.onShareCipher.emit(this.cipher);
return true;
}
return false;
}
async delete(): Promise<boolean> {
if (!await this.promptPassword()) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeleteItemConfirmation' : 'deleteItemConfirmation'),
this.i18nService.t('deleteItem'), this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
await this.deleteCipher();
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeletedItem' : 'deletedItem'));
this.onDeletedCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
}
return true;
}
async restore(): Promise<boolean> {
if (!this.cipher.isDeleted) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('restoreItemConfirmation'), this.i18nService.t('restoreItem'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
await this.restoreCipher();
this.platformUtilsService.showToast('success', null, this.i18nService.t('restoredItem'));
this.onRestoredCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
}
return true;
}
async togglePassword() {
if (!await this.promptPassword()) {
return;
}
this.showPassword = !this.showPassword;
if (this.showPassword) {
this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId);
}
}
async toggleCardNumber() {
if (!await this.promptPassword()) {
return;
}
this.showCardNumber = !this.showCardNumber;
if (this.showCardNumber) {
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
}
}
async toggleCardCode() {
if (!await this.promptPassword()) {
return;
}
this.showCardCode = !this.showCardCode;
if (this.showCardCode) {
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
}
}
async checkPassword() {
if (this.cipher.login == null || this.cipher.login.password == null || this.cipher.login.password === '') {
return;
}
this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password);
const matches = await this.checkPasswordPromise;
if (matches > 0) {
this.platformUtilsService.showToast('warning', null,
this.i18nService.t('passwordExposed', matches.toString()));
} else {
this.platformUtilsService.showToast('success', null, this.i18nService.t('passwordSafe'));
}
}
launch(uri: LoginUriView, cipherId?: string) {
if (!uri.canLaunch) {
return;
}
if (cipherId) {
this.cipherService.updateLastLaunchedDate(cipherId);
}
this.platformUtilsService.launchUri(uri.launchUri);
}
async copy(value: string, typeI18nKey: string, aType: string) {
if (value == null) {
return;
}
if (this.passwordRepromptService.protectedFields().includes(aType) && !await this.promptPassword()) {
return;
}
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(value, copyOptions);
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
if (typeI18nKey === 'password') {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId);
} else if (typeI18nKey === 'securityCode') {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
} else if (aType === 'H_Field') {
this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
}
}
setTextDataOnDrag(event: DragEvent, data: string) {
event.dataTransfer.setData('text', data);
}
async downloadAttachment(attachment: AttachmentView) {
if (!await this.promptPassword()) {
return;
}
const a = (attachment as any);
if (a.downloading) {
return;
}
if (this.cipher.organizationId == null && !this.canAccessPremium) {
this.platformUtilsService.showToast('error', this.i18nService.t('premiumRequired'),
this.i18nService.t('premiumRequiredDesc'));
return;
}
let url: string;
try {
const attachmentDownloadResponse = await this.apiService.getAttachmentData(this.cipher.id, attachment.id);
url = attachmentDownloadResponse.url;
} catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
url = attachment.url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "syncCompleted":
if (message.successfully) {
await this.load();
this.changeDetectorRef.detectChanges();
}
break;
}
});
});
}
a.downloading = true;
const response = await fetch(new Request(url, { cache: 'no-store' }));
if (response.status !== 200) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
a.downloading = false;
return;
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.cleanUp();
}
try {
const buf = await response.arrayBuffer();
const key = attachment.key != null ? attachment.key :
await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
} catch (e) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
}
async load() {
this.cleanUp();
a.downloading = false;
const cipher = await this.cipherService.get(this.cipherId);
this.cipher = await cipher.decrypt();
this.canAccessPremium = await this.stateService.getCanAccessPremium();
if (
this.cipher.type === CipherType.Login &&
this.cipher.login.totp &&
(cipher.organizationUseTotp || this.canAccessPremium)
) {
await this.totpUpdateCode();
const interval = this.totpService.getTimeInterval(this.cipher.login.totp);
await this.totpTick(interval);
this.totpInterval = setInterval(async () => {
await this.totpTick(interval);
}, 1000);
}
protected deleteCipher() {
return this.cipher.isDeleted ? this.cipherService.deleteWithServer(this.cipher.id)
: this.cipherService.softDeleteWithServer(this.cipher.id);
if (this.previousCipherId !== this.cipherId) {
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
}
this.previousCipherId = this.cipherId;
}
async edit() {
if (await this.promptPassword()) {
this.onEditCipher.emit(this.cipher);
return true;
}
protected restoreCipher() {
return this.cipherService.restoreWithServer(this.cipher.id);
return false;
}
async clone() {
if (await this.promptPassword()) {
this.onCloneCipher.emit(this.cipher);
return true;
}
protected async promptPassword() {
if (this.cipher.reprompt === CipherRepromptType.None || this.passwordReprompted) {
return true;
}
return false;
}
return this.passwordReprompted = await this.passwordRepromptService.showPasswordPrompt();
async share() {
if (await this.promptPassword()) {
this.onShareCipher.emit(this.cipher);
return true;
}
private cleanUp() {
this.totpCode = null;
this.cipher = null;
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
this.passwordReprompted = false;
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
return false;
}
async delete(): Promise<boolean> {
if (!(await this.promptPassword())) {
return;
}
private async totpUpdateCode() {
if (this.cipher == null || this.cipher.type !== CipherType.Login || this.cipher.login.totp == null) {
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
return;
}
this.totpCode = await this.totpService.getCode(this.cipher.login.totp);
if (this.totpCode != null) {
if (this.totpCode.length > 4) {
const half = Math.floor(this.totpCode.length / 2);
this.totpCodeFormatted = this.totpCode.substring(0, half) + ' ' + this.totpCode.substring(half);
} else {
this.totpCodeFormatted = this.totpCode;
}
} else {
this.totpCodeFormatted = null;
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t(
this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation"
),
this.i18nService.t("deleteItem"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
private async totpTick(intervalSeconds: number) {
const epoch = Math.round(new Date().getTime() / 1000.0);
const mod = epoch % intervalSeconds;
this.totpSec = intervalSeconds - mod;
this.totpDash = +(Math.round((((78.6 / intervalSeconds) * mod) + 'e+2') as any) + 'e-2');
this.totpLow = this.totpSec <= 7;
if (mod === 0) {
await this.totpUpdateCode();
}
try {
await this.deleteCipher();
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem")
);
this.onDeletedCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
}
return true;
}
async restore(): Promise<boolean> {
if (!this.cipher.isDeleted) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("restoreItemConfirmation"),
this.i18nService.t("restoreItem"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
try {
await this.restoreCipher();
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem"));
this.onRestoredCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
}
return true;
}
async togglePassword() {
if (!(await this.promptPassword())) {
return;
}
this.showPassword = !this.showPassword;
if (this.showPassword) {
this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId);
}
}
async toggleCardNumber() {
if (!(await this.promptPassword())) {
return;
}
this.showCardNumber = !this.showCardNumber;
if (this.showCardNumber) {
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
}
}
async toggleCardCode() {
if (!(await this.promptPassword())) {
return;
}
this.showCardCode = !this.showCardCode;
if (this.showCardCode) {
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
}
}
async checkPassword() {
if (
this.cipher.login == null ||
this.cipher.login.password == null ||
this.cipher.login.password === ""
) {
return;
}
this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password);
const matches = await this.checkPasswordPromise;
if (matches > 0) {
this.platformUtilsService.showToast(
"warning",
null,
this.i18nService.t("passwordExposed", matches.toString())
);
} else {
this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe"));
}
}
launch(uri: LoginUriView, cipherId?: string) {
if (!uri.canLaunch) {
return;
}
if (cipherId) {
this.cipherService.updateLastLaunchedDate(cipherId);
}
this.platformUtilsService.launchUri(uri.launchUri);
}
async copy(value: string, typeI18nKey: string, aType: string) {
if (value == null) {
return;
}
if (
this.passwordRepromptService.protectedFields().includes(aType) &&
!(await this.promptPassword())
) {
return;
}
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(value, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey))
);
if (typeI18nKey === "password") {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId);
} else if (typeI18nKey === "securityCode") {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
} else if (aType === "H_Field") {
this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
}
}
setTextDataOnDrag(event: DragEvent, data: string) {
event.dataTransfer.setData("text", data);
}
async downloadAttachment(attachment: AttachmentView) {
if (!(await this.promptPassword())) {
return;
}
const a = attachment as any;
if (a.downloading) {
return;
}
if (this.cipher.organizationId == null && !this.canAccessPremium) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("premiumRequired"),
this.i18nService.t("premiumRequiredDesc")
);
return;
}
let url: string;
try {
const attachmentDownloadResponse = await this.apiService.getAttachmentData(
this.cipher.id,
attachment.id
);
url = attachmentDownloadResponse.url;
} catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
url = attachment.url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
}
}
a.downloading = true;
const response = await fetch(new Request(url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
a.downloading = false;
return;
}
try {
const buf = await response.arrayBuffer();
const key =
attachment.key != null
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
a.downloading = false;
}
protected deleteCipher() {
return this.cipher.isDeleted
? this.cipherService.deleteWithServer(this.cipher.id)
: this.cipherService.softDeleteWithServer(this.cipher.id);
}
protected restoreCipher() {
return this.cipherService.restoreWithServer(this.cipher.id);
}
protected async promptPassword() {
if (this.cipher.reprompt === CipherRepromptType.None || this.passwordReprompted) {
return true;
}
return (this.passwordReprompted = await this.passwordRepromptService.showPasswordPrompt());
}
private cleanUp() {
this.totpCode = null;
this.cipher = null;
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
this.passwordReprompted = false;
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
}
private async totpUpdateCode() {
if (
this.cipher == null ||
this.cipher.type !== CipherType.Login ||
this.cipher.login.totp == null
) {
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
return;
}
this.totpCode = await this.totpService.getCode(this.cipher.login.totp);
if (this.totpCode != null) {
if (this.totpCode.length > 4) {
const half = Math.floor(this.totpCode.length / 2);
this.totpCodeFormatted =
this.totpCode.substring(0, half) + " " + this.totpCode.substring(half);
} else {
this.totpCodeFormatted = this.totpCode;
}
} else {
this.totpCodeFormatted = null;
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
}
}
private async totpTick(intervalSeconds: number) {
const epoch = Math.round(new Date().getTime() / 1000.0);
const mod = epoch % intervalSeconds;
this.totpSec = intervalSeconds - mod;
this.totpDash = +(Math.round(((78.6 / intervalSeconds) * mod + "e+2") as any) + "e-2");
this.totpLow = this.totpSec <= 7;
if (mod === 0) {
await this.totpUpdateCode();
}
}
}

View File

@@ -1,28 +1,23 @@
import {
Directive,
ElementRef,
Input,
Renderer2,
} from '@angular/core';
import { Directive, ElementRef, Input, Renderer2 } from "@angular/core";
@Directive({
selector: '[appA11yTitle]',
selector: "[appA11yTitle]",
})
export class A11yTitleDirective {
@Input() set appA11yTitle(title: string) {
this.title = title;
@Input() set appA11yTitle(title: string) {
this.title = title;
}
private title: string;
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
if (!this.el.nativeElement.hasAttribute("title")) {
this.renderer.setAttribute(this.el.nativeElement, "title", this.title);
}
private title: string;
constructor(private el: ElementRef, private renderer: Renderer2) { }
ngOnInit() {
if (!this.el.nativeElement.hasAttribute('title')) {
this.renderer.setAttribute(this.el.nativeElement, 'title', this.title);
}
if (!this.el.nativeElement.hasAttribute('aria-label')) {
this.renderer.setAttribute(this.el.nativeElement, 'aria-label', this.title);
}
if (!this.el.nativeElement.hasAttribute("aria-label")) {
this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title);
}
}
}

View File

@@ -1,42 +1,46 @@
import {
Directive,
ElementRef,
Input,
OnChanges,
} from '@angular/core';
import { LogService } from 'jslib-common/abstractions/log.service';
import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
import { LogService } from "jslib-common/abstractions/log.service";
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { ValidationService } from '../services/validation.service';
import { ValidationService } from "../services/validation.service";
@Directive({
selector: '[appApiAction]',
selector: "[appApiAction]",
})
export class ApiActionDirective implements OnChanges {
@Input() appApiAction: Promise<any>;
@Input() appApiAction: Promise<any>;
constructor(private el: ElementRef, private validationService: ValidationService,
private logService: LogService) { }
constructor(
private el: ElementRef,
private validationService: ValidationService,
private logService: LogService
) {}
ngOnChanges(changes: any) {
if (this.appApiAction == null || this.appApiAction.then == null) {
return;
}
this.el.nativeElement.loading = true;
this.appApiAction.then((response: any) => {
this.el.nativeElement.loading = false;
}, (e: any) => {
this.el.nativeElement.loading = false;
if ((e instanceof ErrorResponse || e.constructor.name === 'ErrorResponse') && (e as ErrorResponse).captchaRequired) {
this.logService.error('Captcha required error response: ' + e.getSingleMessage());
return;
}
this.logService?.error(`Received API exception: ${e}`);
this.validationService.showError(e);
});
ngOnChanges(changes: any) {
if (this.appApiAction == null || this.appApiAction.then == null) {
return;
}
this.el.nativeElement.loading = true;
this.appApiAction.then(
(response: any) => {
this.el.nativeElement.loading = false;
},
(e: any) => {
this.el.nativeElement.loading = false;
if (
(e instanceof ErrorResponse || e.constructor.name === "ErrorResponse") &&
(e as ErrorResponse).captchaRequired
) {
this.logService.error("Captcha required error response: " + e.getSingleMessage());
return;
}
this.logService?.error(`Received API exception: ${e}`);
this.validationService.showError(e);
}
);
}
}

View File

@@ -1,33 +1,28 @@
import {
Directive,
ElementRef,
Input,
NgZone,
} from '@angular/core';
import { Directive, ElementRef, Input, NgZone } from "@angular/core";
import { take } from 'rxjs/operators';
import { take } from "rxjs/operators";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive({
selector: '[appAutofocus]',
selector: "[appAutofocus]",
})
export class AutofocusDirective {
@Input() set appAutofocus(condition: boolean | string) {
this.autofocus = condition === '' || condition === true;
}
private autofocus: boolean;
constructor(private el: ElementRef, private ngZone: NgZone) { }
ngOnInit() {
if (!Utils.isMobileBrowser && this.autofocus) {
if (this.ngZone.isStable) {
this.el.nativeElement.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus());
}
}
@Input() set appAutofocus(condition: boolean | string) {
this.autofocus = condition === "" || condition === true;
}
private autofocus: boolean;
constructor(private el: ElementRef, private ngZone: NgZone) {}
ngOnInit() {
if (!Utils.isMobileBrowser && this.autofocus) {
if (this.ngZone.isStable) {
this.el.nativeElement.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus());
}
}
}
}

View File

@@ -1,17 +1,12 @@
import {
Directive,
ElementRef,
HostListener,
} from '@angular/core';
import { Directive, ElementRef, HostListener } from "@angular/core";
@Directive({
selector: '[appBlurClick]',
selector: "[appBlurClick]",
})
export class BlurClickDirective {
constructor(private el: ElementRef) {
}
constructor(private el: ElementRef) {}
@HostListener('click') onClick() {
this.el.nativeElement.blur();
}
@HostListener("click") onClick() {
this.el.nativeElement.blur();
}
}

View File

@@ -1,51 +1,59 @@
import {
Directive,
ElementRef,
HostListener,
OnInit,
} from '@angular/core';
import { Directive, ElementRef, HostListener, OnInit } from "@angular/core";
@Directive({
selector: '[appBoxRow]',
selector: "[appBoxRow]",
})
export class BoxRowDirective implements OnInit {
el: HTMLElement = null;
formEls: Element[];
el: HTMLElement = null;
formEls: Element[];
constructor(private elRef: ElementRef) {
this.el = elRef.nativeElement;
constructor(private elRef: ElementRef) {
this.el = elRef.nativeElement;
}
ngOnInit(): void {
this.formEls = Array.from(
this.el.querySelectorAll('input:not([type="hidden"]), select, textarea')
);
this.formEls.forEach((formEl) => {
formEl.addEventListener(
"focus",
(event: Event) => {
this.el.classList.add("active");
},
false
);
formEl.addEventListener(
"blur",
(event: Event) => {
this.el.classList.remove("active");
},
false
);
});
}
@HostListener("click", ["$event"]) onClick(event: Event) {
const target = event.target as HTMLElement;
if (
target !== this.el &&
!target.classList.contains("progress") &&
!target.classList.contains("progress-bar")
) {
return;
}
ngOnInit(): void {
this.formEls = Array.from(this.el.querySelectorAll('input:not([type="hidden"]), select, textarea'));
this.formEls.forEach(formEl => {
formEl.addEventListener('focus', (event: Event) => {
this.el.classList.add('active');
}, false);
formEl.addEventListener('blur', (event: Event) => {
this.el.classList.remove('active');
}, false);
});
}
@HostListener('click', ['$event']) onClick(event: Event) {
const target = event.target as HTMLElement;
if (target !== this.el && !target.classList.contains('progress') &&
!target.classList.contains('progress-bar')) {
return;
}
if (this.formEls.length > 0) {
const formEl = (this.formEls[0] as HTMLElement);
if (formEl.tagName.toLowerCase() === 'input') {
const inputEl = (formEl as HTMLInputElement);
if (inputEl.type != null && inputEl.type.toLowerCase() === 'checkbox') {
inputEl.click();
return;
}
}
formEl.focus();
if (this.formEls.length > 0) {
const formEl = this.formEls[0] as HTMLElement;
if (formEl.tagName.toLowerCase() === "input") {
const inputEl = formEl as HTMLInputElement;
if (inputEl.type != null && inputEl.type.toLowerCase() === "checkbox") {
inputEl.click();
return;
}
}
formEl.focus();
}
}
}

View File

@@ -1,62 +1,76 @@
import {
CdkFixedSizeVirtualScroll,
FixedSizeVirtualScrollStrategy,
VIRTUAL_SCROLL_STRATEGY,
} from '@angular/cdk/scrolling';
import {
Directive,
forwardRef,
} from '@angular/core';
CdkFixedSizeVirtualScroll,
FixedSizeVirtualScrollStrategy,
VIRTUAL_SCROLL_STRATEGY,
} from "@angular/cdk/scrolling";
import { Directive, forwardRef } from "@angular/core";
// Custom virtual scroll strategy for cdk-virtual-scroll
// Uses a sample list item to set the itemSize for FixedSizeVirtualScrollStrategy
// The use case is the same as FixedSizeVirtualScrollStrategy, but it avoids locking in pixel sizes in the template.
export class CipherListVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
private checkItemSizeCallback: any;
private timeout: any;
private checkItemSizeCallback: any;
private timeout: any;
constructor(itemSize: number, minBufferPx: number, maxBufferPx: number, checkItemSizeCallback: any) {
super(itemSize, minBufferPx, maxBufferPx);
this.checkItemSizeCallback = checkItemSizeCallback;
constructor(
itemSize: number,
minBufferPx: number,
maxBufferPx: number,
checkItemSizeCallback: any
) {
super(itemSize, minBufferPx, maxBufferPx);
this.checkItemSizeCallback = checkItemSizeCallback;
}
onContentRendered() {
if (this.timeout != null) {
clearTimeout(this.timeout);
}
onContentRendered() {
if (this.timeout != null) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(this.checkItemSizeCallback, 500);
}
this.timeout = setTimeout(this.checkItemSizeCallback, 500);
}
}
export function _cipherListVirtualScrollStrategyFactory(cipherListDir: CipherListVirtualScroll) {
return cipherListDir._scrollStrategy;
return cipherListDir._scrollStrategy;
}
@Directive({
selector: 'cdk-virtual-scroll-viewport[itemSize]',
providers: [{
provide: VIRTUAL_SCROLL_STRATEGY,
useFactory: _cipherListVirtualScrollStrategyFactory,
deps: [forwardRef(() => CipherListVirtualScroll)],
}],
selector: "cdk-virtual-scroll-viewport[itemSize]",
providers: [
{
provide: VIRTUAL_SCROLL_STRATEGY,
useFactory: _cipherListVirtualScrollStrategyFactory,
deps: [forwardRef(() => CipherListVirtualScroll)],
},
],
})
export class CipherListVirtualScroll extends CdkFixedSizeVirtualScroll {
_scrollStrategy: CipherListVirtualScrollStrategy;
_scrollStrategy: CipherListVirtualScrollStrategy;
constructor() {
super();
this._scrollStrategy = new CipherListVirtualScrollStrategy(this.itemSize, this.minBufferPx, this.maxBufferPx,
this.checkAndUpdateItemSize);
}
checkAndUpdateItemSize = () => {
const sampleItem = document.querySelector('cdk-virtual-scroll-viewport .virtual-scroll-item') as HTMLElement;
const newItemSize = sampleItem?.offsetHeight;
if (newItemSize != null && newItemSize !== this.itemSize) {
this.itemSize = newItemSize;
this._scrollStrategy.updateItemAndBufferSize(this.itemSize, this.minBufferPx, this.maxBufferPx);
}
constructor() {
super();
this._scrollStrategy = new CipherListVirtualScrollStrategy(
this.itemSize,
this.minBufferPx,
this.maxBufferPx,
this.checkAndUpdateItemSize
);
}
checkAndUpdateItemSize = () => {
const sampleItem = document.querySelector(
"cdk-virtual-scroll-viewport .virtual-scroll-item"
) as HTMLElement;
const newItemSize = sampleItem?.offsetHeight;
if (newItemSize != null && newItemSize !== this.itemSize) {
this.itemSize = newItemSize;
this._scrollStrategy.updateItemAndBufferSize(
this.itemSize,
this.minBufferPx,
this.maxBufferPx
);
}
};
}

View File

@@ -1,20 +1,14 @@
import {
Directive,
ElementRef,
HostListener,
Input,
} from '@angular/core';
import { Directive, ElementRef, HostListener, Input } from "@angular/core";
@Directive({
selector: '[appFallbackSrc]',
selector: "[appFallbackSrc]",
})
export class FallbackSrcDirective {
@Input('appFallbackSrc') appFallbackSrc: string;
@Input("appFallbackSrc") appFallbackSrc: string;
constructor(private el: ElementRef) {
}
constructor(private el: ElementRef) {}
@HostListener('error') onError() {
this.el.nativeElement.src = this.appFallbackSrc;
}
@HostListener("error") onError() {
this.el.nativeElement.src = this.appFallbackSrc;
}
}

View File

@@ -1,37 +1,32 @@
import {
Directive,
ElementRef,
Input,
Renderer2,
} from '@angular/core';
import { Directive, ElementRef, Input, Renderer2 } from "@angular/core";
@Directive({
selector: '[appInputVerbatim]',
selector: "[appInputVerbatim]",
})
export class InputVerbatimDirective {
@Input() set appInputVerbatim(condition: boolean | string) {
this.disableComplete = condition === '' || condition === true;
@Input() set appInputVerbatim(condition: boolean | string) {
this.disableComplete = condition === "" || condition === true;
}
private disableComplete: boolean;
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
if (this.disableComplete && !this.el.nativeElement.hasAttribute("autocomplete")) {
this.renderer.setAttribute(this.el.nativeElement, "autocomplete", "off");
}
private disableComplete: boolean;
constructor(private el: ElementRef, private renderer: Renderer2) { }
ngOnInit() {
if (this.disableComplete && !this.el.nativeElement.hasAttribute('autocomplete')) {
this.renderer.setAttribute(this.el.nativeElement, 'autocomplete', 'off');
}
if (!this.el.nativeElement.hasAttribute('autocapitalize')) {
this.renderer.setAttribute(this.el.nativeElement, 'autocapitalize', 'none');
}
if (!this.el.nativeElement.hasAttribute('autocorrect')) {
this.renderer.setAttribute(this.el.nativeElement, 'autocorrect', 'none');
}
if (!this.el.nativeElement.hasAttribute('spellcheck')) {
this.renderer.setAttribute(this.el.nativeElement, 'spellcheck', 'false');
}
if (!this.el.nativeElement.hasAttribute('inputmode')) {
this.renderer.setAttribute(this.el.nativeElement, 'inputmode', 'verbatim');
}
if (!this.el.nativeElement.hasAttribute("autocapitalize")) {
this.renderer.setAttribute(this.el.nativeElement, "autocapitalize", "none");
}
if (!this.el.nativeElement.hasAttribute("autocorrect")) {
this.renderer.setAttribute(this.el.nativeElement, "autocorrect", "none");
}
if (!this.el.nativeElement.hasAttribute("spellcheck")) {
this.renderer.setAttribute(this.el.nativeElement, "spellcheck", "false");
}
if (!this.el.nativeElement.hasAttribute("inputmode")) {
this.renderer.setAttribute(this.el.nativeElement, "inputmode", "verbatim");
}
}
}

View File

@@ -1,41 +1,37 @@
import {
Directive,
ElementRef,
HostListener,
} from '@angular/core';
import { Directive, ElementRef, HostListener } from "@angular/core";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Directive({
selector: '[appSelectCopy]',
selector: "[appSelectCopy]",
})
export class SelectCopyDirective {
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) { }
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) {}
@HostListener('copy') onCopy() {
if (window == null) {
return;
}
let copyText = '';
const selection = window.getSelection();
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);
const text = range.toString();
// The selection should only contain one line of text. In some cases however, the
// selection contains newlines and space characters from the indentation of following
// sibling nodes. To avoid copying passwords containing trailing newlines and spaces
// that aren't part of the password, the selection has to be trimmed.
let stringEndPos = text.length;
const newLinePos = text.search(/(?:\r\n|\r|\n)/);
if (newLinePos > -1) {
const otherPart = text.substr(newLinePos).trim();
if (otherPart === '') {
stringEndPos = newLinePos;
}
}
copyText += text.substring(0, stringEndPos);
}
this.platformUtilsService.copyToClipboard(copyText, { window: window });
@HostListener("copy") onCopy() {
if (window == null) {
return;
}
let copyText = "";
const selection = window.getSelection();
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);
const text = range.toString();
// The selection should only contain one line of text. In some cases however, the
// selection contains newlines and space characters from the indentation of following
// sibling nodes. To avoid copying passwords containing trailing newlines and spaces
// that aren't part of the password, the selection has to be trimmed.
let stringEndPos = text.length;
const newLinePos = text.search(/(?:\r\n|\r|\n)/);
if (newLinePos > -1) {
const otherPart = text.substr(newLinePos).trim();
if (otherPart === "") {
stringEndPos = newLinePos;
}
}
copyText += text.substring(0, stringEndPos);
}
this.platformUtilsService.copyToClipboard(copyText, { window: window });
}
}

View File

@@ -1,13 +1,10 @@
import {
Directive,
HostListener,
} from '@angular/core';
import { Directive, HostListener } from "@angular/core";
@Directive({
selector: '[appStopClick]',
selector: "[appStopClick]",
})
export class StopClickDirective {
@HostListener('click', ['$event']) onClick($event: MouseEvent) {
$event.preventDefault();
}
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
$event.preventDefault();
}
}

View File

@@ -1,13 +1,10 @@
import {
Directive,
HostListener,
} from '@angular/core';
import { Directive, HostListener } from "@angular/core";
@Directive({
selector: '[appStopProp]',
selector: "[appStopProp]",
})
export class StopPropDirective {
@HostListener('click', ['$event']) onClick($event: MouseEvent) {
$event.stopPropagation();
}
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
$event.stopPropagation();
}
}

View File

@@ -1,54 +1,49 @@
import {
Directive,
ElementRef,
forwardRef,
HostListener,
Input,
Renderer2,
} from '@angular/core';
import {
ControlValueAccessor,
NgControl,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { Directive, ElementRef, forwardRef, HostListener, Input, Renderer2 } from "@angular/core";
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from "@angular/forms";
// ref: https://juristr.com/blog/2018/02/ng-true-value-directive/
@Directive({
selector: 'input[type=checkbox][appTrueFalseValue]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TrueFalseValueDirective),
multi: true,
},
],
selector: "input[type=checkbox][appTrueFalseValue]",
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TrueFalseValueDirective),
multi: true,
},
],
})
export class TrueFalseValueDirective implements ControlValueAccessor {
@Input() trueValue = true;
@Input() falseValue = false;
@Input() trueValue = true;
@Input() falseValue = false;
constructor(private elementRef: ElementRef, private renderer: Renderer2) { }
constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
@HostListener('change', ['$event'])
onHostChange(ev: any) {
this.propagateChange(ev.target.checked ? this.trueValue : this.falseValue);
@HostListener("change", ["$event"])
onHostChange(ev: any) {
this.propagateChange(ev.target.checked ? this.trueValue : this.falseValue);
}
writeValue(obj: any): void {
if (obj === this.trueValue) {
this.renderer.setProperty(this.elementRef.nativeElement, "checked", true);
} else {
this.renderer.setProperty(this.elementRef.nativeElement, "checked", false);
}
}
writeValue(obj: any): void {
if (obj === this.trueValue) {
this.renderer.setProperty(this.elementRef.nativeElement, 'checked', true);
} else {
this.renderer.setProperty(this.elementRef.nativeElement, 'checked', false);
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
/* nothing */
}
registerOnTouched(fn: any): void { /* nothing */ }
setDisabledState?(isDisabled: boolean): void {
/* nothing */
}
setDisabledState?(isDisabled: boolean): void { /* nothing */ }
private propagateChange = (_: any) => { /* nothing */ };
private propagateChange = (_: any) => {
/* nothing */
};
}

View File

@@ -1,53 +1,50 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Utils } from 'jslib-common/misc/utils';
import { Pipe, PipeTransform } from "@angular/core";
import { Utils } from "jslib-common/misc/utils";
/*
An updated pipe that sanitizes HTML, highlights numbers and special characters (in different colors each)
and handles Unicode / Emoji characters correctly.
*/
@Pipe({ name: 'colorPassword' })
@Pipe({ name: "colorPassword" })
export class ColorPasswordPipe implements PipeTransform {
transform(password: string) {
// Convert to an array to handle cases that stings have special characters, ie: emoji.
const passwordArray = Array.from(password);
let colorizedPassword = '';
for (let i = 0; i < passwordArray.length; i++) {
let character = passwordArray[i];
let isSpecial = false;
// Sanitize HTML first.
switch (character) {
case '&':
character = '&amp;';
isSpecial = true;
break;
case '<':
character = '&lt;';
isSpecial = true;
break;
case '>':
character = '&gt;';
isSpecial = true;
break;
case ' ':
character = '&nbsp;';
isSpecial = true;
break;
default:
break;
}
let type = 'letter';
if (character.match(Utils.regexpEmojiPresentation)) {
type = 'emoji';
} else if (isSpecial || character.match(/[^\w ]/)) {
type = 'special';
} else if (character.match(/\d/)) {
type = 'number';
}
colorizedPassword += '<span class="password-' + type + '">' + character + '</span>';
}
return colorizedPassword;
transform(password: string) {
// Convert to an array to handle cases that stings have special characters, ie: emoji.
const passwordArray = Array.from(password);
let colorizedPassword = "";
for (let i = 0; i < passwordArray.length; i++) {
let character = passwordArray[i];
let isSpecial = false;
// Sanitize HTML first.
switch (character) {
case "&":
character = "&amp;";
isSpecial = true;
break;
case "<":
character = "&lt;";
isSpecial = true;
break;
case ">":
character = "&gt;";
isSpecial = true;
break;
case " ":
character = "&nbsp;";
isSpecial = true;
break;
default:
break;
}
let type = "letter";
if (character.match(Utils.regexpEmojiPresentation)) {
type = "emoji";
} else if (isSpecial || character.match(/[^\w ]/)) {
type = "special";
} else if (character.match(/\d/)) {
type = "number";
}
colorizedPassword += '<span class="password-' + type + '">' + character + "</span>";
}
return colorizedPassword;
}
}

View File

@@ -1,17 +1,14 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Pipe, PipeTransform } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Pipe({
name: 'i18n',
name: "i18n",
})
export class I18nPipe implements PipeTransform {
constructor(private i18nService: I18nService) { }
constructor(private i18nService: I18nService) {}
transform(id: string, p1?: string, p2?: string, p3?: string): string {
return this.i18nService.t(id, p1, p2, p3);
}
transform(id: string, p1?: string, p2?: string, p3?: string): string {
return this.i18nService.t(id, p1, p2, p3);
}
}

View File

@@ -1,44 +1,41 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Pipe, PipeTransform } from "@angular/core";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
@Pipe({
name: 'searchCiphers',
name: "searchCiphers",
})
export class SearchCiphersPipe implements PipeTransform {
transform(ciphers: CipherView[], searchText: string, deleted: boolean = false): CipherView[] {
if (ciphers == null || ciphers.length === 0) {
return [];
}
if (searchText == null || searchText.length < 2) {
return ciphers.filter(c => {
return deleted !== c.isDeleted;
});
}
searchText = searchText.trim().toLowerCase();
return ciphers.filter(c => {
if (deleted !== c.isDeleted) {
return false;
}
if (c.name != null && c.name.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (searchText.length >= 8 && c.id.startsWith(searchText)) {
return true;
}
if (c.subTitle != null && c.subTitle.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (c.login && c.login.uri != null && c.login.uri.toLowerCase().indexOf(searchText) > -1) {
return true;
}
return false;
});
transform(ciphers: CipherView[], searchText: string, deleted: boolean = false): CipherView[] {
if (ciphers == null || ciphers.length === 0) {
return [];
}
if (searchText == null || searchText.length < 2) {
return ciphers.filter((c) => {
return deleted !== c.isDeleted;
});
}
searchText = searchText.trim().toLowerCase();
return ciphers.filter((c) => {
if (deleted !== c.isDeleted) {
return false;
}
if (c.name != null && c.name.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (searchText.length >= 8 && c.id.startsWith(searchText)) {
return true;
}
if (c.subTitle != null && c.subTitle.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (c.login && c.login.uri != null && c.login.uri.toLowerCase().indexOf(searchText) > -1) {
return true;
}
return false;
});
}
}

View File

@@ -1,33 +1,48 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: 'search',
name: "search",
})
export class SearchPipe implements PipeTransform {
transform(items: any[], searchText: string, prop1?: string, prop2?: string, prop3?: string): any[] {
if (items == null || items.length === 0) {
return [];
}
if (searchText == null || searchText.length < 2) {
return items;
}
searchText = searchText.trim().toLowerCase();
return items.filter(i => {
if (prop1 != null && i[prop1] != null && i[prop1].toString().toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (prop2 != null && i[prop2] != null && i[prop2].toString().toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (prop3 != null && i[prop3] != null && i[prop3].toString().toLowerCase().indexOf(searchText) > -1) {
return true;
}
return false;
});
transform(
items: any[],
searchText: string,
prop1?: string,
prop2?: string,
prop3?: string
): any[] {
if (items == null || items.length === 0) {
return [];
}
if (searchText == null || searchText.length < 2) {
return items;
}
searchText = searchText.trim().toLowerCase();
return items.filter((i) => {
if (
prop1 != null &&
i[prop1] != null &&
i[prop1].toString().toLowerCase().indexOf(searchText) > -1
) {
return true;
}
if (
prop2 != null &&
i[prop2] != null &&
i[prop2].toString().toLowerCase().indexOf(searchText) > -1
) {
return true;
}
if (
prop3 != null &&
i[prop3] != null &&
i[prop3].toString().toLowerCase().indexOf(searchText) > -1
) {
return true;
}
return false;
});
}
}

View File

@@ -1,22 +1,19 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Pipe, PipeTransform } from "@angular/core";
interface User {
name?: string;
email: string;
name?: string;
email: string;
}
@Pipe({
name: 'userName',
name: "userName",
})
export class UserNamePipe implements PipeTransform {
transform(user?: User): string {
if (user == null) {
return null;
}
return user.name == null || user.name.trim() === '' ? user.email : user.name;
transform(user?: User): string {
if (user == null) {
return null;
}
return user.name == null || user.name.trim() === "" ? user.email : user.name;
}
}

View File

@@ -1,90 +1,89 @@
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-italic-300.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-italic-300.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-italic-400.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-italic-400.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-italic-600.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-italic-600.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-italic-700.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-italic-700.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-italic-800.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-italic-800.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-normal-300.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-normal-300.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-normal-400.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-normal-400.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-normal-600.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-normal-600.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-normal-700.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-normal-700.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-normal-800.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-normal-800.woff) format("woff");
unicode-range: U+0-10FFFF;
}

View File

@@ -1,43 +1,45 @@
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
} from '@angular/router';
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
@Injectable()
export class AuthGuardService implements CanActivate {
constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
private messagingService: MessagingService, private keyConnectorService: KeyConnectorService,
private stateService: StateService) { }
constructor(
private vaultTimeoutService: VaultTimeoutService,
private router: Router,
private messagingService: MessagingService,
private keyConnectorService: KeyConnectorService,
private stateService: StateService
) {}
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) {
this.messagingService.send('authBlocked');
return false;
}
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
if (routerState != null) {
this.messagingService.send('lockedUrl', { url: routerState.url });
}
this.router.navigate(['lock'], { queryParams: { promptBiometric: true }});
return false;
}
if (!routerState.url.includes('remove-password') && await this.keyConnectorService.getConvertAccountRequired()) {
this.router.navigate(['/remove-password']);
return false;
}
return true;
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) {
this.messagingService.send("authBlocked");
return false;
}
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
if (routerState != null) {
this.messagingService.send("lockedUrl", { url: routerState.url });
}
this.router.navigate(["lock"], { queryParams: { promptBiometric: true } });
return false;
}
if (
!routerState.url.includes("remove-password") &&
(await this.keyConnectorService.getConvertAccountRequired())
) {
this.router.navigate(["/remove-password"]);
return false;
}
return true;
}
}

View File

@@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { Injectable } from "@angular/core";
import { BroadcasterService as BaseBroadcasterService } from 'jslib-common/services/broadcaster.service';
import { BroadcasterService as BaseBroadcasterService } from "jslib-common/services/broadcaster.service";
@Injectable()
export class BroadcasterService extends BaseBroadcasterService {
}
export class BroadcasterService extends BaseBroadcasterService {}

View File

@@ -1,480 +1,452 @@
import {
Injector,
LOCALE_ID,
NgModule,
} from '@angular/core';
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
import { ApiService } from 'jslib-common/services/api.service';
import { AppIdService } from 'jslib-common/services/appId.service';
import { AuditService } from 'jslib-common/services/audit.service';
import { AuthService } from 'jslib-common/services/auth.service';
import { CipherService } from 'jslib-common/services/cipher.service';
import { CollectionService } from 'jslib-common/services/collection.service';
import { ConsoleLogService } from 'jslib-common/services/consoleLog.service';
import { CryptoService } from 'jslib-common/services/crypto.service';
import { EnvironmentService } from 'jslib-common/services/environment.service';
import { EventService } from 'jslib-common/services/event.service';
import { ExportService } from 'jslib-common/services/export.service';
import { FileUploadService } from 'jslib-common/services/fileUpload.service';
import { FolderService } from 'jslib-common/services/folder.service';
import { KeyConnectorService } from 'jslib-common/services/keyConnector.service';
import { NotificationsService } from 'jslib-common/services/notifications.service';
import { OrganizationService } from 'jslib-common/services/organization.service';
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
import { PolicyService } from 'jslib-common/services/policy.service';
import { ProviderService } from 'jslib-common/services/provider.service';
import { SearchService } from 'jslib-common/services/search.service';
import { SendService } from 'jslib-common/services/send.service';
import { SettingsService } from 'jslib-common/services/settings.service';
import { StateService } from 'jslib-common/services/state.service';
import { StateMigrationService } from 'jslib-common/services/stateMigration.service';
import { SyncService } from 'jslib-common/services/sync.service';
import { TokenService } from 'jslib-common/services/token.service';
import { TotpService } from 'jslib-common/services/totp.service';
import { UserVerificationService } from 'jslib-common/services/userVerification.service';
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service';
import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunction.service';
import { ApiService } from "jslib-common/services/api.service";
import { AppIdService } from "jslib-common/services/appId.service";
import { AuditService } from "jslib-common/services/audit.service";
import { AuthService } from "jslib-common/services/auth.service";
import { CipherService } from "jslib-common/services/cipher.service";
import { CollectionService } from "jslib-common/services/collection.service";
import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
import { CryptoService } from "jslib-common/services/crypto.service";
import { EnvironmentService } from "jslib-common/services/environment.service";
import { EventService } from "jslib-common/services/event.service";
import { ExportService } from "jslib-common/services/export.service";
import { FileUploadService } from "jslib-common/services/fileUpload.service";
import { FolderService } from "jslib-common/services/folder.service";
import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
import { NotificationsService } from "jslib-common/services/notifications.service";
import { OrganizationService } from "jslib-common/services/organization.service";
import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
import { PolicyService } from "jslib-common/services/policy.service";
import { ProviderService } from "jslib-common/services/provider.service";
import { SearchService } from "jslib-common/services/search.service";
import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from "jslib-common/services/settings.service";
import { StateService } from "jslib-common/services/state.service";
import { StateMigrationService } from "jslib-common/services/stateMigration.service";
import { SyncService } from "jslib-common/services/sync.service";
import { TokenService } from "jslib-common/services/token.service";
import { TotpService } from "jslib-common/services/totp.service";
import { UserVerificationService } from "jslib-common/services/userVerification.service";
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { WebCryptoFunctionService } from "jslib-common/services/webCryptoFunction.service";
import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service';
import { AppIdService as AppIdServiceAbstraction } from 'jslib-common/abstractions/appId.service';
import { AuditService as AuditServiceAbstraction } from 'jslib-common/abstractions/audit.service';
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService as BroadcasterServiceAbstraction } from 'jslib-common/abstractions/broadcaster.service';
import { CipherService as CipherServiceAbstraction } from 'jslib-common/abstractions/cipher.service';
import { CollectionService as CollectionServiceAbstraction } from 'jslib-common/abstractions/collection.service';
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service';
import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service';
import { ExportService as ExportServiceAbstraction } from 'jslib-common/abstractions/export.service';
import { FileUploadService as FileUploadServiceAbstraction } from 'jslib-common/abstractions/fileUpload.service';
import { FolderService as FolderServiceAbstraction } from 'jslib-common/abstractions/folder.service';
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService as KeyConnectorServiceAbstraction } from 'jslib-common/abstractions/keyConnector.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service';
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service';
import { OrganizationService as OrganizationServiceAbstraction } from 'jslib-common/abstractions/organization.service';
import {
PasswordGenerationService as PasswordGenerationServiceAbstraction,
} from 'jslib-common/abstractions/passwordGeneration.service';
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service';
import { ProviderService as ProviderServiceAbstraction } from 'jslib-common/abstractions/provider.service';
import { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service';
import { SendService as SendServiceAbstraction } from 'jslib-common/abstractions/send.service';
import { SettingsService as SettingsServiceAbstraction } from 'jslib-common/abstractions/settings.service';
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service';
import { StateMigrationService as StateMigrationServiceAbstraction } from 'jslib-common/abstractions/stateMigration.service';
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service';
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service';
import { TotpService as TotpServiceAbstraction } from 'jslib-common/abstractions/totp.service';
import { UserVerificationService as UserVerificationServiceAbstraction } from 'jslib-common/abstractions/userVerification.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
import { AuditService as AuditServiceAbstraction } from "jslib-common/abstractions/audit.service";
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CipherService as CipherServiceAbstraction } from "jslib-common/abstractions/cipher.service";
import { CollectionService as CollectionServiceAbstraction } from "jslib-common/abstractions/collection.service";
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { ExportService as ExportServiceAbstraction } from "jslib-common/abstractions/export.service";
import { FileUploadService as FileUploadServiceAbstraction } from "jslib-common/abstractions/fileUpload.service";
import { FolderService as FolderServiceAbstraction } from "jslib-common/abstractions/folder.service";
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { OrganizationService as OrganizationServiceAbstraction } from "jslib-common/abstractions/organization.service";
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "jslib-common/abstractions/passwordGeneration.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService as PolicyServiceAbstraction } from "jslib-common/abstractions/policy.service";
import { ProviderService as ProviderServiceAbstraction } from "jslib-common/abstractions/provider.service";
import { SearchService as SearchServiceAbstraction } from "jslib-common/abstractions/search.service";
import { SendService as SendServiceAbstraction } from "jslib-common/abstractions/send.service";
import { SettingsService as SettingsServiceAbstraction } from "jslib-common/abstractions/settings.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "jslib-common/abstractions/totp.service";
import { UserVerificationService as UserVerificationServiceAbstraction } from "jslib-common/abstractions/userVerification.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { AuthGuardService } from './auth-guard.service';
import { BroadcasterService } from './broadcaster.service';
import { LockGuardService } from './lock-guard.service';
import { ModalService } from './modal.service';
import { PasswordRepromptService } from './passwordReprompt.service';
import { UnauthGuardService } from './unauth-guard.service';
import { ValidationService } from './validation.service';
import { AuthGuardService } from "./auth-guard.service";
import { BroadcasterService } from "./broadcaster.service";
import { LockGuardService } from "./lock-guard.service";
import { ModalService } from "./modal.service";
import { PasswordRepromptService } from "./passwordReprompt.service";
import { UnauthGuardService } from "./unauth-guard.service";
import { ValidationService } from "./validation.service";
@NgModule({
declarations: [],
providers: [
{ provide: 'WINDOW', useValue: window },
{
provide: LOCALE_ID,
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
deps: [I18nServiceAbstraction],
},
ValidationService,
AuthGuardService,
UnauthGuardService,
LockGuardService,
ModalService,
{
provide: AppIdServiceAbstraction,
useClass: AppIdService,
deps: [StorageServiceAbstraction],
},
{
provide: AuditServiceAbstraction,
useClass: AuditService,
deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction],
},
{
provide: AuthServiceAbstraction,
useClass: AuthService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
AppIdServiceAbstraction,
I18nServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
VaultTimeoutServiceAbstraction,
LogService,
CryptoFunctionServiceAbstraction,
KeyConnectorServiceAbstraction,
EnvironmentServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: CipherServiceAbstraction,
useFactory: (
cryptoService: CryptoServiceAbstraction,
settingsService: SettingsServiceAbstraction,
apiService: ApiServiceAbstraction,
fileUploadService: FileUploadServiceAbstraction,
i18nService: I18nServiceAbstraction,
injector: Injector,
logService: LogService,
stateService: StateServiceAbstraction,
) => new CipherService(
cryptoService,
settingsService,
apiService,
fileUploadService,
i18nService,
() => injector.get(SearchServiceAbstraction),
logService,
stateService,
),
deps: [
CryptoServiceAbstraction,
SettingsServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
Injector, // TODO: Get rid of this circular dependency!
LogService,
StateServiceAbstraction,
],
},
{
provide: FolderServiceAbstraction,
useClass: FolderService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
I18nServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
{
provide: CollectionServiceAbstraction,
useClass: CollectionService,
deps: [
CryptoServiceAbstraction,
I18nServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: EnvironmentServiceAbstraction,
useClass: EnvironmentService,
deps: [StateServiceAbstraction],
},
{
provide: TotpServiceAbstraction,
useClass: TotpService,
deps: [
CryptoFunctionServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{ provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] },
{
provide: CryptoServiceAbstraction,
useClass: CryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: PasswordGenerationServiceAbstraction,
useClass: PasswordGenerationService,
deps: [
CryptoServiceAbstraction,
PolicyServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: ApiServiceAbstraction,
useFactory: (tokenService: TokenServiceAbstraction, platformUtilsService: PlatformUtilsServiceAbstraction,
environmentService: EnvironmentServiceAbstraction, messagingService: MessagingServiceAbstraction) =>
new ApiService(tokenService, platformUtilsService, environmentService,
async (expired: boolean) => messagingService.send('logout', { expired: expired })),
deps: [
TokenServiceAbstraction,
PlatformUtilsServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
],
},
{
provide: FileUploadServiceAbstraction,
useClass: FileUploadService,
deps: [
LogService,
ApiServiceAbstraction,
],
},
{
provide: SyncServiceAbstraction,
useFactory: (
apiService: ApiServiceAbstraction,
settingsService: SettingsServiceAbstraction,
folderService: FolderServiceAbstraction,
cipherService: CipherServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
collectionService: CollectionServiceAbstraction,
messagingService: MessagingServiceAbstraction,
policyService: PolicyServiceAbstraction,
sendService: SendServiceAbstraction,
logService: LogService,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction,
organizationService: OrganizationServiceAbstraction,
providerService: ProviderServiceAbstraction,
) => new SyncService(
apiService,
settingsService,
folderService,
cipherService,
cryptoService,
collectionService,
messagingService,
policyService,
sendService,
logService,
keyConnectorService,
stateService,
organizationService,
providerService,
async (expired: boolean) => messagingService.send('logout', { expired: expired })),
deps: [
ApiServiceAbstraction,
SettingsServiceAbstraction,
FolderServiceAbstraction,
CipherServiceAbstraction,
CryptoServiceAbstraction,
CollectionServiceAbstraction,
MessagingServiceAbstraction,
PolicyServiceAbstraction,
SendServiceAbstraction,
LogService,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
OrganizationServiceAbstraction,
ProviderServiceAbstraction,
],
},
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
{
provide: SettingsServiceAbstraction,
useClass: SettingsService,
deps: [StateServiceAbstraction],
},
{
provide: VaultTimeoutServiceAbstraction,
useFactory: (
cipherService: CipherServiceAbstraction,
folderService: FolderServiceAbstraction,
collectionService: CollectionServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
messagingService: MessagingServiceAbstraction,
searchService: SearchServiceAbstraction,
tokenService: TokenServiceAbstraction,
policyService: PolicyServiceAbstraction,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction,
) => new VaultTimeoutService(
cipherService,
folderService,
collectionService,
cryptoService,
platformUtilsService,
messagingService,
searchService,
tokenService,
policyService,
keyConnectorService,
stateService,
null,
async () => messagingService.send('logout', { expired: false }),
),
deps: [
CipherServiceAbstraction,
FolderServiceAbstraction,
CollectionServiceAbstraction,
CryptoServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
SearchServiceAbstraction,
TokenServiceAbstraction,
PolicyServiceAbstraction,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: StateServiceAbstraction,
useClass: StateService,
deps: [
StorageServiceAbstraction,
'SECURE_STORAGE',
LogService,
StateMigrationServiceAbstraction,
],
},
{
provide: StateMigrationServiceAbstraction,
useClass: StateMigrationService,
deps: [
StorageServiceAbstraction,
'SECURE_STORAGE',
],
},
{
provide: ExportServiceAbstraction,
useClass: ExportService,
deps: [
FolderServiceAbstraction,
CipherServiceAbstraction,
ApiServiceAbstraction,
CryptoServiceAbstraction,
],
},
{
provide: SearchServiceAbstraction,
useClass: SearchService,
deps: [
CipherServiceAbstraction,
LogService,
I18nServiceAbstraction,
],
},
{
provide: NotificationsServiceAbstraction,
useFactory: (
syncService: SyncServiceAbstraction,
appIdService: AppIdServiceAbstraction,
apiService: ApiServiceAbstraction,
vaultTimeoutService: VaultTimeoutServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
logService: LogService,
stateService: StateServiceAbstraction,
) => new NotificationsService(
syncService,
appIdService,
apiService,
vaultTimeoutService,
environmentService,
async () => messagingService.send('logout', { expired: true }),
logService,
stateService,
),
deps: [
SyncServiceAbstraction,
AppIdServiceAbstraction,
ApiServiceAbstraction,
VaultTimeoutServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: CryptoFunctionServiceAbstraction,
useClass: WebCryptoFunctionService,
deps: ['WINDOW', PlatformUtilsServiceAbstraction],
},
{
provide: EventServiceAbstraction,
useClass: EventService,
deps: [
ApiServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
],
},
{
provide: PolicyServiceAbstraction,
useClass: PolicyService,
deps: [
StateServiceAbstraction,
OrganizationServiceAbstraction,
ApiServiceAbstraction,
],
},
{
provide: SendServiceAbstraction,
useClass: SendService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
CryptoFunctionServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: KeyConnectorServiceAbstraction,
useClass: KeyConnectorService,
deps: [
StateServiceAbstraction,
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
],
},
{
provide: UserVerificationServiceAbstraction,
useClass: UserVerificationService,
deps: [
CryptoServiceAbstraction,
I18nServiceAbstraction,
ApiServiceAbstraction,
],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
{
provide: OrganizationServiceAbstraction,
useClass: OrganizationService,
deps: [
StateServiceAbstraction,
],
},
{
provide: ProviderServiceAbstraction,
useClass: ProviderService,
deps: [
StateServiceAbstraction,
],
},
],
declarations: [],
providers: [
{ provide: "WINDOW", useValue: window },
{
provide: LOCALE_ID,
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
deps: [I18nServiceAbstraction],
},
ValidationService,
AuthGuardService,
UnauthGuardService,
LockGuardService,
ModalService,
{
provide: AppIdServiceAbstraction,
useClass: AppIdService,
deps: [StorageServiceAbstraction],
},
{
provide: AuditServiceAbstraction,
useClass: AuditService,
deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction],
},
{
provide: AuthServiceAbstraction,
useClass: AuthService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
AppIdServiceAbstraction,
I18nServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
VaultTimeoutServiceAbstraction,
LogService,
CryptoFunctionServiceAbstraction,
KeyConnectorServiceAbstraction,
EnvironmentServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: CipherServiceAbstraction,
useFactory: (
cryptoService: CryptoServiceAbstraction,
settingsService: SettingsServiceAbstraction,
apiService: ApiServiceAbstraction,
fileUploadService: FileUploadServiceAbstraction,
i18nService: I18nServiceAbstraction,
injector: Injector,
logService: LogService,
stateService: StateServiceAbstraction
) =>
new CipherService(
cryptoService,
settingsService,
apiService,
fileUploadService,
i18nService,
() => injector.get(SearchServiceAbstraction),
logService,
stateService
),
deps: [
CryptoServiceAbstraction,
SettingsServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
Injector, // TODO: Get rid of this circular dependency!
LogService,
StateServiceAbstraction,
],
},
{
provide: FolderServiceAbstraction,
useClass: FolderService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
I18nServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
{
provide: CollectionServiceAbstraction,
useClass: CollectionService,
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, StateServiceAbstraction],
},
{
provide: EnvironmentServiceAbstraction,
useClass: EnvironmentService,
deps: [StateServiceAbstraction],
},
{
provide: TotpServiceAbstraction,
useClass: TotpService,
deps: [CryptoFunctionServiceAbstraction, LogService, StateServiceAbstraction],
},
{ provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] },
{
provide: CryptoServiceAbstraction,
useClass: CryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: PasswordGenerationServiceAbstraction,
useClass: PasswordGenerationService,
deps: [CryptoServiceAbstraction, PolicyServiceAbstraction, StateServiceAbstraction],
},
{
provide: ApiServiceAbstraction,
useFactory: (
tokenService: TokenServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction
) =>
new ApiService(
tokenService,
platformUtilsService,
environmentService,
async (expired: boolean) => messagingService.send("logout", { expired: expired })
),
deps: [
TokenServiceAbstraction,
PlatformUtilsServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
],
},
{
provide: FileUploadServiceAbstraction,
useClass: FileUploadService,
deps: [LogService, ApiServiceAbstraction],
},
{
provide: SyncServiceAbstraction,
useFactory: (
apiService: ApiServiceAbstraction,
settingsService: SettingsServiceAbstraction,
folderService: FolderServiceAbstraction,
cipherService: CipherServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
collectionService: CollectionServiceAbstraction,
messagingService: MessagingServiceAbstraction,
policyService: PolicyServiceAbstraction,
sendService: SendServiceAbstraction,
logService: LogService,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction,
organizationService: OrganizationServiceAbstraction,
providerService: ProviderServiceAbstraction
) =>
new SyncService(
apiService,
settingsService,
folderService,
cipherService,
cryptoService,
collectionService,
messagingService,
policyService,
sendService,
logService,
keyConnectorService,
stateService,
organizationService,
providerService,
async (expired: boolean) => messagingService.send("logout", { expired: expired })
),
deps: [
ApiServiceAbstraction,
SettingsServiceAbstraction,
FolderServiceAbstraction,
CipherServiceAbstraction,
CryptoServiceAbstraction,
CollectionServiceAbstraction,
MessagingServiceAbstraction,
PolicyServiceAbstraction,
SendServiceAbstraction,
LogService,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
OrganizationServiceAbstraction,
ProviderServiceAbstraction,
],
},
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
{
provide: SettingsServiceAbstraction,
useClass: SettingsService,
deps: [StateServiceAbstraction],
},
{
provide: VaultTimeoutServiceAbstraction,
useFactory: (
cipherService: CipherServiceAbstraction,
folderService: FolderServiceAbstraction,
collectionService: CollectionServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
messagingService: MessagingServiceAbstraction,
searchService: SearchServiceAbstraction,
tokenService: TokenServiceAbstraction,
policyService: PolicyServiceAbstraction,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction
) =>
new VaultTimeoutService(
cipherService,
folderService,
collectionService,
cryptoService,
platformUtilsService,
messagingService,
searchService,
tokenService,
policyService,
keyConnectorService,
stateService,
null,
async () => messagingService.send("logout", { expired: false })
),
deps: [
CipherServiceAbstraction,
FolderServiceAbstraction,
CollectionServiceAbstraction,
CryptoServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
SearchServiceAbstraction,
TokenServiceAbstraction,
PolicyServiceAbstraction,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: StateServiceAbstraction,
useClass: StateService,
deps: [
StorageServiceAbstraction,
"SECURE_STORAGE",
LogService,
StateMigrationServiceAbstraction,
],
},
{
provide: StateMigrationServiceAbstraction,
useClass: StateMigrationService,
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
},
{
provide: ExportServiceAbstraction,
useClass: ExportService,
deps: [
FolderServiceAbstraction,
CipherServiceAbstraction,
ApiServiceAbstraction,
CryptoServiceAbstraction,
],
},
{
provide: SearchServiceAbstraction,
useClass: SearchService,
deps: [CipherServiceAbstraction, LogService, I18nServiceAbstraction],
},
{
provide: NotificationsServiceAbstraction,
useFactory: (
syncService: SyncServiceAbstraction,
appIdService: AppIdServiceAbstraction,
apiService: ApiServiceAbstraction,
vaultTimeoutService: VaultTimeoutServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
logService: LogService,
stateService: StateServiceAbstraction
) =>
new NotificationsService(
syncService,
appIdService,
apiService,
vaultTimeoutService,
environmentService,
async () => messagingService.send("logout", { expired: true }),
logService,
stateService
),
deps: [
SyncServiceAbstraction,
AppIdServiceAbstraction,
ApiServiceAbstraction,
VaultTimeoutServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: CryptoFunctionServiceAbstraction,
useClass: WebCryptoFunctionService,
deps: ["WINDOW", PlatformUtilsServiceAbstraction],
},
{
provide: EventServiceAbstraction,
useClass: EventService,
deps: [
ApiServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
],
},
{
provide: PolicyServiceAbstraction,
useClass: PolicyService,
deps: [StateServiceAbstraction, OrganizationServiceAbstraction, ApiServiceAbstraction],
},
{
provide: SendServiceAbstraction,
useClass: SendService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
CryptoFunctionServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: KeyConnectorServiceAbstraction,
useClass: KeyConnectorService,
deps: [
StateServiceAbstraction,
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
],
},
{
provide: UserVerificationServiceAbstraction,
useClass: UserVerificationService,
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, ApiServiceAbstraction],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
{
provide: OrganizationServiceAbstraction,
useClass: OrganizationService,
deps: [StateServiceAbstraction],
},
{
provide: ProviderServiceAbstraction,
useClass: ProviderService,
deps: [StateServiceAbstraction],
},
],
})
export class JslibServicesModule {
}
export class JslibServicesModule {}

View File

@@ -1,29 +1,29 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
Router,
} from '@angular/router';
import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router";
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
@Injectable()
export class LockGuardService implements CanActivate {
protected homepage = 'vault';
constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
private stateService: StateService) { }
protected homepage = "vault";
constructor(
private vaultTimeoutService: VaultTimeoutService,
private router: Router,
private stateService: StateService
) {}
async canActivate() {
if (!await this.stateService.getIsAuthenticated()) {
this.router.navigate(['login']);
return false;
}
if (!await this.vaultTimeoutService.isLocked()) {
this.router.navigate([this.homepage]);
return false;
}
return true;
async canActivate() {
if (!(await this.stateService.getIsAuthenticated())) {
this.router.navigate(["login"]);
return false;
}
if (!(await this.vaultTimeoutService.isLocked())) {
this.router.navigate([this.homepage]);
return false;
}
return true;
}
}

View File

@@ -1,164 +1,179 @@
import {
ApplicationRef,
ComponentFactory,
ComponentFactoryResolver,
ComponentRef,
EmbeddedViewRef,
Injectable,
Injector,
Type,
ViewContainerRef
} from '@angular/core';
import { first } from 'rxjs/operators';
ApplicationRef,
ComponentFactory,
ComponentFactoryResolver,
ComponentRef,
EmbeddedViewRef,
Injectable,
Injector,
Type,
ViewContainerRef,
} from "@angular/core";
import { first } from "rxjs/operators";
import { DynamicModalComponent } from '../components/modal/dynamic-modal.component';
import { ModalInjector } from '../components/modal/modal-injector';
import { ModalRef } from '../components/modal/modal.ref';
import { DynamicModalComponent } from "../components/modal/dynamic-modal.component";
import { ModalInjector } from "../components/modal/modal-injector";
import { ModalRef } from "../components/modal/modal.ref";
export class ModalConfig<D = any> {
data?: D;
allowMultipleModals: boolean = false;
data?: D;
allowMultipleModals: boolean = false;
}
@Injectable()
export class ModalService {
protected modalList: ComponentRef<DynamicModalComponent>[] = [];
protected modalList: ComponentRef<DynamicModalComponent>[] = [];
// Lazy loaded modules are not available in componentFactoryResolver,
// therefore modules needs to manually initialize their resolvers.
private factoryResolvers: Map<Type<any>, ComponentFactoryResolver> = new Map();
// Lazy loaded modules are not available in componentFactoryResolver,
// therefore modules needs to manually initialize their resolvers.
private factoryResolvers: Map<Type<any>, ComponentFactoryResolver> = new Map();
constructor(private componentFactoryResolver: ComponentFactoryResolver, private applicationRef: ApplicationRef,
private injector: Injector) {
document.addEventListener('keyup', event => {
if (event.key === 'Escape' && this.modalCount > 0) {
this.topModal.instance.close();
}
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private applicationRef: ApplicationRef,
private injector: Injector
) {
document.addEventListener("keyup", (event) => {
if (event.key === "Escape" && this.modalCount > 0) {
this.topModal.instance.close();
}
});
}
get modalCount() {
return this.modalList.length;
}
private get topModal() {
return this.modalList[this.modalCount - 1];
}
async openViewRef<T>(
componentType: Type<T>,
viewContainerRef: ViewContainerRef,
setComponentParameters: (component: T) => void = null
): Promise<[ModalRef, T]> {
const [modalRef, modalComponentRef] = this.openInternal(componentType, null, false);
modalComponentRef.instance.setComponentParameters = setComponentParameters;
viewContainerRef.insert(modalComponentRef.hostView);
await modalRef.onCreated.pipe(first()).toPromise();
return [modalRef, modalComponentRef.instance.componentRef.instance];
}
open(componentType: Type<any>, config?: ModalConfig) {
if (!(config?.allowMultipleModals ?? false) && this.modalCount > 0) {
return;
}
const [modalRef, _] = this.openInternal(componentType, config, true);
return modalRef;
}
registerComponentFactoryResolver<T>(
componentType: Type<T>,
componentFactoryResolver: ComponentFactoryResolver
): void {
this.factoryResolvers.set(componentType, componentFactoryResolver);
}
resolveComponentFactory<T>(componentType: Type<T>): ComponentFactory<T> {
if (this.factoryResolvers.has(componentType)) {
return this.factoryResolvers.get(componentType).resolveComponentFactory(componentType);
}
return this.componentFactoryResolver.resolveComponentFactory(componentType);
}
protected openInternal(
componentType: Type<any>,
config?: ModalConfig,
attachToDom?: boolean
): [ModalRef, ComponentRef<DynamicModalComponent>] {
const [modalRef, componentRef] = this.createModalComponent(config);
componentRef.instance.childComponentType = componentType;
if (attachToDom) {
this.applicationRef.attachView(componentRef.hostView);
const domElem = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
document.body.appendChild(domElem);
}
modalRef.onClosed.pipe(first()).subscribe(() => {
if (attachToDom) {
this.applicationRef.detachView(componentRef.hostView);
}
componentRef.destroy();
this.modalList.pop();
if (this.modalCount > 0) {
this.topModal.instance.getFocus();
}
});
this.setupHandlers(modalRef);
this.modalList.push(componentRef);
return [modalRef, componentRef];
}
protected setupHandlers(modalRef: ModalRef) {
let backdrop: HTMLElement = null;
// Add backdrop, setup [data-dismiss] handler.
modalRef.onCreated.pipe(first()).subscribe((el) => {
document.body.classList.add("modal-open");
const modalEl: HTMLElement = el.querySelector(".modal");
const dialogEl = modalEl.querySelector(".modal-dialog") as HTMLElement;
backdrop = document.createElement("div");
backdrop.className = "modal-backdrop fade";
backdrop.style.zIndex = `${this.modalCount}040`;
modalEl.prepend(backdrop);
dialogEl.addEventListener("click", (e: Event) => {
e.stopPropagation();
});
dialogEl.style.zIndex = `${this.modalCount}050`;
const modals = Array.from(
el.querySelectorAll('.modal-backdrop, .modal *[data-dismiss="modal"]')
);
for (const closeElement of modals) {
closeElement.addEventListener("click", (event) => {
modalRef.close();
});
}
}
});
get modalCount() {
return this.modalList.length;
}
// onClose is used in Web to hook into bootstrap. On other projects we pipe it directly to closed.
modalRef.onClose.pipe(first()).subscribe(() => {
modalRef.closed();
private get topModal() {
return this.modalList[this.modalCount - 1];
}
if (this.modalCount === 0) {
document.body.classList.remove("modal-open");
}
});
}
async openViewRef<T>(componentType: Type<T>, viewContainerRef: ViewContainerRef,
setComponentParameters: (component: T) => void = null): Promise<[ModalRef, T]> {
protected createModalComponent(
config: ModalConfig
): [ModalRef, ComponentRef<DynamicModalComponent>] {
const modalRef = new ModalRef();
const [modalRef, modalComponentRef] = this.openInternal(componentType, null, false);
modalComponentRef.instance.setComponentParameters = setComponentParameters;
const map = new WeakMap();
map.set(ModalConfig, config);
map.set(ModalRef, modalRef);
viewContainerRef.insert(modalComponentRef.hostView);
const componentFactory =
this.componentFactoryResolver.resolveComponentFactory(DynamicModalComponent);
const componentRef = componentFactory.create(new ModalInjector(this.injector, map));
await modalRef.onCreated.pipe(first()).toPromise();
return [modalRef, modalComponentRef.instance.componentRef.instance];
}
open(componentType: Type<any>, config?: ModalConfig) {
if (!(config?.allowMultipleModals ?? false) && this.modalCount > 0) {
return;
}
const [modalRef, _] = this.openInternal(componentType, config, true);
return modalRef;
}
registerComponentFactoryResolver<T>(componentType: Type<T>, componentFactoryResolver: ComponentFactoryResolver): void {
this.factoryResolvers.set(componentType, componentFactoryResolver);
}
resolveComponentFactory<T>(componentType: Type<T>): ComponentFactory<T> {
if (this.factoryResolvers.has(componentType)) {
return this.factoryResolvers.get(componentType).resolveComponentFactory(componentType);
}
return this.componentFactoryResolver.resolveComponentFactory(componentType);
}
protected openInternal(componentType: Type<any>, config?: ModalConfig, attachToDom?: boolean):
[ModalRef, ComponentRef<DynamicModalComponent>] {
const [modalRef, componentRef] = this.createModalComponent(config);
componentRef.instance.childComponentType = componentType;
if (attachToDom) {
this.applicationRef.attachView(componentRef.hostView);
const domElem = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
document.body.appendChild(domElem);
}
modalRef.onClosed.pipe(first()).subscribe(() => {
if (attachToDom) {
this.applicationRef.detachView(componentRef.hostView);
}
componentRef.destroy();
this.modalList.pop();
if (this.modalCount > 0) {
this.topModal.instance.getFocus();
}
});
this.setupHandlers(modalRef);
this.modalList.push(componentRef);
return [modalRef, componentRef];
}
protected setupHandlers(modalRef: ModalRef) {
let backdrop: HTMLElement = null;
// Add backdrop, setup [data-dismiss] handler.
modalRef.onCreated.pipe(first()).subscribe(el => {
document.body.classList.add('modal-open');
const modalEl: HTMLElement = el.querySelector('.modal');
const dialogEl = modalEl.querySelector('.modal-dialog') as HTMLElement;
backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade';
backdrop.style.zIndex = `${this.modalCount}040`;
modalEl.prepend(backdrop);
dialogEl.addEventListener('click', (e: Event) => {
e.stopPropagation();
});
dialogEl.style.zIndex = `${this.modalCount}050`;
const modals = Array.from(el.querySelectorAll('.modal-backdrop, .modal *[data-dismiss="modal"]'));
for (const closeElement of modals) {
closeElement.addEventListener('click', event => {
modalRef.close();
});
}
});
// onClose is used in Web to hook into bootstrap. On other projects we pipe it directly to closed.
modalRef.onClose.pipe(first()).subscribe(() => {
modalRef.closed();
if (this.modalCount === 0) {
document.body.classList.remove('modal-open');
}
});
}
protected createModalComponent(config: ModalConfig): [ModalRef, ComponentRef<DynamicModalComponent>] {
const modalRef = new ModalRef();
const map = new WeakMap();
map.set(ModalConfig, config);
map.set(ModalRef, modalRef);
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicModalComponent);
const componentRef = componentFactory.create(new ModalInjector(this.injector, map));
return [modalRef, componentRef];
}
return [modalRef, componentRef];
}
}

View File

@@ -1,37 +1,40 @@
import { Injectable } from '@angular/core';
import { Injectable } from "@angular/core";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PasswordRepromptComponent } from '../components/password-reprompt.component';
import { ModalService } from './modal.service';
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
import { ModalService } from "./modal.service";
@Injectable()
export class PasswordRepromptService implements PasswordRepromptServiceAbstraction {
protected component = PasswordRepromptComponent;
protected component = PasswordRepromptComponent;
constructor(private modalService: ModalService, private keyConnectorService: KeyConnectorService) { }
constructor(
private modalService: ModalService,
private keyConnectorService: KeyConnectorService
) {}
protectedFields() {
return ['TOTP', 'Password', 'H_Field', 'Card Number', 'Security Code'];
protectedFields() {
return ["TOTP", "Password", "H_Field", "Card Number", "Security Code"];
}
async showPasswordPrompt() {
if (!(await this.enabled())) {
return true;
}
async showPasswordPrompt() {
if (!await this.enabled()) {
return true;
}
const ref = this.modalService.open(this.component, { allowMultipleModals: true });
const ref = this.modalService.open(this.component, {allowMultipleModals: true});
if (ref == null) {
return false;
}
const result = await ref.onClosedPromise();
return result === true;
if (ref == null) {
return false;
}
async enabled() {
return !await this.keyConnectorService.getUsesKeyConnector();
}
const result = await ref.onClosedPromise();
return result === true;
}
async enabled() {
return !(await this.keyConnectorService.getUsesKeyConnector());
}
}

View File

@@ -1,30 +1,29 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
Router,
} from '@angular/router';
import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router";
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
@Injectable()
export class UnauthGuardService implements CanActivate {
protected homepage = "vault";
constructor(
private vaultTimeoutService: VaultTimeoutService,
private router: Router,
private stateService: StateService
) {}
protected homepage = 'vault';
constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
private stateService: StateService) { }
async canActivate() {
const isAuthed = await this.stateService.getIsAuthenticated();
if (isAuthed) {
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
this.router.navigate(['lock']);
} else {
this.router.navigate([this.homepage]);
}
return false;
}
return true;
async canActivate() {
const isAuthed = await this.stateService.getIsAuthenticated();
if (isAuthed) {
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
this.router.navigate(["lock"]);
} else {
this.router.navigate([this.homepage]);
}
return false;
}
return true;
}
}

View File

@@ -1,36 +1,39 @@
import { Injectable } from '@angular/core';
import { Injectable } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
@Injectable()
export class ValidationService {
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService) { }
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService
) {}
showError(data: any): string[] {
const defaultErrorMessage = this.i18nService.t('unexpectedError');
let errors: string[] = [];
showError(data: any): string[] {
const defaultErrorMessage = this.i18nService.t("unexpectedError");
let errors: string[] = [];
if (data != null && typeof data === 'string') {
errors.push(data);
} else if (data == null || typeof data !== 'object') {
errors.push(defaultErrorMessage);
} else if (data.validationErrors != null) {
errors = errors.concat((data as ErrorResponse).getAllMessages());
} else {
errors.push(data.message ? data.message : defaultErrorMessage);
}
if (errors.length === 1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), errors[0]);
} else if (errors.length > 1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), errors, {
timeout: 5000 * errors.length,
});
}
return errors;
if (data != null && typeof data === "string") {
errors.push(data);
} else if (data == null || typeof data !== "object") {
errors.push(defaultErrorMessage);
} else if (data.validationErrors != null) {
errors = errors.concat((data as ErrorResponse).getAllMessages());
} else {
errors.push(data.message ? data.message : defaultErrorMessage);
}
if (errors.length === 1) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors[0]);
} else if (errors.length > 1) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors, {
timeout: 5000 * errors.length,
});
}
return errors;
}
}

View File

@@ -14,17 +14,9 @@
"declarationDir": "dist/types",
"outDir": "dist",
"paths": {
"jslib-common/*": [
"../common/src/*"
]
"jslib-common/*": ["../common/src/*"]
}
},
"include": [
"src",
"spec"
],
"exclude": [
"node_modules",
"dist"
]
"include": ["src", "spec"],
"exclude": ["node_modules", "dist"]
}