[PM-6426] Implementing abortTimeout for Fido2ClientService using TaskSchedulerService

This commit is contained in:
Cesar Gonzalez 2024-04-01 11:56:35 -05:00
parent 2f517336db
commit f60a37fe2c
No known key found for this signature in database
GPG Key ID: 3381A5457F8CCECF
3 changed files with 55 additions and 37 deletions

View File

@ -830,6 +830,7 @@ export default class MainBackground {
this.authService,
this.vaultSettingsService,
this.domainSettingsService,
this.taskSchedulerService,
this.logService,
);

View File

@ -5,6 +5,7 @@ import { AuthService } from "../../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
import { DomainSettingsService } from "../../../autofill/services/domain-settings.service";
import { ConfigService } from "../../../platform/abstractions/config/config.service";
import { TaskSchedulerService } from "../../../platform/abstractions/task-scheduler.service";
import { Utils } from "../../../platform/misc/utils";
import {
Fido2AuthenticatorError,
@ -34,6 +35,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;
@ -43,6 +45,7 @@ describe("FidoAuthenticatorService", () => {
authService = mock<AuthService>();
vaultSettingsService = mock<VaultSettingsService>();
domainSettingsService = mock<DomainSettingsService>();
taskSchedulerService = mock<TaskSchedulerService>();
client = new Fido2ClientService(
authenticator,
@ -50,6 +53,7 @@ describe("FidoAuthenticatorService", () => {
authService,
vaultSettingsService,
domainSettingsService,
taskSchedulerService,
);
configService.serverConfig$ = of({ environment: { vault: VaultUrl } } as any);
vaultSettingsService.enablePasskeys$ = of(true);

View File

@ -6,6 +6,8 @@ import { AuthenticationStatus } from "../../../auth/enums/authentication-status"
import { DomainSettingsService } from "../../../autofill/services/domain-settings.service";
import { ConfigService } from "../../../platform/abstractions/config/config.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { TaskSchedulerService } from "../../../platform/abstractions/task-scheduler.service";
import { ScheduledTaskNames } from "../../../platform/enums/scheduled-task-name.enum";
import { Utils } from "../../../platform/misc/utils";
import {
Fido2AuthenticatorError,
@ -38,12 +40,26 @@ 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 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,
) {}
@ -151,7 +167,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 timeout = await this.setAbortTimeout(
abortController,
params.authenticatorSelection?.userVerification,
params.timeout,
@ -200,7 +216,11 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
};
}
clearTimeout(timeout);
await this.taskSchedulerService.clearScheduledTask({
taskName: ScheduledTaskNames.fido2ClientAbortTimeout,
timeoutId: timeout,
});
return {
credentialId: Fido2Utils.bufferToString(makeCredentialResult.credentialId),
attestationObject: Fido2Utils.bufferToString(makeCredentialResult.attestationObject),
@ -260,7 +280,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 timeout = await this.setAbortTimeout(
abortController,
params.userVerification,
params.timeout,
);
let getAssertionResult;
try {
@ -297,7 +321,10 @@ 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);
await this.taskSchedulerService.clearScheduledTask({
taskName: ScheduledTaskNames.fido2ClientAbortTimeout,
timeoutId: timeout,
});
return {
authenticatorData: Fido2Utils.bufferToString(getAssertionResult.authenticatorData),
@ -310,43 +337,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 = async (
abortController: AbortController,
userVerification?: UserVerification,
timeout?: number,
): Promise<number | NodeJS.Timeout> => {
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),
return await this.taskSchedulerService.setTimeout(
() => abortController.abort(),
clampedTimeout,
ScheduledTaskNames.fido2ClientAbortTimeout,
);
} else {
timeout = timeout ?? TIMEOUTS.NO_VERIFICATION.DEFAULT;
clampedTimeout = Math.max(
TIMEOUTS.NO_VERIFICATION.MIN,
Math.min(timeout, TIMEOUTS.NO_VERIFICATION.MAX),
);
}
return window.setTimeout(() => abortController.abort(), clampedTimeout);
};
}
/**