0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-24 22:41:28 -05:00

refactor(core,console): update well-known for endpoints

This commit is contained in:
Gao Sun 2023-02-12 14:28:57 +08:00
parent ac199604bf
commit c225719d70
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
12 changed files with 62 additions and 52 deletions

View file

@ -45,7 +45,8 @@ import {
RoleDetailsTabs,
SignInExperiencePage,
UserDetailsTabs,
} from './consts/page-tabs';
adminTenantEndpoint,
} from './consts';
import AppContent from './containers/AppContent';
import AppEndpointsProvider, { AppEndpointsContext } from './containers/AppEndpointsProvider';
import ApiResourcePermissions from './pages/ApiResourceDetails/ApiResourcePermissions';
@ -63,9 +64,9 @@ void initI18n();
const Main = () => {
const swrOptions = useSwrOptions();
const { app, console } = useContext(AppEndpointsContext);
const { userEndpoint } = useContext(AppEndpointsContext);
if (!app || !console) {
if (!userEndpoint) {
return <AppLoading />;
}
@ -162,7 +163,7 @@ const App = () => (
<AppEndpointsProvider>
<LogtoProvider
config={{
endpoint: window.location.origin,
endpoint: adminTenantEndpoint,
appId: adminConsoleApplicationId,
resources: [managementApi.indicator, meApi.indicator],
scopes: [UserScope.Identities, UserScope.CustomData, managementApi.scopeAll],

View file

@ -2,6 +2,8 @@ export * from './applications';
export * from './connectors';
export * from './logs';
export * from './management-api';
export * from './tenants';
export * from './page-tabs';
export const themeStorageKey = 'logto:admin_console:theme';
export const requestTimeout = 20_000;

View file

@ -0,0 +1,4 @@
import { defaultTenantId } from '@logto/schemas';
export const adminTenantEndpoint = process.env.ADMIN_TENANT_ENDPOINT ?? window.location.origin;
export const userTenantId = process.env.USER_TENANT_ID ?? defaultTenantId;

View file

@ -2,13 +2,15 @@ import ky from 'ky';
import type { ReactNode } from 'react';
import { useMemo, useEffect, createContext, useState } from 'react';
import { adminTenantEndpoint, userTenantId } from '@/consts';
type Props = {
children: ReactNode;
};
export type AppEndpoints = {
app?: URL;
console?: URL;
userEndpoint?: URL;
adminEndpoint?: URL;
};
export type AppEndpointKey = keyof AppEndpoints;
@ -21,10 +23,10 @@ const AppEndpointsProvider = ({ children }: Props) => {
useEffect(() => {
const getEndpoint = async () => {
const { app, console } = await ky
.get(new URL('api/.well-known/endpoints', window.location.origin))
.json<{ app: string; console: string }>();
setEndpoints({ app: new URL(app), console: new URL(console) });
const { user } = await ky
.get(new URL(`api/.well-known/endpoints/${userTenantId}`, adminTenantEndpoint))
.json<{ user: string }>();
setEndpoints({ userEndpoint: new URL(user) });
};
void getEndpoint();

View file

@ -6,7 +6,6 @@ import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { managementApi, requestTimeout } from '@/consts';
import type { AppEndpointKey } from '@/containers/AppEndpointsProvider';
import { AppEndpointsContext } from '@/containers/AppEndpointsProvider';
export class RequestError extends Error {
@ -20,18 +19,17 @@ export class RequestError extends Error {
}
}
type Props = {
endpointKey?: AppEndpointKey;
type StaticApiProps = {
prefixUrl: string;
hideErrorToast?: boolean;
resourceIndicator?: string;
};
const useApi = ({
export const useStaticApi = ({
prefixUrl,
hideErrorToast,
endpointKey = 'app',
resourceIndicator = managementApi.indicator,
}: Props = {}) => {
const endpoints = useContext(AppEndpointsContext);
}: StaticApiProps) => {
const { isAuthenticated, getAccessToken } = useLogto();
const { t, i18n } = useTranslation(undefined, { keyPrefix: 'admin_console' });
@ -52,7 +50,7 @@ const useApi = ({
const api = useMemo(
() =>
ky.create({
prefixUrl: endpoints[endpointKey],
prefixUrl,
timeout: requestTimeout,
hooks: {
beforeError: hideErrorToast
@ -76,8 +74,7 @@ const useApi = ({
},
}),
[
endpoints,
endpointKey,
prefixUrl,
hideErrorToast,
toastError,
isAuthenticated,
@ -90,4 +87,10 @@ const useApi = ({
return api;
};
const useApi = (props: Omit<StaticApiProps, 'prefixUrl'> = {}) => {
const { userEndpoint } = useContext(AppEndpointsContext);
return useStaticApi({ ...props, prefixUrl: userEndpoint?.toString() ?? '' });
};
export default useApi;

View file

@ -9,10 +9,10 @@ import type { BareFetcher } from 'swr';
import useSWR from 'swr';
import { z } from 'zod';
import { meApi, themeStorageKey } from '@/consts';
import { meApi, themeStorageKey, adminTenantEndpoint } from '@/consts';
import type { RequestError } from './use-api';
import useApi from './use-api';
import { useStaticApi } from './use-api';
import useLogtoUserId from './use-logto-user-id';
const userPreferencesGuard = z.object({
@ -36,7 +36,7 @@ const useUserPreferences = () => {
const { isAuthenticated, error: authError } = useLogto();
const userId = useLogtoUserId();
const shouldFetch = isAuthenticated && !authError && userId;
const api = useApi({ endpointKey: 'console', resourceIndicator: meApi.indicator });
const api = useStaticApi({ prefixUrl: adminTenantEndpoint, resourceIndicator: meApi.indicator });
const fetcher = useCallback<BareFetcher>(
async (resource, init) => {
const response = await api.get(resource, init);

View file

@ -4,10 +4,11 @@ import type { Optional } from '@silverhand/essentials';
import i18next from 'i18next';
import type { MDXProps } from 'mdx/types';
import type { LazyExoticComponent } from 'react';
import { cloneElement, lazy, Suspense, useEffect, useState } from 'react';
import { useContext, cloneElement, lazy, Suspense, useEffect, useState } from 'react';
import CodeEditor from '@/components/CodeEditor';
import TextLink from '@/components/TextLink';
import { AppEndpointsContext } from '@/containers/AppEndpointsProvider';
import DetailsSummary from '@/mdx-components/DetailsSummary';
import type { SupportedSdk } from '@/types/applications';
import { applicationTypeAndSdkTypeMappings } from '@/types/applications';
@ -53,6 +54,7 @@ const Guide = ({ app, isCompact, onClose }: Props) => {
const sdks = applicationTypeAndSdkTypeMappings[appType];
const [selectedSdk, setSelectedSdk] = useState<Optional<SupportedSdk>>(sdks[0]);
const [activeStepIndex, setActiveStepIndex] = useState(-1);
const { userEndpoint } = useContext(AppEndpointsContext);
// Directly close guide if no SDK available
useEffect(() => {
@ -110,7 +112,7 @@ const Guide = ({ app, isCompact, onClose }: Props) => {
<GuideComponent
appId={appId}
appSecret={appSecret}
endpoint={window.location.origin}
endpoint={userEndpoint}
redirectUris={oidcClientMetadata.redirectUris}
postLogoutRedirectUris={oidcClientMetadata.postLogoutRedirectUris}
activeStepIndex={activeStepIndex}

View file

@ -38,7 +38,7 @@ type GetStartedMetadata = {
const useGetStartedMetadata = () => {
const { getDocumentationUrl } = useDocumentationUrl();
const { configs, updateConfigs } = useConfigs();
const { app } = useContext(AppEndpointsContext);
const { userEndpoint } = useContext(AppEndpointsContext);
const theme = useTheme();
const isLightMode = theme === AppearanceMode.LightMode;
const { data: demoApp, error } = useSWR<Application, RequestError>(
@ -69,7 +69,7 @@ const useGetStartedMetadata = () => {
isHidden: hideDemo,
onClick: async () => {
void updateConfigs({ demoChecked: true });
window.open(new URL('/demo-app', app), '_blank');
window.open(new URL('/demo-app', userEndpoint), '_blank');
},
},
{
@ -135,17 +135,18 @@ const useGetStartedMetadata = () => {
return metadataItems.filter(({ isHidden }) => !isHidden);
}, [
getDocumentationUrl,
hideDemo,
isLightMode,
navigate,
configs?.applicationCreated,
configs?.demoChecked,
configs?.furtherReadingsChecked,
configs?.passwordlessConfigured,
configs?.applicationCreated,
configs?.signInExperienceCustomized,
configs?.passwordlessConfigured,
configs?.socialSignInConfigured,
configs?.furtherReadingsChecked,
hideDemo,
updateConfigs,
userEndpoint,
navigate,
getDocumentationUrl,
]);
return {

View file

@ -31,7 +31,7 @@ const Preview = ({ signInExperience, className }: Props) => {
const { data: allConnectors } = useSWR<ConnectorResponse[], RequestError>('api/connectors');
const previewRef = useRef<HTMLIFrameElement>(null);
const { customPhrases, languages } = useUiLanguages();
const { app: appEndpoint } = useContext(AppEndpointsContext);
const { userEndpoint } = useContext(AppEndpointsContext);
const modeOptions = useMemo(() => {
const light = { value: AppearanceMode.LightMode, title: t('sign_in_exp.preview.light') };
@ -120,9 +120,9 @@ const Preview = ({ signInExperience, className }: Props) => {
previewRef.current?.contentWindow?.postMessage(
{ sender: 'ac_preview', config },
appEndpoint?.origin ?? ''
userEndpoint?.origin ?? ''
);
}, [appEndpoint?.origin, config, customPhrases]);
}, [userEndpoint?.origin, config, customPhrases]);
useEffect(() => {
postPreviewMessage();
@ -210,7 +210,7 @@ const Preview = ({ signInExperience, className }: Props) => {
ref={previewRef}
// Allow all sandbox rules
sandbox={undefined}
src={new URL('/sign-in?preview=true', appEndpoint).toString()}
src={new URL('/sign-in?preview=true', userEndpoint).toString()}
tabIndex={-1}
title={t('sign_in_exp.preview.title')}
/>

View file

@ -1,6 +1,6 @@
import type { ConnectorMetadata } from '@logto/connector-kit';
import { ConnectorType } from '@logto/connector-kit';
import { adminConsoleApplicationId } from '@logto/schemas';
import { adminConsoleApplicationId, adminTenantId } from '@logto/schemas';
import etag from 'etag';
import { EnvSet } from '#src/env-set/index.js';
@ -9,21 +9,22 @@ import { getApplicationIdFromInteraction } from '#src/libraries/session.js';
import type { AnonymousRouter, RouterInitArgs } from './types.js';
export default function wellKnownRoutes<T extends AnonymousRouter>(
...[router, { provider, libraries }]: RouterInitArgs<T>
...[router, { provider, libraries, id }]: RouterInitArgs<T>
) {
const {
signInExperiences: { getSignInExperienceForApplication },
connectors: { getLogtoConnectors },
} = libraries;
router.get('/.well-known/endpoints', async (ctx, next) => {
ctx.body = {
console: EnvSet.values.adminUrlSet.endpoint,
app: EnvSet.values.urlSet.endpoint,
};
if (id === adminTenantId) {
router.get('/.well-known/endpoints/:tenantId', async (ctx, next) => {
ctx.body = {
user: EnvSet.values.urlSet.endpoint.replace('*', ctx.params.tenantId ?? '*'),
};
return next();
});
return next();
});
}
router.get(
'/.well-known/sign-in-exp',

View file

@ -1,5 +0,0 @@
import type TenantContext from './TenantContext.js';
export default abstract class TenantPoolContext<Tenant extends TenantContext = TenantContext> {
public abstract get(tenantId: string): Promise<Tenant>;
}

View file

@ -63,7 +63,6 @@ const App = () => {
<AppBoundary>
<Routes>
<Route element={<AppContent />}>
<Route path="/" element={<Navigate replace to="/sign-in" />} />
<Route path="/sign-in/consent" element={<Consent />} />
<Route
path="/unknown-session"