Update MSC2965 OIDC Discovery implementation (#12245)
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
parent
729eca49e4
commit
7b1e8e3d2f
19 changed files with 350 additions and 300 deletions
|
@ -795,7 +795,7 @@ async function createOidcTokenRefresher(credentials: IMatrixClientCreds): Promis
|
||||||
throw new Error("Expected deviceId in user credentials.");
|
throw new Error("Expected deviceId in user credentials.");
|
||||||
}
|
}
|
||||||
const tokenRefresher = new TokenRefresher(
|
const tokenRefresher = new TokenRefresher(
|
||||||
{ issuer: tokenIssuer },
|
tokenIssuer,
|
||||||
clientId,
|
clientId,
|
||||||
redirectUri,
|
redirectUri,
|
||||||
deviceId,
|
deviceId,
|
||||||
|
|
|
@ -80,9 +80,6 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
||||||
this.setState({ otherHomeserver: ev.target.value });
|
this.setState({ otherHomeserver: ev.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Do we want to support .well-known lookups here?
|
|
||||||
// If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
|
|
||||||
// find their homeserver without demanding they use "https://matrix.org"
|
|
||||||
private validate = withValidation<this, { error?: string }>({
|
private validate = withValidation<this, { error?: string }>({
|
||||||
deriveData: async ({ value }): Promise<{ error?: string }> => {
|
deriveData: async ({ value }): Promise<{ error?: string }> => {
|
||||||
let hsUrl = (value ?? "").trim(); // trim to account for random whitespace
|
let hsUrl = (value ?? "").trim(); // trim to account for random whitespace
|
||||||
|
@ -91,7 +88,10 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
||||||
if (!hsUrl.includes("://")) {
|
if (!hsUrl.includes("://")) {
|
||||||
try {
|
try {
|
||||||
const discoveryResult = await AutoDiscovery.findClientConfig(hsUrl);
|
const discoveryResult = await AutoDiscovery.findClientConfig(hsUrl);
|
||||||
this.validatedConf = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(hsUrl, discoveryResult);
|
this.validatedConf = await AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(
|
||||||
|
hsUrl,
|
||||||
|
discoveryResult,
|
||||||
|
);
|
||||||
return {}; // we have a validated config, we don't need to try the other paths
|
return {}; // we have a validated config, we don't need to try the other paths
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Attempted ${hsUrl} as a server_name but it failed`, e);
|
logger.error(`Attempted ${hsUrl} as a server_name but it failed`, e);
|
||||||
|
|
|
@ -14,18 +14,23 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDelegatedAuthConfig, MatrixClient, M_AUTHENTICATION } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient, discoverAndValidateOIDCIssuerWellKnown } from "matrix-js-sdk/src/matrix";
|
||||||
import { discoverAndValidateAuthenticationConfig } from "matrix-js-sdk/src/oidc/discovery";
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { OidcClient } from "oidc-client-ts";
|
import { OidcClient } from "oidc-client-ts";
|
||||||
|
|
||||||
import { getStoredOidcTokenIssuer, getStoredOidcClientId } from "../../utils/oidc/persistOidcSettings";
|
import { getStoredOidcTokenIssuer, getStoredOidcClientId } from "../../utils/oidc/persistOidcSettings";
|
||||||
import { getDelegatedAuthAccountUrl } from "../../utils/oidc/getDelegatedAuthAccountUrl";
|
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @experimental
|
||||||
* Stores information about configured OIDC provider
|
* Stores information about configured OIDC provider
|
||||||
|
*
|
||||||
|
* In OIDC Native mode the client is registered with OIDC directly and maintains an OIDC token.
|
||||||
|
*
|
||||||
|
* In OIDC Aware mode, the client is aware that the Server is using OIDC, but is using the standard Matrix APIs for most things.
|
||||||
|
* (Notable exceptions are account management, where a link to the account management endpoint will be provided instead.)
|
||||||
|
*
|
||||||
|
* Otherwise, the store is not operating. Auth is then in Legacy mode and everything uses normal Matrix APIs.
|
||||||
*/
|
*/
|
||||||
export class OidcClientStore {
|
export class OidcClientStore {
|
||||||
private oidcClient?: OidcClient;
|
private oidcClient?: OidcClient;
|
||||||
|
@ -47,8 +52,16 @@ export class OidcClientStore {
|
||||||
if (this.authenticatedIssuer) {
|
if (this.authenticatedIssuer) {
|
||||||
await this.getOidcClient();
|
await this.getOidcClient();
|
||||||
} else {
|
} else {
|
||||||
const wellKnown = await this.matrixClient.waitForClientWellKnown();
|
// We are not in OIDC Native mode, as we have no locally stored issuer. Check if the server delegates auth to OIDC.
|
||||||
this._accountManagementEndpoint = getDelegatedAuthAccountUrl(wellKnown);
|
try {
|
||||||
|
const authIssuer = await this.matrixClient.getAuthIssuer();
|
||||||
|
const { accountManagementEndpoint, metadata } = await discoverAndValidateOIDCIssuerWellKnown(
|
||||||
|
authIssuer.issuer,
|
||||||
|
);
|
||||||
|
this._accountManagementEndpoint = accountManagementEndpoint ?? metadata.issuer;
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Auth issuer not found", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,23 +140,18 @@ export class OidcClientStore {
|
||||||
* @returns promise that resolves when initialising OidcClient succeeds or fails
|
* @returns promise that resolves when initialising OidcClient succeeds or fails
|
||||||
*/
|
*/
|
||||||
private async initOidcClient(): Promise<void> {
|
private async initOidcClient(): Promise<void> {
|
||||||
const wellKnown = await this.matrixClient.waitForClientWellKnown();
|
if (!this.authenticatedIssuer) {
|
||||||
if (!wellKnown && !this.authenticatedIssuer) {
|
|
||||||
logger.error("Cannot initialise OIDC client without issuer.");
|
logger.error("Cannot initialise OIDC client without issuer.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const delegatedAuthConfig =
|
|
||||||
(wellKnown && M_AUTHENTICATION.findIn<IDelegatedAuthConfig>(wellKnown)) ?? undefined;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const clientId = getStoredOidcClientId();
|
const clientId = getStoredOidcClientId();
|
||||||
const { account, metadata, signingKeys } = await discoverAndValidateAuthenticationConfig(
|
const { accountManagementEndpoint, metadata, signingKeys } = await discoverAndValidateOIDCIssuerWellKnown(
|
||||||
// if HS has valid delegated auth config in .well-known, use it
|
this.authenticatedIssuer,
|
||||||
// otherwise fallback to the known issuer
|
|
||||||
delegatedAuthConfig ?? { issuer: this.authenticatedIssuer! },
|
|
||||||
);
|
);
|
||||||
// if no account endpoint is configured default to the issuer
|
// if no account endpoint is configured default to the issuer
|
||||||
this._accountManagementEndpoint = account ?? metadata.issuer;
|
this._accountManagementEndpoint = accountManagementEndpoint ?? metadata.issuer;
|
||||||
this.oidcClient = new OidcClient({
|
this.oidcClient = new OidcClient({
|
||||||
...metadata,
|
...metadata,
|
||||||
authority: metadata.issuer,
|
authority: metadata.issuer,
|
||||||
|
|
|
@ -19,9 +19,11 @@ import {
|
||||||
AutoDiscovery,
|
AutoDiscovery,
|
||||||
AutoDiscoveryError,
|
AutoDiscoveryError,
|
||||||
ClientConfig,
|
ClientConfig,
|
||||||
OidcClientConfig,
|
discoverAndValidateOIDCIssuerWellKnown,
|
||||||
M_AUTHENTICATION,
|
|
||||||
IClientWellKnown,
|
IClientWellKnown,
|
||||||
|
MatrixClient,
|
||||||
|
MatrixError,
|
||||||
|
OidcClientConfig,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
@ -217,12 +219,12 @@ export default class AutoDiscoveryUtils {
|
||||||
* @param {boolean} isSynthetic If true, then the discoveryResult was synthesised locally.
|
* @param {boolean} isSynthetic If true, then the discoveryResult was synthesised locally.
|
||||||
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
|
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
|
||||||
*/
|
*/
|
||||||
public static buildValidatedConfigFromDiscovery(
|
public static async buildValidatedConfigFromDiscovery(
|
||||||
serverName?: string,
|
serverName?: string,
|
||||||
discoveryResult?: ClientConfig,
|
discoveryResult?: ClientConfig,
|
||||||
syntaxOnly = false,
|
syntaxOnly = false,
|
||||||
isSynthetic = false,
|
isSynthetic = false,
|
||||||
): ValidatedServerConfig {
|
): Promise<ValidatedServerConfig> {
|
||||||
if (!discoveryResult?.["m.homeserver"]) {
|
if (!discoveryResult?.["m.homeserver"]) {
|
||||||
// This shouldn't happen without major misconfiguration, so we'll log a bit of information
|
// This shouldn't happen without major misconfiguration, so we'll log a bit of information
|
||||||
// in the log so we can find this bit of code but otherwise tell the user "it broke".
|
// in the log so we can find this bit of code but otherwise tell the user "it broke".
|
||||||
|
@ -293,26 +295,20 @@ export default class AutoDiscoveryUtils {
|
||||||
throw new UserFriendlyError("auth|autodiscovery_unexpected_error_hs");
|
throw new UserFriendlyError("auth|autodiscovery_unexpected_error_hs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This isn't inherently auto-discovery but used to be in an earlier incarnation of the MSC,
|
||||||
|
// and shuttling the data together makes a lot of sense
|
||||||
let delegatedAuthentication: OidcClientConfig | undefined;
|
let delegatedAuthentication: OidcClientConfig | undefined;
|
||||||
if (discoveryResult[M_AUTHENTICATION.stable!]?.state === AutoDiscovery.SUCCESS) {
|
let delegatedAuthenticationError: Error | undefined;
|
||||||
const {
|
try {
|
||||||
authorizationEndpoint,
|
const tempClient = new MatrixClient({ baseUrl: preferredHomeserverUrl });
|
||||||
registrationEndpoint,
|
const { issuer } = await tempClient.getAuthIssuer();
|
||||||
tokenEndpoint,
|
delegatedAuthentication = await discoverAndValidateOIDCIssuerWellKnown(issuer);
|
||||||
account,
|
} catch (e) {
|
||||||
issuer,
|
if (e instanceof MatrixError && e.httpStatus === 404 && e.errcode === "M_UNRECOGNIZED") {
|
||||||
metadata,
|
// 404 M_UNRECOGNIZED means the server does not support OIDC
|
||||||
signingKeys,
|
} else {
|
||||||
} = discoveryResult[M_AUTHENTICATION.stable!] as OidcClientConfig;
|
delegatedAuthenticationError = e as Error;
|
||||||
delegatedAuthentication = Object.freeze({
|
}
|
||||||
authorizationEndpoint,
|
|
||||||
registrationEndpoint,
|
|
||||||
tokenEndpoint,
|
|
||||||
account,
|
|
||||||
issuer,
|
|
||||||
metadata,
|
|
||||||
signingKeys,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -321,7 +317,7 @@ export default class AutoDiscoveryUtils {
|
||||||
hsNameIsDifferent: url.hostname !== preferredHomeserverName,
|
hsNameIsDifferent: url.hostname !== preferredHomeserverName,
|
||||||
isUrl: preferredIdentityUrl,
|
isUrl: preferredIdentityUrl,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
warning: hsResult.error,
|
warning: hsResult.error ?? delegatedAuthenticationError ?? null,
|
||||||
isNameResolvable: !isSynthetic,
|
isNameResolvable: !isSynthetic,
|
||||||
delegatedAuthentication,
|
delegatedAuthentication,
|
||||||
} as ValidatedServerConfig;
|
} as ValidatedServerConfig;
|
||||||
|
|
|
@ -14,10 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { OidcClientConfig, IDelegatedAuthConfig } from "matrix-js-sdk/src/matrix";
|
import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
|
||||||
import { ValidatedIssuerConfig } from "matrix-js-sdk/src/oidc/validate";
|
|
||||||
|
|
||||||
export type ValidatedDelegatedAuthConfig = IDelegatedAuthConfig & ValidatedIssuerConfig;
|
|
||||||
|
|
||||||
export interface ValidatedServerConfig {
|
export interface ValidatedServerConfig {
|
||||||
hsUrl: string;
|
hsUrl: string;
|
||||||
|
@ -34,9 +31,9 @@ export interface ValidatedServerConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config related to delegated authentication
|
* Config related to delegated authentication
|
||||||
* Included when delegated auth is configured and valid, otherwise undefined
|
* Included when delegated auth is configured and valid, otherwise undefined.
|
||||||
* From homeserver .well-known m.authentication, and issuer's .well-known/openid-configuration
|
* From issuer's .well-known/openid-configuration.
|
||||||
* Used for OIDC native flow authentication
|
* Used for OIDC native flow authentication.
|
||||||
*/
|
*/
|
||||||
delegatedAuthentication?: OidcClientConfig;
|
delegatedAuthentication?: OidcClientConfig;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDelegatedAuthConfig, OidcTokenRefresher, AccessTokens } from "matrix-js-sdk/src/matrix";
|
import { OidcTokenRefresher, AccessTokens } from "matrix-js-sdk/src/matrix";
|
||||||
import { IdTokenClaims } from "oidc-client-ts";
|
import { IdTokenClaims } from "oidc-client-ts";
|
||||||
|
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
|
@ -28,14 +28,14 @@ export class TokenRefresher extends OidcTokenRefresher {
|
||||||
private readonly deviceId!: string;
|
private readonly deviceId!: string;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
authConfig: IDelegatedAuthConfig,
|
issuer: string,
|
||||||
clientId: string,
|
clientId: string,
|
||||||
redirectUri: string,
|
redirectUri: string,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
idTokenClaims: IdTokenClaims,
|
idTokenClaims: IdTokenClaims,
|
||||||
private readonly userId: string,
|
private readonly userId: string,
|
||||||
) {
|
) {
|
||||||
super(authConfig, clientId, deviceId, redirectUri, idTokenClaims);
|
super(issuer, clientId, deviceId, redirectUri, idTokenClaims);
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
/*
|
|
||||||
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 { IClientWellKnown, IDelegatedAuthConfig, M_AUTHENTICATION } from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the delegated auth account management url if configured
|
|
||||||
* @param clientWellKnown from MatrixClient.getClientWellKnown
|
|
||||||
* @returns the account management url, or undefined
|
|
||||||
*/
|
|
||||||
export const getDelegatedAuthAccountUrl = (clientWellKnown: IClientWellKnown | undefined): string | undefined => {
|
|
||||||
const delegatedAuthConfig = M_AUTHENTICATION.findIn<IDelegatedAuthConfig | undefined>(clientWellKnown);
|
|
||||||
return delegatedAuthConfig?.account;
|
|
||||||
};
|
|
|
@ -15,10 +15,9 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { registerOidcClient } from "matrix-js-sdk/src/oidc/register";
|
import { registerOidcClient, OidcClientConfig } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { IConfigOptions } from "../../IConfigOptions";
|
import { IConfigOptions } from "../../IConfigOptions";
|
||||||
import { ValidatedDelegatedAuthConfig } from "../ValidatedServerConfig";
|
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,12 +45,12 @@ const getStaticOidcClientId = (
|
||||||
* @throws if no clientId is found
|
* @throws if no clientId is found
|
||||||
*/
|
*/
|
||||||
export const getOidcClientId = async (
|
export const getOidcClientId = async (
|
||||||
delegatedAuthConfig: ValidatedDelegatedAuthConfig,
|
delegatedAuthConfig: OidcClientConfig,
|
||||||
staticOidcClients?: IConfigOptions["oidc_static_clients"],
|
staticOidcClients?: IConfigOptions["oidc_static_clients"],
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const staticClientId = getStaticOidcClientId(delegatedAuthConfig.issuer, staticOidcClients);
|
const staticClientId = getStaticOidcClientId(delegatedAuthConfig.metadata.issuer, staticOidcClients);
|
||||||
if (staticClientId) {
|
if (staticClientId) {
|
||||||
logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.issuer}`);
|
logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.metadata.issuer}`);
|
||||||
return staticClientId;
|
return staticClientId;
|
||||||
}
|
}
|
||||||
return await registerOidcClient(delegatedAuthConfig, await PlatformPeg.get()!.getOidcClientMetadata());
|
return await registerOidcClient(delegatedAuthConfig, await PlatformPeg.get()!.getOidcClientMetadata());
|
||||||
|
|
|
@ -683,10 +683,10 @@ describe("Lifecycle", () => {
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
fetchMock.get(
|
fetchMock.get(
|
||||||
`${delegatedAuthConfig.issuer}.well-known/openid-configuration`,
|
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
|
||||||
delegatedAuthConfig.metadata,
|
delegatedAuthConfig.metadata,
|
||||||
);
|
);
|
||||||
fetchMock.get(`${delegatedAuthConfig.issuer}jwks`, {
|
fetchMock.get(`${delegatedAuthConfig.metadata.issuer}jwks`, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -696,12 +696,6 @@ describe("Lifecycle", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// mock oidc config for oidc client initialisation
|
|
||||||
mockClient.waitForClientWellKnown.mockResolvedValue({
|
|
||||||
"m.authentication": {
|
|
||||||
issuer: issuer,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
initSessionStorageMock();
|
initSessionStorageMock();
|
||||||
// set values in session storage as they would be after a successful oidc authentication
|
// set values in session storage as they would be after a successful oidc authentication
|
||||||
persistOidcAuthenticatedSettings(clientId, issuer, idTokenClaims);
|
persistOidcAuthenticatedSettings(clientId, issuer, idTokenClaims);
|
||||||
|
@ -711,7 +705,9 @@ 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(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
|
expect(fetchMock).not.toHaveFetched(
|
||||||
|
`${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 () => {
|
||||||
|
@ -722,7 +718,9 @@ describe("Lifecycle", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// didn't try to initialise token refresher
|
// didn't try to initialise token refresher
|
||||||
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
|
expect(fetchMock).not.toHaveFetched(
|
||||||
|
`${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 () => {
|
||||||
|
@ -738,7 +736,9 @@ describe("Lifecycle", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// didn't try to initialise token refresher
|
// didn't try to initialise token refresher
|
||||||
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
|
expect(fetchMock).not.toHaveFetched(
|
||||||
|
`${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 () => {
|
||||||
|
|
|
@ -211,6 +211,11 @@ describe("<MatrixChat />", () => {
|
||||||
unstable_features: {},
|
unstable_features: {},
|
||||||
versions: SERVER_SUPPORTED_MATRIX_VERSIONS,
|
versions: SERVER_SUPPORTED_MATRIX_VERSIONS,
|
||||||
});
|
});
|
||||||
|
fetchMock.catch({
|
||||||
|
status: 404,
|
||||||
|
body: '{"errcode": "M_UNRECOGNIZED", "error": "Unrecognized request"}',
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
});
|
||||||
|
|
||||||
jest.spyOn(StorageManager, "idbLoad").mockReset();
|
jest.spyOn(StorageManager, "idbLoad").mockReset();
|
||||||
jest.spyOn(StorageManager, "idbSave").mockResolvedValue(undefined);
|
jest.spyOn(StorageManager, "idbSave").mockResolvedValue(undefined);
|
||||||
|
|
|
@ -37,7 +37,6 @@ jest.mock("matrix-js-sdk/src/matrix", () => ({
|
||||||
...jest.requireActual("matrix-js-sdk/src/matrix"),
|
...jest.requireActual("matrix-js-sdk/src/matrix"),
|
||||||
createClient: jest.fn(),
|
createClient: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.useFakeTimers();
|
|
||||||
|
|
||||||
/** The matrix versions our mock server claims to support */
|
/** The matrix versions our mock server claims to support */
|
||||||
const SERVER_SUPPORTED_MATRIX_VERSIONS = ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9"];
|
const SERVER_SUPPORTED_MATRIX_VERSIONS = ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9"];
|
||||||
|
@ -160,11 +159,17 @@ describe("Registration", function () {
|
||||||
// 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.issuer]: {
|
[authConfig.metadata.issuer]: {
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fetchMock.get(`${defaultHsUrl}/_matrix/client/unstable/org.matrix.msc2965/auth_issuer`, {
|
||||||
|
issuer: authConfig.metadata.issuer,
|
||||||
|
});
|
||||||
|
fetchMock.get("https://auth.org/.well-known/openid-configuration", authConfig.metadata);
|
||||||
|
fetchMock.get(authConfig.metadata.jwks_uri!, { keys: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when oidc native flow is not enabled in settings", () => {
|
describe("when oidc native flow is not enabled in settings", () => {
|
||||||
|
@ -192,14 +197,14 @@ describe("Registration", function () {
|
||||||
// no form
|
// no form
|
||||||
expect(container.querySelector("form")).toBeFalsy();
|
expect(container.querySelector("form")).toBeFalsy();
|
||||||
|
|
||||||
expect(screen.getByText("Continue")).toBeTruthy();
|
expect(await screen.findByText("Continue")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should start OIDC login flow as registration on button click", async () => {
|
it("should start OIDC login flow as registration on button click", async () => {
|
||||||
getComponent(defaultHsUrl, defaultIsUrl, authConfig);
|
getComponent(defaultHsUrl, defaultIsUrl, authConfig);
|
||||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Continue"));
|
fireEvent.click(await screen.findByText("Continue"));
|
||||||
|
|
||||||
expect(startOidcLogin).toHaveBeenCalledWith(
|
expect(startOidcLogin).toHaveBeenCalledWith(
|
||||||
authConfig,
|
authConfig,
|
||||||
|
|
|
@ -64,6 +64,11 @@ describe("<ServerPickerDialog />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchMock.resetHistory();
|
fetchMock.resetHistory();
|
||||||
|
fetchMock.catch({
|
||||||
|
status: 404,
|
||||||
|
body: '{"errcode": "M_UNRECOGNIZED", "error": "Unrecognized request"}',
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render dialog", () => {
|
it("should render dialog", () => {
|
||||||
|
|
|
@ -17,17 +17,17 @@ limitations under the License.
|
||||||
import fetchMock from "fetch-mock-jest";
|
import fetchMock from "fetch-mock-jest";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
import { OidcClient } from "oidc-client-ts";
|
import { OidcClient } from "oidc-client-ts";
|
||||||
import { M_AUTHENTICATION } from "matrix-js-sdk/src/matrix";
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { discoverAndValidateAuthenticationConfig } from "matrix-js-sdk/src/oidc/discovery";
|
import { discoverAndValidateOIDCIssuerWellKnown } from "matrix-js-sdk/src/matrix";
|
||||||
import { OidcError } from "matrix-js-sdk/src/oidc/error";
|
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 { mockOpenIdConfiguration } from "../../test-utils/oidc";
|
||||||
|
|
||||||
jest.mock("matrix-js-sdk/src/oidc/discovery", () => ({
|
jest.mock("matrix-js-sdk/src/matrix", () => ({
|
||||||
discoverAndValidateAuthenticationConfig: jest.fn(),
|
...jest.requireActual("matrix-js-sdk/src/matrix"),
|
||||||
|
discoverAndValidateOIDCIssuerWellKnown: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("OidcClientStore", () => {
|
describe("OidcClientStore", () => {
|
||||||
|
@ -36,7 +36,7 @@ describe("OidcClientStore", () => {
|
||||||
const account = metadata.issuer + "account";
|
const account = metadata.issuer + "account";
|
||||||
|
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
waitForClientWellKnown: jest.fn().mockResolvedValue({}),
|
getAuthIssuer: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -44,20 +44,16 @@ describe("OidcClientStore", () => {
|
||||||
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", metadata.issuer);
|
||||||
|
|
||||||
mocked(discoverAndValidateAuthenticationConfig).mockClear().mockResolvedValue({
|
mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({
|
||||||
metadata,
|
metadata,
|
||||||
account,
|
accountManagementEndpoint: account,
|
||||||
issuer: metadata.issuer,
|
authorizationEndpoint: "authorization-endpoint",
|
||||||
});
|
tokenEndpoint: "token-endpoint",
|
||||||
mockClient.waitForClientWellKnown.mockResolvedValue({
|
|
||||||
[M_AUTHENTICATION.stable!]: {
|
|
||||||
issuer: metadata.issuer,
|
|
||||||
account,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
jest.spyOn(logger, "error").mockClear();
|
jest.spyOn(logger, "error").mockClear();
|
||||||
|
|
||||||
fetchMock.get(`${metadata.issuer}.well-known/openid-configuration`, metadata);
|
fetchMock.get(`${metadata.issuer}.well-known/openid-configuration`, metadata);
|
||||||
|
fetchMock.get(`${metadata.issuer}jwks`, { keys: [] });
|
||||||
mockPlatformPeg();
|
mockPlatformPeg();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -78,7 +74,6 @@ describe("OidcClientStore", () => {
|
||||||
|
|
||||||
describe("initialising oidcClient", () => {
|
describe("initialising oidcClient", () => {
|
||||||
it("should initialise oidc client from constructor", () => {
|
it("should initialise oidc client from constructor", () => {
|
||||||
mockClient.waitForClientWellKnown.mockResolvedValue(undefined as any);
|
|
||||||
const store = new OidcClientStore(mockClient);
|
const store = new OidcClientStore(mockClient);
|
||||||
|
|
||||||
// started initialising
|
// started initialising
|
||||||
|
@ -87,7 +82,6 @@ describe("OidcClientStore", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fallback to stored issuer when no client well known is available", async () => {
|
it("should fallback to stored issuer when no client well known is available", async () => {
|
||||||
mockClient.waitForClientWellKnown.mockResolvedValue(undefined as any);
|
|
||||||
const store = new OidcClientStore(mockClient);
|
const store = new OidcClientStore(mockClient);
|
||||||
|
|
||||||
// successfully created oidc client
|
// successfully created oidc client
|
||||||
|
@ -109,10 +103,10 @@ describe("OidcClientStore", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should log and return when discovery and validation fails", async () => {
|
it("should log and return when discovery and validation fails", async () => {
|
||||||
mocked(discoverAndValidateAuthenticationConfig).mockRejectedValue(new Error(OidcError.OpSupport));
|
mocked(discoverAndValidateOIDCIssuerWellKnown).mockRejectedValue(new Error(OidcError.OpSupport));
|
||||||
const store = new OidcClientStore(mockClient);
|
const store = new OidcClientStore(mockClient);
|
||||||
|
|
||||||
await flushPromises();
|
await store.readyPromise;
|
||||||
|
|
||||||
expect(logger.error).toHaveBeenCalledWith(
|
expect(logger.error).toHaveBeenCalledWith(
|
||||||
"Failed to initialise OidcClientStore",
|
"Failed to initialise OidcClientStore",
|
||||||
|
@ -143,15 +137,15 @@ 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(discoverAndValidateAuthenticationConfig).mockClear().mockResolvedValue({
|
mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({
|
||||||
metadata,
|
metadata,
|
||||||
account: undefined,
|
accountManagementEndpoint: undefined,
|
||||||
issuer: metadata.issuer,
|
authorizationEndpoint: "authorization-endpoint",
|
||||||
|
tokenEndpoint: "token-endpoint",
|
||||||
});
|
});
|
||||||
const store = new OidcClientStore(mockClient);
|
const store = new OidcClientStore(mockClient);
|
||||||
|
|
||||||
// @ts-ignore private property
|
await store.readyPromise;
|
||||||
await store.getOidcClient();
|
|
||||||
|
|
||||||
expect(store.accountManagementEndpoint).toEqual(metadata.issuer);
|
expect(store.accountManagementEndpoint).toEqual(metadata.issuer);
|
||||||
});
|
});
|
||||||
|
@ -175,7 +169,7 @@ describe("OidcClientStore", () => {
|
||||||
|
|
||||||
// only called once for multiple calls to getOidcClient
|
// only called once for multiple calls to getOidcClient
|
||||||
// before and after initialisation is complete
|
// before and after initialisation is complete
|
||||||
expect(discoverAndValidateAuthenticationConfig).toHaveBeenCalledTimes(1);
|
expect(discoverAndValidateOIDCIssuerWellKnown).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -199,7 +193,6 @@ describe("OidcClientStore", () => {
|
||||||
|
|
||||||
it("should throw when oidcClient could not be initialised", async () => {
|
it("should throw when oidcClient could not be initialised", async () => {
|
||||||
// make oidcClient initialisation fail
|
// make oidcClient initialisation fail
|
||||||
mockClient.waitForClientWellKnown.mockResolvedValue(undefined as any);
|
|
||||||
localStorage.removeItem("mx_oidc_token_issuer");
|
localStorage.removeItem("mx_oidc_token_issuer");
|
||||||
|
|
||||||
const store = new OidcClientStore(mockClient);
|
const store = new OidcClientStore(mockClient);
|
||||||
|
@ -245,4 +238,17 @@ describe("OidcClientStore", () => {
|
||||||
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
|
expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("OIDC Aware", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve account management endpoint", async () => {
|
||||||
|
mockClient.getAuthIssuer.mockResolvedValue({ issuer: metadata.issuer });
|
||||||
|
const store = new OidcClientStore(mockClient);
|
||||||
|
await store.readyPromise;
|
||||||
|
expect(store.accountManagementEndpoint).toBe(account);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,8 +26,7 @@ export const makeDelegatedAuthConfig = (issuer = "https://auth.org/"): OidcClien
|
||||||
const metadata = mockOpenIdConfiguration(issuer);
|
const metadata = mockOpenIdConfiguration(issuer);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
issuer,
|
accountManagementEndpoint: issuer + "account",
|
||||||
account: issuer + "account",
|
|
||||||
registrationEndpoint: metadata.registration_endpoint,
|
registrationEndpoint: metadata.registration_endpoint,
|
||||||
authorizationEndpoint: metadata.authorization_endpoint,
|
authorizationEndpoint: metadata.authorization_endpoint,
|
||||||
tokenEndpoint: metadata.token_endpoint,
|
tokenEndpoint: metadata.token_endpoint,
|
||||||
|
@ -50,4 +49,5 @@ export const mockOpenIdConfiguration = (issuer = "https://auth.org/"): Validated
|
||||||
response_types_supported: ["code"],
|
response_types_supported: ["code"],
|
||||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
||||||
code_challenge_methods_supported: ["S256"],
|
code_challenge_methods_supported: ["S256"],
|
||||||
|
account_management_uri: issuer + "account",
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,12 +14,22 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AutoDiscovery, AutoDiscoveryAction, ClientConfig, M_AUTHENTICATION } from "matrix-js-sdk/src/matrix";
|
import { AutoDiscovery, AutoDiscoveryAction, ClientConfig } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import fetchMock from "fetch-mock-jest";
|
||||||
|
|
||||||
import AutoDiscoveryUtils from "../../src/utils/AutoDiscoveryUtils";
|
import AutoDiscoveryUtils from "../../src/utils/AutoDiscoveryUtils";
|
||||||
|
import { mockOpenIdConfiguration } from "../test-utils/oidc";
|
||||||
|
|
||||||
describe("AutoDiscoveryUtils", () => {
|
describe("AutoDiscoveryUtils", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fetchMock.catch({
|
||||||
|
status: 404,
|
||||||
|
body: '{"errcode": "M_UNRECOGNIZED", "error": "Unrecognized request"}',
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("buildValidatedConfigFromDiscovery()", () => {
|
describe("buildValidatedConfigFromDiscovery()", () => {
|
||||||
const serverName = "my-server";
|
const serverName = "my-server";
|
||||||
|
|
||||||
|
@ -56,24 +66,24 @@ describe("AutoDiscoveryUtils", () => {
|
||||||
isUrl: "identity.com",
|
isUrl: "identity.com",
|
||||||
};
|
};
|
||||||
|
|
||||||
it("throws an error when discovery result is falsy", () => {
|
it("throws an error when discovery result is falsy", async () => {
|
||||||
expect(() => AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, undefined as any)).toThrow(
|
await expect(() =>
|
||||||
"Unexpected error resolving homeserver configuration",
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, undefined as any),
|
||||||
);
|
).rejects.toThrow("Unexpected error resolving homeserver configuration");
|
||||||
expect(logger.error).toHaveBeenCalled();
|
expect(logger.error).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error when discovery result does not include homeserver config", () => {
|
it("throws an error when discovery result does not include homeserver config", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validIsConfig,
|
...validIsConfig,
|
||||||
} as unknown as ClientConfig;
|
} as unknown as ClientConfig;
|
||||||
expect(() => AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult)).toThrow(
|
await expect(() =>
|
||||||
"Unexpected error resolving homeserver configuration",
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||||
);
|
).rejects.toThrow("Unexpected error resolving homeserver configuration");
|
||||||
expect(logger.error).toHaveBeenCalled();
|
expect(logger.error).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error when identity server config has fail error and recognised error string", () => {
|
it("throws an error when identity server config has fail error and recognised error string", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validHsConfig,
|
...validHsConfig,
|
||||||
"m.identity_server": {
|
"m.identity_server": {
|
||||||
|
@ -81,13 +91,13 @@ describe("AutoDiscoveryUtils", () => {
|
||||||
error: "GenericFailure",
|
error: "GenericFailure",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(() => AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult)).toThrow(
|
await expect(() =>
|
||||||
"Unexpected error resolving identity server configuration",
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||||
);
|
).rejects.toThrow("Unexpected error resolving identity server configuration");
|
||||||
expect(logger.error).toHaveBeenCalled();
|
expect(logger.error).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error when homeserver config has fail error and recognised error string", () => {
|
it("throws an error when homeserver config has fail error and recognised error string", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validIsConfig,
|
...validIsConfig,
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
|
@ -95,25 +105,25 @@ describe("AutoDiscoveryUtils", () => {
|
||||||
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(() => AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult)).toThrow(
|
await expect(() =>
|
||||||
"Homeserver URL does not appear to be a valid Matrix homeserver",
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||||
);
|
).rejects.toThrow("Homeserver URL does not appear to be a valid Matrix homeserver");
|
||||||
expect(logger.error).toHaveBeenCalled();
|
expect(logger.error).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error with fallback message identity server config has fail error", () => {
|
it("throws an error with fallback message identity server config has fail error", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validHsConfig,
|
...validHsConfig,
|
||||||
"m.identity_server": {
|
"m.identity_server": {
|
||||||
state: AutoDiscoveryAction.FAIL_ERROR,
|
state: AutoDiscoveryAction.FAIL_ERROR,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(() => AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult)).toThrow(
|
await expect(() =>
|
||||||
"Unexpected error resolving identity server configuration",
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||||
);
|
).rejects.toThrow("Unexpected error resolving identity server configuration");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error when error is ERROR_INVALID_HOMESERVER", () => {
|
it("throws an error when error is ERROR_INVALID_HOMESERVER", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validIsConfig,
|
...validIsConfig,
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
|
@ -121,12 +131,12 @@ describe("AutoDiscoveryUtils", () => {
|
||||||
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(() => AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult)).toThrow(
|
await expect(() =>
|
||||||
"Homeserver URL does not appear to be a valid Matrix homeserver",
|
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", () => {
|
it("throws an error when homeserver base_url is falsy", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validIsConfig,
|
...validIsConfig,
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
|
@ -134,13 +144,13 @@ describe("AutoDiscoveryUtils", () => {
|
||||||
base_url: "",
|
base_url: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(() => AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult)).toThrow(
|
await expect(() =>
|
||||||
"Unexpected error resolving homeserver configuration",
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||||
);
|
).rejects.toThrow("Unexpected error resolving homeserver configuration");
|
||||||
expect(logger.error).toHaveBeenCalledWith("No homeserver URL configured");
|
expect(logger.error).toHaveBeenCalledWith("No homeserver URL configured");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error when homeserver base_url is not a valid URL", () => {
|
it("throws an error when homeserver base_url is not a valid URL", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validIsConfig,
|
...validIsConfig,
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
|
@ -148,24 +158,25 @@ describe("AutoDiscoveryUtils", () => {
|
||||||
base_url: "banana",
|
base_url: "banana",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(() => AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult)).toThrow(
|
await expect(() =>
|
||||||
"Invalid URL: banana",
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||||
);
|
).rejects.toThrow("Invalid URL: banana");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses hs url hostname when serverName is falsy in args and config", () => {
|
it("uses hs url hostname when serverName is falsy in args and config", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validIsConfig,
|
...validIsConfig,
|
||||||
...validHsConfig,
|
...validHsConfig,
|
||||||
};
|
};
|
||||||
expect(AutoDiscoveryUtils.buildValidatedConfigFromDiscovery("", discoveryResult)).toEqual({
|
await expect(AutoDiscoveryUtils.buildValidatedConfigFromDiscovery("", discoveryResult)).resolves.toEqual({
|
||||||
...expectedValidatedConfig,
|
...expectedValidatedConfig,
|
||||||
hsNameIsDifferent: false,
|
hsNameIsDifferent: false,
|
||||||
hsName: "matrix.org",
|
hsName: "matrix.org",
|
||||||
|
warning: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses serverName from props", () => {
|
it("uses serverName from props", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validIsConfig,
|
...validIsConfig,
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
|
@ -174,16 +185,17 @@ describe("AutoDiscoveryUtils", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const syntaxOnly = true;
|
const syntaxOnly = true;
|
||||||
expect(
|
await expect(
|
||||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
||||||
).toEqual({
|
).resolves.toEqual({
|
||||||
...expectedValidatedConfig,
|
...expectedValidatedConfig,
|
||||||
hsNameIsDifferent: true,
|
hsNameIsDifferent: true,
|
||||||
hsName: serverName,
|
hsName: serverName,
|
||||||
|
warning: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores liveliness error when checking syntax only", () => {
|
it("ignores liveliness error when checking syntax only", async () => {
|
||||||
const discoveryResult = {
|
const discoveryResult = {
|
||||||
...validIsConfig,
|
...validIsConfig,
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
|
@ -193,60 +205,15 @@ describe("AutoDiscoveryUtils", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const syntaxOnly = true;
|
const syntaxOnly = true;
|
||||||
expect(
|
await expect(
|
||||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
||||||
).toEqual({
|
).resolves.toEqual({
|
||||||
...expectedValidatedConfig,
|
...expectedValidatedConfig,
|
||||||
warning: "Homeserver URL does not appear to be a valid Matrix homeserver",
|
warning: "Homeserver URL does not appear to be a valid Matrix homeserver",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores delegated auth config when discovery was not successful", () => {
|
it("handles homeserver too old error", async () => {
|
||||||
const discoveryResult = {
|
|
||||||
...validIsConfig,
|
|
||||||
...validHsConfig,
|
|
||||||
[M_AUTHENTICATION.stable!]: {
|
|
||||||
state: AutoDiscoveryAction.FAIL_ERROR,
|
|
||||||
error: "",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const syntaxOnly = true;
|
|
||||||
expect(
|
|
||||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
|
||||||
).toEqual({
|
|
||||||
...expectedValidatedConfig,
|
|
||||||
delegatedAuthentication: undefined,
|
|
||||||
warning: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets delegated auth config when discovery was successful", () => {
|
|
||||||
const authConfig = {
|
|
||||||
issuer: "https://test.com/",
|
|
||||||
authorizationEndpoint: "https://test.com/auth",
|
|
||||||
registrationEndpoint: "https://test.com/registration",
|
|
||||||
tokenEndpoint: "https://test.com/token",
|
|
||||||
};
|
|
||||||
const discoveryResult: ClientConfig = {
|
|
||||||
...validIsConfig,
|
|
||||||
...validHsConfig,
|
|
||||||
[M_AUTHENTICATION.stable!]: {
|
|
||||||
state: AutoDiscoveryAction.SUCCESS,
|
|
||||||
error: null,
|
|
||||||
...authConfig,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const syntaxOnly = true;
|
|
||||||
expect(
|
|
||||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
|
||||||
).toEqual({
|
|
||||||
...expectedValidatedConfig,
|
|
||||||
delegatedAuthentication: authConfig,
|
|
||||||
warning: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles homeserver too old error", () => {
|
|
||||||
const discoveryResult: ClientConfig = {
|
const discoveryResult: ClientConfig = {
|
||||||
...validIsConfig,
|
...validIsConfig,
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
|
@ -256,12 +223,165 @@ describe("AutoDiscoveryUtils", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const syntaxOnly = true;
|
const syntaxOnly = true;
|
||||||
expect(() =>
|
await expect(() =>
|
||||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
||||||
).toThrow(
|
).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.",
|
"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", () => {
|
describe("authComponentStateForError", () => {
|
||||||
|
|
|
@ -46,8 +46,8 @@ describe("TokenRefresher", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetchMock.get(`${authConfig.issuer}.well-known/openid-configuration`, authConfig.metadata);
|
fetchMock.get(`${issuer}.well-known/openid-configuration`, authConfig.metadata);
|
||||||
fetchMock.get(`${authConfig.issuer}jwks`, {
|
fetchMock.get(`${issuer}jwks`, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -68,7 +68,7 @@ describe("TokenRefresher", () => {
|
||||||
const getPickleKey = jest.fn().mockResolvedValue(pickleKey);
|
const getPickleKey = jest.fn().mockResolvedValue(pickleKey);
|
||||||
mockPlatformPeg({ getPickleKey });
|
mockPlatformPeg({ getPickleKey });
|
||||||
|
|
||||||
const refresher = new TokenRefresher(authConfig, clientId, redirectUri, deviceId, idTokenClaims, userId);
|
const refresher = new TokenRefresher(issuer, clientId, redirectUri, deviceId, idTokenClaims, userId);
|
||||||
|
|
||||||
await refresher.oidcClientReady;
|
await refresher.oidcClientReady;
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ describe("TokenRefresher", () => {
|
||||||
const getPickleKey = jest.fn().mockResolvedValue(null);
|
const getPickleKey = jest.fn().mockResolvedValue(null);
|
||||||
mockPlatformPeg({ getPickleKey });
|
mockPlatformPeg({ getPickleKey });
|
||||||
|
|
||||||
const refresher = new TokenRefresher(authConfig, clientId, redirectUri, deviceId, idTokenClaims, userId);
|
const refresher = new TokenRefresher(issuer, clientId, redirectUri, deviceId, idTokenClaims, userId);
|
||||||
|
|
||||||
await refresher.oidcClientReady;
|
await refresher.oidcClientReady;
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,10 @@ describe("OIDC authorization", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
fetchMock.get(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`, delegatedAuthConfig.metadata);
|
fetchMock.get(
|
||||||
|
`${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
|
||||||
|
delegatedAuthConfig.metadata,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
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 { M_AUTHENTICATION } from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
import { getDelegatedAuthAccountUrl } from "../../../src/utils/oidc/getDelegatedAuthAccountUrl";
|
|
||||||
|
|
||||||
describe("getDelegatedAuthAccountUrl()", () => {
|
|
||||||
it("should return undefined when wk is undefined", () => {
|
|
||||||
expect(getDelegatedAuthAccountUrl(undefined)).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return undefined when wk has no authentication config", () => {
|
|
||||||
expect(getDelegatedAuthAccountUrl({})).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return undefined when wk authentication config has no configured account url", () => {
|
|
||||||
expect(
|
|
||||||
getDelegatedAuthAccountUrl({
|
|
||||||
[M_AUTHENTICATION.stable!]: {
|
|
||||||
issuer: "issuer.org",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the account url for authentication config using the unstable prefix", () => {
|
|
||||||
expect(
|
|
||||||
getDelegatedAuthAccountUrl({
|
|
||||||
[M_AUTHENTICATION.unstable!]: {
|
|
||||||
issuer: "issuer.org",
|
|
||||||
account: "issuer.org/account",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toEqual("issuer.org/account");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the account url for authentication config using the stable prefix", () => {
|
|
||||||
expect(
|
|
||||||
getDelegatedAuthAccountUrl({
|
|
||||||
[M_AUTHENTICATION.stable!]: {
|
|
||||||
issuer: "issuer.org",
|
|
||||||
account: "issuer.org/account",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toEqual("issuer.org/account");
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -16,15 +16,15 @@ limitations under the License.
|
||||||
|
|
||||||
import fetchMockJest from "fetch-mock-jest";
|
import fetchMockJest from "fetch-mock-jest";
|
||||||
import { OidcError } from "matrix-js-sdk/src/oidc/error";
|
import { OidcError } from "matrix-js-sdk/src/oidc/error";
|
||||||
|
import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { getOidcClientId } from "../../../src/utils/oidc/registerClient";
|
import { getOidcClientId } from "../../../src/utils/oidc/registerClient";
|
||||||
import { ValidatedDelegatedAuthConfig } from "../../../src/utils/ValidatedServerConfig";
|
|
||||||
import { mockPlatformPeg } from "../../test-utils";
|
import { mockPlatformPeg } from "../../test-utils";
|
||||||
import PlatformPeg from "../../../src/PlatformPeg";
|
import PlatformPeg from "../../../src/PlatformPeg";
|
||||||
|
import { makeDelegatedAuthConfig } from "../../test-utils/oidc";
|
||||||
|
|
||||||
describe("getOidcClientId()", () => {
|
describe("getOidcClientId()", () => {
|
||||||
const issuer = "https://auth.com/";
|
const issuer = "https://auth.com/";
|
||||||
const registrationEndpoint = "https://auth.com/register";
|
|
||||||
const clientName = "Element";
|
const clientName = "Element";
|
||||||
const baseUrl = "https://just.testing";
|
const baseUrl = "https://just.testing";
|
||||||
const dynamicClientId = "xyz789";
|
const dynamicClientId = "xyz789";
|
||||||
|
@ -33,12 +33,7 @@ describe("getOidcClientId()", () => {
|
||||||
client_id: "abc123",
|
client_id: "abc123",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const delegatedAuthConfig = {
|
const delegatedAuthConfig = makeDelegatedAuthConfig(issuer);
|
||||||
issuer,
|
|
||||||
registrationEndpoint,
|
|
||||||
authorizationEndpoint: issuer + "auth",
|
|
||||||
tokenEndpoint: issuer + "token",
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetchMockJest.mockClear();
|
fetchMockJest.mockClear();
|
||||||
|
@ -63,11 +58,10 @@ describe("getOidcClientId()", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw when no static clientId is configured and no registration endpoint", async () => {
|
it("should throw when no static clientId is configured and no registration endpoint", async () => {
|
||||||
const authConfigWithoutRegistration: ValidatedDelegatedAuthConfig = {
|
const authConfigWithoutRegistration: OidcClientConfig = makeDelegatedAuthConfig(
|
||||||
...delegatedAuthConfig,
|
"https://issuerWithoutStaticClientId.org/",
|
||||||
issuer: "https://issuerWithoutStaticClientId.org/",
|
);
|
||||||
registrationEndpoint: undefined,
|
authConfigWithoutRegistration.registrationEndpoint = undefined;
|
||||||
};
|
|
||||||
await expect(getOidcClientId(authConfigWithoutRegistration, staticOidcClients)).rejects.toThrow(
|
await expect(getOidcClientId(authConfigWithoutRegistration, staticOidcClients)).rejects.toThrow(
|
||||||
OidcError.DynamicRegistrationNotSupported,
|
OidcError.DynamicRegistrationNotSupported,
|
||||||
);
|
);
|
||||||
|
@ -76,7 +70,7 @@ describe("getOidcClientId()", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle when staticOidcClients object is falsy", async () => {
|
it("should handle when staticOidcClients object is falsy", async () => {
|
||||||
const authConfigWithoutRegistration: ValidatedDelegatedAuthConfig = {
|
const authConfigWithoutRegistration: OidcClientConfig = {
|
||||||
...delegatedAuthConfig,
|
...delegatedAuthConfig,
|
||||||
registrationEndpoint: undefined,
|
registrationEndpoint: undefined,
|
||||||
};
|
};
|
||||||
|
@ -88,14 +82,14 @@ describe("getOidcClientId()", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should make correct request to register client", async () => {
|
it("should make correct request to register client", async () => {
|
||||||
fetchMockJest.post(registrationEndpoint, {
|
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
|
||||||
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(
|
||||||
registrationEndpoint,
|
delegatedAuthConfig.registrationEndpoint!,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
@ -120,14 +114,14 @@ describe("getOidcClientId()", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw when registration request fails", async () => {
|
it("should throw when registration request fails", async () => {
|
||||||
fetchMockJest.post(registrationEndpoint, {
|
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
|
||||||
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(registrationEndpoint, {
|
fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
|
||||||
status: 200,
|
status: 200,
|
||||||
// no clientId in response
|
// no clientId in response
|
||||||
body: "{}",
|
body: "{}",
|
||||||
|
|
Loading…
Reference in a new issue