mirror of
https://github.com/element-hq/element-web.git
synced 2025-12-15 02:00:24 +00:00
Compare commits
7 Commits
hs/better-
...
t3chguy/oi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e36c24f94 | ||
|
|
8c4c94bc92 | ||
|
|
053003c717 | ||
|
|
e4dd805939 | ||
|
|
c20d8dce70 | ||
|
|
709f63dd86 | ||
|
|
0830c878f7 |
@@ -31,8 +31,7 @@ export function shouldShowQr(
|
|||||||
): boolean {
|
): boolean {
|
||||||
const msc4108Supported = !!versions?.unstable_features?.["org.matrix.msc4108"];
|
const msc4108Supported = !!versions?.unstable_features?.["org.matrix.msc4108"];
|
||||||
|
|
||||||
const deviceAuthorizationGrantSupported =
|
const deviceAuthorizationGrantSupported = oidcClientConfig?.grant_types_supported.includes(DEVICE_CODE_SCOPE);
|
||||||
oidcClientConfig?.metadata?.grant_types_supported.includes(DEVICE_CODE_SCOPE);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!!deviceAuthorizationGrantSupported &&
|
!!deviceAuthorizationGrantSupported &&
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { lazy, Suspense, useCallback, useContext, useEffect, useRef, useState } from "react";
|
import React, { lazy, Suspense, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { discoverAndValidateOIDCIssuerWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { defer } from "matrix-js-sdk/src/utils";
|
import { defer } from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
@@ -163,10 +163,7 @@ const SessionManagerTab: React.FC<{
|
|||||||
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
||||||
const oidcClientConfig = useAsyncMemo(async () => {
|
const oidcClientConfig = useAsyncMemo(async () => {
|
||||||
try {
|
try {
|
||||||
const authIssuer = await matrixClient?.getAuthIssuer();
|
return await matrixClient?.getAuthMetadata();
|
||||||
if (authIssuer) {
|
|
||||||
return discoverAndValidateOIDCIssuerWellKnown(authIssuer.issuer);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Failed to discover OIDC metadata", e);
|
logger.error("Failed to discover OIDC metadata", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,11 +50,8 @@ export class OidcClientStore {
|
|||||||
} else {
|
} else {
|
||||||
// We are not in OIDC Native mode, as we have no locally stored issuer. Check if the server delegates auth to OIDC.
|
// We are not in OIDC Native mode, as we have no locally stored issuer. Check if the server delegates auth to OIDC.
|
||||||
try {
|
try {
|
||||||
const authIssuer = await this.matrixClient.getAuthIssuer();
|
const authMetadata = await this.matrixClient.getAuthMetadata();
|
||||||
const { accountManagementEndpoint, metadata } = await discoverAndValidateOIDCIssuerWellKnown(
|
this.setAccountManagementEndpoint(authMetadata.account_management_uri, authMetadata.issuer);
|
||||||
authIssuer.issuer,
|
|
||||||
);
|
|
||||||
this.setAccountManagementEndpoint(accountManagementEndpoint, metadata.issuer);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Auth issuer not found", e);
|
console.log("Auth issuer not found", e);
|
||||||
}
|
}
|
||||||
@@ -153,14 +150,11 @@ export class OidcClientStore {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const clientId = getStoredOidcClientId();
|
const clientId = getStoredOidcClientId();
|
||||||
const { accountManagementEndpoint, metadata, signingKeys } = await discoverAndValidateOIDCIssuerWellKnown(
|
const authMetadata = await discoverAndValidateOIDCIssuerWellKnown(this.authenticatedIssuer);
|
||||||
this.authenticatedIssuer,
|
this.setAccountManagementEndpoint(authMetadata.account_management_uri, authMetadata.issuer);
|
||||||
);
|
|
||||||
this.setAccountManagementEndpoint(accountManagementEndpoint, metadata.issuer);
|
|
||||||
this.oidcClient = new OidcClient({
|
this.oidcClient = new OidcClient({
|
||||||
...metadata,
|
authority: authMetadata.issuer,
|
||||||
authority: metadata.issuer,
|
signingKeys: authMetadata.signingKeys ?? undefined,
|
||||||
signingKeys,
|
|
||||||
redirect_uri: PlatformPeg.get()!.getOidcCallbackUrl().href,
|
redirect_uri: PlatformPeg.get()!.getOidcCallbackUrl().href,
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
AutoDiscovery,
|
AutoDiscovery,
|
||||||
AutoDiscoveryError,
|
AutoDiscoveryError,
|
||||||
ClientConfig,
|
ClientConfig,
|
||||||
discoverAndValidateOIDCIssuerWellKnown,
|
|
||||||
IClientWellKnown,
|
IClientWellKnown,
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
MatrixError,
|
MatrixError,
|
||||||
@@ -293,8 +292,7 @@ export default class AutoDiscoveryUtils {
|
|||||||
let delegatedAuthenticationError: Error | undefined;
|
let delegatedAuthenticationError: Error | undefined;
|
||||||
try {
|
try {
|
||||||
const tempClient = new MatrixClient({ baseUrl: preferredHomeserverUrl });
|
const tempClient = new MatrixClient({ baseUrl: preferredHomeserverUrl });
|
||||||
const { issuer } = await tempClient.getAuthIssuer();
|
delegatedAuthentication = await tempClient.getAuthMetadata();
|
||||||
delegatedAuthentication = await discoverAndValidateOIDCIssuerWellKnown(issuer);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof MatrixError && e.httpStatus === 404 && e.errcode === "M_UNRECOGNIZED") {
|
if (e instanceof MatrixError && e.httpStatus === 404 && e.errcode === "M_UNRECOGNIZED") {
|
||||||
// 404 M_UNRECOGNIZED means the server does not support OIDC
|
// 404 M_UNRECOGNIZED means the server does not support OIDC
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const startOidcLogin = async (
|
|||||||
const prompt = isRegistration ? "create" : undefined;
|
const prompt = isRegistration ? "create" : undefined;
|
||||||
|
|
||||||
const authorizationUrl = await generateOidcAuthorizationUrl({
|
const authorizationUrl = await generateOidcAuthorizationUrl({
|
||||||
metadata: delegatedAuthConfig.metadata,
|
metadata: delegatedAuthConfig,
|
||||||
redirectUri,
|
redirectUri,
|
||||||
clientId,
|
clientId,
|
||||||
homeserverUrl,
|
homeserverUrl,
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
|
|||||||
* @returns whether user registration is supported
|
* @returns whether user registration is supported
|
||||||
*/
|
*/
|
||||||
export const isUserRegistrationSupported = (delegatedAuthConfig: OidcClientConfig): boolean => {
|
export const isUserRegistrationSupported = (delegatedAuthConfig: OidcClientConfig): boolean => {
|
||||||
// The OidcMetadata type from oidc-client-ts does not include `prompt_values_supported`
|
const supportedPrompts = delegatedAuthConfig.prompt_values_supported;
|
||||||
// even though it is part of the OIDC spec, so cheat TS here to access it
|
|
||||||
const supportedPrompts = (delegatedAuthConfig.metadata as Record<string, unknown>)["prompt_values_supported"];
|
|
||||||
return Array.isArray(supportedPrompts) && supportedPrompts?.includes("create");
|
return Array.isArray(supportedPrompts) && supportedPrompts?.includes("create");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ export const getOidcClientId = async (
|
|||||||
delegatedAuthConfig: OidcClientConfig,
|
delegatedAuthConfig: OidcClientConfig,
|
||||||
staticOidcClients?: IConfigOptions["oidc_static_clients"],
|
staticOidcClients?: IConfigOptions["oidc_static_clients"],
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const staticClientId = getStaticOidcClientId(delegatedAuthConfig.metadata.issuer, staticOidcClients);
|
const staticClientId = getStaticOidcClientId(delegatedAuthConfig.issuer, staticOidcClients);
|
||||||
if (staticClientId) {
|
if (staticClientId) {
|
||||||
logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.metadata.issuer}`);
|
logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.issuer}`);
|
||||||
return staticClientId;
|
return staticClientId;
|
||||||
}
|
}
|
||||||
return await registerOidcClient(delegatedAuthConfig, await PlatformPeg.get()!.getOidcClientMetadata());
|
return await registerOidcClient(delegatedAuthConfig, await PlatformPeg.get()!.getOidcClientMetadata());
|
||||||
|
|||||||
@@ -6,41 +6,4 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
|
export { makeDelegatedAuthConfig, mockOpenIdConfiguration } from "matrix-js-sdk/src/testing";
|
||||||
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 {
|
|
||||||
accountManagementEndpoint: issuer + "account",
|
|
||||||
registrationEndpoint: metadata.registration_endpoint,
|
|
||||||
authorizationEndpoint: metadata.authorization_endpoint,
|
|
||||||
tokenEndpoint: metadata.token_endpoint,
|
|
||||||
metadata,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Useful for mocking <issuer>/.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",
|
|
||||||
device_authorization_endpoint: issuer + "device",
|
|
||||||
jwks_uri: issuer + "jwks",
|
|
||||||
response_types_supported: ["code"],
|
|
||||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
||||||
code_challenge_methods_supported: ["S256"],
|
|
||||||
account_management_uri: issuer + "account",
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -749,11 +749,8 @@ describe("Lifecycle", () => {
|
|||||||
"eyJhbGciOiJSUzI1NiIsImtpZCI6Imh4ZEhXb0Y5bW4ifQ.eyJzdWIiOiIwMUhQUDJGU0JZREU5UDlFTU04REQ3V1pIUiIsImlzcyI6Imh0dHBzOi8vYXV0aC1vaWRjLmxhYi5lbGVtZW50LmRldi8iLCJpYXQiOjE3MTUwNzE5ODUsImF1dGhfdGltZSI6MTcwNzk5MDMxMiwiY19oYXNoIjoidGt5R1RhUjU5aTk3YXoyTU4yMGdidyIsImV4cCI6MTcxNTA3NTU4NSwibm9uY2UiOiJxaXhwM0hFMmVaIiwiYXVkIjoiMDFIWDk0Mlg3QTg3REgxRUs2UDRaNjI4WEciLCJhdF9oYXNoIjoiNFlFUjdPRlVKTmRTeEVHV2hJUDlnZyJ9.HxODneXvSTfWB5Vc4cf7b8GiN2gdwUuTiyVqZuupWske2HkZiJZUt5Lsxg9BW3gz28POkE0Ln17snlkmy02B_AD3DQxKOOxQCzIIARHdfFvZxgGWsMdFcVQZDW7rtXcqgj-SpVaUQ_8acsgxSrz_DF2o0O4tto0PT6wVUiw8KlBmgWTscWPeAWe-39T-8EiQ8Wi16h6oSPcz2NzOQ7eOM_S9fDkOorgcBkRGLl1nrahrPSdWJSGAeruk5mX4YxN714YThFDyEA2t9YmKpjaiSQ2tT-Xkd7tgsZqeirNs2ni9mIiFX3bRX6t2AhUNzA7MaX9ZyizKGa6go3BESO_oDg";
|
"eyJhbGciOiJSUzI1NiIsImtpZCI6Imh4ZEhXb0Y5bW4ifQ.eyJzdWIiOiIwMUhQUDJGU0JZREU5UDlFTU04REQ3V1pIUiIsImlzcyI6Imh0dHBzOi8vYXV0aC1vaWRjLmxhYi5lbGVtZW50LmRldi8iLCJpYXQiOjE3MTUwNzE5ODUsImF1dGhfdGltZSI6MTcwNzk5MDMxMiwiY19oYXNoIjoidGt5R1RhUjU5aTk3YXoyTU4yMGdidyIsImV4cCI6MTcxNTA3NTU4NSwibm9uY2UiOiJxaXhwM0hFMmVaIiwiYXVkIjoiMDFIWDk0Mlg3QTg3REgxRUs2UDRaNjI4WEciLCJhdF9oYXNoIjoiNFlFUjdPRlVKTmRTeEVHV2hJUDlnZyJ9.HxODneXvSTfWB5Vc4cf7b8GiN2gdwUuTiyVqZuupWske2HkZiJZUt5Lsxg9BW3gz28POkE0Ln17snlkmy02B_AD3DQxKOOxQCzIIARHdfFvZxgGWsMdFcVQZDW7rtXcqgj-SpVaUQ_8acsgxSrz_DF2o0O4tto0PT6wVUiw8KlBmgWTscWPeAWe-39T-8EiQ8Wi16h6oSPcz2NzOQ7eOM_S9fDkOorgcBkRGLl1nrahrPSdWJSGAeruk5mX4YxN714YThFDyEA2t9YmKpjaiSQ2tT-Xkd7tgsZqeirNs2ni9mIiFX3bRX6t2AhUNzA7MaX9ZyizKGa6go3BESO_oDg";
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
fetchMock.get(
|
fetchMock.get(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`, delegatedAuthConfig);
|
||||||
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
|
fetchMock.get(`${delegatedAuthConfig.issuer}jwks`, {
|
||||||
delegatedAuthConfig.metadata,
|
|
||||||
);
|
|
||||||
fetchMock.get(`${delegatedAuthConfig.metadata.issuer}jwks`, {
|
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -772,9 +769,7 @@ describe("Lifecycle", () => {
|
|||||||
await setLoggedIn(credentials);
|
await setLoggedIn(credentials);
|
||||||
|
|
||||||
// didn't try to initialise token refresher
|
// didn't try to initialise token refresher
|
||||||
expect(fetchMock).not.toHaveFetched(
|
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
|
||||||
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not try to create a token refresher without a deviceId", async () => {
|
it("should not try to create a token refresher without a deviceId", async () => {
|
||||||
@@ -785,9 +780,7 @@ describe("Lifecycle", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// didn't try to initialise token refresher
|
// didn't try to initialise token refresher
|
||||||
expect(fetchMock).not.toHaveFetched(
|
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
|
||||||
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not try to create a token refresher without an issuer in session storage", async () => {
|
it("should not try to create a token refresher without an issuer in session storage", async () => {
|
||||||
@@ -803,9 +796,7 @@ describe("Lifecycle", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// didn't try to initialise token refresher
|
// didn't try to initialise token refresher
|
||||||
expect(fetchMock).not.toHaveFetched(
|
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
|
||||||
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create a client with a tokenRefreshFunction", async () => {
|
it("should create a client with a tokenRefreshFunction", async () => {
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ describe("Login", function () {
|
|||||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||||
|
|
||||||
// didn't try to register
|
// didn't try to register
|
||||||
expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registrationEndpoint);
|
expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registration_endpoint);
|
||||||
// continued with normal setup
|
// continued with normal setup
|
||||||
expect(mockClient.loginFlows).toHaveBeenCalled();
|
expect(mockClient.loginFlows).toHaveBeenCalled();
|
||||||
// normal password login rendered
|
// normal password login rendered
|
||||||
@@ -394,25 +394,25 @@ describe("Login", function () {
|
|||||||
it("should attempt to register oidc client", async () => {
|
it("should attempt to register oidc client", async () => {
|
||||||
// dont mock, spy so we can check config values were correctly passed
|
// dont mock, spy so we can check config values were correctly passed
|
||||||
jest.spyOn(registerClientUtils, "getOidcClientId");
|
jest.spyOn(registerClientUtils, "getOidcClientId");
|
||||||
fetchMock.post(delegatedAuth.registrationEndpoint!, { status: 500 });
|
fetchMock.post(delegatedAuth.registration_endpoint!, { status: 500 });
|
||||||
getComponent(hsUrl, isUrl, delegatedAuth);
|
getComponent(hsUrl, isUrl, delegatedAuth);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||||
|
|
||||||
// tried to register
|
// tried to register
|
||||||
expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registrationEndpoint, expect.any(Object));
|
expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registration_endpoint, expect.any(Object));
|
||||||
// called with values from config
|
// called with values from config
|
||||||
expect(registerClientUtils.getOidcClientId).toHaveBeenCalledWith(delegatedAuth, oidcStaticClientsConfig);
|
expect(registerClientUtils.getOidcClientId).toHaveBeenCalledWith(delegatedAuth, oidcStaticClientsConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fallback to normal login when client registration fails", async () => {
|
it("should fallback to normal login when client registration fails", async () => {
|
||||||
fetchMock.post(delegatedAuth.registrationEndpoint!, { status: 500 });
|
fetchMock.post(delegatedAuth.registration_endpoint!, { status: 500 });
|
||||||
getComponent(hsUrl, isUrl, delegatedAuth);
|
getComponent(hsUrl, isUrl, delegatedAuth);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||||
|
|
||||||
// tried to register
|
// tried to register
|
||||||
expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registrationEndpoint, expect.any(Object));
|
expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registration_endpoint, expect.any(Object));
|
||||||
expect(logger.error).toHaveBeenCalledWith(new Error(OidcError.DynamicRegistrationFailed));
|
expect(logger.error).toHaveBeenCalledWith(new Error(OidcError.DynamicRegistrationFailed));
|
||||||
|
|
||||||
// continued with normal setup
|
// continued with normal setup
|
||||||
@@ -423,7 +423,7 @@ describe("Login", function () {
|
|||||||
|
|
||||||
// short term during active development, UI will be added in next PRs
|
// short term during active development, UI will be added in next PRs
|
||||||
it("should show continue button when oidc native flow is correctly configured", async () => {
|
it("should show continue button when oidc native flow is correctly configured", async () => {
|
||||||
fetchMock.post(delegatedAuth.registrationEndpoint!, { client_id: "abc123" });
|
fetchMock.post(delegatedAuth.registration_endpoint!, { client_id: "abc123" });
|
||||||
getComponent(hsUrl, isUrl, delegatedAuth);
|
getComponent(hsUrl, isUrl, delegatedAuth);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||||
@@ -455,7 +455,7 @@ describe("Login", function () {
|
|||||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||||
|
|
||||||
// didn't try to register
|
// didn't try to register
|
||||||
expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registrationEndpoint);
|
expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registration_endpoint);
|
||||||
// continued with normal setup
|
// continued with normal setup
|
||||||
expect(mockClient.loginFlows).toHaveBeenCalled();
|
expect(mockClient.loginFlows).toHaveBeenCalled();
|
||||||
// oidc-aware 'continue' button displayed
|
// oidc-aware 'continue' button displayed
|
||||||
|
|||||||
@@ -158,24 +158,26 @@ describe("Registration", function () {
|
|||||||
describe("when delegated authentication is configured and enabled", () => {
|
describe("when delegated authentication is configured and enabled", () => {
|
||||||
const authConfig = makeDelegatedAuthConfig();
|
const authConfig = makeDelegatedAuthConfig();
|
||||||
const clientId = "test-client-id";
|
const clientId = "test-client-id";
|
||||||
// @ts-ignore
|
authConfig.prompt_values_supported = ["create"];
|
||||||
authConfig.metadata["prompt_values_supported"] = ["create"];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// mock a statically registered client to avoid dynamic registration
|
// mock a statically registered client to avoid dynamic registration
|
||||||
SdkConfig.put({
|
SdkConfig.put({
|
||||||
oidc_static_clients: {
|
oidc_static_clients: {
|
||||||
[authConfig.metadata.issuer]: {
|
[authConfig.issuer]: {
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchMock.get(`${defaultHsUrl}/_matrix/client/unstable/org.matrix.msc2965/auth_issuer`, {
|
fetchMock.get(`${defaultHsUrl}/_matrix/client/unstable/org.matrix.msc2965/auth_issuer`, {
|
||||||
issuer: authConfig.metadata.issuer,
|
issuer: authConfig.issuer,
|
||||||
});
|
});
|
||||||
fetchMock.get("https://auth.org/.well-known/openid-configuration", authConfig.metadata);
|
fetchMock.get("https://auth.org/.well-known/openid-configuration", {
|
||||||
fetchMock.get(authConfig.metadata.jwks_uri!, { keys: [] });
|
...authConfig,
|
||||||
|
signingKeys: undefined,
|
||||||
|
});
|
||||||
|
fetchMock.get(authConfig.jwks_uri!, { keys: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display oidc-native continue button", async () => {
|
it("should display oidc-native continue button", async () => {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ import SettingsStore from "../../../../../../../src/settings/SettingsStore";
|
|||||||
import { getClientInformationEventType } from "../../../../../../../src/utils/device/clientInformation";
|
import { getClientInformationEventType } from "../../../../../../../src/utils/device/clientInformation";
|
||||||
import { SDKContext, SdkContextClass } from "../../../../../../../src/contexts/SDKContext";
|
import { SDKContext, SdkContextClass } from "../../../../../../../src/contexts/SDKContext";
|
||||||
import { OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
|
import { OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
|
||||||
import { mockOpenIdConfiguration } from "../../../../../../test-utils/oidc";
|
import { makeDelegatedAuthConfig } from "../../../../../../test-utils/oidc";
|
||||||
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
|
||||||
|
|
||||||
mockPlatformPeg();
|
mockPlatformPeg();
|
||||||
@@ -215,7 +215,7 @@ describe("<SessionManagerTab />", () => {
|
|||||||
getPushers: jest.fn(),
|
getPushers: jest.fn(),
|
||||||
setPusher: jest.fn(),
|
setPusher: jest.fn(),
|
||||||
setLocalNotificationSettings: jest.fn(),
|
setLocalNotificationSettings: jest.fn(),
|
||||||
getAuthIssuer: jest.fn().mockReturnValue(new Promise(() => {})),
|
getAuthMetadata: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_UNRECOGNIZED" }, 404)),
|
||||||
});
|
});
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
jest.spyOn(logger, "error").mockRestore();
|
jest.spyOn(logger, "error").mockRestore();
|
||||||
@@ -1615,7 +1615,6 @@ describe("<SessionManagerTab />", () => {
|
|||||||
describe("MSC4108 QR code login", () => {
|
describe("MSC4108 QR code login", () => {
|
||||||
const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
|
const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
|
||||||
const issuer = "https://issuer.org";
|
const issuer = "https://issuer.org";
|
||||||
const openIdConfiguration = mockOpenIdConfiguration(issuer);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
settingsValueSpy.mockClear().mockReturnValue(true);
|
settingsValueSpy.mockClear().mockReturnValue(true);
|
||||||
@@ -1631,16 +1630,16 @@ describe("<SessionManagerTab />", () => {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
mockClient.getAuthIssuer.mockResolvedValue({ issuer });
|
const delegatedAuthConfig = makeDelegatedAuthConfig(issuer);
|
||||||
mockCrypto.exportSecretsBundle = jest.fn();
|
mockClient.getAuthMetadata.mockResolvedValue({
|
||||||
fetchMock.mock(`${issuer}/.well-known/openid-configuration`, {
|
...delegatedAuthConfig,
|
||||||
...openIdConfiguration,
|
|
||||||
grant_types_supported: [
|
grant_types_supported: [
|
||||||
...openIdConfiguration.grant_types_supported,
|
...delegatedAuthConfig.grant_types_supported,
|
||||||
"urn:ietf:params:oauth:grant-type:device_code",
|
"urn:ietf:params:oauth:grant-type:device_code",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
fetchMock.mock(openIdConfiguration.jwks_uri!, {
|
mockCrypto.exportSecretsBundle = jest.fn();
|
||||||
|
fetchMock.mock(delegatedAuthConfig.jwks_uri!, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { OidcError } from "matrix-js-sdk/src/oidc/error";
|
|||||||
|
|
||||||
import { OidcClientStore } from "../../../../src/stores/oidc/OidcClientStore";
|
import { OidcClientStore } from "../../../../src/stores/oidc/OidcClientStore";
|
||||||
import { flushPromises, getMockClientWithEventEmitter, mockPlatformPeg } from "../../../test-utils";
|
import { flushPromises, getMockClientWithEventEmitter, mockPlatformPeg } from "../../../test-utils";
|
||||||
import { mockOpenIdConfiguration } from "../../../test-utils/oidc";
|
import { makeDelegatedAuthConfig } from "../../../test-utils/oidc";
|
||||||
|
|
||||||
jest.mock("matrix-js-sdk/src/matrix", () => ({
|
jest.mock("matrix-js-sdk/src/matrix", () => ({
|
||||||
...jest.requireActual("matrix-js-sdk/src/matrix"),
|
...jest.requireActual("matrix-js-sdk/src/matrix"),
|
||||||
@@ -24,28 +24,30 @@ jest.mock("matrix-js-sdk/src/matrix", () => ({
|
|||||||
|
|
||||||
describe("OidcClientStore", () => {
|
describe("OidcClientStore", () => {
|
||||||
const clientId = "test-client-id";
|
const clientId = "test-client-id";
|
||||||
const metadata = mockOpenIdConfiguration();
|
const authConfig = makeDelegatedAuthConfig();
|
||||||
const account = metadata.issuer + "account";
|
const account = authConfig.issuer + "account";
|
||||||
|
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
getAuthIssuer: jest.fn(),
|
getAuthMetadata: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
localStorage.setItem("mx_oidc_client_id", clientId);
|
localStorage.setItem("mx_oidc_client_id", clientId);
|
||||||
localStorage.setItem("mx_oidc_token_issuer", metadata.issuer);
|
localStorage.setItem("mx_oidc_token_issuer", authConfig.issuer);
|
||||||
|
|
||||||
mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({
|
mocked(discoverAndValidateOIDCIssuerWellKnown)
|
||||||
metadata,
|
.mockClear()
|
||||||
accountManagementEndpoint: account,
|
.mockResolvedValue({
|
||||||
authorizationEndpoint: "authorization-endpoint",
|
...authConfig,
|
||||||
tokenEndpoint: "token-endpoint",
|
account_management_uri: account,
|
||||||
});
|
authorization_endpoint: "authorization-endpoint",
|
||||||
|
token_endpoint: "token-endpoint",
|
||||||
|
});
|
||||||
jest.spyOn(logger, "error").mockClear();
|
jest.spyOn(logger, "error").mockClear();
|
||||||
|
|
||||||
fetchMock.get(`${metadata.issuer}.well-known/openid-configuration`, metadata);
|
fetchMock.get(`${authConfig.issuer}.well-known/openid-configuration`, authConfig);
|
||||||
fetchMock.get(`${metadata.issuer}jwks`, { keys: [] });
|
fetchMock.get(`${authConfig.issuer}jwks`, { keys: [] });
|
||||||
mockPlatformPeg();
|
mockPlatformPeg();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,7 +118,7 @@ describe("OidcClientStore", () => {
|
|||||||
const client = await store.getOidcClient();
|
const client = await store.getOidcClient();
|
||||||
|
|
||||||
expect(client?.settings.client_id).toEqual(clientId);
|
expect(client?.settings.client_id).toEqual(clientId);
|
||||||
expect(client?.settings.authority).toEqual(metadata.issuer);
|
expect(client?.settings.authority).toEqual(authConfig.issuer);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set account management endpoint when configured", async () => {
|
it("should set account management endpoint when configured", async () => {
|
||||||
@@ -129,17 +131,19 @@ describe("OidcClientStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should set account management endpoint to issuer when not configured", async () => {
|
it("should set account management endpoint to issuer when not configured", async () => {
|
||||||
mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({
|
mocked(discoverAndValidateOIDCIssuerWellKnown)
|
||||||
metadata,
|
.mockClear()
|
||||||
accountManagementEndpoint: undefined,
|
.mockResolvedValue({
|
||||||
authorizationEndpoint: "authorization-endpoint",
|
...authConfig,
|
||||||
tokenEndpoint: "token-endpoint",
|
account_management_uri: undefined,
|
||||||
});
|
authorization_endpoint: "authorization-endpoint",
|
||||||
|
token_endpoint: "token-endpoint",
|
||||||
|
});
|
||||||
const store = new OidcClientStore(mockClient);
|
const store = new OidcClientStore(mockClient);
|
||||||
|
|
||||||
await store.readyPromise;
|
await store.readyPromise;
|
||||||
|
|
||||||
expect(store.accountManagementEndpoint).toEqual(metadata.issuer);
|
expect(store.accountManagementEndpoint).toEqual(authConfig.issuer);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reuse initialised oidc client", async () => {
|
it("should reuse initialised oidc client", async () => {
|
||||||
@@ -175,7 +179,7 @@ describe("OidcClientStore", () => {
|
|||||||
|
|
||||||
fetchMock.resetHistory();
|
fetchMock.resetHistory();
|
||||||
fetchMock.post(
|
fetchMock.post(
|
||||||
metadata.revocation_endpoint,
|
authConfig.revocation_endpoint,
|
||||||
{
|
{
|
||||||
status: 200,
|
status: 200,
|
||||||
},
|
},
|
||||||
@@ -197,7 +201,7 @@ describe("OidcClientStore", () => {
|
|||||||
|
|
||||||
await store.revokeTokens(accessToken, refreshToken);
|
await store.revokeTokens(accessToken, refreshToken);
|
||||||
|
|
||||||
expect(fetchMock).toHaveFetchedTimes(2, metadata.revocation_endpoint);
|
expect(fetchMock).toHaveFetchedTimes(2, authConfig.revocation_endpoint);
|
||||||
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
|
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
|
||||||
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(refreshToken, "refresh_token");
|
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(refreshToken, "refresh_token");
|
||||||
});
|
});
|
||||||
@@ -206,14 +210,14 @@ describe("OidcClientStore", () => {
|
|||||||
// fail once, then succeed
|
// fail once, then succeed
|
||||||
fetchMock
|
fetchMock
|
||||||
.postOnce(
|
.postOnce(
|
||||||
metadata.revocation_endpoint,
|
authConfig.revocation_endpoint,
|
||||||
{
|
{
|
||||||
status: 404,
|
status: 404,
|
||||||
},
|
},
|
||||||
{ overwriteRoutes: true, sendAsJson: true },
|
{ overwriteRoutes: true, sendAsJson: true },
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
metadata.revocation_endpoint,
|
authConfig.revocation_endpoint,
|
||||||
{
|
{
|
||||||
status: 200,
|
status: 200,
|
||||||
},
|
},
|
||||||
@@ -226,7 +230,7 @@ describe("OidcClientStore", () => {
|
|||||||
"Failed to revoke tokens",
|
"Failed to revoke tokens",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchMock).toHaveFetchedTimes(2, metadata.revocation_endpoint);
|
expect(fetchMock).toHaveFetchedTimes(2, authConfig.revocation_endpoint);
|
||||||
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
|
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -237,7 +241,10 @@ describe("OidcClientStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should resolve account management endpoint", async () => {
|
it("should resolve account management endpoint", async () => {
|
||||||
mockClient.getAuthIssuer.mockResolvedValue({ issuer: metadata.issuer });
|
mockClient.getAuthMetadata.mockResolvedValue({
|
||||||
|
...authConfig,
|
||||||
|
account_management_uri: account,
|
||||||
|
});
|
||||||
const store = new OidcClientStore(mockClient);
|
const store = new OidcClientStore(mockClient);
|
||||||
await store.readyPromise;
|
await store.readyPromise;
|
||||||
expect(store.accountManagementEndpoint).toBe(account);
|
expect(store.accountManagementEndpoint).toBe(account);
|
||||||
|
|||||||
@@ -355,21 +355,19 @@ describe("AutoDiscoveryUtils", () => {
|
|||||||
hsNameIsDifferent: true,
|
hsNameIsDifferent: true,
|
||||||
hsName: serverName,
|
hsName: serverName,
|
||||||
delegatedAuthentication: expect.objectContaining({
|
delegatedAuthentication: expect.objectContaining({
|
||||||
accountManagementActionsSupported: [
|
issuer,
|
||||||
|
account_management_actions_supported: [
|
||||||
"org.matrix.profile",
|
"org.matrix.profile",
|
||||||
"org.matrix.sessions_list",
|
"org.matrix.sessions_list",
|
||||||
"org.matrix.session_view",
|
"org.matrix.session_view",
|
||||||
"org.matrix.session_end",
|
"org.matrix.session_end",
|
||||||
"org.matrix.cross_signing_reset",
|
"org.matrix.cross_signing_reset",
|
||||||
],
|
],
|
||||||
accountManagementEndpoint: "https://auth.matrix.org/account/",
|
account_management_uri: "https://auth.matrix.org/account/",
|
||||||
authorizationEndpoint: "https://auth.matrix.org/auth",
|
authorization_endpoint: "https://auth.matrix.org/auth",
|
||||||
metadata: expect.objectContaining({
|
registration_endpoint: "https://auth.matrix.org/registration",
|
||||||
issuer,
|
|
||||||
}),
|
|
||||||
registrationEndpoint: "https://auth.matrix.org/registration",
|
|
||||||
signingKeys: [],
|
signingKeys: [],
|
||||||
tokenEndpoint: "https://auth.matrix.org/token",
|
token_endpoint: "https://auth.matrix.org/token",
|
||||||
}),
|
}),
|
||||||
warning: null,
|
warning: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe("TokenRefresher", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetchMock.get(`${issuer}.well-known/openid-configuration`, authConfig.metadata);
|
fetchMock.get(`${issuer}.well-known/openid-configuration`, authConfig);
|
||||||
fetchMock.get(`${issuer}jwks`, {
|
fetchMock.get(`${issuer}jwks`, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -61,10 +61,7 @@ describe("OIDC authorization", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
fetchMock.get(
|
fetchMock.get(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`, delegatedAuthConfig);
|
||||||
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
|
|
||||||
delegatedAuthConfig.metadata,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ describe("getOidcClientId()", () => {
|
|||||||
const authConfigWithoutRegistration: OidcClientConfig = makeDelegatedAuthConfig(
|
const authConfigWithoutRegistration: OidcClientConfig = makeDelegatedAuthConfig(
|
||||||
"https://issuerWithoutStaticClientId.org/",
|
"https://issuerWithoutStaticClientId.org/",
|
||||||
);
|
);
|
||||||
authConfigWithoutRegistration.registrationEndpoint = undefined;
|
authConfigWithoutRegistration.registration_endpoint = undefined;
|
||||||
await expect(getOidcClientId(authConfigWithoutRegistration, staticOidcClients)).rejects.toThrow(
|
await expect(getOidcClientId(authConfigWithoutRegistration, staticOidcClients)).rejects.toThrow(
|
||||||
OidcError.DynamicRegistrationNotSupported,
|
OidcError.DynamicRegistrationNotSupported,
|
||||||
);
|
);
|
||||||
@@ -69,7 +69,7 @@ describe("getOidcClientId()", () => {
|
|||||||
it("should handle when staticOidcClients object is falsy", async () => {
|
it("should handle when staticOidcClients object is falsy", async () => {
|
||||||
const authConfigWithoutRegistration: OidcClientConfig = {
|
const authConfigWithoutRegistration: OidcClientConfig = {
|
||||||
...delegatedAuthConfig,
|
...delegatedAuthConfig,
|
||||||
registrationEndpoint: undefined,
|
registration_endpoint: undefined,
|
||||||
};
|
};
|
||||||
await expect(getOidcClientId(authConfigWithoutRegistration)).rejects.toThrow(
|
await expect(getOidcClientId(authConfigWithoutRegistration)).rejects.toThrow(
|
||||||
OidcError.DynamicRegistrationNotSupported,
|
OidcError.DynamicRegistrationNotSupported,
|
||||||
@@ -79,14 +79,14 @@ describe("getOidcClientId()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should make correct request to register client", async () => {
|
it("should make correct request to register client", async () => {
|
||||||
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
|
fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: JSON.stringify({ client_id: dynamicClientId }),
|
body: JSON.stringify({ client_id: dynamicClientId }),
|
||||||
});
|
});
|
||||||
expect(await getOidcClientId(delegatedAuthConfig)).toEqual(dynamicClientId);
|
expect(await getOidcClientId(delegatedAuthConfig)).toEqual(dynamicClientId);
|
||||||
// didn't try to register
|
// didn't try to register
|
||||||
expect(fetchMockJest).toHaveBeenCalledWith(
|
expect(fetchMockJest).toHaveBeenCalledWith(
|
||||||
delegatedAuthConfig.registrationEndpoint!,
|
delegatedAuthConfig.registration_endpoint!,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
@@ -111,14 +111,14 @@ describe("getOidcClientId()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should throw when registration request fails", async () => {
|
it("should throw when registration request fails", async () => {
|
||||||
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
|
fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
|
||||||
status: 500,
|
status: 500,
|
||||||
});
|
});
|
||||||
await expect(getOidcClientId(delegatedAuthConfig)).rejects.toThrow(OidcError.DynamicRegistrationFailed);
|
await expect(getOidcClientId(delegatedAuthConfig)).rejects.toThrow(OidcError.DynamicRegistrationFailed);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw when registration response is invalid", async () => {
|
it("should throw when registration response is invalid", async () => {
|
||||||
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
|
fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
|
||||||
status: 200,
|
status: 200,
|
||||||
// no clientId in response
|
// no clientId in response
|
||||||
body: "{}",
|
body: "{}",
|
||||||
|
|||||||
Reference in New Issue
Block a user