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:
parent
2c58dbb344
commit
cdc71dd661
2
jslib
2
jslib
|
@ -1 +1 @@
|
||||||
Subproject commit b6f102938fe7c17631cb1b2e356438c5e4456529
|
Subproject commit a72c8a60c1b7a6980bceee456c53a9ea7b9b3451
|
|
@ -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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -211,6 +211,10 @@ $fa-font-path: "~font-awesome/fonts";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.swal2-validation-message {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
date-input-polyfill {
|
date-input-polyfill {
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue