1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-23 17:02:25 +03:00

Update MSC2965 OIDC Discovery implementation (#4064)

This commit is contained in:
Michael Telatynski
2024-02-23 16:43:11 +00:00
committed by GitHub
parent be3913e8a5
commit a26fc46ed4
14 changed files with 77 additions and 420 deletions

View File

@@ -16,7 +16,6 @@ limitations under the License.
import { IdTokenClaims, Log, OidcClient, SigninResponse, SigninState, WebStorageStateStore } from "oidc-client-ts";
import { IDelegatedAuthConfig } from "../client";
import { subtleCrypto, TextEncoder } from "../crypto/crypto";
import { logger } from "../logger";
import { randomString } from "../randomstring";
@@ -209,7 +208,7 @@ export const completeAuthorizationCodeGrant = async (
code: string,
state: string,
): Promise<{
oidcClientSettings: IDelegatedAuthConfig & { clientId: string };
oidcClientSettings: { clientId: string; issuer: string };
tokenResponse: BearerTokenResponse;
homeserverUrl: string;
idTokenClaims: IdTokenClaims;

View File

@@ -14,36 +14,35 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MetadataService, OidcClientSettingsStore, SigningKey } from "oidc-client-ts";
import { MetadataService, OidcClientSettingsStore } from "oidc-client-ts";
import { IDelegatedAuthConfig } from "../client";
import { isValidatedIssuerMetadata, ValidatedIssuerMetadata, validateWellKnownAuthentication } from "./validate";
import { isValidatedIssuerMetadata, validateOIDCIssuerWellKnown } from "./validate";
import { Method, timeoutSignal } from "../http-api";
import { OidcClientConfig } from "./index";
/**
* @experimental
* Discover and validate delegated auth configuration
* - m.authentication config is present and valid
* - delegated auth issuer openid-configuration is reachable
* - delegated auth issuer openid-configuration is configured correctly for us
* Fetches https://oidc-issuer.example.com/.well-known/openid-configuration and other files linked therein.
* When successful, validated metadata is returned
* @param wellKnown - configuration object as returned
* by the .well-known auto-discovery endpoint
* @param issuer - the OIDC issuer as returned by the /auth_issuer API
* @returns validated authentication metadata and optionally signing keys
* @throws when delegated auth config is invalid or unreachable
*/
export const discoverAndValidateAuthenticationConfig = async (
authenticationConfig?: IDelegatedAuthConfig,
): Promise<
IDelegatedAuthConfig & {
metadata: ValidatedIssuerMetadata;
signingKeys?: SigningKey[];
}
> => {
const homeserverAuthenticationConfig = validateWellKnownAuthentication(authenticationConfig);
export const discoverAndValidateOIDCIssuerWellKnown = async (issuer: string): Promise<OidcClientConfig> => {
const issuerOpenIdConfigUrl = new URL(".well-known/openid-configuration", issuer);
const issuerWellKnownResponse = await fetch(issuerOpenIdConfigUrl, {
method: Method.Get,
signal: timeoutSignal(5000),
});
const issuerWellKnown = await issuerWellKnownResponse.json();
const validatedIssuerConfig = validateOIDCIssuerWellKnown(issuerWellKnown);
// create a temporary settings store so we can use metadata service for discovery
// create a temporary settings store, so we can use metadata service for discovery
const settings = new OidcClientSettingsStore({
authority: homeserverAuthenticationConfig.issuer,
authority: issuer,
redirect_uri: "", // Not known yet, this is here to make the type checker happy
client_id: "", // Not known yet, this is here to make the type checker happy
});
@@ -54,7 +53,7 @@ export const discoverAndValidateAuthenticationConfig = async (
isValidatedIssuerMetadata(metadata);
return {
...homeserverAuthenticationConfig,
...validatedIssuerConfig,
metadata,
signingKeys,
};

View File

@@ -14,9 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import type { SigningKey } from "oidc-client-ts";
import { ValidatedIssuerConfig, ValidatedIssuerMetadata } from "./validate";
export * from "./authorize";
export * from "./discovery";
export * from "./error";
export * from "./register";
export * from "./tokenRefresher";
export * from "./validate";
/**
* Validated config for native OIDC authentication, as returned by {@link discoverAndValidateOIDCIssuerWellKnown}.
* Contains metadata and signing keys from the issuer's well-known (https://oidc-issuer.example.com/.well-known/openid-configuration).
*/
export interface OidcClientConfig extends ValidatedIssuerConfig {
metadata: ValidatedIssuerMetadata;
signingKeys?: SigningKey[];
}

View File

@@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { IDelegatedAuthConfig } from "../client";
import { OidcClientConfig } from ".";
import { OidcError } from "./error";
import { Method } from "../http-api";
import { logger } from "../logger";
import { ValidatedIssuerConfig } from "./validate";
import { NonEmptyArray } from "../@types/common";
/**
@@ -112,13 +111,13 @@ const doRegistration = async (
/**
* Attempts dynamic registration against the configured registration endpoint
* @param delegatedAuthConfig - Auth config from ValidatedServerConfig
* @param delegatedAuthConfig - Auth config from {@link discoverAndValidateOIDCIssuerWellKnown}
* @param clientMetadata - The metadata for the client which to register
* @returns Promise<string> resolved with registered clientId
* @throws when registration is not supported, on failed request or invalid response
*/
export const registerOidcClient = async (
delegatedAuthConfig: IDelegatedAuthConfig & ValidatedIssuerConfig,
delegatedAuthConfig: OidcClientConfig,
clientMetadata: OidcRegistrationClientMetadata,
): Promise<string> => {
if (!delegatedAuthConfig.registrationEndpoint) {

View File

@@ -17,9 +17,8 @@ limitations under the License.
import { IdTokenClaims, OidcClient, WebStorageStateStore } from "oidc-client-ts";
import { AccessTokens } from "../http-api";
import { IDelegatedAuthConfig } from "../client";
import { generateScope } from "./authorize";
import { discoverAndValidateAuthenticationConfig } from "./discovery";
import { discoverAndValidateOIDCIssuerWellKnown } from "./discovery";
import { logger } from "../logger";
/**
@@ -42,9 +41,9 @@ export class OidcTokenRefresher {
public constructor(
/**
* Delegated auth config as found in matrix client .well-known
* The OIDC issuer as returned by the /auth_issuer API
*/
authConfig: IDelegatedAuthConfig,
issuer: string,
/**
* id of this client as registered with the OP
*/
@@ -63,17 +62,17 @@ export class OidcTokenRefresher {
*/
private readonly idTokenClaims: IdTokenClaims,
) {
this.oidcClientReady = this.initialiseOidcClient(authConfig, clientId, deviceId, redirectUri);
this.oidcClientReady = this.initialiseOidcClient(issuer, clientId, deviceId, redirectUri);
}
private async initialiseOidcClient(
authConfig: IDelegatedAuthConfig,
issuer: string,
clientId: string,
deviceId: string,
redirectUri: string,
): Promise<void> {
try {
const config = await discoverAndValidateAuthenticationConfig(authConfig);
const config = await discoverAndValidateOIDCIssuerWellKnown(issuer);
const scope = generateScope(deviceId);

View File

@@ -17,7 +17,6 @@ limitations under the License.
import { jwtDecode } from "jwt-decode";
import { OidcMetadata, SigninResponse } from "oidc-client-ts";
import { IDelegatedAuthConfig } from "../client";
import { logger } from "../logger";
import { OidcError } from "./error";
@@ -35,31 +34,6 @@ export type ValidatedIssuerConfig = {
accountManagementActionsSupported?: string[];
};
/**
* Validates MSC2965 m.authentication config
* Returns valid configuration
* @param wellKnown - client well known as returned from ./well-known/client/matrix
* @returns config - when present and valid
* @throws when config is not found or invalid
*/
export const validateWellKnownAuthentication = (authentication?: IDelegatedAuthConfig): IDelegatedAuthConfig => {
if (!authentication) {
throw new Error(OidcError.NotSupported);
}
if (
typeof authentication.issuer === "string" &&
(!authentication.hasOwnProperty("account") || typeof authentication.account === "string")
) {
return {
issuer: authentication.issuer,
account: authentication.account,
};
}
throw new Error(OidcError.Misconfigured);
};
const isRecord = (value: unknown): value is Record<string, unknown> =>
!!value && typeof value === "object" && !Array.isArray(value);
const requiredStringProperty = (wellKnown: Record<string, unknown>, key: string): boolean => {
@@ -150,7 +124,11 @@ export type ValidatedIssuerMetadata = Partial<OidcMetadata> &
| "response_types_supported"
| "grant_types_supported"
| "code_challenge_methods_supported"
>;
> & {
// MSC2965 extensions to the OIDC spec
account_management_uri?: string;
account_management_actions_supported?: string[];
};
/**
* Wraps validateOIDCIssuerWellKnown in a type assertion