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:
parent
ac199604bf
commit
c225719d70
12 changed files with 62 additions and 52 deletions
|
@ -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],
|
||||
|
|
|
@ -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;
|
||||
|
|
4
packages/console/src/consts/tenants.ts
Normal file
4
packages/console/src/consts/tenants.ts
Normal 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;
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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')}
|
||||
/>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue