1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-13 01:58:44 +02:00

[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.authService,
this.vaultSettingsService, this.vaultSettingsService,
this.domainSettingsService, this.domainSettingsService,
this.taskSchedulerService,
this.logService, this.logService,
); );

View File

@ -5,6 +5,7 @@ import { AuthService } from "../../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service";
import { ConfigService } from "../../../platform/abstractions/config/config.service"; import { ConfigService } from "../../../platform/abstractions/config/config.service";
import { TaskSchedulerService } from "../../../platform/abstractions/task-scheduler.service";
import { Utils } from "../../../platform/misc/utils"; import { Utils } from "../../../platform/misc/utils";
import { import {
Fido2AuthenticatorError, Fido2AuthenticatorError,
@ -34,6 +35,7 @@ describe("FidoAuthenticatorService", () => {
let authService!: MockProxy<AuthService>; let authService!: MockProxy<AuthService>;
let vaultSettingsService: MockProxy<VaultSettingsService>; let vaultSettingsService: MockProxy<VaultSettingsService>;
let domainSettingsService: MockProxy<DomainSettingsService>; let domainSettingsService: MockProxy<DomainSettingsService>;
let taskSchedulerService: MockProxy<TaskSchedulerService>;
let client!: Fido2ClientService; let client!: Fido2ClientService;
let tab!: chrome.tabs.Tab; let tab!: chrome.tabs.Tab;
@ -43,6 +45,7 @@ describe("FidoAuthenticatorService", () => {
authService = mock<AuthService>(); authService = mock<AuthService>();
vaultSettingsService = mock<VaultSettingsService>(); vaultSettingsService = mock<VaultSettingsService>();
domainSettingsService = mock<DomainSettingsService>(); domainSettingsService = mock<DomainSettingsService>();
taskSchedulerService = mock<TaskSchedulerService>();
client = new Fido2ClientService( client = new Fido2ClientService(
authenticator, authenticator,
@ -50,6 +53,7 @@ describe("FidoAuthenticatorService", () => {
authService, authService,
vaultSettingsService, vaultSettingsService,
domainSettingsService, domainSettingsService,
taskSchedulerService,
); );
configService.serverConfig$ = of({ environment: { vault: VaultUrl } } as any); configService.serverConfig$ = of({ environment: { vault: VaultUrl } } as any);
vaultSettingsService.enablePasskeys$ = of(true); 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 { DomainSettingsService } from "../../../autofill/services/domain-settings.service";
import { ConfigService } from "../../../platform/abstractions/config/config.service"; import { ConfigService } from "../../../platform/abstractions/config/config.service";
import { LogService } from "../../../platform/abstractions/log.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 { Utils } from "../../../platform/misc/utils";
import { import {
Fido2AuthenticatorError, 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. * It is highly recommended that the W3C specification is used a reference when reading this code.
*/ */
export class Fido2ClientService implements Fido2ClientServiceAbstraction { 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( constructor(
private authenticator: Fido2AuthenticatorService, private authenticator: Fido2AuthenticatorService,
private configService: ConfigService, private configService: ConfigService,
private authService: AuthService, private authService: AuthService,
private vaultSettingsService: VaultSettingsService, private vaultSettingsService: VaultSettingsService,
private domainSettingsService: DomainSettingsService, private domainSettingsService: DomainSettingsService,
private taskSchedulerService: TaskSchedulerService,
private logService?: LogService, private logService?: LogService,
) {} ) {}
@ -151,7 +167,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
this.logService?.info(`[Fido2Client] Aborted with AbortController`); this.logService?.info(`[Fido2Client] Aborted with AbortController`);
throw new DOMException("The operation either timed out or was not allowed.", "AbortError"); throw new DOMException("The operation either timed out or was not allowed.", "AbortError");
} }
const timeout = setAbortTimeout( const timeout = await this.setAbortTimeout(
abortController, abortController,
params.authenticatorSelection?.userVerification, params.authenticatorSelection?.userVerification,
params.timeout, params.timeout,
@ -200,7 +216,11 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
}; };
} }
clearTimeout(timeout); await this.taskSchedulerService.clearScheduledTask({
taskName: ScheduledTaskNames.fido2ClientAbortTimeout,
timeoutId: timeout,
});
return { return {
credentialId: Fido2Utils.bufferToString(makeCredentialResult.credentialId), credentialId: Fido2Utils.bufferToString(makeCredentialResult.credentialId),
attestationObject: Fido2Utils.bufferToString(makeCredentialResult.attestationObject), 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"); 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; let getAssertionResult;
try { try {
@ -297,7 +321,10 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
this.logService?.info(`[Fido2Client] Aborted with AbortController`); this.logService?.info(`[Fido2Client] Aborted with AbortController`);
throw new DOMException("The operation either timed out or was not allowed.", "AbortError"); 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 { return {
authenticatorData: Fido2Utils.bufferToString(getAssertionResult.authenticatorData), authenticatorData: Fido2Utils.bufferToString(getAssertionResult.authenticatorData),
@ -310,43 +337,29 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
signature: Fido2Utils.bufferToString(getAssertionResult.signature), signature: Fido2Utils.bufferToString(getAssertionResult.signature),
}; };
} }
}
const TIMEOUTS = { private setAbortTimeout = async (
NO_VERIFICATION: { abortController: AbortController,
DEFAULT: 120000, userVerification?: UserVerification,
MIN: 30000, timeout?: number,
MAX: 180000, ): Promise<number | NodeJS.Timeout> => {
}, let clampedTimeout: number;
WITH_VERIFICATION: {
DEFAULT: 300000,
MIN: 30000,
MAX: 600000,
},
};
function setAbortTimeout( const { WITH_VERIFICATION, NO_VERIFICATION } = this.TIMEOUTS;
abortController: AbortController, if (userVerification === "required") {
userVerification?: UserVerification, timeout = timeout ?? WITH_VERIFICATION.DEFAULT;
timeout?: number, clampedTimeout = Math.max(WITH_VERIFICATION.MIN, Math.min(timeout, WITH_VERIFICATION.MAX));
): number { } else {
let clampedTimeout: number; timeout = timeout ?? NO_VERIFICATION.DEFAULT;
clampedTimeout = Math.max(NO_VERIFICATION.MIN, Math.min(timeout, NO_VERIFICATION.MAX));
}
if (userVerification === "required") { return await this.taskSchedulerService.setTimeout(
timeout = timeout ?? TIMEOUTS.WITH_VERIFICATION.DEFAULT; () => abortController.abort(),
clampedTimeout = Math.max( clampedTimeout,
TIMEOUTS.WITH_VERIFICATION.MIN, ScheduledTaskNames.fido2ClientAbortTimeout,
Math.min(timeout, TIMEOUTS.WITH_VERIFICATION.MAX),
); );
} 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);
} }
/** /**