"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
This commit is contained in:
Thomas Rittson 2021-05-18 10:08:28 +10:00 committed by GitHub
parent 7b3f9f12a4
commit 3d4ecaeb6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 45 additions and 14 deletions

View File

@ -24,8 +24,8 @@ export abstract class CipherService {
getAllDecryptedForUrl: (url: string, includeOtherTypes?: CipherType[],
defaultMatch?: UriMatchType) => Promise<CipherView[]>;
getAllFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
getLastUsedForUrl: (url: string) => Promise<CipherView>;
getLastLaunchedForUrl: (url: string) => Promise<CipherView>;
getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
getNextCipherForUrl: (url: string) => Promise<CipherView>;
updateLastUsedIndexForUrl: (url: string) => void;
updateLastUsedDate: (id: string) => Promise<void>;

View File

@ -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() {

View File

@ -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) {

View File

@ -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));

View File

@ -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,

View File

@ -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 => {

View File

@ -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 {

View File

@ -454,16 +454,16 @@ export class CipherService implements CipherServiceAbstraction {
}
}
async getLastUsedForUrl(url: string): Promise<CipherView> {
return this.getCipherForUrl(url, true, false);
async getLastUsedForUrl(url: string, autofillOnPageLoad: boolean = false): Promise<CipherView> {
return this.getCipherForUrl(url, true, false, autofillOnPageLoad);
}
async getLastLaunchedForUrl(url: string): Promise<CipherView> {
return this.getCipherForUrl(url, false, true);
async getLastLaunchedForUrl(url: string, autofillOnPageLoad: boolean = false): Promise<CipherView> {
return this.getCipherForUrl(url, false, true, autofillOnPageLoad);
}
async getNextCipherForUrl(url: string): Promise<CipherView> {
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<CipherView> {
if (!this.sortedCiphersCache.isCached(url)) {
const ciphers = await this.getAllDecryptedForUrl(url);
private async getCipherForUrl(url: string, lastUsed: boolean, lastLaunched: boolean, autofillOnPageLoad: boolean): Promise<CipherView> {
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);
}
}
}

View File

@ -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;