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();
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 |