soft locking with protected pin
This commit is contained in:
parent
76c53bc641
commit
0bdbfd7984
|
@ -1,6 +1,9 @@
|
||||||
export abstract class LockService {
|
export abstract class LockService {
|
||||||
|
pinLocked: boolean;
|
||||||
|
isLocked: () => Promise<boolean>;
|
||||||
checkLock: () => Promise<void>;
|
checkLock: () => Promise<void>;
|
||||||
lock: () => Promise<void>;
|
lock: (allowSoftLock?: boolean) => Promise<void>;
|
||||||
setLockOption: (lockOption: number) => Promise<void>;
|
setLockOption: (lockOption: number) => Promise<void>;
|
||||||
isPinLockSet: () => Promise<boolean>;
|
isPinLockSet: () => Promise<[boolean, boolean]>;
|
||||||
|
clear: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export class LockComponent implements OnInit {
|
||||||
protected onSuccessfulSubmit: () => void;
|
protected onSuccessfulSubmit: () => void;
|
||||||
|
|
||||||
private invalidPinAttempts = 0;
|
private invalidPinAttempts = 0;
|
||||||
|
private pinSet: [boolean, boolean];
|
||||||
|
|
||||||
constructor(protected router: Router, protected i18nService: I18nService,
|
constructor(protected router: Router, protected i18nService: I18nService,
|
||||||
protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService,
|
protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService,
|
||||||
|
@ -32,7 +33,9 @@ export class LockComponent implements OnInit {
|
||||||
protected storageService: StorageService, protected lockService: LockService) { }
|
protected storageService: StorageService, protected lockService: LockService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
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();
|
this.email = await this.userService.getEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,13 +55,25 @@ export class LockComponent implements OnInit {
|
||||||
const kdfIterations = await this.userService.getKdfIterations();
|
const kdfIterations = await this.userService.getKdfIterations();
|
||||||
|
|
||||||
if (this.pinLock) {
|
if (this.pinLock) {
|
||||||
const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey);
|
let failed = true;
|
||||||
try {
|
try {
|
||||||
const protectedKeyCs = new CipherString(pinProtectedKey);
|
if (this.pinSet[0]) {
|
||||||
const pinKey = await this.cryptoService.makePinKey(this.pin, this.email, kdf, kdfIterations);
|
const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin);
|
||||||
const decKey = await this.cryptoService.decryptToBytes(protectedKeyCs, pinKey);
|
const decPin = await this.cryptoService.decryptToUtf8(new CipherString(protectedPin));
|
||||||
await this.setKeyAndContinue(new SymmetricCryptoKey(decKey));
|
this.lockService.pinLocked = false;
|
||||||
} catch {
|
failed = decPin !== this.pin;
|
||||||
|
this.doContinue();
|
||||||
|
} else {
|
||||||
|
const pinProtectedKey = await this.storageService.get<string>(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++;
|
this.invalidPinAttempts++;
|
||||||
if (this.invalidPinAttempts >= 5) {
|
if (this.invalidPinAttempts >= 5) {
|
||||||
this.messagingService.send('logout');
|
this.messagingService.send('logout');
|
||||||
|
@ -97,6 +112,10 @@ export class LockComponent implements OnInit {
|
||||||
|
|
||||||
private async setKeyAndContinue(key: SymmetricCryptoKey) {
|
private async setKeyAndContinue(key: SymmetricCryptoKey) {
|
||||||
await this.cryptoService.setKey(key);
|
await this.cryptoService.setKey(key);
|
||||||
|
this.doContinue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private doContinue() {
|
||||||
this.messagingService.send('unlocked');
|
this.messagingService.send('unlocked');
|
||||||
if (this.onSuccessfulSubmit != null) {
|
if (this.onSuccessfulSubmit != null) {
|
||||||
this.onSuccessfulSubmit();
|
this.onSuccessfulSubmit();
|
||||||
|
|
|
@ -4,13 +4,13 @@ import {
|
||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { CryptoService } from '../../abstractions/crypto.service';
|
import { LockService } from '../../abstractions/lock.service';
|
||||||
import { MessagingService } from '../../abstractions/messaging.service';
|
import { MessagingService } from '../../abstractions/messaging.service';
|
||||||
import { UserService } from '../../abstractions/user.service';
|
import { UserService } from '../../abstractions/user.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthGuardService implements CanActivate {
|
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) { }
|
private messagingService: MessagingService) { }
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
|
@ -20,8 +20,8 @@ export class AuthGuardService implements CanActivate {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasKey = await this.cryptoService.hasKey();
|
const locked = await this.lockService.isLocked();
|
||||||
if (!hasKey) {
|
if (locked) {
|
||||||
this.router.navigate(['lock']);
|
this.router.navigate(['lock']);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ export class ConstantsService {
|
||||||
static readonly dontShowIdentitiesCurrentTab: string = 'dontShowIdentitiesCurrentTab';
|
static readonly dontShowIdentitiesCurrentTab: string = 'dontShowIdentitiesCurrentTab';
|
||||||
static readonly defaultUriMatch: string = 'defaultUriMatch';
|
static readonly defaultUriMatch: string = 'defaultUriMatch';
|
||||||
static readonly pinProtectedKey: string = 'pinProtectedKey';
|
static readonly pinProtectedKey: string = 'pinProtectedKey';
|
||||||
|
static readonly protectedPin: string = 'protectedPin';
|
||||||
|
|
||||||
readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey;
|
readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey;
|
||||||
readonly disableGaKey: string = ConstantsService.disableGaKey;
|
readonly disableGaKey: string = ConstantsService.disableGaKey;
|
||||||
|
@ -39,4 +40,5 @@ export class ConstantsService {
|
||||||
readonly dontShowIdentitiesCurrentTab: string = ConstantsService.dontShowIdentitiesCurrentTab;
|
readonly dontShowIdentitiesCurrentTab: string = ConstantsService.dontShowIdentitiesCurrentTab;
|
||||||
readonly defaultUriMatch: string = ConstantsService.defaultUriMatch;
|
readonly defaultUriMatch: string = ConstantsService.defaultUriMatch;
|
||||||
readonly pinProtectedKey: string = ConstantsService.pinProtectedKey;
|
readonly pinProtectedKey: string = ConstantsService.pinProtectedKey;
|
||||||
|
readonly protectedPin: string = ConstantsService.protectedPin;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { SearchService } from '../abstractions/search.service';
|
||||||
import { StorageService } from '../abstractions/storage.service';
|
import { StorageService } from '../abstractions/storage.service';
|
||||||
|
|
||||||
export class LockService implements LockServiceAbstraction {
|
export class LockService implements LockServiceAbstraction {
|
||||||
|
pinLocked = false;
|
||||||
|
|
||||||
private inited = false;
|
private inited = false;
|
||||||
|
|
||||||
constructor(private cipherService: CipherService, private folderService: FolderService,
|
constructor(private cipherService: CipherService, private folderService: FolderService,
|
||||||
|
@ -32,12 +34,24 @@ export class LockService implements LockServiceAbstraction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isLocked(): Promise<boolean> {
|
||||||
|
if (this.pinLocked) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasKey = await this.cryptoService.hasKey();
|
||||||
|
return !hasKey;
|
||||||
|
}
|
||||||
|
|
||||||
async checkLock(): Promise<void> {
|
async checkLock(): Promise<void> {
|
||||||
if (this.platformUtilsService.isViewOpen()) {
|
if (this.platformUtilsService.isViewOpen()) {
|
||||||
// Do not lock
|
// Do not lock
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.pinLocked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const hasKey = await this.cryptoService.hasKey();
|
const hasKey = await this.cryptoService.hasKey();
|
||||||
if (!hasKey) {
|
if (!hasKey) {
|
||||||
// no key so no need to lock
|
// no key so no need to lock
|
||||||
|
@ -61,11 +75,19 @@ export class LockService implements LockServiceAbstraction {
|
||||||
const diffSeconds = ((new Date()).getTime() - lastActive) / 1000;
|
const diffSeconds = ((new Date()).getTime() - lastActive) / 1000;
|
||||||
if (diffSeconds >= lockOptionSeconds) {
|
if (diffSeconds >= lockOptionSeconds) {
|
||||||
// need to lock now
|
// need to lock now
|
||||||
await this.lock();
|
await this.lock(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async lock(): Promise<void> {
|
async lock(allowSoftLock = false): Promise<void> {
|
||||||
|
if (allowSoftLock) {
|
||||||
|
const pinSet = await this.isPinLockSet();
|
||||||
|
if (pinSet[0]) {
|
||||||
|
await this.pinLock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.cryptoService.clearKey(),
|
this.cryptoService.clearKey(),
|
||||||
this.cryptoService.clearOrgKeys(true),
|
this.cryptoService.clearOrgKeys(true),
|
||||||
|
@ -88,8 +110,21 @@ export class LockService implements LockServiceAbstraction {
|
||||||
await this.cryptoService.toggleKey();
|
await this.cryptoService.toggleKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
async isPinLockSet(): Promise<boolean> {
|
async isPinLockSet(): Promise<[boolean, boolean]> {
|
||||||
|
const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin);
|
||||||
const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey);
|
const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey);
|
||||||
return pinProtectedKey != null;
|
return [protectedPin != null, pinProtectedKey != null];
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): Promise<any> {
|
||||||
|
return this.storageService.remove(ConstantsService.protectedPin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async pinLock(): Promise<void> {
|
||||||
|
this.pinLocked = true;
|
||||||
|
this.messagingService.send('locked');
|
||||||
|
if (this.lockedCallback != null) {
|
||||||
|
await this.lockedCallback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import { NotificationType } from '../enums/notificationType';
|
||||||
|
|
||||||
import { ApiService } from '../abstractions/api.service';
|
import { ApiService } from '../abstractions/api.service';
|
||||||
import { AppIdService } from '../abstractions/appId.service';
|
import { AppIdService } from '../abstractions/appId.service';
|
||||||
import { CryptoService } from '../abstractions/crypto.service';
|
|
||||||
import { EnvironmentService } from '../abstractions/environment.service';
|
import { EnvironmentService } from '../abstractions/environment.service';
|
||||||
|
import { LockService } from '../abstractions/lock.service';
|
||||||
import { NotificationsService as NotificationsServiceAbstraction } from '../abstractions/notifications.service';
|
import { NotificationsService as NotificationsServiceAbstraction } from '../abstractions/notifications.service';
|
||||||
import { SyncService } from '../abstractions/sync.service';
|
import { SyncService } from '../abstractions/sync.service';
|
||||||
import { UserService } from '../abstractions/user.service';
|
import { UserService } from '../abstractions/user.service';
|
||||||
|
@ -27,7 +27,7 @@ export class NotificationsService implements NotificationsServiceAbstraction {
|
||||||
|
|
||||||
constructor(private userService: UserService, private syncService: SyncService,
|
constructor(private userService: UserService, private syncService: SyncService,
|
||||||
private appIdService: AppIdService, private apiService: ApiService,
|
private appIdService: AppIdService, private apiService: ApiService,
|
||||||
private cryptoService: CryptoService, private logoutCallback: () => Promise<void>) { }
|
private lockService: LockService, private logoutCallback: () => Promise<void>) { }
|
||||||
|
|
||||||
async init(environmentService: EnvironmentService): Promise<void> {
|
async init(environmentService: EnvironmentService): Promise<void> {
|
||||||
this.inited = false;
|
this.inited = false;
|
||||||
|
@ -185,7 +185,7 @@ export class NotificationsService implements NotificationsServiceAbstraction {
|
||||||
|
|
||||||
private async isAuthedAndUnlocked() {
|
private async isAuthedAndUnlocked() {
|
||||||
if (await this.userService.isAuthenticated()) {
|
if (await this.userService.isAuthenticated()) {
|
||||||
return this.cryptoService.hasKey();
|
return this.lockService.isLocked();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue