From c1113852a420bf8efb77b26563dca8efd4237c07 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Wed, 1 Apr 2020 10:18:36 -0500 Subject: [PATCH] [Auto-Logout] Implement Vault Timeout Options (#424) * Update jslib (31a2574 -> 28e3fff) * Initial commit of vault timeout implentation Co-authored-by: Vincent Salucci --- jslib | 2 +- src/app/accounts/lock.component.ts | 6 ++--- src/app/accounts/settings.component.html | 31 ++++++++++++++++++++---- src/app/accounts/settings.component.ts | 29 ++++++++++++---------- src/app/app.component.ts | 15 +++++++----- src/app/services.module.ts | 18 ++++++++------ src/locales/en/messages.json | 20 ++++++++++++--- src/main.ts | 5 ++-- src/main/powerMonitor.main.ts | 27 ++++++++++++--------- src/scss/misc.scss | 21 ++++++++++++++++ 10 files changed, 121 insertions(+), 53 deletions(-) diff --git a/jslib b/jslib index 31a25740..28e3fff7 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 31a257407be7f8f47624b0d021363aaf2cfda2d7 +Subproject commit 28e3fff739e64c2dd80d3d98717e2921895d16df diff --git a/src/app/accounts/lock.component.ts b/src/app/accounts/lock.component.ts index 4bc02870..5dab61c3 100644 --- a/src/app/accounts/lock.component.ts +++ b/src/app/accounts/lock.component.ts @@ -4,12 +4,12 @@ import { Router } from '@angular/router'; import { CryptoService } from 'jslib/abstractions/crypto.service'; import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; -import { LockService } from 'jslib/abstractions/lock.service'; import { MessagingService } from 'jslib/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { StateService } from 'jslib/abstractions/state.service'; import { StorageService } from 'jslib/abstractions/storage.service'; import { UserService } from 'jslib/abstractions/user.service'; +import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; import { LockComponent as BaseLockComponent } from 'jslib/angular/components/lock.component'; @@ -21,9 +21,9 @@ export class LockComponent extends BaseLockComponent { constructor(router: Router, i18nService: I18nService, platformUtilsService: PlatformUtilsService, messagingService: MessagingService, userService: UserService, cryptoService: CryptoService, - storageService: StorageService, lockService: LockService, + storageService: StorageService, vaultTimeoutService: VaultTimeoutService, environmentService: EnvironmentService, stateService: StateService) { super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService, - storageService, lockService, environmentService, stateService); + storageService, vaultTimeoutService, environmentService, stateService); } } diff --git a/src/app/accounts/settings.component.html b/src/app/accounts/settings.component.html index 0783946e..c2608bed 100644 --- a/src/app/accounts/settings.component.html +++ b/src/app/accounts/settings.component.html @@ -8,12 +8,33 @@
- - + - {{'lockOptionsDesc' | i18n}} + {{'vaultTimeoutDesc' | i18n}} +
+
+ +
+ +
+ {{'vaultTimeoutActionLockDesc' | i18n}} +
+ +
+ {{'vaultTimeoutActionLogOutDesc' | i18n}}
diff --git a/src/app/accounts/settings.component.ts b/src/app/accounts/settings.component.ts index 4c469cbb..94af59a9 100644 --- a/src/app/accounts/settings.component.ts +++ b/src/app/accounts/settings.component.ts @@ -11,12 +11,12 @@ import { DeviceType } from 'jslib/enums/deviceType'; import { CryptoService } from 'jslib/abstractions/crypto.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; -import { LockService } from 'jslib/abstractions/lock.service'; import { MessagingService } from 'jslib/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { StateService } from 'jslib/abstractions/state.service'; import { StorageService } from 'jslib/abstractions/storage.service'; import { UserService } from 'jslib/abstractions/user.service'; +import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; import { ConstantsService } from 'jslib/services/constants.service'; @@ -29,7 +29,8 @@ import { Utils } from 'jslib/misc/utils'; templateUrl: 'settings.component.html', }) export class SettingsComponent implements OnInit { - lockOption: number = null; + vaultTimeout: number = null; + vaultTimeoutAction: string; pin: boolean = null; disableFavicons: boolean = false; enableMinToTray: boolean = false; @@ -38,7 +39,7 @@ export class SettingsComponent implements OnInit { showMinToTray: boolean = false; startToTray: boolean = false; locale: string; - lockOptions: any[]; + vaultTimeouts: any[]; localeOptions: any[]; theme: string; themeOptions: any[]; @@ -49,14 +50,14 @@ export class SettingsComponent implements OnInit { constructor(private analytics: Angulartics2, private toasterService: ToasterService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, - private storageService: StorageService, private lockService: LockService, + private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService, private stateService: StateService, private messagingService: MessagingService, private userService: UserService, private cryptoService: CryptoService) { const trayKey = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop ? 'enableMenuBar' : 'enableTray'; this.enableTrayText = this.i18nService.t(trayKey); this.enableTrayDescText = this.i18nService.t(trayKey + 'Desc'); - this.lockOptions = [ + this.vaultTimeouts = [ // { name: i18nService.t('immediately'), value: 0 }, { name: i18nService.t('oneMinute'), value: 1 }, { name: i18nService.t('fiveMinutes'), value: 5 }, @@ -69,10 +70,10 @@ export class SettingsComponent implements OnInit { ]; if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) { - this.lockOptions.push({ name: i18nService.t('onLocked'), value: -2 }); + this.vaultTimeouts.push({ name: i18nService.t('onLocked'), value: -2 }); } - this.lockOptions = this.lockOptions.concat([ + this.vaultTimeouts = this.vaultTimeouts.concat([ { name: i18nService.t('onRestart'), value: -1 }, { name: i18nService.t('never'), value: null }, ]); @@ -109,8 +110,9 @@ export class SettingsComponent implements OnInit { async ngOnInit() { this.showMinToTray = this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop; - this.lockOption = await this.storageService.get(ConstantsService.lockOptionKey); - const pinSet = await this.lockService.isPinLockSet(); + this.vaultTimeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); + this.vaultTimeoutAction = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); + const pinSet = await this.vaultTimeoutService.isPinLockSet(); this.pin = pinSet[0] || pinSet[1]; this.disableFavicons = await this.storageService.get(ConstantsService.disableFaviconKey); this.enableMinToTray = await this.storageService.get(ElectronConstants.enableMinimizeToTrayKey); @@ -122,8 +124,9 @@ export class SettingsComponent implements OnInit { this.clearClipboard = await this.storageService.get(ConstantsService.clearClipboardKey); } - async saveLockOption() { - await this.lockService.setLockOption(this.lockOption != null ? this.lockOption : null); + async saveVaultTimeoutOptions() { + await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout != null ? this.vaultTimeout : null, + this.vaultTimeoutAction); } async updatePin() { @@ -171,7 +174,7 @@ export class SettingsComponent implements OnInit { if (masterPassOnRestart) { const encPin = await this.cryptoService.encrypt(pin); await this.storageService.save(ConstantsService.protectedPin, encPin.encryptedString); - this.lockService.pinProtectedKey = pinProtectedKey; + this.vaultTimeoutService.pinProtectedKey = pinProtectedKey; } else { await this.storageService.save(ConstantsService.pinProtectedKey, pinProtectedKey.encryptedString); } @@ -181,7 +184,7 @@ export class SettingsComponent implements OnInit { } if (!this.pin) { await this.cryptoService.clearPinProtectedKey(); - await this.lockService.clear(); + await this.vaultTimeoutService.clear(); } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 54a8f62b..f05db5bb 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -36,11 +36,11 @@ import { CryptoService } from 'jslib/abstractions/crypto.service'; import { EventService } from 'jslib/abstractions/event.service'; import { FolderService } from 'jslib/abstractions/folder.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; -import { LockService } from 'jslib/abstractions/lock.service'; import { MessagingService } from 'jslib/abstractions/messaging.service'; import { NotificationsService } from 'jslib/abstractions/notifications.service'; import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { PolicyService } from 'jslib/abstractions/policy.service'; import { SearchService } from 'jslib/abstractions/search.service'; import { SettingsService } from 'jslib/abstractions/settings.service'; import { StateService } from 'jslib/abstractions/state.service'; @@ -49,6 +49,7 @@ import { SyncService } from 'jslib/abstractions/sync.service'; import { SystemService } from 'jslib/abstractions/system.service'; import { TokenService } from 'jslib/abstractions/token.service'; import { UserService } from 'jslib/abstractions/user.service'; +import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; import { ConstantsService } from 'jslib/services/constants.service'; @@ -90,12 +91,13 @@ export class AppComponent implements OnInit { private authService: AuthService, private router: Router, private analytics: Angulartics2, private toasterService: ToasterService, private i18nService: I18nService, private sanitizer: DomSanitizer, private ngZone: NgZone, - private lockService: LockService, private storageService: StorageService, + private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService, private cryptoService: CryptoService, private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService, private collectionService: CollectionService, private searchService: SearchService, private notificationsService: NotificationsService, private platformUtilsService: PlatformUtilsService, private systemService: SystemService, - private stateService: StateService, private eventService: EventService) { } + private stateService: StateService, private eventService: EventService, + private policyService: PolicyService) { } ngOnInit() { this.ngZone.runOutsideAngular(() => { @@ -136,7 +138,7 @@ export class AppComponent implements OnInit { this.logOut(!!message.expired); break; case 'lockVault': - await this.lockService.lock(true); + await this.vaultTimeoutService.lock(true); break; case 'locked': if (this.modal != null) { @@ -205,7 +207,7 @@ export class AppComponent implements OnInit { private async updateAppMenu() { this.messagingService.send('updateAppMenu', { isAuthenticated: await this.userService.isAuthenticated(), - isLocked: await this.lockService.isLocked(), + isLocked: await this.vaultTimeoutService.isLocked(), }); } @@ -224,8 +226,9 @@ export class AppComponent implements OnInit { this.folderService.clear(userId), this.collectionService.clear(userId), this.passwordGenerationService.clear(), - this.lockService.clear(), + this.vaultTimeoutService.clear(), this.stateService.purge(), + this.policyService.clear(userId), ]); this.searchService.clearIndex(); diff --git a/src/app/services.module.ts b/src/app/services.module.ts index 1d49a7e9..df76b2f7 100644 --- a/src/app/services.module.ts +++ b/src/app/services.module.ts @@ -38,7 +38,6 @@ import { EnvironmentService } from 'jslib/services/environment.service'; import { EventService } from 'jslib/services/event.service'; import { ExportService } from 'jslib/services/export.service'; import { FolderService } from 'jslib/services/folder.service'; -import { LockService } from 'jslib/services/lock.service'; import { NotificationsService } from 'jslib/services/notifications.service'; import { PasswordGenerationService } from 'jslib/services/passwordGeneration.service'; import { PolicyService } from 'jslib/services/policy.service'; @@ -50,6 +49,7 @@ import { SystemService } from 'jslib/services/system.service'; import { TokenService } from 'jslib/services/token.service'; import { TotpService } from 'jslib/services/totp.service'; import { UserService } from 'jslib/services/user.service'; +import { VaultTimeoutService } from 'jslib/services/vaultTimeout.service'; import { WebCryptoFunctionService } from 'jslib/services/webCryptoFunction.service'; import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service'; @@ -65,7 +65,6 @@ import { EventService as EventServiceAbstraction } from 'jslib/abstractions/even import { ExportService as ExportServiceAbstraction } from 'jslib/abstractions/export.service'; import { FolderService as FolderServiceAbstraction } from 'jslib/abstractions/folder.service'; import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service'; -import { LockService as LockServiceAbstraction } from 'jslib/abstractions/lock.service'; import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service'; import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service'; import { NotificationsService as NotificationsServiceAbstraction } from 'jslib/abstractions/notifications.service'; @@ -83,6 +82,7 @@ import { SystemService as SystemServiceAbstraction } from 'jslib/abstractions/sy import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service'; import { TotpService as TotpServiceAbstraction } from 'jslib/abstractions/totp.service'; import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service'; +import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib/abstractions/vaultTimeout.service'; const logService = new ElectronLogService(); const i18nService = new I18nService(window.navigator.language, './locales'); @@ -109,8 +109,9 @@ const folderService = new FolderService(cryptoService, userService, apiService, const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService); searchService = new SearchService(cipherService, platformUtilsService); const policyService = new PolicyService(userService, storageService); -const lockService = new LockService(cipherService, folderService, collectionService, - cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, null); +const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService, + cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, null, + async () => messagingService.send('logout', { expired: false })); const syncService = new SyncService(userService, apiService, settingsService, folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService, async (expired: boolean) => messagingService.send('logout', { expired: expired })); @@ -122,10 +123,11 @@ const authService = new AuthService(cryptoService, apiService, const exportService = new ExportService(folderService, cipherService, apiService); const auditService = new AuditService(cryptoFunctionService, apiService); const notificationsService = new NotificationsService(userService, syncService, appIdService, - apiService, lockService, async () => messagingService.send('logout', { expired: true })); + apiService, vaultTimeoutService, async () => messagingService.send('logout', { expired: true })); const environmentService = new EnvironmentService(apiService, storageService, notificationsService); const eventService = new EventService(storageService, apiService, userService, cipherService); -const systemService = new SystemService(storageService, lockService, messagingService, platformUtilsService, null); +const systemService = new SystemService(storageService, vaultTimeoutService, messagingService, platformUtilsService, + null); const analytics = new Analytics(window, () => isDev(), platformUtilsService, storageService, appIdService); containerService.attachToGlobal(window); @@ -134,7 +136,7 @@ export function initFactory(): Function { return async () => { await environmentService.setUrlsFromStorage(); syncService.fullSync(true); - lockService.init(true); + vaultTimeoutService.init(true); const locale = await storageService.get(ConstantsService.localeKey); await i18nService.init(locale); eventService.init(true); @@ -197,7 +199,7 @@ export function initFactory(): Function { { provide: MessagingServiceAbstraction, useValue: messagingService }, { provide: BroadcasterService, useValue: broadcasterService }, { provide: SettingsServiceAbstraction, useValue: settingsService }, - { provide: LockServiceAbstraction, useValue: lockService }, + { provide: VaultTimeoutServiceAbstraction, useValue: vaultTimeoutService }, { provide: StorageServiceAbstraction, useValue: storageService }, { provide: StateServiceAbstraction, useValue: stateService }, { provide: LogServiceAbstraction, useValue: logService }, diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index b1ebadb5..bd9e7f4f 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -769,11 +769,11 @@ "twoStepLogin": { "message": "Two-step Login" }, - "lockOptions": { - "message": "Lock Options" + "vaultTimeout": { + "message": "Vault Timeout" }, - "lockOptionsDesc": { - "message": "Choose when your vault locks. A locked vault requires that you re-enter your master password to access it again." + "vaultTimeoutDesc": { + "message": "Choose when your vault will timeout and perform the selected action." }, "immediately": { "message": "Immediately" @@ -1279,5 +1279,17 @@ }, "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." + }, + "vaultTimeoutAction": { + "message": "Vault Timeout Action" + }, + "vaultTimeoutActionLockDesc": { + "message": "A locked vault requires that you re-enter your master password to access it again." + }, + "vaultTimeoutActionLogOutDesc": { + "message": "A logged out vault requires that you re-authenticate to access it again." + }, + "lock": { + "message": "Lock" } } diff --git a/src/main.ts b/src/main.ts index 4842d14e..8bfd6b4b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -82,8 +82,9 @@ export class Main { this.i18nService = new I18nService('en', './locales/'); const storageDefaults: any = {}; - // Default lock options to "on restart". - storageDefaults[ConstantsService.lockOptionKey] = -1; + // Default vault timeout to "on restart", and action to "lock" + storageDefaults[ConstantsService.vaultTimeoutKey] = -1; + storageDefaults[ConstantsService.vaultTimeoutActionKey] = 'lock'; this.storageService = new ElectronStorageService(app.getPath('userData'), storageDefaults); this.windowMain = new WindowMain(this.storageService, true); diff --git a/src/main/powerMonitor.main.ts b/src/main/powerMonitor.main.ts index 7563f109..32ad1972 100644 --- a/src/main/powerMonitor.main.ts +++ b/src/main/powerMonitor.main.ts @@ -21,9 +21,10 @@ export class PowerMonitorMain { if (!isSnapStore()) { // System sleep powerMonitor.on('suspend', async () => { - const lockOption = await this.getLockOption(); - if (lockOption === -3) { - this.main.messagingService.send('lockVault'); + const options = await this.getVaultTimeoutOptions(); + if (options[0] === -3) { + options[1] === 'lock' ? this.main.messagingService.send('lockVault') : + this.main.messagingService.send('logout', { expired: false }); } }); } @@ -31,9 +32,10 @@ export class PowerMonitorMain { if (process.platform !== 'linux') { // System locked powerMonitor.on('lock-screen', async () => { - const lockOption = await this.getLockOption(); - if (lockOption === -2) { - this.main.messagingService.send('lockVault'); + const options = await this.getVaultTimeoutOptions(); + if (options[0] === -2) { + options[1] === 'lock' ? this.main.messagingService.send('lockVault') : + this.main.messagingService.send('logout', { expired: false }); } }); } @@ -47,9 +49,10 @@ export class PowerMonitorMain { return; } - const lockOption = await this.getLockOption(); - if (lockOption === -4) { - this.main.messagingService.send('lockVault'); + const options = await this.getVaultTimeoutOptions(); + if (options[0] === -4) { + options[1] === 'lock' ? this.main.messagingService.send('lockVault') : + this.main.messagingService.send('logout', { expired: false }); } } @@ -57,7 +60,9 @@ export class PowerMonitorMain { }, IdleCheckInterval); } - private getLockOption(): Promise { - return this.main.storageService.get(ConstantsService.lockOptionKey); + private async getVaultTimeoutOptions(): Promise<[number, string]> { + const timeout = await this.main.storageService.get(ConstantsService.vaultTimeoutKey); + const action = await this.main.storageService.get(ConstantsService.vaultTimeoutActionKey); + return [timeout, action]; } } diff --git a/src/scss/misc.scss b/src/scss/misc.scss index 54324d78..06696705 100644 --- a/src/scss/misc.scss +++ b/src/scss/misc.scss @@ -253,6 +253,27 @@ form, .form { } } + .radio { + position: relative; + display: block; + padding-left: 18px; + margin-top: 4px; + + &.radio-mt-2 { + margin-top: 2px; + } + + label { + margin-bottom: 0; + } + + input[type="radio"] { + position: absolute; + margin-top: 4px; + margin-left: -18px; + } + } + .help-block { margin-top: 3px; display: block;