Start supporting OAuth 2.0 authentication API

This commit is contained in:
Andrew Ferrazzutti
2025-08-29 10:30:54 -04:00
parent c1694fe706
commit e52083e29a
15 changed files with 526 additions and 76 deletions

View File

@@ -90,6 +90,7 @@
"e2ee_unsupported_description": "Your web browser does not support encrypted calls. Supported browsers include Chrome, Safari, and Firefox 117+.",
"generic": "Something went wrong",
"generic_description": "Submitting debug logs will help us track down the problem.",
"homeserver_misconfig": "Misconfigured homeserver",
"insufficient_capacity": "Insufficient capacity",
"insufficient_capacity_description": "The server has reached its maximum capacity and you cannot join the call at this time. Try again later, or contact your server admin if the problem persists.",
"matrix_rtc_focus_missing": "The server is not configured to work with {{brand}}. Please contact your server admin (Domain: {{domain}}, Error Code: {{ errorCode }}).",

View File

@@ -20,6 +20,7 @@ import { logger } from "matrix-js-sdk/lib/logger";
import { HomePage } from "./home/HomePage";
import { LoginPage } from "./auth/LoginPage";
import { OidcRedirectPage } from "./auth/OidcRedirectPage";
import { RegisterPage } from "./auth/RegisterPage";
import { RoomPage } from "./room/RoomPage";
import { ClientProvider } from "./ClientContext";
@@ -88,6 +89,7 @@ export const App: FC<Props> = ({ vm }) => {
<Routes>
<SentryRoute path="/" element={<HomePage />} />
<SentryRoute path="/login" element={<LoginPage />} />
<SentryRoute path="/after_login" element={<OidcRedirectPage />} />
<SentryRoute path="/register" element={<RegisterPage />} />
<SentryRoute path="*" element={<RoomPage />} />
</Routes>

View File

@@ -21,6 +21,7 @@ const TestComponent: FC<
<ClientContextProvider
value={{
state: "valid",
oidcClientConfig: null,
disconnected: false,
supportedFeatures: {
reactions: true,

View File

@@ -1,5 +1,5 @@
/*
Copyright 2021-2024 New Vector Ltd.
Copyright 2021-2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
@@ -19,7 +19,7 @@ import {
import { useNavigate } from "react-router-dom";
import { logger } from "matrix-js-sdk/lib/logger";
import { type ISyncStateData, type SyncState } from "matrix-js-sdk/lib/sync";
import { ClientEvent, type MatrixClient } from "matrix-js-sdk";
import { ClientEvent, type OidcClientConfig, type MatrixClient } from "matrix-js-sdk";
import type { WidgetApi } from "matrix-widget-api";
import { ErrorPage } from "./FullScreenView";
@@ -29,6 +29,7 @@ import {
RegistrationType,
} from "./analytics/PosthogAnalytics";
import { useEventTarget } from "./useEvents";
import { getAuthMetadata } from "./utils/oidc/discovery";
import { OpenElsewhereError } from "./RichError";
declare global {
@@ -42,6 +43,7 @@ export type ClientState = ValidClientState | ErrorState;
export type ValidClientState = {
state: "valid";
oidcClientConfig: OidcClientConfig | null,
authenticated?: AuthenticatedClient;
// 'Disconnected' rather than 'connected' because it tracks specifically
// whether the client is supposed to be connected but is not
@@ -74,17 +76,20 @@ export const useClientState = (): ClientState | undefined => use(ClientContext);
export function useClient(): {
client?: MatrixClient;
setClient?: (client: MatrixClient, session: Session) => void;
oidcClientConfig: OidcClientConfig | null | undefined;
} {
let client;
let setClient;
let oidcClientConfig;
const clientState = useClientState();
if (clientState?.state === "valid") {
client = clientState.authenticated?.client;
setClient = clientState.setClient;
oidcClientConfig = clientState.oidcClientConfig;
}
return { client, setClient };
return { client, setClient, oidcClientConfig };
}
// Plain representation of the `ClientContext` as a helper for old components that expected an object with multiple fields.
@@ -140,6 +145,12 @@ interface Props {
export const ClientProvider: FC<Props> = ({ children }) => {
const navigate = useNavigate();
// null = no OIDC config, undefined = loading
const [oidcClientConfig, setOidcClientConfig] = useState<
OidcClientConfig | null | undefined
>(undefined);
const [oidcErr, setOidcErr] = useState<Error | undefined>(undefined);
// null = signed out, undefined = loading
const [initClientState, setInitClientState] = useState<
InitResult | null | undefined
@@ -153,6 +164,11 @@ export const ClientProvider: FC<Props> = ({ children }) => {
if (initializing.current) return;
initializing.current = true;
if (!widget) {
// TODO: spec says this may change over time & should be refreshed upon cache expiry
getAuthMetadata().then(setOidcClientConfig, setOidcErr);
}
loadClient()
.then((initResult) => {
setInitClientState(initResult);
@@ -251,12 +267,18 @@ export const ClientProvider: FC<Props> = ({ children }) => {
const [supportsReactions, setSupportsReactions] = useState(false);
const [supportsThumbnails, setSupportsThumbnails] = useState(false);
const state: ClientState | undefined = useMemo(() => {
if (alreadyOpenedErr) {
return { state: "error", error: alreadyOpenedErr };
const state = useMemo((): ClientState | undefined => {
const error = alreadyOpenedErr || oidcErr;
if (error) {
return { state: "error", error };
}
if (initClientState === undefined) return undefined;
if (
initClientState === undefined ||
oidcClientConfig === undefined
) {
return undefined;
}
const authenticated =
initClientState === null
@@ -270,6 +292,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
return {
state: "valid",
oidcClientConfig,
authenticated,
setClient,
disconnected: isDisconnected,
@@ -280,6 +303,8 @@ export const ClientProvider: FC<Props> = ({ children }) => {
};
}, [
alreadyOpenedErr,
oidcErr,
oidcClientConfig,
changePassword,
initClientState,
logout,
@@ -345,8 +370,9 @@ export const ClientProvider: FC<Props> = ({ children }) => {
};
}, [initClientState, onSync]);
if (alreadyOpenedErr) {
return <ErrorPage widget={widget} error={alreadyOpenedErr} />;
const error = alreadyOpenedErr || oidcErr;
if (error) {
return <ErrorPage widget={widget} error={error} />;
}
return <ClientContext value={state}>{children}</ClientContext>;

View File

@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
*/
import { useTranslation } from "react-i18next";
import { PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { ErrorIcon, PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import type { FC, ReactNode } from "react";
import { ErrorView } from "./ErrorView";
@@ -51,3 +51,29 @@ export class OpenElsewhereError extends RichError {
super("App opened in another tab", <OpenElsewhere />);
}
}
const HomeserverMisconfig: FC<{message?: string}> = (props) => {
const { t } = useTranslation();
// TODO: don't want to show "Return to home screen" button for an error as fatal as this
return (
<ErrorView
widget={widget}
Icon={ErrorIcon}
title={t("error.homeserver_misconfig")}
fatal={true}
>
{props.message && (
<p>
{props.message}
</p>
)}
</ErrorView>
);
};
export class HomeserverMisconfigError extends RichError {
public constructor(message?: string) {
super(message || "Unknown error", <HomeserverMisconfig message={message} />);
}
}

View File

@@ -1,11 +1,11 @@
/*
Copyright 2021-2024 New Vector Ltd.
Copyright 2021-2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { type FC, type FormEvent, useCallback, useRef, useState } from "react";
import { type FC, type FormEvent, type ReactElement, useCallback, useRef, useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
import { Button } from "@vector-im/compound-web";
@@ -19,13 +19,14 @@ import { usePageTitle } from "../usePageTitle";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { Config } from "../config/Config";
import { Link } from "../button/Link";
import { LoadingPage } from "../FullScreenView";
import { getOidcClientId } from "../utils/oidc/registerClient";
import { startOidcLogin } from "../utils/oidc/authorize";
export const LoginPage: FC = () => {
const { t } = useTranslation();
usePageTitle(t("login_title"));
const { client, setClient } = useClient();
const login = useInteractiveLogin(client);
const homeserver = Config.defaultHomeserverUrl(); // TODO: Make this configurable
const usernameRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
@@ -36,19 +37,23 @@ export const LoginPage: FC = () => {
// TODO: Handle hitting login page with authenticated client
const onSubmitLoginForm = useCallback(
(e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
let node: ReactElement;
const { client, setClient, oidcClientConfig } = useClient();
if (oidcClientConfig === null) {
const login = useInteractiveLogin(client);
const onSubmitLoginForm = useCallback(
async (e: FormEvent<HTMLFormElement>): Promise<void> => {
e.preventDefault();
setLoading(true);
if (!homeserver || !usernameRef.current || !passwordRef.current) {
setError(Error("Login parameters are undefined"));
setLoading(false);
return;
}
if (!homeserver) {
setError(Error("Login parameters are undefined"));
setLoading(false);
return;
}
login(homeserver, usernameRef.current.value, passwordRef.current.value)
.then(async ([client, session]) => {
try {
const [client, session] = await login(homeserver, usernameRef.current?.value ?? "", passwordRef.current?.value ?? "");
if (!setClient) {
return;
}
@@ -66,20 +71,84 @@ export const LoginPage: FC = () => {
await navigate("/");
}
PosthogAnalytics.instance.eventLogin.track();
})
.catch((error) => {
} catch (error: any) {
setError(error);
setLoading(false);
});
},
[login, location, navigate, homeserver, setClient],
);
// we need to limit the length of the homserver name to not cover the whole loginview input with the string.
let shortendHomeserverName = Config.defaultServerName()?.slice(0, 25);
shortendHomeserverName =
shortendHomeserverName?.length !== Config.defaultServerName()?.length
? shortendHomeserverName + "..."
: shortendHomeserverName;
}
},
[login, location, navigate, homeserver, setClient],
);
// we need to limit the length of the homserver name to not cover the whole loginview input with the string.
let shortendHomeserverName = Config.defaultServerName()?.slice(0, 25);
shortendHomeserverName =
shortendHomeserverName?.length !== Config.defaultServerName()?.length
? shortendHomeserverName + "..."
: shortendHomeserverName;
node = (
<form onSubmit={onSubmitLoginForm}>
<FieldRow>
<InputField
type="text"
ref={usernameRef}
placeholder={t("common.username")}
label={t("common.username")}
autoCorrect="off"
autoCapitalize="none"
prefix="@"
suffix={`:${shortendHomeserverName}`}
data-testid="login_username"
/>
</FieldRow>
<FieldRow>
<InputField
type="password"
ref={passwordRef}
placeholder={t("common.password")}
label={t("common.password")}
data-testid="login_password"
/>
</FieldRow>
{error && (
<FieldRow>
<ErrorMessage error={error} />
</FieldRow>
)}
<FieldRow>
<Button
type="submit"
disabled={loading}
data-testid="login_login"
>
{loading ? t("logging_in") : t("login_title")}
</Button>
</FieldRow>
</form>
);
} else if (oidcClientConfig !== undefined) {
if (!homeserver) {
setError(Error("No homeserver is configured"));
setLoading(false);
return;
}
node = <Button
kind="primary"
onClick={async () => {
// TODO: get the clientId at the same time as doing OIDC discovery
const clientId = await getOidcClientId(oidcClientConfig);
await startOidcLogin(
oidcClientConfig,
clientId,
homeserver,
);
}}
>
{t("room_auth_view_continue_button") /* TODO: make a dedicated word for this */}
</Button>;
} else {
return <LoadingPage />;
}
return (
<>
<div className={styles.container}>
@@ -89,44 +158,7 @@ export const LoginPage: FC = () => {
<h2>{t("log_in")}</h2>
<h4>{t("login_subheading")}</h4>
<form onSubmit={onSubmitLoginForm}>
<FieldRow>
<InputField
type="text"
ref={usernameRef}
placeholder={t("common.username")}
label={t("common.username")}
autoCorrect="off"
autoCapitalize="none"
prefix="@"
suffix={`:${shortendHomeserverName}`}
data-testid="login_username"
/>
</FieldRow>
<FieldRow>
<InputField
type="password"
ref={passwordRef}
placeholder={t("common.password")}
label={t("common.password")}
data-testid="login_password"
/>
</FieldRow>
{error && (
<FieldRow>
<ErrorMessage error={error} />
</FieldRow>
)}
<FieldRow>
<Button
type="submit"
disabled={loading}
data-testid="login_login"
>
{loading ? t("logging_in") : t("login_title")}
</Button>
</FieldRow>
</form>
{node}
</div>
<div className={styles.authLinks}>
<p>{t("login_auth_links_prompt")}</p>

View File

@@ -0,0 +1,104 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { useState, type FC } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { createClient, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { useClient } from "../ClientContext";
import { usePageTitle } from "../usePageTitle";
import { completeOidcLogin } from "../utils/oidc/authorize";
import { initClient } from "../utils/matrix";
/**
* TODO: Yoinked from element-web, so move to SDK if possible
* Gets information about the owner of a given access token.
* @returns Promise that resolves with whoami response
* @throws when whoami request fails
*/
async function getUserIdFromAccessToken(
accessToken: string,
homeserverUrl: string,
): Promise<ReturnType<MatrixClient["whoami"]>> {
try {
const client = createClient({
baseUrl: homeserverUrl,
accessToken: accessToken,
});
return await client.whoami();
} catch (error) {
throw new Error("Failed to retrieve userId using accessToken");
}
}
export const OidcRedirectPage: FC = async () => {
const { t } = useTranslation();
// TODO: probably want a new page title
usePageTitle(t("login_title"));
const navigate = useNavigate();
const location = useLocation();
const [_, setError] = useState<Error>();
const { setClient } = useClient();
if (!setClient) {
return;
}
// TODO: make reactive
try {
const queryParams = new URLSearchParams(location.search);
const { accessToken, refreshToken, homeserverUrl, idToken, clientId, issuer } =
await completeOidcLogin(queryParams);
const {
user_id: userId,
device_id: deviceId,
} = await getUserIdFromAccessToken(accessToken, homeserverUrl);
const session = {
user_id: userId,
access_token: accessToken,
device_id: deviceId!, // TODO: make sure this really is always defined
passwordlessUser: false,
};
console.debug(`TODO: use ${refreshToken}`);
const client = await initClient(
{
baseUrl: homeserverUrl,
accessToken,
userId,
deviceId,
},
false,
);
setClient(client, session);
console.debug(`TODO: use ${clientId}, ${issuer}, ${idToken}`);
// persistOidcAuthenticatedSettings(clientId, issuer, idToken);
const locationState = location.state;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (locationState && locationState.from) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await navigate(locationState.from);
} else {
await navigate("/");
}
} catch (error: any) {
setError(error);
return;
}
return (
<></>
);
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2022-2024 New Vector Ltd.
Copyright 2022-2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
@@ -151,6 +151,25 @@ export interface ConfigOptions {
*/
membership_event_expiry_ms?: number;
};
/**
* Configuration for OIDC issuers where a static client_id has been issued for the app.
* Otherwise dynamic client registration is attempted.
*/
oidc_static_clients?: {
[issuer: string]: { client_id: string };
};
oidc_metadata?: {
client_name?: string;
client_uri?: string;
redirect_uris?: string[];
logo_uri?: string;
application_type?: string;
tos_uri?: string;
policy_uri?: string;
contacts?: string[];
}
}
// Overrides members from ConfigOptions that are always provided by the

View File

@@ -75,6 +75,7 @@ function renderWithMockClient(
<ClientContextProvider
value={{
state: "valid",
oidcClientConfig: null,
disconnected: false,
supportedFeatures: {
reactions: true,

View File

@@ -0,0 +1,96 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { completeAuthorizationCodeGrant, generateOidcAuthorizationUrl } from "matrix-js-sdk/src/oidc/authorize";
import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
import { type IdTokenClaims } from "oidc-client-ts";
import { getOidcCallbackUrl } from "./callbackUrl";
import { OidcClientError } from "./error";
/**
* TODO: Mostly yoinked from element-web, so move to SDK if possible
*
* Start OIDC authorization code flow
* Generates auth params, stores them in session storage and
* Navigates to configured authorization endpoint
* @param delegatedAuthConfig from discovery
* @param clientId this client's id as registered with configured issuer
* @param homeserverUrl target homeserver
* @returns Promise that resolves after we have navigated to auth endpoint
*/
export async function startOidcLogin(
delegatedAuthConfig: OidcClientConfig,
clientId: string,
homeserverUrl: string,
isRegistration?: boolean,
): Promise<void> {
const redirectUri = getOidcCallbackUrl().href;
const nonce = secureRandomString(10);
const prompt = isRegistration ? "create" : undefined;
const authorizationUrl = await generateOidcAuthorizationUrl({
metadata: delegatedAuthConfig,
redirectUri,
clientId,
homeserverUrl,
identityServerUrl: undefined,
nonce,
prompt,
});
window.location.href = authorizationUrl;
}
// TODO: Mostly yoinked from element-web, so move to SDK if possible
type CompleteOidcLoginResponse = {
// url of the homeserver selected during login
homeserverUrl: string;
// accessToken gained from OIDC token issuer
accessToken: string;
// refreshToken gained from OIDC token issuer, when falsy token cannot be refreshed
refreshToken?: string;
// idToken gained from OIDC token issuer
idToken: string;
// this client's id as registered with the OIDC issuer
clientId: string;
// issuer used during authentication
issuer: string;
// claims of the given access token; used during token refresh to validate new tokens
idTokenClaims: IdTokenClaims;
};
/**
* TODO: Mostly yoinked from element-web, so move to SDK if possible
*
* Attempt to complete authorization code flow to get an access token
* @param queryParams the query-parameters extracted from the real query-string of the starting URI.
* @returns Promise that resolves with a CompleteOidcLoginResponse when login was successful
* @throws When we failed to get a valid access token
*/
export async function completeOidcLogin(queryParams: URLSearchParams): Promise<CompleteOidcLoginResponse> {
const code = queryParams.get("code");
const state = queryParams.get("state");
if (!code || !state) {
throw new Error(OidcClientError.InvalidQueryParameters);
}
const { homeserverUrl, tokenResponse, idTokenClaims, oidcClientSettings } =
await completeAuthorizationCodeGrant(code, state);
return {
homeserverUrl,
accessToken: tokenResponse.access_token,
refreshToken: tokenResponse.refresh_token,
idToken: tokenResponse.id_token,
clientId: oidcClientSettings.clientId,
issuer: oidcClientSettings.issuer,
idTokenClaims,
};
}

View File

@@ -0,0 +1,14 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
/**
* The URL to return to after a successful OIDC authentication
*/
export function getOidcCallbackUrl(): URL {
// TODO: save the path somewhere
return new URL("after_login", window.location.origin);
}

View File

@@ -0,0 +1,30 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { MatrixClient, MatrixError, OidcClientConfig } from "matrix-js-sdk";
import { Config } from "../../config/Config";
import { HomeserverMisconfigError } from "../../RichError";
export async function getAuthMetadata(): Promise<OidcClientConfig | null> {
const baseUrl = Config.defaultHomeserverUrl(); // TODO: Make this configurable
if (!baseUrl) {
throw new Error("No homeserver URL configured");
}
const tempClient = new MatrixClient({ baseUrl });
try {
return await tempClient.getAuthMetadata();
} catch (e) {
if (e instanceof MatrixError && e.httpStatus === 404 && e.errcode === "M_UNRECOGNIZED") {
// 404 M_UNRECOGNIZED means the server does not support OIDC
return null;
} else {
throw new HomeserverMisconfigError(e instanceof Error ? e.message : undefined);
}
}
}

16
src/utils/oidc/error.ts Normal file
View File

@@ -0,0 +1,16 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
/**
* TODO: Yoinked from element-web, so move to SDK if possible
*
* Errors thrown by EC during OIDC native flow authentication.
* Intended to be logged, not read by users.
*/
export enum OidcClientError {
InvalidQueryParameters = "Invalid query parameters for OIDC native login. `code` and `state` are required.",
}

View File

@@ -0,0 +1,21 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
// TODO: File yoinked from element-web, so move to SDK if possible
import { type OidcClientConfig } from "matrix-js-sdk/src/matrix";
/**
* Check the create prompt is supported by the OP, if so, we can do a registration flow
* https://openid.net/specs/openid-connect-prompt-create-1_0.html
* @param delegatedAuthConfig config as returned from discovery
* @returns whether user registration is supported
*/
export function isUserRegistrationSupported(delegatedAuthConfig: OidcClientConfig): boolean {
const supportedPrompts = delegatedAuthConfig.prompt_values_supported;
return Array.isArray(supportedPrompts) && supportedPrompts?.includes("create");
}

View File

@@ -0,0 +1,61 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { logger } from "matrix-js-sdk/src/logger";
import { registerOidcClient, type OidcClientConfig } from "matrix-js-sdk";
import { ConfigOptions } from "../../config/ConfigOptions";
import { Config } from "../../config/Config";
import { getOidcCallbackUrl } from "./callbackUrl";
/**
* TODO: Mostly yoinked from element-web, so move to SDK if possible
*
* Get the statically configured clientId for the issuer
* @param issuer delegated auth OIDC issuer
* @param staticOidcClients static client config from config.json
* @returns clientId if found, otherwise undefined
*/
function getStaticOidcClientId(
issuer: string,
staticOidcClients?: ConfigOptions["oidc_static_clients"],
): string | undefined {
// static_oidc_clients are configured with a trailing slash
const issuerWithTrailingSlash = issuer.endsWith("/") ? issuer : issuer + "/";
return staticOidcClients?.[issuerWithTrailingSlash]?.client_id;
}
/**
* TODO: Mostly yoinked from element-web, so move to SDK if possible
*
* Get the statically configured clientId for an OIDC OP
* @param delegatedAuthConfig Auth config from OP
* @returns resolves with clientId
* @throws if no clientId is found
*/
export async function getOidcClientId(
delegatedAuthConfig: OidcClientConfig,
): Promise<string> {
const config = Config.get();
const staticClientId = getStaticOidcClientId(delegatedAuthConfig.issuer, config.oidc_static_clients);
if (staticClientId) {
logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.issuer}`);
return staticClientId;
}
return await registerOidcClient(
delegatedAuthConfig,
{
clientName: config.oidc_metadata?.client_name ?? "Element Call",
clientUri: config.oidc_metadata?.client_uri ?? window.location.origin,
redirectUris: [getOidcCallbackUrl().href],
applicationType: "web",
contacts: config.oidc_metadata?.contacts,
tosUri: config.oidc_metadata?.tos_uri,
policyUri: config.oidc_metadata?.policy_uri,
},
);
}