[Auto-Logout] Implement upstream changes (#506)

* Initial commit of auto logout functionality

* Update jslib 31a2574 -> 28e3fff

* Reverting prod URLs

* Set log out expired param to false

Co-authored-by: Vincent Salucci <vsalucci@bitwarden.com>
This commit is contained in:
Vincent Salucci 2020-03-30 09:59:47 -05:00 committed by GitHub
parent 5bf3ca2708
commit d58550c2b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 81 additions and 38 deletions

2
jslib

@ -1 +1 @@
Subproject commit 31a257407be7f8f47624b0d021363aaf2cfda2d7 Subproject commit 28e3fff739e64c2dd80d3d98717e2921895d16df

View File

@ -4,12 +4,12 @@ import { Router } from '@angular/router';
import { CryptoService } from 'jslib/abstractions/crypto.service'; import { CryptoService } from 'jslib/abstractions/crypto.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { LockService } from 'jslib/abstractions/lock.service';
import { MessagingService } from 'jslib/abstractions/messaging.service'; import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service'; import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service'; import { StorageService } from 'jslib/abstractions/storage.service';
import { UserService } from 'jslib/abstractions/user.service'; import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
import { RouterService } from '../services/router.service'; import { RouterService } from '../services/router.service';
@ -23,11 +23,11 @@ export class LockComponent extends BaseLockComponent {
constructor(router: Router, i18nService: I18nService, constructor(router: Router, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService, platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
userService: UserService, cryptoService: CryptoService, userService: UserService, cryptoService: CryptoService,
storageService: StorageService, lockService: LockService, storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService, private routerService: RouterService, environmentService: EnvironmentService, private routerService: RouterService,
stateService: StateService) { stateService: StateService) {
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService, super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
storageService, lockService, environmentService, stateService); storageService, vaultTimeoutService, environmentService, stateService);
} }
async ngOnInit() { async ngOnInit() {

View File

@ -35,7 +35,6 @@ import { CryptoService } from 'jslib/abstractions/crypto.service';
import { EventService } from 'jslib/abstractions/event.service'; import { EventService } from 'jslib/abstractions/event.service';
import { FolderService } from 'jslib/abstractions/folder.service'; import { FolderService } from 'jslib/abstractions/folder.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { LockService } from 'jslib/abstractions/lock.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 { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
@ -46,6 +45,7 @@ import { StateService } from 'jslib/abstractions/state.service';
import { SyncService } from 'jslib/abstractions/sync.service'; import { SyncService } from 'jslib/abstractions/sync.service';
import { TokenService } from 'jslib/abstractions/token.service'; import { TokenService } from 'jslib/abstractions/token.service';
import { UserService } from 'jslib/abstractions/user.service'; import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
import { ConstantsService } from 'jslib/services/constants.service'; import { ConstantsService } from 'jslib/services/constants.service';
@ -78,7 +78,7 @@ export class AppComponent implements OnDestroy, OnInit {
private authService: AuthService, private router: Router, private analytics: Angulartics2, private authService: AuthService, private router: Router, private analytics: Angulartics2,
private toasterService: ToasterService, private i18nService: I18nService, private toasterService: ToasterService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private ngZone: NgZone, private platformUtilsService: PlatformUtilsService, private ngZone: NgZone,
private lockService: LockService, private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
private cryptoService: CryptoService, private collectionService: CollectionService, private cryptoService: CryptoService, private collectionService: CollectionService,
private sanitizer: DomSanitizer, private searchService: SearchService, private sanitizer: DomSanitizer, private searchService: SearchService,
private notificationsService: NotificationsService, private routerService: RouterService, private notificationsService: NotificationsService, private routerService: RouterService,
@ -110,7 +110,7 @@ export class AppComponent implements OnDestroy, OnInit {
this.logOut(!!message.expired); this.logOut(!!message.expired);
break; break;
case 'lockVault': case 'lockVault':
await this.lockService.lock(); await this.vaultTimeoutService.lock();
break; break;
case 'locked': case 'locked':
this.notificationsService.updateConnection(false); this.notificationsService.updateConnection(false);

View File

@ -38,7 +38,6 @@ import { EventService as EventLoggingService } from 'jslib/services/event.servic
import { ExportService } from 'jslib/services/export.service'; import { ExportService } from 'jslib/services/export.service';
import { FolderService } from 'jslib/services/folder.service'; import { FolderService } from 'jslib/services/folder.service';
import { ImportService } from 'jslib/services/import.service'; import { ImportService } from 'jslib/services/import.service';
import { LockService } from 'jslib/services/lock.service';
import { NotificationsService } from 'jslib/services/notifications.service'; import { NotificationsService } from 'jslib/services/notifications.service';
import { PasswordGenerationService } from 'jslib/services/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib/services/passwordGeneration.service';
import { PolicyService } from 'jslib/services/policy.service'; import { PolicyService } from 'jslib/services/policy.service';
@ -49,6 +48,7 @@ import { SyncService } from 'jslib/services/sync.service';
import { TokenService } from 'jslib/services/token.service'; import { TokenService } from 'jslib/services/token.service';
import { TotpService } from 'jslib/services/totp.service'; import { TotpService } from 'jslib/services/totp.service';
import { UserService } from 'jslib/services/user.service'; import { UserService } from 'jslib/services/user.service';
import { VaultTimeoutService } from 'jslib/services/vaultTimeout.service';
import { WebCryptoFunctionService } from 'jslib/services/webCryptoFunction.service'; import { WebCryptoFunctionService } from 'jslib/services/webCryptoFunction.service';
import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service'; import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service';
@ -65,7 +65,6 @@ import { ExportService as ExportServiceAbstraction } from 'jslib/abstractions/ex
import { FolderService as FolderServiceAbstraction } from 'jslib/abstractions/folder.service'; import { FolderService as FolderServiceAbstraction } from 'jslib/abstractions/folder.service';
import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service'; import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service';
import { ImportService as ImportServiceAbstraction } from 'jslib/abstractions/import.service'; import { ImportService as ImportServiceAbstraction } from 'jslib/abstractions/import.service';
import { LockService as LockServiceAbstraction } from 'jslib/abstractions/lock.service';
import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service'; import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service';
import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service'; import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service';
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib/abstractions/notifications.service'; import { NotificationsService as NotificationsServiceAbstraction } from 'jslib/abstractions/notifications.service';
@ -82,6 +81,7 @@ import { SyncService as SyncServiceAbstraction } from 'jslib/abstractions/sync.s
import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service'; import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service';
import { TotpService as TotpServiceAbstraction } from 'jslib/abstractions/totp.service'; import { TotpService as TotpServiceAbstraction } from 'jslib/abstractions/totp.service';
import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service'; import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib/abstractions/vaultTimeout.service';
const i18nService = new I18nService(window.navigator.language, 'locales'); const i18nService = new I18nService(window.navigator.language, 'locales');
const stateService = new StateService(); const stateService = new StateService();
@ -108,8 +108,9 @@ const folderService = new FolderService(cryptoService, userService, apiService,
const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService); const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
searchService = new SearchService(cipherService, platformUtilsService); searchService = new SearchService(cipherService, platformUtilsService);
const policyService = new PolicyService(userService, storageService); const policyService = new PolicyService(userService, storageService);
const lockService = new LockService(cipherService, folderService, collectionService, const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, null); cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, null,
async () => messagingService.send('logout', { expired: false }));
const syncService = new SyncService(userService, apiService, settingsService, const syncService = new SyncService(userService, apiService, settingsService,
folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService, folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
async (expired: boolean) => messagingService.send('logout', { expired: expired })); async (expired: boolean) => messagingService.send('logout', { expired: expired }));
@ -121,7 +122,7 @@ const authService = new AuthService(cryptoService, apiService,
const exportService = new ExportService(folderService, cipherService, apiService); const exportService = new ExportService(folderService, cipherService, apiService);
const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService); const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService);
const notificationsService = new NotificationsService(userService, syncService, appIdService, 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 environmentService = new EnvironmentService(apiService, storageService, notificationsService);
const auditService = new AuditService(cryptoFunctionService, apiService); const auditService = new AuditService(cryptoFunctionService, apiService);
const eventLoggingService = new EventLoggingService(storageService, apiService, userService, cipherService); const eventLoggingService = new EventLoggingService(storageService, apiService, userService, cipherService);
@ -156,7 +157,7 @@ export function initFactory(): Function {
}); });
setTimeout(() => notificationsService.init(environmentService), 3000); setTimeout(() => notificationsService.init(environmentService), 3000);
lockService.init(true); vaultTimeoutService.init(true);
const locale = await storageService.get<string>(ConstantsService.localeKey); const locale = await storageService.get<string>(ConstantsService.localeKey);
await i18nService.init(locale); await i18nService.init(locale);
eventLoggingService.init(true); eventLoggingService.init(true);
@ -205,7 +206,7 @@ export function initFactory(): Function {
{ provide: MessagingServiceAbstraction, useValue: messagingService }, { provide: MessagingServiceAbstraction, useValue: messagingService },
{ provide: BroadcasterService, useValue: broadcasterService }, { provide: BroadcasterService, useValue: broadcasterService },
{ provide: SettingsServiceAbstraction, useValue: settingsService }, { provide: SettingsServiceAbstraction, useValue: settingsService },
{ provide: LockServiceAbstraction, useValue: lockService }, { provide: VaultTimeoutServiceAbstraction, useValue: vaultTimeoutService },
{ provide: StorageServiceAbstraction, useValue: storageService }, { provide: StorageServiceAbstraction, useValue: storageService },
{ provide: StateServiceAbstraction, useValue: stateService }, { provide: StateServiceAbstraction, useValue: stateService },
{ provide: ExportServiceAbstraction, useValue: exportService }, { provide: ExportServiceAbstraction, useValue: exportService },

View File

@ -4,18 +4,18 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { LockService } from 'jslib/abstractions/lock.service';
import { UserService } from 'jslib/abstractions/user.service'; import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
@Injectable() @Injectable()
export class UnauthGuardService implements CanActivate { export class UnauthGuardService implements CanActivate {
constructor(private lockService: LockService, private userService: UserService, constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService,
private router: Router) { } private router: Router) { }
async canActivate() { async canActivate() {
const isAuthed = await this.userService.isAuthenticated(); const isAuthed = await this.userService.isAuthenticated();
if (isAuthed) { if (isAuthed) {
const locked = await this.lockService.isLocked(); const locked = await this.vaultTimeoutService.isLocked();
if (locked) { if (locked) {
this.router.navigate(['lock']); this.router.navigate(['lock']);
} else { } else {

View File

@ -6,14 +6,33 @@
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<div class="form-group"> <div class="form-group">
<label for="lockOption">{{'lockOptions' | i18n}}</label> <label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
<select id="lockOption" name="LockOption" [(ngModel)]="lockOption" class="form-control"> <select id="vaultTimeout" name="VaultTimeout" [(ngModel)]="vaultTimeout" class="form-control">
<option *ngFor="let o of lockOptions" [ngValue]="o.value">{{o.name}}</option> <option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
</select> </select>
<small class="form-text text-muted">{{'lockOptionsDesc' | i18n}}</small> <small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<label>{{'vaultTimeoutAction' | i18n}}</label>
<div class="form-check form-check-block">
<input class="form-check-input" type="radio" name="vaultTimeoutAction" id="vaultTimeoutActionLock"
value="lock" [(ngModel)]="vaultTimeoutAction">
<label class="form-check-label" for="vaultTimeoutActionLock">
{{'lock' | i18n}}
<small>{{'vaultTimeoutActionLockDesc' | i18n}}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="vaultTimeoutAction" id="vaultTimeoutActionLogOut"
value="logOut" [(ngModel)]="vaultTimeoutAction">
<label class="form-check-label" for="vaultTimeoutActionLogOut">
{{'logOut' | i18n}}
<small>{{'vaultTimeoutActionLogOutDesc' | i18n}}</small>
</label>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<div class="form-group"> <div class="form-group">

View File

@ -7,10 +7,10 @@ import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2'; import { Angulartics2 } from 'angulartics2';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { LockService } from 'jslib/abstractions/lock.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service'; import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service'; import { StorageService } from 'jslib/abstractions/storage.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
import { ConstantsService } from 'jslib/services/constants.service'; import { ConstantsService } from 'jslib/services/constants.service';
@ -21,20 +21,21 @@ import { Utils } from 'jslib/misc/utils';
templateUrl: 'options.component.html', templateUrl: 'options.component.html',
}) })
export class OptionsComponent implements OnInit { export class OptionsComponent implements OnInit {
lockOption: number = null; vaultTimeout: number = null;
vaultTimeoutAction: string = 'lock';
disableIcons: boolean; disableIcons: boolean;
enableGravatars: boolean; enableGravatars: boolean;
locale: string; locale: string;
lockOptions: any[]; vaultTimeouts: any[];
localeOptions: any[]; localeOptions: any[];
private startingLocale: string; private startingLocale: string;
constructor(private storageService: StorageService, private stateService: StateService, constructor(private storageService: StorageService, private stateService: StateService,
private analytics: Angulartics2, private i18nService: I18nService, private analytics: Angulartics2, private i18nService: I18nService,
private toasterService: ToasterService, private lockService: LockService, private toasterService: ToasterService, private vaultTimeoutService: VaultTimeoutService,
private platformUtilsService: PlatformUtilsService) { private platformUtilsService: PlatformUtilsService) {
this.lockOptions = [ this.vaultTimeouts = [
{ name: i18nService.t('oneMinute'), value: 1 }, { name: i18nService.t('oneMinute'), value: 1 },
{ name: i18nService.t('fiveMinutes'), value: 5 }, { name: i18nService.t('fiveMinutes'), value: 5 },
{ name: i18nService.t('fifteenMinutes'), value: 15 }, { name: i18nService.t('fifteenMinutes'), value: 15 },
@ -44,7 +45,7 @@ export class OptionsComponent implements OnInit {
{ name: i18nService.t('onRefresh'), value: -1 }, { name: i18nService.t('onRefresh'), value: -1 },
]; ];
if (this.platformUtilsService.isDev()) { if (this.platformUtilsService.isDev()) {
this.lockOptions.push({ name: i18nService.t('never'), value: null }); this.vaultTimeouts.push({ name: i18nService.t('never'), value: null });
} }
const localeOptions: any[] = []; const localeOptions: any[] = [];
@ -61,14 +62,16 @@ export class OptionsComponent implements OnInit {
} }
async ngOnInit() { async ngOnInit() {
this.lockOption = await this.storageService.get<number>(ConstantsService.lockOptionKey); this.vaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
this.disableIcons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey); this.disableIcons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
this.enableGravatars = await this.storageService.get<boolean>('enableGravatars'); this.enableGravatars = await this.storageService.get<boolean>('enableGravatars');
this.locale = this.startingLocale = await this.storageService.get<string>(ConstantsService.localeKey); this.locale = this.startingLocale = await this.storageService.get<string>(ConstantsService.localeKey);
} }
async submit() { async submit() {
await this.lockService.setLockOption(this.lockOption != null ? this.lockOption : null); await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout != null ? this.vaultTimeout : null,
this.vaultTimeoutAction);
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableIcons); await this.storageService.save(ConstantsService.disableFaviconKey, this.disableIcons);
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableIcons); await this.stateService.save(ConstantsService.disableFaviconKey, this.disableIcons);
await this.storageService.save('enableGravatars', this.enableGravatars); await this.storageService.save('enableGravatars', this.enableGravatars);

View File

@ -2816,11 +2816,11 @@
"filters": { "filters": {
"message": "Filters" "message": "Filters"
}, },
"lockOptions": { "vaultTimeout": {
"message": "Lock Options" "message": "Vault Timeout"
}, },
"lockOptionsDesc": { "vaultTimeoutDesc": {
"message": "Choose when your vault locks. A locked vault requires that you re-enter your master password to access it again." "message": "Choose when your vault will timeout and perform the selected action."
}, },
"oneMinute": { "oneMinute": {
"message": "1 minute" "message": "1 minute"
@ -3040,5 +3040,17 @@
}, },
"userPreference": { "userPreference": {
"message": "User Preference" "message": "User Preference"
},
"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"
} }
} }

View File

@ -4,16 +4,24 @@ import { ConstantsService } from 'jslib/services';
export class HtmlStorageService implements StorageService { export class HtmlStorageService implements StorageService {
private localStorageKeys = new Set(['appId', 'anonymousAppId', 'rememberedEmail', 'passwordGenerationOptions', private localStorageKeys = new Set(['appId', 'anonymousAppId', 'rememberedEmail', 'passwordGenerationOptions',
ConstantsService.disableFaviconKey, ConstantsService.lockOptionKey, 'rememberEmail', 'enableGravatars', ConstantsService.disableFaviconKey, 'rememberEmail', 'enableGravatars', ConstantsService.localeKey,
ConstantsService.localeKey, ConstantsService.lockOptionKey, ConstantsService.autoConfirmFingerprints]); ConstantsService.autoConfirmFingerprints, ConstantsService.vaultTimeoutKey,
ConstantsService.vaultTimeoutActionKey]);
private localStorageStartsWithKeys = ['twoFactorToken_', ConstantsService.collapsedGroupingsKey + '_']; private localStorageStartsWithKeys = ['twoFactorToken_', ConstantsService.collapsedGroupingsKey + '_'];
constructor(private platformUtilsService: PlatformUtilsService) { } constructor(private platformUtilsService: PlatformUtilsService) { }
async init() { async init() {
const lockOption = await this.get<number>(ConstantsService.lockOptionKey); // LockOption -> VaultTimeout (uses the same legacy string value for backwards compat)
if (lockOption == null && !this.platformUtilsService.isDev()) { const vaultTimeout = await this.get<number>(ConstantsService.vaultTimeoutKey);
await this.save(ConstantsService.lockOptionKey, 15); if (vaultTimeout == null && !this.platformUtilsService.isDev()) {
await this.save(ConstantsService.vaultTimeoutKey, 15);
}
// Default Action to lock
const vaultTimeoutAction = await this.get<string>(ConstantsService.vaultTimeoutActionKey);
if (vaultTimeoutAction == null) {
await this.save(ConstantsService.vaultTimeoutActionKey, 'lock');
} }
} }