From aa017a49c60c3d59d29861cdf182c65b0be9ddca Mon Sep 17 00:00:00 2001 From: Chris Keogh Date: Thu, 7 Mar 2024 12:01:18 +1300 Subject: [PATCH 1/9] add timedoutinseconds to getJson method and options --- docs/oidc-client-ts.api.md | 5 ++++- src/JsonService.ts | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md index fc28ae363..45d708f49 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 5d0f0f916..233552e5d 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; } /** @@ -77,6 +78,7 @@ export class JsonService { public async getJson(url: string, { token, credentials, + timeoutInSeconds, }: GetJsonOpts = {}): Promise> { const logger = this._logger.create("getJson"); const headers: HeadersInit = { @@ -92,7 +94,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"); From d042f97e853831e45560b80600e55fedef0bcc63 Mon Sep 17 00:00:00 2001 From: Chris Keogh Date: Thu, 7 Mar 2024 12:03:01 +1300 Subject: [PATCH 2/9] add global requestTimeoutInSeconds option --- src/OidcClientSettings.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/OidcClientSettings.ts b/src/OidcClientSettings.ts index 5e915ef1a..e9e23f11e 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; From 4178ca42907f99531cbd40a5a6ec5167b69c5424 Mon Sep 17 00:00:00 2001 From: Chris Keogh Date: Thu, 7 Mar 2024 12:04:38 +1300 Subject: [PATCH 3/9] call metadata service with requestTimeoutInSeconds --- src/MetadataService.test.ts | 8 ++++---- src/MetadataService.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MetadataService.test.ts b/src/MetadataService.test.ts index 53f1318e2..fef212b5e 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 d9e56d2b6..51a22610f 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)) { From 63f5460246c86d1ef36485b32fd7891ca21fa38d Mon Sep 17 00:00:00 2001 From: Chris Keogh Date: Thu, 7 Mar 2024 12:05:40 +1300 Subject: [PATCH 4/9] add requestTimeoutInSeconds to all jsonservice calls --- src/TokenClient.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/TokenClient.ts b/src/TokenClient.ts index a56558a24..7cc393136 100644 --- a/src/TokenClient.ts +++ b/src/TokenClient.ts @@ -124,7 +124,12 @@ 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; @@ -175,7 +180,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; @@ -262,7 +267,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"); } } From a89b971819a59ed09fc94a0894011af2c8464e8c Mon Sep 17 00:00:00 2001 From: Chris Keogh Date: Thu, 7 Mar 2024 12:06:25 +1300 Subject: [PATCH 5/9] add requestTimeoutInSeconds to calls to userInfo --- src/UserInfoService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/UserInfoService.ts b/src/UserInfoService.ts index ed3ad6e60..a610bdd54 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); From e4fe7c61b32f957007154c0bca71e161d40dcfb0 Mon Sep 17 00:00:00 2001 From: Chris Keogh Date: Thu, 7 Mar 2024 12:07:15 +1300 Subject: [PATCH 6/9] if requestTimeoutInSeconds is defined, use that instead of silentRequestTimeoutInSeconds --- src/UserManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UserManager.ts b/src/UserManager.ts index 81af6738c..3a662bb5b 100644 --- a/src/UserManager.ts +++ b/src/UserManager.ts @@ -332,7 +332,7 @@ export class UserManager { protected async _useRefreshToken(args: UseRefreshTokenArgs): Promise { const response = await this._client.useRefreshToken({ ...args, - timeoutInSeconds: this.settings.silentRequestTimeoutInSeconds, + timeoutInSeconds: this.settings.requestTimeoutInSeconds ?? this.settings.silentRequestTimeoutInSeconds, }); const user = new User({ ...args.state, ...response }); @@ -344,7 +344,7 @@ export class UserManager { /** * * Notify the parent window of response (callback) from the authorization endpoint. - * It is recommend to use {@link UserManager.signinCallback} instead. + * It is recommended to use {@link UserManager.signinCallback} instead. * * @returns A promise * From 1ec331b66d4bbbbb7a6df46c343c36c7b10d942f Mon Sep 17 00:00:00 2001 From: Chris Keogh Date: Sun, 10 Mar 2024 16:52:47 +1300 Subject: [PATCH 7/9] Don't override silentRequestTimeoutInSeconds with requestTimeoutInSeconds is defined --- src/OidcClientSettings.ts | 2 +- src/UserManager.ts | 2 +- src/UserManagerSettings.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OidcClientSettings.ts b/src/OidcClientSettings.ts index e9e23f11e..0c90c5fb5 100644 --- a/src/OidcClientSettings.ts +++ b/src/OidcClientSettings.ts @@ -207,7 +207,7 @@ export class OidcClientSettingsStore { // behavior flags filterProtocolClaims = true, loadUserInfo = false, - requestTimeoutInSeconds , + requestTimeoutInSeconds, staleStateAgeInSeconds = DefaultStaleStateAgeInSeconds, mergeClaimsStrategy = { array: "replace" }, disablePKCE = false, diff --git a/src/UserManager.ts b/src/UserManager.ts index 3a662bb5b..72bf4f9b7 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({ + timeoutInSeconds: this.settings.silentRequestTimeoutInSeconds, ...args, - timeoutInSeconds: this.settings.requestTimeoutInSeconds ?? this.settings.silentRequestTimeoutInSeconds, }); const user = new User({ ...args.state, ...response }); diff --git a/src/UserManagerSettings.ts b/src/UserManagerSettings.ts index cf6ae3662..f27f59218 100644 --- a/src/UserManagerSettings.ts +++ b/src/UserManagerSettings.ts @@ -134,6 +134,7 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore { iframeNotifyParentOrigin = args.iframeNotifyParentOrigin, iframeScriptOrigin = args.iframeScriptOrigin, + requestTimeoutInSeconds, silent_redirect_uri = args.redirect_uri, silentRequestTimeoutInSeconds = DefaultSilentRequestTimeoutInSeconds, automaticSilentRenew = true, @@ -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; this.automaticSilentRenew = automaticSilentRenew; this.validateSubOnSilentRenew = validateSubOnSilentRenew; this.includeIdTokenInSilentRenew = includeIdTokenInSilentRenew; From 0bffb811281603cab14807cbbde4075b945a3152 Mon Sep 17 00:00:00 2001 From: Chris Keogh Date: Wed, 1 May 2024 19:25:55 +1200 Subject: [PATCH 8/9] Set silentRequestTimeoutInSeconds if defined, else set to requestTimeoutInSeconds if defined, else set to DefaultSilentRequestTimeoutInSeconds --- src/UserManagerSettings.test.ts | 44 +++++++++++++++++++++++++++++++++ src/UserManagerSettings.ts | 4 +-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/UserManagerSettings.test.ts b/src/UserManagerSettings.test.ts index e1e3cb397..7cc5c764e 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 f27f59218..92124c77a 100644 --- a/src/UserManagerSettings.ts +++ b/src/UserManagerSettings.ts @@ -136,7 +136,7 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore { requestTimeoutInSeconds, silent_redirect_uri = args.redirect_uri, - silentRequestTimeoutInSeconds = DefaultSilentRequestTimeoutInSeconds, + silentRequestTimeoutInSeconds, automaticSilentRenew = true, validateSubOnSilentRenew = true, includeIdTokenInSilentRenew = false, @@ -169,7 +169,7 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore { this.iframeScriptOrigin = iframeScriptOrigin; this.silent_redirect_uri = silent_redirect_uri; - this.silentRequestTimeoutInSeconds = silentRequestTimeoutInSeconds ?? requestTimeoutInSeconds; + this.silentRequestTimeoutInSeconds = silentRequestTimeoutInSeconds || requestTimeoutInSeconds || DefaultSilentRequestTimeoutInSeconds; this.automaticSilentRenew = automaticSilentRenew; this.validateSubOnSilentRenew = validateSubOnSilentRenew; this.includeIdTokenInSilentRenew = includeIdTokenInSilentRenew; From 2e193299e884c5643566ba7774fccd050cec58e2 Mon Sep 17 00:00:00 2001 From: Chris Keogh Date: Wed, 1 May 2024 19:35:09 +1200 Subject: [PATCH 9/9] Call postForm with extraHeaders --- src/TokenClient.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TokenClient.ts b/src/TokenClient.ts index a669eeb6a..ff94e4ad8 100644 --- a/src/TokenClient.ts +++ b/src/TokenClient.ts @@ -134,6 +134,7 @@ export class TokenClient { basicAuth, timeoutInSeconds: this._settings.requestTimeoutInSeconds, initCredentials: this._settings.fetchRequestCredentials, + extraHeaders, }); logger.debug("got response");