1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 09:13:33 +00:00

[PM-6426] Create TaskSchedulerService and update long lived timeouts in the extension to leverage the new service (#8566)

* [PM-6426] Create TaskSchedulerService and update usage of long lived timeouts

* [PM-6426] Implementing nextSync timeout using TaskScheduler

* [PM-6426] Implementing systemClearClipboard using TaskScheduler

* [PM-6426] Fixing race condition with setting/unsetting active alarms

* [PM-6426] Implementing clear clipboard call on generatePasswordToClipboard with the TaskSchedulerService

* [PM-6426] Implementing abortTimeout for Fido2ClientService using TaskSchedulerService

* [PM-6426] Implementing reconnect timer timeout for NotificationService using the TaskSchedulerService

* [PM-6426] Implementing reconnect timer timeout for NotificationService using the TaskSchedulerService

* [PM-6426] Implementing sessionTimeout for LoginStrategyService using TaskSchedulerService

* [PM-6426] Implementing eventUploadInterval using TaskScheduler

* [PM-6426] Adding jest tests for the base TaskSchedulerService class

* [PM-6426] Updating jest tests for GeneratePasswordToClipboardCommand

* [PM-6426] Setting up the full sync process as an interval rather than a timeout

* [PM-6426] Renaming the scheduleNextSync alarm name

* [PM-6426] Fixing dependency references in services.module.ts

* [PM-6426] Adding jest tests for added BrowserApi methods

* [PM-6426] Refactoring small detail for how we identify the clear clipboard timeout in SystemService

* [PM-6426] Ensuring that we await clearing an established scheduled task for the notification service

* [PM-6426] Changing the name of the state definition for the TaskScheduler

* [PM-6426] Implementing jest tests for the BrowserTaskSchedulerService

* [PM-6426] Implementing jest tests for the BrowserTaskSchedulerService

* [PM-6426] Adding jest tests for the base TaskSchedulerService class

* [PM-6426] Finalizing jest tests for BrowserTaskScheduler class

* [PM-6426] Finalizing documentation on BrowserTaskSchedulerService

* [PM-6426] Fixing jest test for LoginStrategyService

* [PM-6426] Implementing compatibility for the browser.alarms api

* [PM-6426] Fixing how we check for the browser alarms api

* [PM-6426] Adding jest tests to the BrowserApi implementation

* [PM-6426] Aligning the implementation with our code guidelines for Angular components

* [PM-6426] Fixing jest tests and lint errors

* [PM-6426] Moving alarms api calls out of BrowserApi and structuring them within the BrowserTaskSchedulerService

* [PM-6426] Reworking implementation to register handlers separately from the call to those handlers

* [PM-6426] Adjusting how we register the fullSync scheduled task

* [PM-6426] Implementing approach for incorporating the user UUID when setting task handlers

* [PM-6426] Attempting to re-work implementation to facilitate userId-spcific alarms

* [PM-6426] Refactoring smaller details of the implementation

* [PM-6426] Working through the details of the implementation and setting up final refinments

* [PM-6426] Fixing some issues surrounding duplicate alarms triggering

* [PM-6426] Adjusting name for generate password to clipboard command task name

* [PM-6426] Fixing generate password to clipboard command jest tests

* [PM-6426] Working through jest tests and implementing a method to guard against setting a task without having a registered callback

* [PM-6426] Working through jest tests and implementing a method to guard against setting a task without having a registered callback

* [PM-6426] Implementing methodology for having a fallback to setTimeout if the browser context is lost in some manner

* [PM-6426] Working through jest tests

* [PM-6426] Working through jest tests

* [PM-6426] Working through jest tests

* [PM-6426] Working through jest tests

* [PM-6426] Finalizing stepped setInterval implementation

* [PM-6426] Implementing Jest tests for DefaultTaskSchedulerService

* [PM-6426] Adjusting jest tests

* [PM-6426] Adjusting jest tests

* [PM-6426] Adjusting jest tests

* [PM-6426] Fixing issues identified in code review

* [PM-6426] Fixing issues identified in code review

* [PM-6426] Removing user-based alarms and fixing an issue found with setting steppedd alarm interavals

* [PM-6426] Removing user-based alarms and fixing an issue found with setting steppedd alarm interavals

* [PM-6426] Fixing issue with typing information on a test

* [PM-6426] Using the getUpperBoundDelayInMinutes method to handle setting stepped alarms and setTimeout fallbacks

* [PM-6426] Removing the potential for the TaskScheduler to be optional

* [PM-6426] Reworking implementation to leverage subscription based deregistration of alarms

* [PM-6426] Fixing jest tests

* [PM-6426] Implementing foreground and background task scheduler services to avoid duplication of task scheudlers and to have the background setup as a fallback to the poopup tasks

* [PM-6426] Implementing foreground and background task scheduler services to avoid duplication of task scheudlers and to have the background setup as a fallback to the poopup tasks

* [PM-6426] Merging main into branch

* [PM-6426] Fixing issues with the CLI Service Container implementation

* [PM-6426] Reworking swallowed promises to contain a catch statement allow us to debug potential issues with registrations of alarms

* [PM-6426] Adding jest tests to the ForegroundTaskSchedulerService and the BackgroundTaskSchedulerService

* [PM-6426] Adding jest tests to the ForegroundTaskSchedulerService and the BackgroundTaskSchedulerService

* [PM-6426] Adding jest tests to the ForegroundTaskSchedulerService and the BackgroundTaskSchedulerService

* [PM-6426] Adding jest tests to the ForegroundTaskSchedulerService and the BackgroundTaskSchedulerService

* [PM-6426] Adjusting implementation based on code review feedback

* [PM-6426] Reworking file structure

* [PM-6426] Reworking file structure

* [PM-6426] Adding comments to provide clarity on how the login strategy cache experiation state is used

* [PM-6426] Catching and logging erorrs that appear from methods that return a promise within VaultTimeoutService
This commit is contained in:
Cesar Gonzalez
2024-07-15 10:32:30 -05:00
committed by GitHub
parent 5fcf4bbd10
commit 974162b1a4
39 changed files with 1854 additions and 283 deletions

View File

@@ -4,6 +4,7 @@ import { of } from "rxjs";
import { AuthService } from "../../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
import { DomainSettingsService } from "../../../autofill/services/domain-settings.service";
import { Utils } from "../../../platform/misc/utils";
import { VaultSettingsService } from "../../../vault/abstractions/vault-settings/vault-settings.service";
import { ConfigService } from "../../abstractions/config/config.service";
import {
@@ -17,7 +18,7 @@ import {
CreateCredentialParams,
FallbackRequestedError,
} from "../../abstractions/fido2/fido2-client.service.abstraction";
import { Utils } from "../../misc/utils";
import { TaskSchedulerService } from "../../scheduling/task-scheduler.service";
import * as DomainUtils from "./domain-utils";
import { Fido2AuthenticatorService } from "./fido2-authenticator.service";
@@ -35,6 +36,7 @@ describe("FidoAuthenticatorService", () => {
let authService!: MockProxy<AuthService>;
let vaultSettingsService: MockProxy<VaultSettingsService>;
let domainSettingsService: MockProxy<DomainSettingsService>;
let taskSchedulerService: MockProxy<TaskSchedulerService>;
let client!: Fido2ClientService;
let tab!: chrome.tabs.Tab;
let isValidRpId!: jest.SpyInstance;
@@ -45,6 +47,7 @@ describe("FidoAuthenticatorService", () => {
authService = mock<AuthService>();
vaultSettingsService = mock<VaultSettingsService>();
domainSettingsService = mock<DomainSettingsService>();
taskSchedulerService = mock<TaskSchedulerService>();
isValidRpId = jest.spyOn(DomainUtils, "isValidRpId");
@@ -54,6 +57,7 @@ describe("FidoAuthenticatorService", () => {
authService,
vaultSettingsService,
domainSettingsService,
taskSchedulerService,
);
configService.serverConfig$ = of({ environment: { vault: VaultUrl } } as any);
vaultSettingsService.enablePasskeys$ = of(true);

View File

@@ -1,4 +1,4 @@
import { firstValueFrom } from "rxjs";
import { firstValueFrom, Subscription } from "rxjs";
import { parse } from "tldts";
import { AuthService } from "../../../auth/abstractions/auth.service";
@@ -27,6 +27,8 @@ import {
} from "../../abstractions/fido2/fido2-client.service.abstraction";
import { LogService } from "../../abstractions/log.service";
import { Utils } from "../../misc/utils";
import { ScheduledTaskNames } from "../../scheduling/scheduled-task-name.enum";
import { TaskSchedulerService } from "../../scheduling/task-scheduler.service";
import { isValidRpId } from "./domain-utils";
import { Fido2Utils } from "./fido2-utils";
@@ -38,14 +40,33 @@ import { Fido2Utils } from "./fido2-utils";
* It is highly recommended that the W3C specification is used a reference when reading this code.
*/
export class Fido2ClientService implements Fido2ClientServiceAbstraction {
private timeoutAbortController: AbortController;
private readonly TIMEOUTS = {
NO_VERIFICATION: {
DEFAULT: 120000,
MIN: 30000,
MAX: 180000,
},
WITH_VERIFICATION: {
DEFAULT: 300000,
MIN: 30000,
MAX: 600000,
},
};
constructor(
private authenticator: Fido2AuthenticatorService,
private configService: ConfigService,
private authService: AuthService,
private vaultSettingsService: VaultSettingsService,
private domainSettingsService: DomainSettingsService,
private taskSchedulerService: TaskSchedulerService,
private logService?: LogService,
) {}
) {
this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.fido2ClientAbortTimeout, () =>
this.timeoutAbortController?.abort(),
);
}
async isFido2FeatureEnabled(hostname: string, origin: string): Promise<boolean> {
const isUserLoggedIn =
@@ -161,7 +182,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
this.logService?.info(`[Fido2Client] Aborted with AbortController`);
throw new DOMException("The operation either timed out or was not allowed.", "AbortError");
}
const timeout = setAbortTimeout(
const timeoutSubscription = this.setAbortTimeout(
abortController,
params.authenticatorSelection?.userVerification,
params.timeout,
@@ -210,7 +231,8 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
};
}
clearTimeout(timeout);
timeoutSubscription?.unsubscribe();
return {
credentialId: Fido2Utils.bufferToString(makeCredentialResult.credentialId),
attestationObject: Fido2Utils.bufferToString(makeCredentialResult.attestationObject),
@@ -273,7 +295,11 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
throw new DOMException("The operation either timed out or was not allowed.", "AbortError");
}
const timeout = setAbortTimeout(abortController, params.userVerification, params.timeout);
const timeoutSubscription = this.setAbortTimeout(
abortController,
params.userVerification,
params.timeout,
);
let getAssertionResult;
try {
@@ -310,7 +336,8 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
this.logService?.info(`[Fido2Client] Aborted with AbortController`);
throw new DOMException("The operation either timed out or was not allowed.", "AbortError");
}
clearTimeout(timeout);
timeoutSubscription?.unsubscribe();
return {
authenticatorData: Fido2Utils.bufferToString(getAssertionResult.authenticatorData),
@@ -323,43 +350,29 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
signature: Fido2Utils.bufferToString(getAssertionResult.signature),
};
}
}
const TIMEOUTS = {
NO_VERIFICATION: {
DEFAULT: 120000,
MIN: 30000,
MAX: 180000,
},
WITH_VERIFICATION: {
DEFAULT: 300000,
MIN: 30000,
MAX: 600000,
},
};
private setAbortTimeout = (
abortController: AbortController,
userVerification?: UserVerification,
timeout?: number,
): Subscription => {
let clampedTimeout: number;
function setAbortTimeout(
abortController: AbortController,
userVerification?: UserVerification,
timeout?: number,
): number {
let clampedTimeout: number;
const { WITH_VERIFICATION, NO_VERIFICATION } = this.TIMEOUTS;
if (userVerification === "required") {
timeout = timeout ?? WITH_VERIFICATION.DEFAULT;
clampedTimeout = Math.max(WITH_VERIFICATION.MIN, Math.min(timeout, WITH_VERIFICATION.MAX));
} else {
timeout = timeout ?? NO_VERIFICATION.DEFAULT;
clampedTimeout = Math.max(NO_VERIFICATION.MIN, Math.min(timeout, NO_VERIFICATION.MAX));
}
if (userVerification === "required") {
timeout = timeout ?? TIMEOUTS.WITH_VERIFICATION.DEFAULT;
clampedTimeout = Math.max(
TIMEOUTS.WITH_VERIFICATION.MIN,
Math.min(timeout, TIMEOUTS.WITH_VERIFICATION.MAX),
this.timeoutAbortController = abortController;
return this.taskSchedulerService.setTimeout(
ScheduledTaskNames.fido2ClientAbortTimeout,
clampedTimeout,
);
} else {
timeout = timeout ?? TIMEOUTS.NO_VERIFICATION.DEFAULT;
clampedTimeout = Math.max(
TIMEOUTS.NO_VERIFICATION.MIN,
Math.min(timeout, TIMEOUTS.NO_VERIFICATION.MAX),
);
}
return self.setTimeout(() => abortController.abort(), clampedTimeout);
};
}
/**

View File

@@ -1,4 +1,4 @@
import { firstValueFrom, map, timeout } from "rxjs";
import { firstValueFrom, map, Subscription, timeout } from "rxjs";
import { PinServiceAbstraction } from "../../../../auth/src/common/abstractions";
import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service";
@@ -13,10 +13,12 @@ import { PlatformUtilsService } from "../abstractions/platform-utils.service";
import { SystemService as SystemServiceAbstraction } from "../abstractions/system.service";
import { BiometricStateService } from "../biometrics/biometric-state.service";
import { Utils } from "../misc/utils";
import { ScheduledTaskNames } from "../scheduling/scheduled-task-name.enum";
import { TaskSchedulerService } from "../scheduling/task-scheduler.service";
export class SystemService implements SystemServiceAbstraction {
private reloadInterval: any = null;
private clearClipboardTimeout: any = null;
private clearClipboardTimeoutSubscription: Subscription;
private clearClipboardTimeoutFunction: () => Promise<any> = null;
constructor(
@@ -28,7 +30,13 @@ export class SystemService implements SystemServiceAbstraction {
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private biometricStateService: BiometricStateService,
private accountService: AccountService,
) {}
private taskSchedulerService: TaskSchedulerService,
) {
this.taskSchedulerService.registerTaskHandler(
ScheduledTaskNames.systemClearClipboardTimeout,
() => this.clearPendingClipboard(),
);
}
async startProcessReload(authService: AuthService): Promise<void> {
const accounts = await firstValueFrom(this.accountService.accounts$);
@@ -111,25 +119,22 @@ export class SystemService implements SystemServiceAbstraction {
}
async clearClipboard(clipboardValue: string, timeoutMs: number = null): Promise<void> {
if (this.clearClipboardTimeout != null) {
clearTimeout(this.clearClipboardTimeout);
this.clearClipboardTimeout = null;
}
this.clearClipboardTimeoutSubscription?.unsubscribe();
if (Utils.isNullOrWhitespace(clipboardValue)) {
return;
}
const clearClipboardDelay = await firstValueFrom(
this.autofillSettingsService.clearClipboardDelay$,
);
if (clearClipboardDelay == null) {
return;
let taskTimeoutInMs = timeoutMs;
if (!taskTimeoutInMs) {
const clearClipboardDelayInSeconds = await firstValueFrom(
this.autofillSettingsService.clearClipboardDelay$,
);
taskTimeoutInMs = clearClipboardDelayInSeconds ? clearClipboardDelayInSeconds * 1000 : null;
}
if (timeoutMs == null) {
timeoutMs = clearClipboardDelay * 1000;
if (!taskTimeoutInMs) {
return;
}
this.clearClipboardTimeoutFunction = async () => {
@@ -139,9 +144,10 @@ export class SystemService implements SystemServiceAbstraction {
}
};
this.clearClipboardTimeout = setTimeout(async () => {
await this.clearPendingClipboard();
}, timeoutMs);
this.clearClipboardTimeoutSubscription = this.taskSchedulerService.setTimeout(
ScheduledTaskNames.systemClearClipboardTimeout,
taskTimeoutInMs,
);
}
async clearPendingClipboard() {