diff --git a/src/abstractions/lock.service.ts b/src/abstractions/lock.service.ts index 9c39c25351..ede31db0b1 100644 --- a/src/abstractions/lock.service.ts +++ b/src/abstractions/lock.service.ts @@ -1,6 +1,9 @@ export abstract class LockService { + pinLocked: boolean; + isLocked: () => Promise; checkLock: () => Promise; - lock: () => Promise; + lock: (allowSoftLock?: boolean) => Promise; setLockOption: (lockOption: number) => Promise; - isPinLockSet: () => Promise; + isPinLockSet: () => Promise<[boolean, boolean]>; + clear: () => Promise; } diff --git a/src/angular/components/lock.component.ts b/src/angular/components/lock.component.ts index 37f2f72603..715c43f5b0 100644 --- a/src/angular/components/lock.component.ts +++ b/src/angular/components/lock.component.ts @@ -25,6 +25,7 @@ export class LockComponent implements OnInit { protected onSuccessfulSubmit: () => void; private invalidPinAttempts = 0; + private pinSet: [boolean, boolean]; constructor(protected router: Router, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService, @@ -32,7 +33,9 @@ export class LockComponent implements OnInit { protected storageService: StorageService, protected lockService: LockService) { } async ngOnInit() { - this.pinLock = await this.lockService.isPinLockSet(); + this.pinSet = await this.lockService.isPinLockSet(); + const hasKey = await this.cryptoService.hasKey(); + this.pinLock = (this.pinSet[0] && hasKey) || this.pinSet[1]; this.email = await this.userService.getEmail(); } @@ -52,13 +55,25 @@ export class LockComponent implements OnInit { const kdfIterations = await this.userService.getKdfIterations(); if (this.pinLock) { - const pinProtectedKey = await this.storageService.get(ConstantsService.pinProtectedKey); + let failed = true; try { - const protectedKeyCs = new CipherString(pinProtectedKey); - const pinKey = await this.cryptoService.makePinKey(this.pin, this.email, kdf, kdfIterations); - const decKey = await this.cryptoService.decryptToBytes(protectedKeyCs, pinKey); - await this.setKeyAndContinue(new SymmetricCryptoKey(decKey)); - } catch { + if (this.pinSet[0]) { + const protectedPin = await this.storageService.get(ConstantsService.protectedPin); + const decPin = await this.cryptoService.decryptToUtf8(new CipherString(protectedPin)); + this.lockService.pinLocked = false; + failed = decPin !== this.pin; + this.doContinue(); + } else { + const pinProtectedKey = await this.storageService.get(ConstantsService.pinProtectedKey); + const protectedKeyCs = new CipherString(pinProtectedKey); + const pinKey = await this.cryptoService.makePinKey(this.pin, this.email, kdf, kdfIterations); + const decKey = await this.cryptoService.decryptToBytes(protectedKeyCs, pinKey); + failed = false; + await this.setKeyAndContinue(new SymmetricCryptoKey(decKey)); + } + } catch { } + + if (failed) { this.invalidPinAttempts++; if (this.invalidPinAttempts >= 5) { this.messagingService.send('logout'); @@ -97,6 +112,10 @@ export class LockComponent implements OnInit { private async setKeyAndContinue(key: SymmetricCryptoKey) { await this.cryptoService.setKey(key); + this.doContinue(); + } + + private doContinue() { this.messagingService.send('unlocked'); if (this.onSuccessfulSubmit != null) { this.onSuccessfulSubmit(); diff --git a/src/angular/services/auth-guard.service.ts b/src/angular/services/auth-guard.service.ts index 1db4e5726c..6956df71d1 100644 --- a/src/angular/services/auth-guard.service.ts +++ b/src/angular/services/auth-guard.service.ts @@ -4,13 +4,13 @@ import { Router, } from '@angular/router'; -import { CryptoService } from '../../abstractions/crypto.service'; +import { LockService } from '../../abstractions/lock.service'; import { MessagingService } from '../../abstractions/messaging.service'; import { UserService } from '../../abstractions/user.service'; @Injectable() export class AuthGuardService implements CanActivate { - constructor(private cryptoService: CryptoService, private userService: UserService, private router: Router, + constructor(private lockService: LockService, private userService: UserService, private router: Router, private messagingService: MessagingService) { } async canActivate() { @@ -20,8 +20,8 @@ export class AuthGuardService implements CanActivate { return false; } - const hasKey = await this.cryptoService.hasKey(); - if (!hasKey) { + const locked = await this.lockService.isLocked(); + if (locked) { this.router.navigate(['lock']); return false; } diff --git a/src/services/constants.service.ts b/src/services/constants.service.ts index 41c5a1b395..755e3335e8 100644 --- a/src/services/constants.service.ts +++ b/src/services/constants.service.ts @@ -19,6 +19,7 @@ export class ConstantsService { static readonly dontShowIdentitiesCurrentTab: string = 'dontShowIdentitiesCurrentTab'; static readonly defaultUriMatch: string = 'defaultUriMatch'; static readonly pinProtectedKey: string = 'pinProtectedKey'; + static readonly protectedPin: string = 'protectedPin'; readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey; readonly disableGaKey: string = ConstantsService.disableGaKey; @@ -39,4 +40,5 @@ export class ConstantsService { readonly dontShowIdentitiesCurrentTab: string = ConstantsService.dontShowIdentitiesCurrentTab; readonly defaultUriMatch: string = ConstantsService.defaultUriMatch; readonly pinProtectedKey: string = ConstantsService.pinProtectedKey; + readonly protectedPin: string = ConstantsService.protectedPin; } diff --git a/src/services/lock.service.ts b/src/services/lock.service.ts index ff1c976ae6..1576c1582c 100644 --- a/src/services/lock.service.ts +++ b/src/services/lock.service.ts @@ -11,6 +11,8 @@ import { SearchService } from '../abstractions/search.service'; import { StorageService } from '../abstractions/storage.service'; export class LockService implements LockServiceAbstraction { + pinLocked = false; + private inited = false; constructor(private cipherService: CipherService, private folderService: FolderService, @@ -32,12 +34,24 @@ export class LockService implements LockServiceAbstraction { } } + async isLocked(): Promise { + if (this.pinLocked) { + return true; + } + const hasKey = await this.cryptoService.hasKey(); + return !hasKey; + } + async checkLock(): Promise { if (this.platformUtilsService.isViewOpen()) { // Do not lock return; } + if (this.pinLocked) { + return; + } + const hasKey = await this.cryptoService.hasKey(); if (!hasKey) { // no key so no need to lock @@ -61,11 +75,19 @@ export class LockService implements LockServiceAbstraction { const diffSeconds = ((new Date()).getTime() - lastActive) / 1000; if (diffSeconds >= lockOptionSeconds) { // need to lock now - await this.lock(); + await this.lock(true); } } - async lock(): Promise { + async lock(allowSoftLock = false): Promise { + if (allowSoftLock) { + const pinSet = await this.isPinLockSet(); + if (pinSet[0]) { + await this.pinLock(); + return; + } + } + await Promise.all([ this.cryptoService.clearKey(), this.cryptoService.clearOrgKeys(true), @@ -88,8 +110,21 @@ export class LockService implements LockServiceAbstraction { await this.cryptoService.toggleKey(); } - async isPinLockSet(): Promise { + async isPinLockSet(): Promise<[boolean, boolean]> { + const protectedPin = await this.storageService.get(ConstantsService.protectedPin); const pinProtectedKey = await this.storageService.get(ConstantsService.pinProtectedKey); - return pinProtectedKey != null; + return [protectedPin != null, pinProtectedKey != null]; + } + + clear(): Promise { + return this.storageService.remove(ConstantsService.protectedPin); + } + + private async pinLock(): Promise { + this.pinLocked = true; + this.messagingService.send('locked'); + if (this.lockedCallback != null) { + await this.lockedCallback(); + } } } diff --git a/src/services/notifications.service.ts b/src/services/notifications.service.ts index 9ad374bf65..5a52f527e3 100644 --- a/src/services/notifications.service.ts +++ b/src/services/notifications.service.ts @@ -5,8 +5,8 @@ import { NotificationType } from '../enums/notificationType'; import { ApiService } from '../abstractions/api.service'; import { AppIdService } from '../abstractions/appId.service'; -import { CryptoService } from '../abstractions/crypto.service'; import { EnvironmentService } from '../abstractions/environment.service'; +import { LockService } from '../abstractions/lock.service'; import { NotificationsService as NotificationsServiceAbstraction } from '../abstractions/notifications.service'; import { SyncService } from '../abstractions/sync.service'; import { UserService } from '../abstractions/user.service'; @@ -27,7 +27,7 @@ export class NotificationsService implements NotificationsServiceAbstraction { constructor(private userService: UserService, private syncService: SyncService, private appIdService: AppIdService, private apiService: ApiService, - private cryptoService: CryptoService, private logoutCallback: () => Promise) { } + private lockService: LockService, private logoutCallback: () => Promise) { } async init(environmentService: EnvironmentService): Promise { this.inited = false; @@ -185,7 +185,7 @@ export class NotificationsService implements NotificationsServiceAbstraction { private async isAuthedAndUnlocked() { if (await this.userService.isAuthenticated()) { - return this.cryptoService.hasKey(); + return this.lockService.isLocked(); } return false; }