diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md index 17a02f00..6cd73dab 100644 --- a/docs/oidc-client-ts.api.md +++ b/docs/oidc-client-ts.api.md @@ -369,6 +369,7 @@ export interface OidcClientSettings { prompt?: string; redirect_uri: string; refreshTokenAllowedScope?: string | undefined; + requestTimeoutInSeconds?: number | undefined; resource?: string | string[]; response_mode?: "query" | "fragment"; response_type?: string; @@ -382,7 +383,7 @@ export interface OidcClientSettings { // @public export class OidcClientSettingsStore { - constructor({ authority, metadataUrl, metadata, signingKeys, metadataSeed, client_id, client_secret, response_type, scope, redirect_uri, post_logout_redirect_uri, client_authentication, prompt, display, max_age, ui_locales, acr_values, resource, response_mode, filterProtocolClaims, loadUserInfo, staleStateAgeInSeconds, mergeClaimsStrategy, disablePKCE, stateStore, revokeTokenAdditionalContentTypes, fetchRequestCredentials, refreshTokenAllowedScope, extraQueryParams, extraTokenParams, extraHeaders, }: OidcClientSettings); + constructor({ authority, metadataUrl, metadata, signingKeys, metadataSeed, client_id, client_secret, response_type, scope, redirect_uri, post_logout_redirect_uri, client_authentication, prompt, display, max_age, ui_locales, acr_values, resource, response_mode, filterProtocolClaims, loadUserInfo, requestTimeoutInSeconds, staleStateAgeInSeconds, mergeClaimsStrategy, disablePKCE, stateStore, revokeTokenAdditionalContentTypes, fetchRequestCredentials, refreshTokenAllowedScope, extraQueryParams, extraTokenParams, extraHeaders, }: OidcClientSettings); // (undocumented) readonly acr_values: string | undefined; // (undocumented) @@ -430,6 +431,8 @@ export class OidcClientSettingsStore { // (undocumented) readonly refreshTokenAllowedScope: string | undefined; // (undocumented) + readonly requestTimeoutInSeconds: number | undefined; + // (undocumented) readonly resource: string | string[] | undefined; // (undocumented) readonly response_mode: "query" | "fragment" | undefined; diff --git a/src/JsonService.ts b/src/JsonService.ts index 00b001c2..d7561178 100644 --- a/src/JsonService.ts +++ b/src/JsonService.ts @@ -16,6 +16,7 @@ export type JwtHandler = (text: string) => Promise>; export interface GetJsonOpts { token?: string; credentials?: RequestCredentials; + timeoutInSeconds?: number; } /** @@ -78,6 +79,7 @@ export class JsonService { public async getJson(url: string, { token, credentials, + timeoutInSeconds, }: GetJsonOpts = {}): Promise> { const logger = this._logger.create("getJson"); const headers: HeadersInit = { @@ -93,7 +95,7 @@ export class JsonService { let response: Response; try { logger.debug("url:", url); - response = await this.fetchWithTimeout(url, { method: "GET", headers, credentials }); + response = await this.fetchWithTimeout(url, { method: "GET", headers, timeoutInSeconds, credentials }); } catch (err) { logger.error("Network Error"); diff --git a/src/MetadataService.test.ts b/src/MetadataService.test.ts index 53f1318e..fef212b5 100644 --- a/src/MetadataService.test.ts +++ b/src/MetadataService.test.ts @@ -58,7 +58,7 @@ describe("MetadataService", () => { await subject.getMetadata(); // assert - expect(getJsonMock).toHaveBeenCalledWith("authority/.well-known/openid-configuration", { credentials: "same-origin" }); + expect(getJsonMock).toHaveBeenCalledWith("authority/.well-known/openid-configuration", { credentials: "same-origin", timeoutInSeconds: undefined } ); }); it("should fail when no authority or metadataUrl configured", async () => { @@ -93,7 +93,7 @@ describe("MetadataService", () => { await subject.getMetadata(); // assert - expect(getJsonMock).toHaveBeenCalledWith("http://sts/metadata", { credentials: "same-origin" }); + expect(getJsonMock).toHaveBeenCalledWith("http://sts/metadata", { credentials: "same-origin", timeoutInSeconds: undefined }); }); it("should return metadata from json call", async () => { @@ -196,7 +196,7 @@ describe("MetadataService", () => { await subject.getMetadata(); // assert - expect(getJsonMock).toHaveBeenCalledWith("http://sts/metadata", { credentials: "include" }); + expect(getJsonMock).toHaveBeenCalledWith("http://sts/metadata", { credentials: "include", timeoutInSeconds: undefined }); }); }); @@ -535,7 +535,7 @@ describe("MetadataService", () => { await subject.getSigningKeys(); // assert - expect(getJsonMock).toHaveBeenCalledWith("http://sts/metadata/keys"); + expect(getJsonMock).toHaveBeenCalledWith("http://sts/metadata/keys", { "timeoutInSeconds": undefined }); }); it("should return keys from jwks_uri", async () => { diff --git a/src/MetadataService.ts b/src/MetadataService.ts index d9e56d2b..51a22610 100644 --- a/src/MetadataService.ts +++ b/src/MetadataService.ts @@ -60,7 +60,7 @@ export class MetadataService { } logger.debug("getting metadata from", this._metadataUrl); - const metadata = await this._jsonService.getJson(this._metadataUrl, { credentials: this._fetchRequestCredentials }); + const metadata = await this._jsonService.getJson(this._metadataUrl, { credentials: this._fetchRequestCredentials, timeoutInSeconds: this._settings.requestTimeoutInSeconds }); logger.debug("merging remote JSON with seed metadata"); this._metadata = Object.assign({}, this._settings.metadataSeed, metadata); @@ -133,7 +133,7 @@ export class MetadataService { const jwks_uri = await this.getKeysEndpoint(false); logger.debug("got jwks_uri", jwks_uri); - const keySet = await this._jsonService.getJson(jwks_uri); + const keySet = await this._jsonService.getJson(jwks_uri, { timeoutInSeconds: this._settings.requestTimeoutInSeconds }); logger.debug("got key set", keySet); if (!Array.isArray(keySet.keys)) { diff --git a/src/OidcClientSettings.ts b/src/OidcClientSettings.ts index 5e915ef1..0c90c5fb 100644 --- a/src/OidcClientSettings.ts +++ b/src/OidcClientSettings.ts @@ -137,6 +137,11 @@ export interface OidcClientSettings { * Only scopes in this list will be passed in the token refresh request. */ refreshTokenAllowedScope?: string | undefined; + + /** + * Defines request timeouts globally across all requests made to the authorisation server + */ + requestTimeoutInSeconds?: number | undefined; } /** @@ -188,6 +193,7 @@ export class OidcClientSettingsStore { public readonly fetchRequestCredentials: RequestCredentials; public readonly refreshTokenAllowedScope: string | undefined; public readonly disablePKCE: boolean; + public readonly requestTimeoutInSeconds: number | undefined; public constructor({ // metadata related @@ -201,6 +207,7 @@ export class OidcClientSettingsStore { // behavior flags filterProtocolClaims = true, loadUserInfo = false, + requestTimeoutInSeconds, staleStateAgeInSeconds = DefaultStaleStateAgeInSeconds, mergeClaimsStrategy = { array: "replace" }, disablePKCE = false, @@ -257,6 +264,7 @@ export class OidcClientSettingsStore { this.revokeTokenAdditionalContentTypes = revokeTokenAdditionalContentTypes; this.fetchRequestCredentials = fetchRequestCredentials ? fetchRequestCredentials : "same-origin"; + this.requestTimeoutInSeconds = requestTimeoutInSeconds; if (stateStore) { this.stateStore = stateStore; diff --git a/src/TokenClient.ts b/src/TokenClient.ts index 6df06b19..ff94e4ad 100644 --- a/src/TokenClient.ts +++ b/src/TokenClient.ts @@ -129,7 +129,14 @@ export class TokenClient { const url = await this._metadataService.getTokenEndpoint(false); logger.debug("got token endpoint"); - const response = await this._jsonService.postForm(url, { body: params, basicAuth, initCredentials: this._settings.fetchRequestCredentials, extraHeaders }); + const response = await this._jsonService.postForm(url, { + body: params, + basicAuth, + timeoutInSeconds: this._settings.requestTimeoutInSeconds, + initCredentials: this._settings.fetchRequestCredentials, + extraHeaders, + }); + logger.debug("got response"); return response; @@ -180,7 +187,7 @@ export class TokenClient { const url = await this._metadataService.getTokenEndpoint(false); logger.debug("got token endpoint"); - const response = await this._jsonService.postForm(url, { body: params, basicAuth, initCredentials: this._settings.fetchRequestCredentials }); + const response = await this._jsonService.postForm(url, { body: params, basicAuth, timeoutInSeconds: this._settings.requestTimeoutInSeconds, initCredentials: this._settings.fetchRequestCredentials }); logger.debug("got response"); return response; @@ -268,7 +275,7 @@ export class TokenClient { params.set("client_secret", this._settings.client_secret); } - await this._jsonService.postForm(url, { body: params }); + await this._jsonService.postForm(url, { body: params, timeoutInSeconds: this._settings.requestTimeoutInSeconds }); logger.debug("got response"); } } diff --git a/src/UserInfoService.ts b/src/UserInfoService.ts index ed3ad6e6..a610bdd5 100644 --- a/src/UserInfoService.ts +++ b/src/UserInfoService.ts @@ -36,6 +36,7 @@ export class UserInfoService { const claims = await this._jsonService.getJson(url, { token, credentials: this._settings.fetchRequestCredentials, + timeoutInSeconds: this._settings.requestTimeoutInSeconds, }); logger.debug("got claims", claims); diff --git a/src/UserManager.ts b/src/UserManager.ts index be5b33ef..51b0df07 100644 --- a/src/UserManager.ts +++ b/src/UserManager.ts @@ -331,8 +331,8 @@ export class UserManager { protected async _useRefreshToken(args: UseRefreshTokenArgs): Promise { const response = await this._client.useRefreshToken({ - ...args, timeoutInSeconds: this.settings.silentRequestTimeoutInSeconds, + ...args, }); const user = new User({ ...args.state, ...response }); diff --git a/src/UserManagerSettings.test.ts b/src/UserManagerSettings.test.ts index e1e3cb39..7cc5c764 100644 --- a/src/UserManagerSettings.test.ts +++ b/src/UserManagerSettings.test.ts @@ -376,4 +376,48 @@ describe("UserManagerSettings", () => { expect(subject.stopCheckSessionOnError).toEqual(true); }); }); + + describe("silentRequestTimeoutInSeconds", () => { + it("should set if defined in the constructor", () => { + const temp = 100; + + // act + const subject = new UserManagerSettingsStore({ + authority: "authority", + client_id: "client", + redirect_uri: "redirect", + silentRequestTimeoutInSeconds : temp, + }); + + // assert + expect(subject.silentRequestTimeoutInSeconds).toEqual(temp); + }); + + it("should set to requestTimeoutInSeconds if defined in the constructor", () => { + const temp = 100; + + // act + const subject = new UserManagerSettingsStore({ + authority: "authority", + client_id: "client", + redirect_uri: "redirect", + requestTimeoutInSeconds : temp, + }); + + // assert + expect(subject.silentRequestTimeoutInSeconds).toEqual(temp); + }); + + it("should set to the default if neither requestTimeoutInSeconds are defined", () => { + // act + const subject = new UserManagerSettingsStore({ + authority: "authority", + client_id: "client", + redirect_uri: "redirect", + }); + + // assert + expect(subject.silentRequestTimeoutInSeconds).toEqual(10); + }); + }); }); diff --git a/src/UserManagerSettings.ts b/src/UserManagerSettings.ts index cf6ae366..92124c77 100644 --- a/src/UserManagerSettings.ts +++ b/src/UserManagerSettings.ts @@ -134,8 +134,9 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore { iframeNotifyParentOrigin = args.iframeNotifyParentOrigin, iframeScriptOrigin = args.iframeScriptOrigin, + requestTimeoutInSeconds, silent_redirect_uri = args.redirect_uri, - silentRequestTimeoutInSeconds = DefaultSilentRequestTimeoutInSeconds, + silentRequestTimeoutInSeconds, automaticSilentRenew = true, validateSubOnSilentRenew = true, includeIdTokenInSilentRenew = false, @@ -168,7 +169,7 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore { this.iframeScriptOrigin = iframeScriptOrigin; this.silent_redirect_uri = silent_redirect_uri; - this.silentRequestTimeoutInSeconds = silentRequestTimeoutInSeconds; + this.silentRequestTimeoutInSeconds = silentRequestTimeoutInSeconds || requestTimeoutInSeconds || DefaultSilentRequestTimeoutInSeconds; this.automaticSilentRenew = automaticSilentRenew; this.validateSubOnSilentRenew = validateSubOnSilentRenew; this.includeIdTokenInSilentRenew = includeIdTokenInSilentRenew;