diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index ba4b2415a66..3e24a8d3464 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -15,14 +15,14 @@ limitations under the License. */ import React, { ReactNode } from "react"; -import { AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/autodiscovery"; +import { AutoDiscovery, ClientConfig, OidcClientConfig } from "matrix-js-sdk/src/autodiscovery"; import { M_AUTHENTICATION } from "matrix-js-sdk/src/client"; import { logger } from "matrix-js-sdk/src/logger"; import { IClientWellKnown } from "matrix-js-sdk/src/matrix"; import { _t, UserFriendlyError } from "../languageHandler"; import SdkConfig from "../SdkConfig"; -import { ValidatedDelegatedAuthConfig, ValidatedServerConfig } from "./ValidatedServerConfig"; +import { ValidatedServerConfig } from "./ValidatedServerConfig"; const LIVELINESS_DISCOVERY_ERRORS: string[] = [ AutoDiscovery.ERROR_INVALID_HOMESERVER, @@ -259,25 +259,25 @@ export default class AutoDiscoveryUtils { throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); } - let delegatedAuthentication: - | { - authorizationEndpoint: string; - registrationEndpoint?: string; - tokenEndpoint: string; - account?: string; - issuer: string; - } - | undefined; + let delegatedAuthentication: OidcClientConfig | undefined; if (discoveryResult[M_AUTHENTICATION.stable!]?.state === AutoDiscovery.SUCCESS) { - const { authorizationEndpoint, registrationEndpoint, tokenEndpoint, account, issuer } = discoveryResult[ - M_AUTHENTICATION.stable! - ] as ValidatedDelegatedAuthConfig; + const { + authorizationEndpoint, + registrationEndpoint, + tokenEndpoint, + account, + issuer, + metadata, + signingKeys, + } = discoveryResult[M_AUTHENTICATION.stable!] as OidcClientConfig; delegatedAuthentication = Object.freeze({ authorizationEndpoint, registrationEndpoint, tokenEndpoint, account, issuer, + metadata, + signingKeys, }); } diff --git a/src/utils/ValidatedServerConfig.ts b/src/utils/ValidatedServerConfig.ts index cb3edf3a9c8..b41ac6a642c 100644 --- a/src/utils/ValidatedServerConfig.ts +++ b/src/utils/ValidatedServerConfig.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { OidcClientConfig } from "matrix-js-sdk/src/autodiscovery"; import { IDelegatedAuthConfig } from "matrix-js-sdk/src/client"; import { ValidatedIssuerConfig } from "matrix-js-sdk/src/oidc/validate"; @@ -38,5 +39,5 @@ export interface ValidatedServerConfig { * From homeserver .well-known m.authentication, and issuer's .well-known/openid-configuration * Used for OIDC native flow authentication */ - delegatedAuthentication?: ValidatedDelegatedAuthConfig; + delegatedAuthentication?: OidcClientConfig; } diff --git a/src/utils/oidc/authorize.ts b/src/utils/oidc/authorize.ts index 22e7a11bce1..823e3cfab4a 100644 --- a/src/utils/oidc/authorize.ts +++ b/src/utils/oidc/authorize.ts @@ -14,34 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { - AuthorizationParams, - generateAuthorizationParams, - generateAuthorizationUrl, -} from "matrix-js-sdk/src/oidc/authorize"; - -import { ValidatedDelegatedAuthConfig } from "../ValidatedServerConfig"; - -/** - * Store authorization params for retrieval when returning from OIDC OP - * @param authorizationParams from `generateAuthorizationParams` - * @param delegatedAuthConfig used for future interactions with OP - * @param clientId this client's id as registered with configured issuer - * @param homeserver target homeserver - */ -const storeAuthorizationParams = ( - { redirectUri, state, nonce, codeVerifier }: AuthorizationParams, - { issuer }: ValidatedDelegatedAuthConfig, - clientId: string, - homeserver: string, -): void => { - window.sessionStorage.setItem(`oidc_${state}_nonce`, nonce); - window.sessionStorage.setItem(`oidc_${state}_redirectUri`, redirectUri); - window.sessionStorage.setItem(`oidc_${state}_codeVerifier`, codeVerifier); - window.sessionStorage.setItem(`oidc_${state}_clientId`, clientId); - window.sessionStorage.setItem(`oidc_${state}_issuer`, issuer); - window.sessionStorage.setItem(`oidc_${state}_homeserver`, homeserver); -}; +import { OidcClientConfig } from "matrix-js-sdk/src/autodiscovery"; +import { generateOidcAuthorizationUrl } from "matrix-js-sdk/src/oidc/authorize"; +import { randomString } from "matrix-js-sdk/src/randomstring"; /** * Start OIDC authorization code flow @@ -49,25 +24,28 @@ const storeAuthorizationParams = ( * Navigates to configured authorization endpoint * @param delegatedAuthConfig from discovery * @param clientId this client's id as registered with configured issuer - * @param homeserver target homeserver + * @param homeserverUrl target homeserver + * @param identityServerUrl OPTIONAL target identity server * @returns Promise that resolves after we have navigated to auth endpoint */ export const startOidcLogin = async ( - delegatedAuthConfig: ValidatedDelegatedAuthConfig, + delegatedAuthConfig: OidcClientConfig, clientId: string, - homeserver: string, + homeserverUrl: string, + identityServerUrl?: string, ): Promise => { - // TODO(kerrya) afterloginfragment https://github.com/vector-im/element-web/issues/25656 const redirectUri = window.location.origin; - const authParams = generateAuthorizationParams({ redirectUri }); - storeAuthorizationParams(authParams, delegatedAuthConfig, clientId, homeserver); + const nonce = randomString(10); - const authorizationUrl = await generateAuthorizationUrl( - delegatedAuthConfig.authorizationEndpoint, + const authorizationUrl = await generateOidcAuthorizationUrl({ + metadata: delegatedAuthConfig.metadata, + redirectUri, clientId, - authParams, - ); + homeserverUrl, + identityServerUrl, + nonce, + }); window.location.href = authorizationUrl; }; diff --git a/test/test-utils/oidc.ts b/test/test-utils/oidc.ts new file mode 100644 index 00000000000..aa042eba5d4 --- /dev/null +++ b/test/test-utils/oidc.ts @@ -0,0 +1,53 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { OidcClientConfig } from "matrix-js-sdk/src/autodiscovery"; +import { ValidatedIssuerMetadata } from "matrix-js-sdk/src/oidc/validate"; + +/** + * Makes a valid OidcClientConfig with minimum valid values + * @param issuer used as the base for all other urls + * @returns OidcClientConfig + */ +export const makeDelegatedAuthConfig = (issuer = "https://auth.org/"): OidcClientConfig => { + const metadata = mockOpenIdConfiguration(issuer); + + return { + issuer, + account: issuer + "account", + registrationEndpoint: metadata.registration_endpoint, + authorizationEndpoint: metadata.authorization_endpoint, + tokenEndpoint: metadata.token_endpoint, + metadata, + }; +}; + +/** + * Useful for mocking /.well-known/openid-configuration + * @param issuer used as the base for all other urls + * @returns ValidatedIssuerMetadata + */ +export const mockOpenIdConfiguration = (issuer = "https://auth.org/"): ValidatedIssuerMetadata => ({ + issuer, + revocation_endpoint: issuer + "revoke", + token_endpoint: issuer + "token", + authorization_endpoint: issuer + "auth", + registration_endpoint: issuer + "registration", + jwks_uri: issuer + "jwks", + response_types_supported: ["code"], + grant_types_supported: ["authorization_code", "refresh_token"], + code_challenge_methods_supported: ["S256"], +}); diff --git a/test/utils/oidc/authorize-test.ts b/test/utils/oidc/authorize-test.ts index 5abdb19862a..3dda4da2e9f 100644 --- a/test/utils/oidc/authorize-test.ts +++ b/test/utils/oidc/authorize-test.ts @@ -18,23 +18,17 @@ import fetchMockJest from "fetch-mock-jest"; import * as randomStringUtils from "matrix-js-sdk/src/randomstring"; import { startOidcLogin } from "../../../src/utils/oidc/authorize"; +import { makeDelegatedAuthConfig, mockOpenIdConfiguration } from "../../test-utils/oidc"; describe("startOidcLogin()", () => { const issuer = "https://auth.com/"; - const authorizationEndpoint = "https://auth.com/authorization"; const homeserver = "https://matrix.org"; const clientId = "xyz789"; const baseUrl = "https://test.com"; - const delegatedAuthConfig = { - issuer, - registrationEndpoint: issuer + "registration", - authorizationEndpoint, - tokenEndpoint: issuer + "token", - }; + const delegatedAuthConfig = makeDelegatedAuthConfig(issuer); const sessionStorageGetSpy = jest.spyOn(sessionStorage.__proto__, "setItem").mockReturnValue(undefined); - const randomStringMockImpl = (length: number) => new Array(length).fill("x").join(""); // to restore later const realWindowLocation = window.location; @@ -53,6 +47,10 @@ describe("startOidcLogin()", () => { origin: baseUrl, }; + fetchMockJest.get( + delegatedAuthConfig.metadata.issuer + ".well-known/openid-configuration", + mockOpenIdConfiguration(), + ); jest.spyOn(randomStringUtils, "randomString").mockRestore(); }); @@ -60,23 +58,6 @@ describe("startOidcLogin()", () => { window.location = realWindowLocation; }); - it("should store authorization params in session storage", async () => { - jest.spyOn(randomStringUtils, "randomString").mockReset().mockImplementation(randomStringMockImpl); - await startOidcLogin(delegatedAuthConfig, clientId, homeserver); - - const state = randomStringUtils.randomString(8); - - expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_nonce`, randomStringUtils.randomString(8)); - expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_redirectUri`, baseUrl); - expect(sessionStorageGetSpy).toHaveBeenCalledWith( - `oidc_${state}_codeVerifier`, - randomStringUtils.randomString(64), - ); - expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_clientId`, clientId); - expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_issuer`, issuer); - expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_homeserver`, homeserver); - }); - it("navigates to authorization endpoint with correct parameters", async () => { await startOidcLogin(delegatedAuthConfig, clientId, homeserver);