1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-28 05:03:59 +03:00

Abstract logout-causing error type from tokenRefreshFunction calls (#4765)

* Abstract logout-causing error type from tokenRefreshFunction calls

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2025-03-25 10:31:58 +00:00
committed by GitHub
parent f4de8837fd
commit 2090319bdd
4 changed files with 48 additions and 7 deletions

View File

@@ -20,7 +20,7 @@ limitations under the License.
import fetchMock from "fetch-mock-jest"; import fetchMock from "fetch-mock-jest";
import { OidcTokenRefresher } from "../../../src"; import { OidcTokenRefresher, TokenRefreshLogoutError } from "../../../src";
import { logger } from "../../../src/logger"; import { logger } from "../../../src/logger";
import { makeDelegatedAuthConfig } from "../../test-utils/oidc"; import { makeDelegatedAuthConfig } from "../../test-utils/oidc";
@@ -266,5 +266,27 @@ describe("OidcTokenRefresher", () => {
refreshToken: "second-new-refresh-token", refreshToken: "second-new-refresh-token",
}); });
}); });
it("should throw TokenRefreshLogoutError when expired", async () => {
fetchMock.post(
config.token_endpoint,
{
status: 400,
headers: {
"Content-Type": "application/json",
},
body: {
error: "invalid_grant",
error_description: "The provided access grant is invalid, expired, or revoked.",
},
},
{ overwriteRoutes: true },
);
const refresher = new OidcTokenRefresher(authConfig.issuer, clientId, redirectUri, deviceId, idTokenClaims);
await refresher.oidcClientReady;
await expect(refresher.doRefreshAccessToken("refresh-token")).rejects.toThrow(TokenRefreshLogoutError);
});
}); });
}); });

View File

@@ -212,3 +212,17 @@ export class TokenRefreshError extends Error {
return "TokenRefreshError"; return "TokenRefreshError";
} }
} }
/**
* Construct a TokenRefreshError. This indicates that a request failed due to the token being expired,
* and attempting to refresh said token failed in a way indicative of token invalidation.
*/
export class TokenRefreshLogoutError extends Error {
public constructor(cause?: Error) {
super(cause?.message ?? "");
}
public get name(): string {
return "TokenRefreshLogoutError";
}
}

View File

@@ -18,12 +18,10 @@ limitations under the License.
* This is an internal module. See {@link MatrixHttpApi} for the public class. * This is an internal module. See {@link MatrixHttpApi} for the public class.
*/ */
import { ErrorResponse as OidcAuthError } from "oidc-client-ts";
import { checkObjectHasKeys, encodeParams } from "../utils.ts"; import { checkObjectHasKeys, encodeParams } from "../utils.ts";
import { type TypedEventEmitter } from "../models/typed-event-emitter.ts"; import { type TypedEventEmitter } from "../models/typed-event-emitter.ts";
import { Method } from "./method.ts"; import { Method } from "./method.ts";
import { ConnectionError, MatrixError, TokenRefreshError } from "./errors.ts"; import { ConnectionError, MatrixError, TokenRefreshError, TokenRefreshLogoutError } from "./errors.ts";
import { import {
HttpApiEvent, HttpApiEvent,
type HttpApiEventHandlerMap, type HttpApiEventHandlerMap,
@@ -234,7 +232,8 @@ export class FetchHttpApi<O extends IHttpOpts> {
return TokenRefreshOutcome.Success; return TokenRefreshOutcome.Success;
} catch (error) { } catch (error) {
this.opts.logger?.warn("Failed to refresh token", error); this.opts.logger?.warn("Failed to refresh token", error);
if (error instanceof OidcAuthError || error instanceof MatrixError) { // If we get a TokenError or MatrixError, we should log out, otherwise assume transient
if (error instanceof TokenRefreshLogoutError || error instanceof MatrixError) {
return TokenRefreshOutcome.Logout; return TokenRefreshOutcome.Logout;
} }
return TokenRefreshOutcome.Failure; return TokenRefreshOutcome.Failure;

View File

@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { type IdTokenClaims, OidcClient, WebStorageStateStore } from "oidc-client-ts"; import { type IdTokenClaims, OidcClient, WebStorageStateStore, ErrorResponse } from "oidc-client-ts";
import { type AccessTokens } from "../http-api/index.ts"; import { type AccessTokens, TokenRefreshLogoutError } from "../http-api/index.ts";
import { generateScope } from "./authorize.ts"; import { generateScope } from "./authorize.ts";
import { discoverAndValidateOIDCIssuerWellKnown } from "./discovery.ts"; import { discoverAndValidateOIDCIssuerWellKnown } from "./discovery.ts";
import { logger } from "../logger.ts"; import { logger } from "../logger.ts";
@@ -104,6 +104,12 @@ export class OidcTokenRefresher {
try { try {
const tokens = await this.inflightRefreshRequest; const tokens = await this.inflightRefreshRequest;
return tokens; return tokens;
} catch (e) {
// If we encounter an OIDC error then signal that it should cause a logout by upgrading it to a TokenRefreshLogoutError
if (e instanceof ErrorResponse) {
throw new TokenRefreshLogoutError(e);
}
throw e;
} finally { } finally {
this.inflightRefreshRequest = undefined; this.inflightRefreshRequest = undefined;
} }