1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-30 04:23:07 +03:00

Switch OIDC primarily to new /auth_metadata API (#4626)

This commit is contained in:
Michael Telatynski
2025-01-22 13:48:27 +00:00
committed by GitHub
parent 61375ef38a
commit c0e30ceca0
16 changed files with 267 additions and 193 deletions

View File

@ -40,8 +40,8 @@ jest.mock("jwt-decode");
describe("oidc authorization", () => {
const delegatedAuthConfig = makeDelegatedAuthConfig();
const authorizationEndpoint = delegatedAuthConfig.authorizationEndpoint;
const tokenEndpoint = delegatedAuthConfig.tokenEndpoint;
const authorizationEndpoint = delegatedAuthConfig.authorization_endpoint;
const tokenEndpoint = delegatedAuthConfig.token_endpoint;
const clientId = "xyz789";
const baseUrl = "https://test.com";
@ -52,10 +52,7 @@ describe("oidc authorization", () => {
jest.spyOn(logger, "warn");
jest.setSystemTime(now);
fetchMock.get(
delegatedAuthConfig.metadata.issuer + ".well-known/openid-configuration",
mockOpenIdConfiguration(),
);
fetchMock.get(delegatedAuthConfig.issuer + ".well-known/openid-configuration", mockOpenIdConfiguration());
globalThis.TextEncoder = TextEncoder;
});
@ -127,11 +124,9 @@ describe("oidc authorization", () => {
it("should generate url with correct parameters", async () => {
const nonce = "abc123";
const metadata = delegatedAuthConfig.metadata;
const authUrl = new URL(
await generateOidcAuthorizationUrl({
metadata,
metadata: delegatedAuthConfig,
homeserverUrl: baseUrl,
clientId,
redirectUri: baseUrl,
@ -156,11 +151,9 @@ describe("oidc authorization", () => {
it("should generate url with create prompt", async () => {
const nonce = "abc123";
const metadata = delegatedAuthConfig.metadata;
const authUrl = new URL(
await generateOidcAuthorizationUrl({
metadata,
metadata: delegatedAuthConfig,
homeserverUrl: baseUrl,
clientId,
redirectUri: baseUrl,

View File

@ -42,13 +42,13 @@ describe("registerOidcClient()", () => {
});
it("should make correct request to register client", async () => {
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
status: 200,
body: JSON.stringify({ client_id: dynamicClientId }),
});
expect(await registerOidcClient(delegatedAuthConfig, metadata)).toEqual(dynamicClientId);
expect(fetchMockJest).toHaveBeenCalledWith(
delegatedAuthConfig.registrationEndpoint!,
delegatedAuthConfig.registration_endpoint,
expect.objectContaining({
headers: {
"Accept": "application/json",
@ -72,7 +72,7 @@ describe("registerOidcClient()", () => {
});
it("should throw when registration request fails", async () => {
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
status: 500,
});
await expect(() => registerOidcClient(delegatedAuthConfig, metadata)).rejects.toThrow(
@ -81,7 +81,7 @@ describe("registerOidcClient()", () => {
});
it("should throw when registration response is invalid", async () => {
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
status: 200,
// no clientId in response
body: "{}",
@ -96,7 +96,7 @@ describe("registerOidcClient()", () => {
registerOidcClient(
{
...delegatedAuthConfig,
registrationEndpoint: undefined,
registration_endpoint: undefined,
},
metadata,
),
@ -108,10 +108,7 @@ describe("registerOidcClient()", () => {
registerOidcClient(
{
...delegatedAuthConfig,
metadata: {
...delegatedAuthConfig.metadata,
grant_types_supported: [delegatedAuthConfig.metadata.grant_types_supported[0]],
},
grant_types_supported: [delegatedAuthConfig.grant_types_supported[0]],
},
metadata,
),

View File

@ -55,8 +55,8 @@ describe("OidcTokenRefresher", () => {
});
beforeEach(() => {
fetchMock.get(`${config.metadata.issuer}.well-known/openid-configuration`, config.metadata);
fetchMock.get(`${config.metadata.issuer}jwks`, {
fetchMock.get(`${config.issuer}.well-known/openid-configuration`, config);
fetchMock.get(`${config.issuer}jwks`, {
status: 200,
headers: {
"Content-Type": "application/json",
@ -64,7 +64,7 @@ describe("OidcTokenRefresher", () => {
keys: [],
});
fetchMock.post(config.tokenEndpoint, {
fetchMock.post(config.token_endpoint, {
status: 200,
headers: {
"Content-Type": "application/json",
@ -81,7 +81,7 @@ describe("OidcTokenRefresher", () => {
it("throws when oidc client cannot be initialised", async () => {
jest.spyOn(logger, "error");
fetchMock.get(
`${config.metadata.issuer}.well-known/openid-configuration`,
`${config.issuer}.well-known/openid-configuration`,
{
ok: false,
status: 404,
@ -126,7 +126,7 @@ describe("OidcTokenRefresher", () => {
const result = await refresher.doRefreshAccessToken("refresh-token");
expect(fetchMock).toHaveFetched(config.tokenEndpoint, {
expect(fetchMock).toHaveFetched(config.token_endpoint, {
method: "POST",
});
@ -153,7 +153,7 @@ describe("OidcTokenRefresher", () => {
it("should only have one inflight refresh request at once", async () => {
fetchMock
.postOnce(
config.tokenEndpoint,
config.token_endpoint,
{
status: 200,
headers: {
@ -164,7 +164,7 @@ describe("OidcTokenRefresher", () => {
{ overwriteRoutes: true },
)
.postOnce(
config.tokenEndpoint,
config.token_endpoint,
{
status: 200,
headers: {
@ -188,7 +188,7 @@ describe("OidcTokenRefresher", () => {
const result2 = await first;
// only one call to token endpoint
expect(fetchMock).toHaveFetchedTimes(1, config.tokenEndpoint);
expect(fetchMock).toHaveFetchedTimes(1, config.token_endpoint);
expect(result1).toEqual({
accessToken: "first-new-access-token",
refreshToken: "first-new-refresh-token",
@ -208,7 +208,7 @@ describe("OidcTokenRefresher", () => {
it("should log and rethrow when token refresh fails", async () => {
fetchMock.post(
config.tokenEndpoint,
config.token_endpoint,
{
status: 503,
headers: {
@ -228,7 +228,7 @@ describe("OidcTokenRefresher", () => {
// make sure inflight request is cleared after a failure
fetchMock
.postOnce(
config.tokenEndpoint,
config.token_endpoint,
{
status: 503,
headers: {
@ -238,7 +238,7 @@ describe("OidcTokenRefresher", () => {
{ overwriteRoutes: true },
)
.postOnce(
config.tokenEndpoint,
config.token_endpoint,
{
status: 200,
headers: {

View File

@ -18,17 +18,18 @@ import { mocked } from "jest-mock";
import { jwtDecode } from "jwt-decode";
import { logger } from "../../../src/logger";
import { validateIdToken, validateOIDCIssuerWellKnown } from "../../../src/oidc/validate";
import { ValidatedAuthMetadata, validateIdToken, validateAuthMetadata } from "../../../src/oidc/validate";
import { OidcError } from "../../../src/oidc/error";
jest.mock("jwt-decode");
describe("validateOIDCIssuerWellKnown", () => {
const validWk: any = {
const validWk: ValidatedAuthMetadata = {
issuer: "https://test.org",
authorization_endpoint: "https://test.org/authorize",
token_endpoint: "https://authorize.org/token",
registration_endpoint: "https://authorize.org/regsiter",
revocation_endpoint: "https://authorize.org/regsiter",
registration_endpoint: "https://authorize.org/register",
revocation_endpoint: "https://authorize.org/revoke",
response_types_supported: ["code"],
grant_types_supported: ["authorization_code"],
code_challenge_methods_supported: ["S256"],
@ -44,14 +45,14 @@ describe("validateOIDCIssuerWellKnown", () => {
it("should throw OP support error when wellKnown is not an object", () => {
expect(() => {
validateOIDCIssuerWellKnown([]);
validateAuthMetadata([]);
}).toThrow(OidcError.OpSupport);
expect(logger.error).toHaveBeenCalledWith("Issuer configuration not found or malformed");
});
it("should log all errors before throwing", () => {
expect(() => {
validateOIDCIssuerWellKnown({
validateAuthMetadata({
...validWk,
authorization_endpoint: undefined,
response_types_supported: [],
@ -62,24 +63,31 @@ describe("validateOIDCIssuerWellKnown", () => {
});
it("should return validated issuer config", () => {
expect(validateOIDCIssuerWellKnown(validWk)).toEqual({
authorizationEndpoint: validWk.authorization_endpoint,
tokenEndpoint: validWk.token_endpoint,
registrationEndpoint: validWk.registration_endpoint,
accountManagementActionsSupported: ["org.matrix.cross_signing_reset"],
accountManagementEndpoint: "https://authorize.org/account",
});
expect(validateAuthMetadata(validWk)).toEqual(
expect.objectContaining({
issuer: validWk.issuer,
authorization_endpoint: validWk.authorization_endpoint,
token_endpoint: validWk.token_endpoint,
registration_endpoint: validWk.registration_endpoint,
account_management_actions_supported: ["org.matrix.cross_signing_reset"],
account_management_uri: "https://authorize.org/account",
}),
);
});
it("should return validated issuer config without registrationendpoint", () => {
const wk = { ...validWk };
delete wk.registration_endpoint;
expect(validateOIDCIssuerWellKnown(wk)).toEqual({
authorizationEndpoint: validWk.authorization_endpoint,
tokenEndpoint: validWk.token_endpoint,
registrationEndpoint: undefined,
accountManagementActionsSupported: ["org.matrix.cross_signing_reset"],
accountManagementEndpoint: "https://authorize.org/account",
it("should return validated issuer config without registration_endpoint", () => {
const { registration_endpoint: _, ...wk } = validWk;
expect(validateAuthMetadata(wk)).toEqual({
issuer: validWk.issuer,
authorization_endpoint: validWk.authorization_endpoint,
token_endpoint: validWk.token_endpoint,
revocation_endpoint: validWk.revocation_endpoint,
registration_endpoint: undefined,
account_management_actions_supported: ["org.matrix.cross_signing_reset"],
account_management_uri: "https://authorize.org/account",
code_challenge_methods_supported: ["S256"],
grant_types_supported: ["authorization_code"],
response_types_supported: ["code"],
});
});
@ -106,7 +114,7 @@ describe("validateOIDCIssuerWellKnown", () => {
...validWk,
[key]: value,
};
expect(() => validateOIDCIssuerWellKnown(wk)).toThrow(OidcError.OpSupport);
expect(() => validateAuthMetadata(wk)).toThrow(OidcError.OpSupport);
});
});