mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
Apply Prettier (#581)
This commit is contained in:
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("") : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">×</span>
|
||||
selector: "[toast-component2]",
|
||||
template: `
|
||||
<button
|
||||
*ngIf="options.closeButton"
|
||||
(click)="remove()"
|
||||
type="button"
|
||||
class="toast-close-button"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span aria-hidden="true">×</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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 = '&';
|
||||
isSpecial = true;
|
||||
break;
|
||||
case '<':
|
||||
character = '<';
|
||||
isSpecial = true;
|
||||
break;
|
||||
case '>':
|
||||
character = '>';
|
||||
isSpecial = true;
|
||||
break;
|
||||
case ' ':
|
||||
character = ' ';
|
||||
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 = "&";
|
||||
isSpecial = true;
|
||||
break;
|
||||
case "<":
|
||||
character = "<";
|
||||
isSpecial = true;
|
||||
break;
|
||||
case ">":
|
||||
character = ">";
|
||||
isSpecial = true;
|
||||
break;
|
||||
case " ":
|
||||
character = " ";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user