From 3d4ecaeb6aa5a09c74ce23ee526499f408aee02d Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Tue, 18 May 2021 10:08:28 +1000 Subject: [PATCH] "Auto-fill on page load" options (#199) * add autofill on page load props to models and view For new per-login autofill on page load settings * filter and cache ciphers per autofill setting Used by the new autofill on page load feature to identify matching ciphers and filter according to their autofill setting * fix null check on array * fix linting and style errors * change cacheKey to avoid collision with real url * Fix linting, set default value for aopl-options * Fix linting * update UI * Remove autofillOnPageLoad from export * Change enum to boolean * Add storage key for autofillOnPageLoad default * fix style --- src/abstractions/cipher.service.ts | 4 +-- src/angular/components/add-edit.component.ts | 6 ++++ src/models/api/loginApi.ts | 2 ++ src/models/data/loginData.ts | 2 ++ src/models/domain/login.ts | 3 ++ src/models/request/cipherRequest.ts | 1 + src/models/view/loginView.ts | 2 ++ src/services/cipher.service.ts | 37 +++++++++++++------- src/services/constants.service.ts | 2 ++ 9 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/abstractions/cipher.service.ts b/src/abstractions/cipher.service.ts index 0fbd115ded..941c3826ac 100644 --- a/src/abstractions/cipher.service.ts +++ b/src/abstractions/cipher.service.ts @@ -24,8 +24,8 @@ export abstract class CipherService { getAllDecryptedForUrl: (url: string, includeOtherTypes?: CipherType[], defaultMatch?: UriMatchType) => Promise; getAllFromApiForOrganization: (organizationId: string) => Promise; - getLastUsedForUrl: (url: string) => Promise; - getLastLaunchedForUrl: (url: string) => Promise; + getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; + getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; getNextCipherForUrl: (url: string) => Promise; updateLastUsedIndexForUrl: (url: string) => void; updateLastUsedDate: (id: string) => Promise; diff --git a/src/angular/components/add-edit.component.ts b/src/angular/components/add-edit.component.ts index 08cb58c471..ed15a106d9 100644 --- a/src/angular/components/add-edit.component.ts +++ b/src/angular/components/add-edit.component.ts @@ -84,6 +84,7 @@ export class AddEditComponent implements OnInit { addFieldTypeOptions: any[]; uriMatchOptions: any[]; ownershipOptions: any[] = []; + autofillOnPageLoadOptions: any[]; currentDate = new Date(); allowPersonal = true; reprompt: boolean = false; @@ -151,6 +152,11 @@ export class AddEditComponent implements OnInit { { name: i18nService.t('exact'), value: UriMatchType.Exact }, { name: i18nService.t('never'), value: UriMatchType.Never }, ]; + this.autofillOnPageLoadOptions = [ + { name: i18nService.t('autoFillOnPageLoadUseDefault'), value: null }, + { name: i18nService.t('autoFillOnPageLoadYes'), value: true }, + { name: i18nService.t('autoFillOnPageLoadNo'), value: false }, + ]; } async ngOnInit() { diff --git a/src/models/api/loginApi.ts b/src/models/api/loginApi.ts index c71a4cec25..a02cde3851 100644 --- a/src/models/api/loginApi.ts +++ b/src/models/api/loginApi.ts @@ -8,6 +8,7 @@ export class LoginApi extends BaseResponse { password: string; passwordRevisionDate: string; totp: string; + autofillOnPageLoad: boolean; constructor(data: any = null) { super(data); @@ -18,6 +19,7 @@ export class LoginApi extends BaseResponse { this.password = this.getResponseProperty('Password'); this.passwordRevisionDate = this.getResponseProperty('PasswordRevisionDate'); this.totp = this.getResponseProperty('Totp'); + this.autofillOnPageLoad = this.getResponseProperty('AutofillOnPageLoad'); const uris = this.getResponseProperty('Uris'); if (uris != null) { diff --git a/src/models/data/loginData.ts b/src/models/data/loginData.ts index 90673e5695..7b5d702741 100644 --- a/src/models/data/loginData.ts +++ b/src/models/data/loginData.ts @@ -8,6 +8,7 @@ export class LoginData { password: string; passwordRevisionDate: string; totp: string; + autofillOnPageLoad: boolean; constructor(data?: LoginApi) { if (data == null) { @@ -18,6 +19,7 @@ export class LoginData { this.password = data.password; this.passwordRevisionDate = data.passwordRevisionDate; this.totp = data.totp; + this.autofillOnPageLoad = data.autofillOnPageLoad; if (data.uris) { this.uris = data.uris.map(u => new LoginUriData(u)); diff --git a/src/models/domain/login.ts b/src/models/domain/login.ts index 348b0af814..33bd12161e 100644 --- a/src/models/domain/login.ts +++ b/src/models/domain/login.ts @@ -14,6 +14,7 @@ export class Login extends Domain { password: EncString; passwordRevisionDate?: Date; totp: EncString; + autofillOnPageLoad: boolean; constructor(obj?: LoginData, alreadyEncrypted: boolean = false) { super(); @@ -22,6 +23,7 @@ export class Login extends Domain { } this.passwordRevisionDate = obj.passwordRevisionDate != null ? new Date(obj.passwordRevisionDate) : null; + this.autofillOnPageLoad = obj.autofillOnPageLoad; this.buildDomainModel(this, obj, { username: null, password: null, @@ -57,6 +59,7 @@ export class Login extends Domain { toLoginData(): LoginData { const l = new LoginData(); l.passwordRevisionDate = this.passwordRevisionDate != null ? this.passwordRevisionDate.toISOString() : null; + l.autofillOnPageLoad = this.autofillOnPageLoad; this.buildDataModel(this, l, { username: null, password: null, diff --git a/src/models/request/cipherRequest.ts b/src/models/request/cipherRequest.ts index bbed355ad5..17068c0929 100644 --- a/src/models/request/cipherRequest.ts +++ b/src/models/request/cipherRequest.ts @@ -51,6 +51,7 @@ export class CipherRequest { this.login.passwordRevisionDate = cipher.login.passwordRevisionDate != null ? cipher.login.passwordRevisionDate.toISOString() : null; this.login.totp = cipher.login.totp ? cipher.login.totp.encryptedString : null; + this.login.autofillOnPageLoad = cipher.login.autofillOnPageLoad; if (cipher.login.uris != null) { this.login.uris = cipher.login.uris.map(u => { diff --git a/src/models/view/loginView.ts b/src/models/view/loginView.ts index 24c8f0aca4..7db2431004 100644 --- a/src/models/view/loginView.ts +++ b/src/models/view/loginView.ts @@ -10,6 +10,7 @@ export class LoginView implements View { passwordRevisionDate?: Date = null; totp: string = null; uris: LoginUriView[] = null; + autofillOnPageLoad: boolean = null; constructor(l?: Login) { if (!l) { @@ -17,6 +18,7 @@ export class LoginView implements View { } this.passwordRevisionDate = l.passwordRevisionDate; + this.autofillOnPageLoad = l.autofillOnPageLoad; } get uri(): string { diff --git a/src/services/cipher.service.ts b/src/services/cipher.service.ts index 04abfc97d3..eaf77a56e2 100644 --- a/src/services/cipher.service.ts +++ b/src/services/cipher.service.ts @@ -454,16 +454,16 @@ export class CipherService implements CipherServiceAbstraction { } } - async getLastUsedForUrl(url: string): Promise { - return this.getCipherForUrl(url, true, false); + async getLastUsedForUrl(url: string, autofillOnPageLoad: boolean = false): Promise { + return this.getCipherForUrl(url, true, false, autofillOnPageLoad); } - async getLastLaunchedForUrl(url: string): Promise { - return this.getCipherForUrl(url, false, true); + async getLastLaunchedForUrl(url: string, autofillOnPageLoad: boolean = false): Promise { + return this.getCipherForUrl(url, false, true, autofillOnPageLoad); } async getNextCipherForUrl(url: string): Promise { - return this.getCipherForUrl(url, false, false); + return this.getCipherForUrl(url, false, false, false); } updateLastUsedIndexForUrl(url: string) { @@ -1032,6 +1032,7 @@ export class CipherService implements CipherServiceAbstraction { case CipherType.Login: cipher.login = new Login(); cipher.login.passwordRevisionDate = model.login.passwordRevisionDate; + cipher.login.autofillOnPageLoad = model.login.autofillOnPageLoad; await this.encryptObjProperty(model.login, cipher.login, { username: null, password: null, @@ -1093,21 +1094,33 @@ export class CipherService implements CipherServiceAbstraction { } } - private async getCipherForUrl(url: string, lastUsed: boolean, lastLaunched: boolean): Promise { - if (!this.sortedCiphersCache.isCached(url)) { - const ciphers = await this.getAllDecryptedForUrl(url); + private async getCipherForUrl(url: string, lastUsed: boolean, lastLaunched: boolean, autofillOnPageLoad: boolean): Promise { + const cacheKey = autofillOnPageLoad ? 'autofillOnPageLoad-' + url : url; + + if (!this.sortedCiphersCache.isCached(cacheKey)) { + let ciphers = await this.getAllDecryptedForUrl(url); if (!ciphers) { return null; } - this.sortedCiphersCache.addCiphers(url, ciphers); + + if (autofillOnPageLoad) { + const autofillOnPageLoadDefault = await this.storageService.get(ConstantsService.autoFillOnPageLoadDefaultKey); + ciphers = ciphers.filter(cipher => cipher.login.autofillOnPageLoad || + (cipher.login.autofillOnPageLoad === null && autofillOnPageLoadDefault)); + if (ciphers.length === 0) { + return null; + } + } + + this.sortedCiphersCache.addCiphers(cacheKey, ciphers); } if (lastLaunched) { - return this.sortedCiphersCache.getLastLaunched(url); + return this.sortedCiphersCache.getLastLaunched(cacheKey); } else if (lastUsed) { - return this.sortedCiphersCache.getLastUsed(url); + return this.sortedCiphersCache.getLastUsed(cacheKey); } else { - return this.sortedCiphersCache.getNext(url); + return this.sortedCiphersCache.getNext(cacheKey); } } } diff --git a/src/services/constants.service.ts b/src/services/constants.service.ts index 23a24d134e..faa88b04e9 100644 --- a/src/services/constants.service.ts +++ b/src/services/constants.service.ts @@ -8,6 +8,7 @@ export class ConstantsService { static readonly disableBadgeCounterKey: string = 'disableBadgeCounter'; static readonly disableAutoTotpCopyKey: string = 'disableAutoTotpCopy'; static readonly enableAutoFillOnPageLoadKey: string = 'enableAutoFillOnPageLoad'; + static readonly autoFillOnPageLoadDefaultKey: string = 'autoFillOnPageLoadDefault'; static readonly vaultTimeoutKey: string = 'lockOption'; static readonly vaultTimeoutActionKey: string = 'vaultTimeoutAction'; static readonly lastActiveKey: string = 'lastActive'; @@ -39,6 +40,7 @@ export class ConstantsService { readonly disableBadgeCounterKey: string = ConstantsService.disableBadgeCounterKey; readonly disableAutoTotpCopyKey: string = ConstantsService.disableAutoTotpCopyKey; readonly enableAutoFillOnPageLoadKey: string = ConstantsService.enableAutoFillOnPageLoadKey; + readonly autoFillOnPageLoadDefaultKey: string = ConstantsService.autoFillOnPageLoadDefaultKey; readonly vaultTimeoutKey: string = ConstantsService.vaultTimeoutKey; readonly vaultTimeoutActionKey: string = ConstantsService.vaultTimeoutActionKey; readonly lastActiveKey: string = ConstantsService.lastActiveKey;