mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(console): add metadata and support endpoint and audience variables in api guides (#4515)
refactor(console): support endpoint and audience variables in api guides
This commit is contained in:
parent
86113e0415
commit
a449a0a21f
7 changed files with 60 additions and 28 deletions
|
@ -3,6 +3,7 @@ import TabItem from '@mdx/components/TabItem';
|
|||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import Steps from '@/mdx-components/Steps';
|
||||
import Step from '@/mdx-components/Step';
|
||||
import { appendPath } from '@silverhand/essentials';
|
||||
|
||||
<Steps>
|
||||
|
||||
|
@ -64,16 +65,22 @@ pnpm add jose
|
|||
|
||||
### Retrieve Logto's OIDC configurations
|
||||
|
||||
You will need a JWK public key set and the token issuer to verify the signature and source of the received JWS token. All the latest public Logto Authorization Configurations can be found at `https://<your-logto-domain>/oidc/.well-known/openid-configuration`.
|
||||
<p>
|
||||
You will need a JWK public key set and the token issuer to verify the signature and source of the received JWS token. All the latest public Logto Authorization Configurations can be found at <code>{`${appendPath(props.endpoint, '/oidc/.well-known/openid-configuration')}`}</code>.
|
||||
</p>
|
||||
|
||||
e.g. Call `https://logto.dev/oidc/.well-known/openid-configuration`. And locate the following two fields in the response body:
|
||||
<p>
|
||||
e.g. Call <code>{`${appendPath(props.endpoint, '/oidc/.well-known/openid-configuration')}`}</code>. And locate the following two fields in the response body:
|
||||
</p>
|
||||
|
||||
```ts
|
||||
{
|
||||
"jwks_uri": "https://logto.dev/oidc/jwks",
|
||||
"issuer": "https://logto.dev/oidc"
|
||||
}
|
||||
```
|
||||
<pre>
|
||||
<code className="language-ts">
|
||||
{`{
|
||||
"jwks_uri": "${appendPath(props.endpoint, '/oidc/jwks')}",
|
||||
"issuer": "${appendPath(props.endpoint, '/oidc')}"
|
||||
}`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
### Add auth middleware
|
||||
|
||||
|
@ -83,8 +90,9 @@ Jose's `jwtVerify` method may helps you to verify the token's JWS format, token
|
|||
For <a href="https://docs.logto.io/docs/recipes/rbac/" target="_blank" rel="noopener">🔐 RBAC</a>, scope validation is also required.
|
||||
</InlineNotification>
|
||||
|
||||
```ts
|
||||
// auth-middleware.ts
|
||||
<pre>
|
||||
<code className="language-ts">
|
||||
{`// auth-middleware.ts
|
||||
|
||||
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
||||
|
||||
|
@ -98,12 +106,12 @@ export const verifyAuthFromRequest = async (req, res, next) => {
|
|||
// The raw Bearer Token extracted from the request header
|
||||
token,
|
||||
// Generate a jwks using jwks_uri inquired from Logto server
|
||||
createRemoteJWKSet('https://<your-logto-domain>/oidc/jwks'),
|
||||
createRemoteJWKSet('${appendPath(props.endpoint, '/oidc/jwks')}'),
|
||||
{
|
||||
// Expected issuer of the token, should be issued by the Logto server
|
||||
issuer: 'https://<your-logto-domain>/oidc',
|
||||
issuer: '${appendPath(props.endpoint, '/oidc')}',
|
||||
// Expected audience token, should be the resource indicator of the current API
|
||||
audience: '<your request listener resource indicator>',
|
||||
audience: '${props.audience}',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -114,8 +122,9 @@ export const verifyAuthFromRequest = async (req, res, next) => {
|
|||
userId = payload.sub;
|
||||
|
||||
return next();
|
||||
};
|
||||
```
|
||||
};`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
</Step>
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ import { type GuideMetadata } from '../types';
|
|||
|
||||
const metadata: Readonly<GuideMetadata> = Object.freeze({
|
||||
name: 'Express',
|
||||
description: 'Protect your API on node (Express)',
|
||||
description:
|
||||
'Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.',
|
||||
target: 'API',
|
||||
});
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ export type GuideContextType = {
|
|||
origin: string;
|
||||
callback: string;
|
||||
};
|
||||
audience?: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
|
@ -50,6 +51,7 @@ export const GuideContext = createContext<GuideContextType>({
|
|||
postLogoutRedirectUris: [],
|
||||
isCompact: false,
|
||||
sampleUrls: { origin: '', callback: '' },
|
||||
audience: '',
|
||||
});
|
||||
|
||||
function Guide({ className, guideId, isEmpty, isLoading, onClose }: Props) {
|
||||
|
|
|
@ -1,29 +1,38 @@
|
|||
import { type Resource } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useMemo } from 'react';
|
||||
import { useContext, useMemo } from 'react';
|
||||
|
||||
import guides from '@/assets/docs/guides';
|
||||
import Guide, { GuideContext, type GuideContextType } from '@/components/Guide';
|
||||
import { AppDataContext } from '@/contexts/AppDataProvider';
|
||||
import useCustomDomain from '@/hooks/use-custom-domain';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
guideId: string;
|
||||
apiResource?: Resource;
|
||||
isCompact?: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function ApiGuide({ className, guideId, isCompact, onClose }: Props) {
|
||||
function ApiGuide({ className, guideId, apiResource, isCompact, onClose }: Props) {
|
||||
const { tenantEndpoint } = useContext(AppDataContext);
|
||||
const { applyDomain: applyCustomDomain } = useCustomDomain();
|
||||
const guide = guides.find(({ id }) => id === guideId);
|
||||
|
||||
const memorizedContext = useMemo(
|
||||
() =>
|
||||
conditional(
|
||||
!!guide && {
|
||||
metadata: guide.metadata,
|
||||
Logo: guide.Logo,
|
||||
isCompact: Boolean(isCompact),
|
||||
}
|
||||
!!guide &&
|
||||
!!apiResource && {
|
||||
metadata: guide.metadata,
|
||||
Logo: guide.Logo,
|
||||
isCompact: Boolean(isCompact),
|
||||
endpoint: applyCustomDomain(tenantEndpoint?.href ?? ''),
|
||||
audience: apiResource.indicator,
|
||||
}
|
||||
) satisfies GuideContextType | undefined,
|
||||
[guide, isCompact]
|
||||
[apiResource, applyCustomDomain, guide, isCompact, tenantEndpoint?.href]
|
||||
);
|
||||
|
||||
return memorizedContext ? (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { type Resource } from '@logto/schemas';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -14,10 +15,11 @@ import ApiGuide from '../ApiGuide';
|
|||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
apiResource: Resource;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function GuideDrawer({ onClose }: Props) {
|
||||
function GuideDrawer({ apiResource, onClose }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.guide' });
|
||||
const guides = useApiGuideMetadata();
|
||||
const [selectedGuide, setSelectedGuide] = useState<SelectedGuide>();
|
||||
|
@ -59,6 +61,7 @@ function GuideDrawer({ onClose }: Props) {
|
|||
isCompact
|
||||
className={styles.guide}
|
||||
guideId={selectedGuide.id}
|
||||
apiResource={apiResource}
|
||||
onClose={() => {
|
||||
setSelectedGuide(undefined);
|
||||
}}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { type Resource } from '@logto/schemas';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import ModalHeader from '@/components/Guide/ModalHeader';
|
||||
|
@ -9,10 +10,11 @@ import * as styles from './index.module.scss';
|
|||
|
||||
type Props = {
|
||||
guideId: string;
|
||||
apiResource?: Resource;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function GuideModal({ guideId, onClose }: Props) {
|
||||
function GuideModal({ guideId, apiResource, onClose }: Props) {
|
||||
return (
|
||||
<Modal shouldCloseOnEsc isOpen className={modalStyles.fullScreen} onRequestClose={onClose}>
|
||||
<div className={styles.modalContainer}>
|
||||
|
@ -25,7 +27,12 @@ function GuideModal({ guideId, onClose }: Props) {
|
|||
requestSuccessMessage="guide.request_guide_successfully"
|
||||
onClose={onClose}
|
||||
/>
|
||||
<ApiGuide className={styles.guide} guideId={guideId} onClose={onClose} />
|
||||
<ApiGuide
|
||||
className={styles.guide}
|
||||
guideId={guideId}
|
||||
apiResource={apiResource}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -83,6 +83,7 @@ function ApiResourceDetails() {
|
|||
return (
|
||||
<GuideModal
|
||||
guideId={guideId}
|
||||
apiResource={data}
|
||||
onClose={() => {
|
||||
navigate(`/api-resources/${id}`);
|
||||
}}
|
||||
|
@ -129,7 +130,7 @@ function ApiResourceDetails() {
|
|||
}}
|
||||
/>
|
||||
<Drawer isOpen={isGuideDrawerOpen} onClose={onCloseDrawer}>
|
||||
<GuideDrawer onClose={onCloseDrawer} />
|
||||
<GuideDrawer apiResource={data} onClose={onCloseDrawer} />
|
||||
</Drawer>
|
||||
<ActionMenu
|
||||
buttonProps={{ icon: <More className={styles.moreIcon} />, size: 'large' }}
|
||||
|
|
Loading…
Reference in a new issue