You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-23 17:02:25 +03:00
Allow specifying more OIDC client metadata for dynamic registration (#4070)
This commit is contained in:
committed by
GitHub
parent
2cf7d819d9
commit
fe46fec161
@@ -17,13 +17,22 @@ limitations under the License.
|
|||||||
import fetchMockJest from "fetch-mock-jest";
|
import fetchMockJest from "fetch-mock-jest";
|
||||||
|
|
||||||
import { OidcError } from "../../../src/oidc/error";
|
import { OidcError } from "../../../src/oidc/error";
|
||||||
import { registerOidcClient } from "../../../src/oidc/register";
|
import { OidcRegistrationClientMetadata, registerOidcClient } from "../../../src/oidc/register";
|
||||||
|
|
||||||
describe("registerOidcClient()", () => {
|
describe("registerOidcClient()", () => {
|
||||||
const issuer = "https://auth.com/";
|
const issuer = "https://auth.com/";
|
||||||
const registrationEndpoint = "https://auth.com/register";
|
const registrationEndpoint = "https://auth.com/register";
|
||||||
const clientName = "Element";
|
const clientName = "Element";
|
||||||
const baseUrl = "https://just.testing";
|
const baseUrl = "https://just.testing";
|
||||||
|
const metadata: OidcRegistrationClientMetadata = {
|
||||||
|
clientUri: baseUrl,
|
||||||
|
redirectUris: [baseUrl],
|
||||||
|
clientName,
|
||||||
|
applicationType: "web",
|
||||||
|
tosUri: "http://tos-uri",
|
||||||
|
policyUri: "http://policy-uri",
|
||||||
|
contacts: ["admin@example.com"],
|
||||||
|
};
|
||||||
const dynamicClientId = "xyz789";
|
const dynamicClientId = "xyz789";
|
||||||
|
|
||||||
const delegatedAuthConfig = {
|
const delegatedAuthConfig = {
|
||||||
@@ -42,14 +51,19 @@ describe("registerOidcClient()", () => {
|
|||||||
status: 200,
|
status: 200,
|
||||||
body: JSON.stringify({ client_id: dynamicClientId }),
|
body: JSON.stringify({ client_id: dynamicClientId }),
|
||||||
});
|
});
|
||||||
expect(await registerOidcClient(delegatedAuthConfig, clientName, baseUrl)).toEqual(dynamicClientId);
|
expect(await registerOidcClient(delegatedAuthConfig, metadata)).toEqual(dynamicClientId);
|
||||||
expect(fetchMockJest).toHaveBeenCalledWith(registrationEndpoint, {
|
expect(fetchMockJest).toHaveBeenCalledWith(
|
||||||
headers: {
|
registrationEndpoint,
|
||||||
"Accept": "application/json",
|
expect.objectContaining({
|
||||||
"Content-Type": "application/json",
|
headers: {
|
||||||
},
|
"Accept": "application/json",
|
||||||
method: "POST",
|
"Content-Type": "application/json",
|
||||||
body: JSON.stringify({
|
},
|
||||||
|
method: "POST",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(JSON.parse(fetchMockJest.mock.calls[0][1]!.body as string)).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
client_name: clientName,
|
client_name: clientName,
|
||||||
client_uri: baseUrl,
|
client_uri: baseUrl,
|
||||||
response_types: ["code"],
|
response_types: ["code"],
|
||||||
@@ -59,14 +73,14 @@ describe("registerOidcClient()", () => {
|
|||||||
token_endpoint_auth_method: "none",
|
token_endpoint_auth_method: "none",
|
||||||
application_type: "web",
|
application_type: "web",
|
||||||
}),
|
}),
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw when registration request fails", async () => {
|
it("should throw when registration request fails", async () => {
|
||||||
fetchMockJest.post(registrationEndpoint, {
|
fetchMockJest.post(registrationEndpoint, {
|
||||||
status: 500,
|
status: 500,
|
||||||
});
|
});
|
||||||
await expect(() => registerOidcClient(delegatedAuthConfig, clientName, baseUrl)).rejects.toThrow(
|
await expect(() => registerOidcClient(delegatedAuthConfig, metadata)).rejects.toThrow(
|
||||||
OidcError.DynamicRegistrationFailed,
|
OidcError.DynamicRegistrationFailed,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -77,7 +91,7 @@ describe("registerOidcClient()", () => {
|
|||||||
// no clientId in response
|
// no clientId in response
|
||||||
body: "{}",
|
body: "{}",
|
||||||
});
|
});
|
||||||
await expect(() => registerOidcClient(delegatedAuthConfig, clientName, baseUrl)).rejects.toThrow(
|
await expect(() => registerOidcClient(delegatedAuthConfig, metadata)).rejects.toThrow(
|
||||||
OidcError.DynamicRegistrationInvalid,
|
OidcError.DynamicRegistrationInvalid,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
17
src/@types/common.ts
Normal file
17
src/@types/common.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type NonEmptyArray<T> = [T, ...T[]];
|
||||||
@@ -56,6 +56,7 @@ export * from "./crypto/store/localStorage-crypto-store";
|
|||||||
export * from "./crypto/store/indexeddb-crypto-store";
|
export * from "./crypto/store/indexeddb-crypto-store";
|
||||||
export type { OutgoingRoomKeyRequest } from "./crypto/store/base";
|
export type { OutgoingRoomKeyRequest } from "./crypto/store/base";
|
||||||
export * from "./content-repo";
|
export * from "./content-repo";
|
||||||
|
export * from "./@types/common";
|
||||||
export * from "./@types/uia";
|
export * from "./@types/uia";
|
||||||
export * from "./@types/event";
|
export * from "./@types/event";
|
||||||
export * from "./@types/PushRules";
|
export * from "./@types/PushRules";
|
||||||
|
|||||||
@@ -19,16 +19,37 @@ import { OidcError } from "./error";
|
|||||||
import { Method } from "../http-api";
|
import { Method } from "../http-api";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import { ValidatedIssuerConfig } from "./validate";
|
import { ValidatedIssuerConfig } from "./validate";
|
||||||
|
import { NonEmptyArray } from "../@types/common";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client metadata passed to registration endpoint
|
* Client metadata passed to registration endpoint
|
||||||
*/
|
*/
|
||||||
export type OidcRegistrationClientMetadata = {
|
export type OidcRegistrationClientMetadata = {
|
||||||
clientName: string;
|
clientName: OidcRegistrationRequestBody["client_name"];
|
||||||
clientUri: string;
|
clientUri: OidcRegistrationRequestBody["client_uri"];
|
||||||
redirectUris: string[];
|
logoUri?: OidcRegistrationRequestBody["logo_uri"];
|
||||||
|
applicationType: OidcRegistrationRequestBody["application_type"];
|
||||||
|
redirectUris: OidcRegistrationRequestBody["redirect_uris"];
|
||||||
|
contacts: OidcRegistrationRequestBody["contacts"];
|
||||||
|
tosUri: OidcRegistrationRequestBody["tos_uri"];
|
||||||
|
policyUri: OidcRegistrationRequestBody["policy_uri"];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface OidcRegistrationRequestBody {
|
||||||
|
client_name: string;
|
||||||
|
client_uri: string;
|
||||||
|
logo_uri?: string;
|
||||||
|
contacts: NonEmptyArray<string>;
|
||||||
|
tos_uri: string;
|
||||||
|
policy_uri: string;
|
||||||
|
redirect_uris?: NonEmptyArray<string>;
|
||||||
|
response_types?: NonEmptyArray<string>;
|
||||||
|
grant_types?: NonEmptyArray<string>;
|
||||||
|
id_token_signed_response_alg: string;
|
||||||
|
token_endpoint_auth_method: string;
|
||||||
|
application_type: "web" | "native";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make the client registration request
|
* Make the client registration request
|
||||||
* @param registrationEndpoint - URL as returned from issuer ./well-known/openid-configuration
|
* @param registrationEndpoint - URL as returned from issuer ./well-known/openid-configuration
|
||||||
@@ -42,7 +63,7 @@ const doRegistration = async (
|
|||||||
clientMetadata: OidcRegistrationClientMetadata,
|
clientMetadata: OidcRegistrationClientMetadata,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
// https://openid.net/specs/openid-connect-registration-1_0.html
|
// https://openid.net/specs/openid-connect-registration-1_0.html
|
||||||
const metadata = {
|
const metadata: OidcRegistrationRequestBody = {
|
||||||
client_name: clientMetadata.clientName,
|
client_name: clientMetadata.clientName,
|
||||||
client_uri: clientMetadata.clientUri,
|
client_uri: clientMetadata.clientUri,
|
||||||
response_types: ["code"],
|
response_types: ["code"],
|
||||||
@@ -50,7 +71,11 @@ const doRegistration = async (
|
|||||||
redirect_uris: clientMetadata.redirectUris,
|
redirect_uris: clientMetadata.redirectUris,
|
||||||
id_token_signed_response_alg: "RS256",
|
id_token_signed_response_alg: "RS256",
|
||||||
token_endpoint_auth_method: "none",
|
token_endpoint_auth_method: "none",
|
||||||
application_type: "web",
|
application_type: clientMetadata.applicationType,
|
||||||
|
logo_uri: clientMetadata.logoUri,
|
||||||
|
contacts: clientMetadata.contacts,
|
||||||
|
policy_uri: clientMetadata.policyUri,
|
||||||
|
tos_uri: clientMetadata.tosUri,
|
||||||
};
|
};
|
||||||
const headers = {
|
const headers = {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
@@ -88,25 +113,16 @@ const doRegistration = async (
|
|||||||
/**
|
/**
|
||||||
* Attempts dynamic registration against the configured registration endpoint
|
* Attempts dynamic registration against the configured registration endpoint
|
||||||
* @param delegatedAuthConfig - Auth config from ValidatedServerConfig
|
* @param delegatedAuthConfig - Auth config from ValidatedServerConfig
|
||||||
* @param clientName - Client name to register with the OP, eg 'Element'
|
* @param clientMetadata - The metadata for the client which to register
|
||||||
* @param baseUrl - URL of the home page of the Client, eg 'https://app.element.io/'
|
|
||||||
* @returns Promise<string> resolved with registered clientId
|
* @returns Promise<string> resolved with registered clientId
|
||||||
* @throws when registration is not supported, on failed request or invalid response
|
* @throws when registration is not supported, on failed request or invalid response
|
||||||
*/
|
*/
|
||||||
export const registerOidcClient = async (
|
export const registerOidcClient = async (
|
||||||
delegatedAuthConfig: IDelegatedAuthConfig & ValidatedIssuerConfig,
|
delegatedAuthConfig: IDelegatedAuthConfig & ValidatedIssuerConfig,
|
||||||
clientName: string,
|
clientMetadata: OidcRegistrationClientMetadata,
|
||||||
baseUrl: string,
|
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const clientMetadata = {
|
|
||||||
clientName,
|
|
||||||
clientUri: baseUrl,
|
|
||||||
redirectUris: [baseUrl],
|
|
||||||
};
|
|
||||||
if (!delegatedAuthConfig.registrationEndpoint) {
|
if (!delegatedAuthConfig.registrationEndpoint) {
|
||||||
throw new Error(OidcError.DynamicRegistrationNotSupported);
|
throw new Error(OidcError.DynamicRegistrationNotSupported);
|
||||||
}
|
}
|
||||||
const clientId = await doRegistration(delegatedAuthConfig.registrationEndpoint, clientMetadata);
|
return doRegistration(delegatedAuthConfig.registrationEndpoint, clientMetadata);
|
||||||
|
|
||||||
return clientId;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user