Password reprompt (#1784)

* Add support for password reprompt

* Rename passwordPrompt to reprompt.

* Move showPasswordDialog to paltformutils

* Fix swal2 validation error styling

* Group imports

* Update src/_locales/en/messages.json

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
This commit is contained in:
Oscar Hinton 2021-05-03 20:56:38 +02:00 committed by GitHub
parent 2c58dbb344
commit cdc71dd661
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 179 additions and 30 deletions

2
jslib

@ -1 +1 @@
Subproject commit b6f102938fe7c17631cb1b2e356438c5e4456529 Subproject commit a72c8a60c1b7a6980bceee456c53a9ea7b9b3451

View File

@ -1703,5 +1703,14 @@
}, },
"sendOptionsPolicyInEffect": { "sendOptionsPolicyInEffect": {
"message": "One or more organization policies are affecting your Send options." "message": "One or more organization policies are affecting your Send options."
},
"passwordPrompt": {
"message": "Master password re-prompt"
},
"passwordConfirmation": {
"message": "Master password confirmation"
},
"passwordConfirmationDesc": {
"message": "This action is protected. To continue, please re-enter your master password to verify your identity."
} }
} }

View File

@ -1,4 +1,5 @@
import { CipherType } from 'jslib/enums'; import { CipherType } from 'jslib/enums';
import { CipherRepromptType } from 'jslib/enums/cipherRepromptType';
import { import {
ApiService, ApiService,
@ -63,6 +64,7 @@ import { PolicyService as PolicyServiceAbstraction } from 'jslib/abstractions/po
import { SearchService as SearchServiceAbstraction } from 'jslib/abstractions/search.service'; import { SearchService as SearchServiceAbstraction } from 'jslib/abstractions/search.service';
import { SendService as SendServiceAbstraction } from 'jslib/abstractions/send.service'; import { SendService as SendServiceAbstraction } from 'jslib/abstractions/send.service';
import { SystemService as SystemServiceAbstraction } from 'jslib/abstractions/system.service'; import { SystemService as SystemServiceAbstraction } from 'jslib/abstractions/system.service';
import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service';
import { Utils } from 'jslib/misc/utils'; import { Utils } from 'jslib/misc/utils';
@ -86,8 +88,6 @@ import BrowserStorageService from '../services/browserStorage.service';
import I18nService from '../services/i18n.service'; import I18nService from '../services/i18n.service';
import VaultTimeoutService from '../services/vaultTimeout.service'; import VaultTimeoutService from '../services/vaultTimeout.service';
import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service';
export default class MainBackground { export default class MainBackground {
messagingService: MessagingServiceAbstraction; messagingService: MessagingServiceAbstraction;
storageService: StorageServiceAbstraction; storageService: StorageServiceAbstraction;
@ -583,7 +583,7 @@ export default class MainBackground {
} }
private async loadLoginContextMenuOptions(cipher: any) { private async loadLoginContextMenuOptions(cipher: any) {
if (cipher == null || cipher.type !== CipherType.Login) { if (cipher == null || cipher.type !== CipherType.Login || cipher.reprompt !== CipherRepromptType.None) {
return; return;
} }

View File

@ -33,6 +33,7 @@ import { StorageService } from 'jslib/abstractions/storage.service';
import { ConstantsService } from 'jslib/services/constants.service'; import { ConstantsService } from 'jslib/services/constants.service';
import BrowserPlatformUtilsService from 'src/services/browserPlatformUtils.service';
import { routerTransition } from './app-routing.animations'; import { routerTransition } from './app-routing.animations';
@Component({ @Component({
@ -105,6 +106,8 @@ export class AppComponent implements OnInit {
}); });
} else if (msg.command === 'showDialog') { } else if (msg.command === 'showDialog') {
await this.showDialog(msg); await this.showDialog(msg);
} else if (msg.command === 'showPasswordDialog') {
await this.showPasswordDialog(msg);
} else if (msg.command === 'showToast') { } else if (msg.command === 'showToast') {
this.ngZone.run(() => { this.ngZone.run(() => {
this.showToast(msg); this.showToast(msg);
@ -248,4 +251,30 @@ export class AppComponent implements OnInit {
confirmed: confirmed.value, confirmed: confirmed.value,
}); });
} }
private async showPasswordDialog(msg: any) {
const platformUtils = this.platformUtilsService as BrowserPlatformUtilsService;
const result = await Swal.fire({
heightAuto: false,
title: msg.title,
input: 'password',
text: msg.body,
confirmButtonText: this.i18nService.t('ok'),
showCancelButton: true,
cancelButtonText: this.i18nService.t('cancel'),
inputAttributes: {
autocapitalize: 'off',
autocorrect: 'off',
},
inputValidator: async (value: string): Promise<any> => {
if (await platformUtils.resolvePasswordDialogPromise(msg.dialogId, false, value)) {
return false;
}
return this.i18nService.t('invalidMasterPassword');
},
});
platformUtils.resolvePasswordDialogPromise(msg.dialogId, true, null);
}
} }

View File

@ -14,12 +14,11 @@ import { CipherView } from 'jslib/models/view/cipherView';
import { EventService } from 'jslib/abstractions/event.service'; import { EventService } from 'jslib/abstractions/event.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordRepromptService } from 'jslib/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { TotpService } from 'jslib/abstractions/totp.service'; import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service'; import { UserService } from 'jslib/abstractions/user.service';
import { PopupUtilsService } from '../services/popup-utils.service';
@Component({ @Component({
selector: 'app-action-buttons', selector: 'app-action-buttons',
templateUrl: 'action-buttons.component.html', templateUrl: 'action-buttons.component.html',
@ -35,7 +34,8 @@ export class ActionButtonsComponent {
constructor(private toasterService: ToasterService, private i18nService: I18nService, constructor(private toasterService: ToasterService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private eventService: EventService, private platformUtilsService: PlatformUtilsService, private eventService: EventService,
private totpService: TotpService, private userService: UserService) { } private totpService: TotpService, private userService: UserService,
private passwordRepromptService: PasswordRepromptService) { }
async ngOnInit() { async ngOnInit() {
this.userHasPremiumAccess = await this.userService.canAccessPremium(); this.userHasPremiumAccess = await this.userService.canAccessPremium();
@ -46,6 +46,10 @@ export class ActionButtonsComponent {
} }
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) { async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
if (this.passwordRepromptService.protectedFields().includes(aType) && !await this.passwordRepromptService.showPasswordPrompt()) {
return;
}
if (value == null || aType === 'TOTP' && !this.displayTotpCopyButton(cipher)) { if (value == null || aType === 'TOTP' && !this.displayTotpCopyButton(cipher)) {
return; return;
} else if (value === cipher.login.totp) { } else if (value === cipher.login.totp) {

View File

@ -211,6 +211,10 @@ $fa-font-path: "~font-awesome/fonts";
} }
} }
} }
.swal2-validation-message {
margin-top: 20px;
}
} }
date-input-polyfill { date-input-polyfill {

View File

@ -16,6 +16,7 @@ import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service'; import { PolicyService } from 'jslib/abstractions/policy.service';
import { SendService } from 'jslib/abstractions/send.service'; import { SendService } from 'jslib/abstractions/send.service';
import { TokenService } from 'jslib/abstractions/token.service';
import { UserService } from 'jslib/abstractions/user.service'; import { UserService } from 'jslib/abstractions/user.service';
import { PopupUtilsService } from '../services/popup-utils.service'; import { PopupUtilsService } from '../services/popup-utils.service';

View File

@ -31,6 +31,7 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service'; import { MessagingService } from 'jslib/abstractions/messaging.service';
import { NotificationsService } from 'jslib/abstractions/notifications.service'; import { NotificationsService } from 'jslib/abstractions/notifications.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service'; import { PolicyService } from 'jslib/abstractions/policy.service';
import { SearchService as SearchServiceAbstraction } from 'jslib/abstractions/search.service'; import { SearchService as SearchServiceAbstraction } from 'jslib/abstractions/search.service';
@ -43,6 +44,7 @@ import { TokenService } from 'jslib/abstractions/token.service';
import { TotpService } from 'jslib/abstractions/totp.service'; import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service'; import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
import { PasswordRepromptService } from 'jslib/services/passwordReprompt.service';
import { AutofillService } from '../../services/abstractions/autofill.service'; import { AutofillService } from '../../services/abstractions/autofill.service';
import BrowserMessagingService from '../../services/browserMessaging.service'; import BrowserMessagingService from '../../services/browserMessaging.service';
@ -63,11 +65,13 @@ function getBgService<T>(service: string) {
}; };
} }
export const stateService = new StateService(); const stateService = new StateService();
export const messagingService = new BrowserMessagingService(); const messagingService = new BrowserMessagingService();
export const searchService = new PopupSearchService(getBgService<SearchService>('searchService')(), const searchService = new PopupSearchService(getBgService<SearchService>('searchService')(),
getBgService<CipherService>('cipherService')(), getBgService<ConsoleLogService>('consoleLogService')(), getBgService<CipherService>('cipherService')(), getBgService<ConsoleLogService>('consoleLogService')(),
getBgService<I18nService>('i18nService')()); getBgService<I18nService>('i18nService')());
const passwordRepromptService = new PasswordRepromptService(getBgService<I18nService>('i18nService')(),
getBgService<CryptoService>('cryptoService')(), getBgService<PlatformUtilsService>('platformUtilsService')());
export function initFactory(platformUtilsService: PlatformUtilsService, i18nService: I18nService, storageService: StorageService, export function initFactory(platformUtilsService: PlatformUtilsService, i18nService: I18nService, storageService: StorageService,
popupUtilsService: PopupUtilsService): Function { popupUtilsService: PopupUtilsService): Function {
@ -174,6 +178,7 @@ export function initFactory(platformUtilsService: PlatformUtilsService, i18nServ
useFactory: () => getBgService<I18nService>('i18nService')().translationLocale, useFactory: () => getBgService<I18nService>('i18nService')().translationLocale,
deps: [], deps: [],
}, },
{ provide: PasswordRepromptServiceAbstraction, useValue: passwordRepromptService },
], ],
}) })
export class ServicesModule { export class ServicesModule {

View File

@ -83,10 +83,19 @@
<input id="cardCardholderName" type="text" name="Card.CardCardholderName" <input id="cardCardholderName" type="text" name="Card.CardCardholderName"
[(ngModel)]="cipher.card.cardholderName"> [(ngModel)]="cipher.card.cardholderName">
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="cardNumber">{{'number' | i18n}}</label> <label for="cardNumber">{{'number' | i18n}}</label>
<input id="cardNumber" type="text" name="Card.Number" [(ngModel)]="cipher.card.number" <input id="cardNumber" class="monospaced" type="{{showCardNumber ? 'text' : 'password'}}"
appInputVerbatim> name="Card.Number" [(ngModel)]="cipher.card.number" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleCardNumber()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber}"></i>
</a>
</div>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="cardBrand">{{'brand' | i18n}}</label> <label for="cardBrand">{{'brand' | i18n}}</label>
@ -271,6 +280,11 @@
<label for="favorite">{{'favorite' | i18n}}</label> <label for="favorite">{{'favorite' | i18n}}</label>
<input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite"> <input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite">
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="passwordPrompt">{{'passwordPrompt' | i18n}}</label>
<input id="passwordPrompt" type="checkbox" name="PasswordPrompt" [ngModel]="reprompt"
(change)="repromptChanged()">
</div>
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick <a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
(click)="attachments()" *ngIf="editMode && showAttachments && !cloneMode"> (click)="attachments()" *ngIf="editMode && showAttachments && !cloneMode">
<div class="row-main">{{'attachments' | i18n}}</div> <div class="row-main">{{'attachments' | i18n}}</div>

View File

@ -14,12 +14,14 @@ import { BrowserApi } from '../../browser/browserApi';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { CipherRepromptType } from 'jslib/enums/cipherRepromptType';
import { CipherType } from 'jslib/enums/cipherType'; import { CipherType } from 'jslib/enums/cipherType';
import { CipherView } from 'jslib/models/view/cipherView'; import { CipherView } from 'jslib/models/view/cipherView';
import { CipherService } from 'jslib/abstractions/cipher.service'; import { CipherService } from 'jslib/abstractions/cipher.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordRepromptService } from 'jslib/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service'; import { SearchService } from 'jslib/abstractions/search.service';
import { StorageService } from 'jslib/abstractions/storage.service'; import { StorageService } from 'jslib/abstractions/storage.service';
@ -61,7 +63,8 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
private toasterService: ToasterService, private i18nService: I18nService, private router: Router, private toasterService: ToasterService, private i18nService: I18nService, private router: Router,
private ngZone: NgZone, private broadcasterService: BroadcasterService, private ngZone: NgZone, private broadcasterService: BroadcasterService,
private changeDetectorRef: ChangeDetectorRef, private syncService: SyncService, private changeDetectorRef: ChangeDetectorRef, private syncService: SyncService,
private searchService: SearchService, private storageService: StorageService) { private searchService: SearchService, private storageService: StorageService,
private passwordRepromptService: PasswordRepromptService) {
} }
async ngOnInit() { async ngOnInit() {
@ -128,6 +131,10 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
} }
async fillCipher(cipher: CipherView) { async fillCipher(cipher: CipherView) {
if (cipher.reprompt !== CipherRepromptType.None && !await this.passwordRepromptService.showPasswordPrompt()) {
return;
}
this.totpCode = null; this.totpCode = null;
if (this.totpTimeout != null) { if (this.totpTimeout != null) {
window.clearTimeout(this.totpTimeout); window.clearTimeout(this.totpTimeout);

View File

@ -99,11 +99,17 @@
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.number"> <div class="box-content-row box-content-row-flex" *ngIf="cipher.card.number">
<div class="row-main"> <div class="row-main">
<span class="row-label">{{'number' | i18n}}</span> <span class="row-label">{{'number' | i18n}}</span>
{{cipher.card.number}} <span [hidden]="showCardNumber" class="monospaced">{{cipher.card.maskedNumber}}</span>
<span [hidden]="!showCardNumber" class="monospaced">{{cipher.card.number}}</span>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="toggleCardNumber()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber}"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyNumber' | i18n}}" <a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyNumber' | i18n}}"
(click)="copy(cipher.card.number, 'number', 'Number')"> (click)="copy(cipher.card.number, 'number', 'Card Number')">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a> </a>
</div> </div>

View File

@ -16,6 +16,7 @@ import { CryptoService } from 'jslib/abstractions/crypto.service';
import { EventService } from 'jslib/abstractions/event.service'; import { EventService } from 'jslib/abstractions/event.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service'; import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordRepromptService } from 'jslib/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { TokenService } from 'jslib/abstractions/token.service'; import { TokenService } from 'jslib/abstractions/token.service';
import { TotpService } from 'jslib/abstractions/totp.service'; import { TotpService } from 'jslib/abstractions/totp.service';
@ -53,10 +54,12 @@ export class ViewComponent extends BaseViewComponent {
private router: Router, private location: Location, private router: Router, private location: Location,
broadcasterService: BroadcasterService, ngZone: NgZone, broadcasterService: BroadcasterService, ngZone: NgZone,
changeDetectorRef: ChangeDetectorRef, userService: UserService, changeDetectorRef: ChangeDetectorRef, userService: UserService,
eventService: EventService, private autofillService: AutofillService, apiService: ApiService, eventService: EventService, private autofillService: AutofillService,
private messagingService: MessagingService, private popupUtilsService: PopupUtilsService) { private messagingService: MessagingService, private popupUtilsService: PopupUtilsService,
apiService: ApiService, passwordRepromptService: PasswordRepromptService) {
super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService, super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService,
auditService, window, broadcasterService, ngZone, changeDetectorRef, userService, eventService, apiService); auditService, window, broadcasterService, ngZone, changeDetectorRef, userService, eventService,
apiService, passwordRepromptService);
} }
ngOnInit() { ngOnInit() {
@ -112,32 +115,45 @@ export class ViewComponent extends BaseViewComponent {
await this.loadPageDetails(); await this.loadPageDetails();
} }
edit() { async edit() {
if (this.cipher.isDeleted) { if (this.cipher.isDeleted) {
return false; return false;
} }
super.edit(); if (!await super.edit()) {
this.router.navigate(['/edit-cipher'], { queryParams: { cipherId: this.cipher.id } }); return false;
} }
clone() { this.router.navigate(['/edit-cipher'], { queryParams: { cipherId: this.cipher.id } });
return true;
}
async clone() {
if (this.cipher.isDeleted) { if (this.cipher.isDeleted) {
return false; return false;
} }
super.clone();
if (!await super.clone()) {
return false;
}
this.router.navigate(['/clone-cipher'], { this.router.navigate(['/clone-cipher'], {
queryParams: { queryParams: {
cloneMode: true, cloneMode: true,
cipherId: this.cipher.id, cipherId: this.cipher.id,
}, },
}); });
return true;
}
async share() {
if (!await super.share()) {
return false;
} }
share() {
super.share();
if (this.cipher.organizationId == null) { if (this.cipher.organizationId == null) {
this.router.navigate(['/share-cipher'], { replaceUrl: true, queryParams: { cipherId: this.cipher.id } }); this.router.navigate(['/share-cipher'], { replaceUrl: true, queryParams: { cipherId: this.cipher.id } });
} }
return true;
} }
async fillCipher() { async fillCipher() {
@ -220,6 +236,10 @@ export class ViewComponent extends BaseViewComponent {
} }
private async doAutofill() { private async doAutofill() {
if (!await this.promptPassword()) {
return false;
}
if (this.pageDetails == null || this.pageDetails.length === 0) { if (this.pageDetails == null || this.pageDetails.length === 0) {
this.platformUtilsService.showToast('error', null, this.platformUtilsService.showToast('error', null,
this.i18nService.t('autofillError')); this.i18nService.t('autofillError'));

View File

@ -20,6 +20,7 @@ import {
} from 'jslib/abstractions'; } from 'jslib/abstractions';
import { EventService } from 'jslib/abstractions/event.service'; import { EventService } from 'jslib/abstractions/event.service';
import { CipherRepromptType } from 'jslib/enums/cipherRepromptType';
import { EventType } from 'jslib/enums/eventType'; import { EventType } from 'jslib/enums/eventType';
const CardAttributes: string[] = ['autoCompleteType', 'data-stripe', 'htmlName', 'htmlID', 'label-tag', const CardAttributes: string[] = ['autoCompleteType', 'data-stripe', 'htmlName', 'htmlID', 'label-tag',
@ -254,6 +255,10 @@ export default class AutofillService implements AutofillServiceInterface {
} }
} }
if (cipher.reprompt !== CipherRepromptType.None) {
return;
}
const totpCode = await this.doAutoFill({ const totpCode = await this.doAutoFill({
cipher: cipher, cipher: cipher,
pageDetails: pageDetails, pageDetails: pageDetails,

View File

@ -12,6 +12,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
identityClientId: string = 'browser'; identityClientId: string = 'browser';
private showDialogResolves = new Map<number, { resolve: (value: boolean) => void, date: Date }>(); private showDialogResolves = new Map<number, { resolve: (value: boolean) => void, date: Date }>();
private passwordDialogResolves = new Map<number, { tryResolve: (canceled: boolean, password: string) => Promise<boolean>, date: Date }>();
private deviceCache: DeviceType = null; private deviceCache: DeviceType = null;
private prefersColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)'); private prefersColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)');
@ -149,6 +150,33 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
}); });
} }
async showPasswordDialog(title: string, body: string, passwordValidation: (value: string) => Promise<boolean>) {
const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
this.messagingService.send('showPasswordDialog', {
title: title,
body: body,
dialogId: dialogId,
});
return new Promise<boolean>(resolve => {
this.passwordDialogResolves.set(dialogId, {
tryResolve: async (canceled: boolean, password: string) => {
if (canceled) {
resolve(false);
return false;
}
if (await passwordValidation(password)) {
resolve(true);
return true;
}
},
date: new Date(),
});
});
}
isDev(): boolean { isDev(): boolean {
return process.env.ENV === 'development'; return process.env.ENV === 'development';
} }
@ -256,16 +284,33 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
} }
// Clean up old promises // Clean up old promises
const deleteIds: number[] = [];
this.showDialogResolves.forEach((val, key) => { this.showDialogResolves.forEach((val, key) => {
const age = new Date().getTime() - val.date.getTime(); const age = new Date().getTime() - val.date.getTime();
if (age > DialogPromiseExpiration) { if (age > DialogPromiseExpiration) {
deleteIds.push(key); this.showDialogResolves.delete(key);
} }
}); });
deleteIds.forEach(id => { }
this.showDialogResolves.delete(id);
async resolvePasswordDialogPromise(dialogId: number, canceled: boolean, password: string): Promise<boolean> {
let result = false;
if (this.passwordDialogResolves.has(dialogId)) {
const resolveObj = this.passwordDialogResolves.get(dialogId);
if (await resolveObj.tryResolve(canceled, password)) {
this.passwordDialogResolves.delete(dialogId);
result = true;
}
}
// Clean up old promises
this.passwordDialogResolves.forEach((val, key) => {
const age = new Date().getTime() - val.date.getTime();
if (age > DialogPromiseExpiration) {
this.passwordDialogResolves.delete(key);
}
}); });
return result;
} }
async supportsBiometric() { async supportsBiometric() {