387 lines
15 KiB
TypeScript
387 lines
15 KiB
TypeScript
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|
Please see LICENSE files in the repository root for full details.
|
|
*/
|
|
|
|
import { AutoDiscovery, AutoDiscoveryAction, ClientConfig } from "matrix-js-sdk/src/matrix";
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
import fetchMock from "fetch-mock-jest";
|
|
|
|
import AutoDiscoveryUtils from "../../../src/utils/AutoDiscoveryUtils";
|
|
import { mockOpenIdConfiguration } from "../../test-utils/oidc";
|
|
|
|
describe("AutoDiscoveryUtils", () => {
|
|
beforeEach(() => {
|
|
fetchMock.catch({
|
|
status: 404,
|
|
body: '{"errcode": "M_UNRECOGNIZED", "error": "Unrecognized request"}',
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
});
|
|
|
|
describe("buildValidatedConfigFromDiscovery()", () => {
|
|
const serverName = "my-server";
|
|
|
|
beforeEach(() => {
|
|
// don't litter console with expected errors
|
|
jest.spyOn(logger, "error")
|
|
.mockClear()
|
|
.mockImplementation(() => {});
|
|
});
|
|
|
|
afterAll(() => {
|
|
jest.spyOn(logger, "error").mockRestore();
|
|
});
|
|
|
|
const validIsConfig = {
|
|
"m.identity_server": {
|
|
state: AutoDiscoveryAction.SUCCESS,
|
|
base_url: "identity.com",
|
|
},
|
|
};
|
|
const validHsConfig = {
|
|
"m.homeserver": {
|
|
state: AutoDiscoveryAction.SUCCESS,
|
|
base_url: "https://matrix.org",
|
|
},
|
|
};
|
|
|
|
const expectedValidatedConfig = {
|
|
hsName: serverName,
|
|
hsNameIsDifferent: true,
|
|
hsUrl: "https://matrix.org",
|
|
isDefault: false,
|
|
isNameResolvable: true,
|
|
isUrl: "identity.com",
|
|
};
|
|
|
|
it("throws an error when discovery result is falsy", async () => {
|
|
await expect(() =>
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, undefined as any),
|
|
).rejects.toThrow("Unexpected error resolving homeserver configuration");
|
|
expect(logger.error).toHaveBeenCalled();
|
|
});
|
|
|
|
it("throws an error when discovery result does not include homeserver config", async () => {
|
|
const discoveryResult = {
|
|
...validIsConfig,
|
|
} as unknown as ClientConfig;
|
|
await expect(() =>
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
|
).rejects.toThrow("Unexpected error resolving homeserver configuration");
|
|
expect(logger.error).toHaveBeenCalled();
|
|
});
|
|
|
|
it("throws an error when identity server config has fail error and recognised error string", async () => {
|
|
const discoveryResult = {
|
|
...validHsConfig,
|
|
"m.identity_server": {
|
|
state: AutoDiscoveryAction.FAIL_ERROR,
|
|
error: "GenericFailure",
|
|
},
|
|
};
|
|
await expect(() =>
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
|
).rejects.toThrow("Unexpected error resolving identity server configuration");
|
|
expect(logger.error).toHaveBeenCalled();
|
|
});
|
|
|
|
it("throws an error when homeserver config has fail error and recognised error string", async () => {
|
|
const discoveryResult = {
|
|
...validIsConfig,
|
|
"m.homeserver": {
|
|
state: AutoDiscoveryAction.FAIL_ERROR,
|
|
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
|
},
|
|
};
|
|
await expect(() =>
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
|
).rejects.toThrow("Homeserver URL does not appear to be a valid Matrix homeserver");
|
|
expect(logger.error).toHaveBeenCalled();
|
|
});
|
|
|
|
it("throws an error with fallback message identity server config has fail error", async () => {
|
|
const discoveryResult = {
|
|
...validHsConfig,
|
|
"m.identity_server": {
|
|
state: AutoDiscoveryAction.FAIL_ERROR,
|
|
},
|
|
};
|
|
await expect(() =>
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
|
).rejects.toThrow("Unexpected error resolving identity server configuration");
|
|
});
|
|
|
|
it("throws an error when error is ERROR_INVALID_HOMESERVER", async () => {
|
|
const discoveryResult = {
|
|
...validIsConfig,
|
|
"m.homeserver": {
|
|
state: AutoDiscoveryAction.FAIL_ERROR,
|
|
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
|
},
|
|
};
|
|
await expect(() =>
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
|
).rejects.toThrow("Homeserver URL does not appear to be a valid Matrix homeserver");
|
|
});
|
|
|
|
it("throws an error when homeserver base_url is falsy", async () => {
|
|
const discoveryResult = {
|
|
...validIsConfig,
|
|
"m.homeserver": {
|
|
state: AutoDiscoveryAction.SUCCESS,
|
|
base_url: "",
|
|
},
|
|
};
|
|
await expect(() =>
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
|
).rejects.toThrow("Unexpected error resolving homeserver configuration");
|
|
expect(logger.error).toHaveBeenCalledWith("No homeserver URL configured");
|
|
});
|
|
|
|
it("throws an error when homeserver base_url is not a valid URL", async () => {
|
|
const discoveryResult = {
|
|
...validIsConfig,
|
|
"m.homeserver": {
|
|
state: AutoDiscoveryAction.SUCCESS,
|
|
base_url: "banana",
|
|
},
|
|
};
|
|
await expect(() =>
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
|
).rejects.toThrow("Invalid URL: banana");
|
|
});
|
|
|
|
it("uses hs url hostname when serverName is falsy in args and config", async () => {
|
|
const discoveryResult = {
|
|
...validIsConfig,
|
|
...validHsConfig,
|
|
};
|
|
await expect(AutoDiscoveryUtils.buildValidatedConfigFromDiscovery("", discoveryResult)).resolves.toEqual({
|
|
...expectedValidatedConfig,
|
|
hsNameIsDifferent: false,
|
|
hsName: "matrix.org",
|
|
warning: null,
|
|
});
|
|
});
|
|
|
|
it("uses serverName from props", async () => {
|
|
const discoveryResult = {
|
|
...validIsConfig,
|
|
"m.homeserver": {
|
|
...validHsConfig["m.homeserver"],
|
|
server_name: "should not use this name",
|
|
},
|
|
};
|
|
const syntaxOnly = true;
|
|
await expect(
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
|
).resolves.toEqual({
|
|
...expectedValidatedConfig,
|
|
hsNameIsDifferent: true,
|
|
hsName: serverName,
|
|
warning: null,
|
|
});
|
|
});
|
|
|
|
it("ignores liveliness error when checking syntax only", async () => {
|
|
const discoveryResult = {
|
|
...validIsConfig,
|
|
"m.homeserver": {
|
|
...validHsConfig["m.homeserver"],
|
|
state: AutoDiscoveryAction.FAIL_ERROR,
|
|
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
|
},
|
|
};
|
|
const syntaxOnly = true;
|
|
await expect(
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
|
).resolves.toEqual({
|
|
...expectedValidatedConfig,
|
|
warning: "Homeserver URL does not appear to be a valid Matrix homeserver",
|
|
});
|
|
});
|
|
|
|
it("handles homeserver too old error", async () => {
|
|
const discoveryResult: ClientConfig = {
|
|
...validIsConfig,
|
|
"m.homeserver": {
|
|
state: AutoDiscoveryAction.FAIL_ERROR,
|
|
error: AutoDiscovery.ERROR_UNSUPPORTED_HOMESERVER_SPEC_VERSION,
|
|
base_url: "https://matrix.org",
|
|
},
|
|
};
|
|
const syntaxOnly = true;
|
|
await expect(() =>
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
|
).rejects.toThrow(
|
|
"Your homeserver is too old and does not support the minimum API version required. Please contact your server owner, or upgrade your server.",
|
|
);
|
|
});
|
|
|
|
it("should validate delegated oidc auth", async () => {
|
|
const issuer = "https://auth.matrix.org/";
|
|
fetchMock.get(
|
|
`${validHsConfig["m.homeserver"].base_url}/_matrix/client/unstable/org.matrix.msc2965/auth_issuer`,
|
|
{
|
|
issuer,
|
|
},
|
|
);
|
|
fetchMock.get(`${issuer}.well-known/openid-configuration`, {
|
|
...mockOpenIdConfiguration(issuer),
|
|
"scopes_supported": ["openid", "email"],
|
|
"response_modes_supported": ["form_post", "query", "fragment"],
|
|
"token_endpoint_auth_methods_supported": [
|
|
"client_secret_basic",
|
|
"client_secret_post",
|
|
"client_secret_jwt",
|
|
"private_key_jwt",
|
|
"none",
|
|
],
|
|
"token_endpoint_auth_signing_alg_values_supported": [
|
|
"HS256",
|
|
"HS384",
|
|
"HS512",
|
|
"RS256",
|
|
"RS384",
|
|
"RS512",
|
|
"PS256",
|
|
"PS384",
|
|
"PS512",
|
|
"ES256",
|
|
"ES384",
|
|
"ES256K",
|
|
],
|
|
"revocation_endpoint_auth_methods_supported": [
|
|
"client_secret_basic",
|
|
"client_secret_post",
|
|
"client_secret_jwt",
|
|
"private_key_jwt",
|
|
"none",
|
|
],
|
|
"revocation_endpoint_auth_signing_alg_values_supported": [
|
|
"HS256",
|
|
"HS384",
|
|
"HS512",
|
|
"RS256",
|
|
"RS384",
|
|
"RS512",
|
|
"PS256",
|
|
"PS384",
|
|
"PS512",
|
|
"ES256",
|
|
"ES384",
|
|
"ES256K",
|
|
],
|
|
"introspection_endpoint": `${issuer}oauth2/introspect`,
|
|
"introspection_endpoint_auth_methods_supported": [
|
|
"client_secret_basic",
|
|
"client_secret_post",
|
|
"client_secret_jwt",
|
|
"private_key_jwt",
|
|
"none",
|
|
],
|
|
"introspection_endpoint_auth_signing_alg_values_supported": [
|
|
"HS256",
|
|
"HS384",
|
|
"HS512",
|
|
"RS256",
|
|
"RS384",
|
|
"RS512",
|
|
"PS256",
|
|
"PS384",
|
|
"PS512",
|
|
"ES256",
|
|
"ES384",
|
|
"ES256K",
|
|
],
|
|
"userinfo_endpoint": `${issuer}oauth2/userinfo`,
|
|
"subject_types_supported": ["public"],
|
|
"id_token_signing_alg_values_supported": [
|
|
"RS256",
|
|
"RS384",
|
|
"RS512",
|
|
"ES256",
|
|
"ES384",
|
|
"PS256",
|
|
"PS384",
|
|
"PS512",
|
|
"ES256K",
|
|
],
|
|
"userinfo_signing_alg_values_supported": [
|
|
"RS256",
|
|
"RS384",
|
|
"RS512",
|
|
"ES256",
|
|
"ES384",
|
|
"PS256",
|
|
"PS384",
|
|
"PS512",
|
|
"ES256K",
|
|
],
|
|
"display_values_supported": ["page"],
|
|
"claim_types_supported": ["normal"],
|
|
"claims_supported": ["iss", "sub", "aud", "iat", "exp", "nonce", "auth_time", "at_hash", "c_hash"],
|
|
"claims_parameter_supported": false,
|
|
"request_parameter_supported": false,
|
|
"request_uri_parameter_supported": false,
|
|
"prompt_values_supported": ["none", "login", "create"],
|
|
"device_authorization_endpoint": `${issuer}oauth2/device`,
|
|
"org.matrix.matrix-authentication-service.graphql_endpoint": `${issuer}graphql`,
|
|
"account_management_uri": `${issuer}account/`,
|
|
"account_management_actions_supported": [
|
|
"org.matrix.profile",
|
|
"org.matrix.sessions_list",
|
|
"org.matrix.session_view",
|
|
"org.matrix.session_end",
|
|
"org.matrix.cross_signing_reset",
|
|
],
|
|
});
|
|
fetchMock.get(`${issuer}jwks`, {
|
|
keys: [],
|
|
});
|
|
|
|
const discoveryResult = {
|
|
...validIsConfig,
|
|
...validHsConfig,
|
|
};
|
|
await expect(
|
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
|
).resolves.toEqual({
|
|
...expectedValidatedConfig,
|
|
hsNameIsDifferent: true,
|
|
hsName: serverName,
|
|
delegatedAuthentication: expect.objectContaining({
|
|
accountManagementActionsSupported: [
|
|
"org.matrix.profile",
|
|
"org.matrix.sessions_list",
|
|
"org.matrix.session_view",
|
|
"org.matrix.session_end",
|
|
"org.matrix.cross_signing_reset",
|
|
],
|
|
accountManagementEndpoint: "https://auth.matrix.org/account/",
|
|
authorizationEndpoint: "https://auth.matrix.org/auth",
|
|
metadata: expect.objectContaining({
|
|
issuer,
|
|
}),
|
|
registrationEndpoint: "https://auth.matrix.org/registration",
|
|
signingKeys: [],
|
|
tokenEndpoint: "https://auth.matrix.org/token",
|
|
}),
|
|
warning: null,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("authComponentStateForError", () => {
|
|
const error = new Error("TEST");
|
|
|
|
it("should return expected error for the registration page", () => {
|
|
expect(AutoDiscoveryUtils.authComponentStateForError(error, "register")).toMatchSnapshot();
|
|
});
|
|
});
|
|
});
|