diff --git a/packages/console/src/assets/docs/guides/generate-metadata.js b/packages/console/src/assets/docs/guides/generate-metadata.js index 8a8bcf7bc..5093ada71 100644 --- a/packages/console/src/assets/docs/guides/generate-metadata.js +++ b/packages/console/src/assets/docs/guides/generate-metadata.js @@ -56,8 +56,7 @@ for (const { name, logo } of metadata) { Logo: ${logo ? `lazy(async () => import('./${name}/${logo}'))` : 'undefined'}, Component: lazy(async () => import('./${name}/README.mdx')), metadata: ${camelCase(name)}, - }, -` + },` ); } diff --git a/packages/console/src/assets/docs/guides/index.ts b/packages/console/src/assets/docs/guides/index.ts index 9ce084862..1e4a6815e 100644 --- a/packages/console/src/assets/docs/guides/index.ts +++ b/packages/console/src/assets/docs/guides/index.ts @@ -21,6 +21,7 @@ import webGptPlugin from './web-gpt-plugin/index'; import webJava from './web-java/index'; import webNext from './web-next/index'; import webNextAppRouter from './web-next-app-router/index'; +import webOutline from './web-outline/index'; import webPhp from './web-php/index'; import webPython from './web-python/index'; @@ -103,19 +104,18 @@ const guides: Readonly = Object.freeze([ Component: lazy(async () => import('./web-express/README.mdx')), metadata: webExpress, }, - { - id: 'web-gpt-plugin', - Logo: lazy(async () => import('./web-gpt-plugin/logo.svg')), - Component: lazy(async () => import('./web-gpt-plugin/README.mdx')), - metadata: webGptPlugin, - }, - { id: 'web-go', Logo: lazy(async () => import('./web-go/logo.svg')), Component: lazy(async () => import('./web-go/README.mdx')), metadata: webGo, }, + { + id: 'web-gpt-plugin', + Logo: lazy(async () => import('./web-gpt-plugin/logo.svg')), + Component: lazy(async () => import('./web-gpt-plugin/README.mdx')), + metadata: webGptPlugin, + }, { id: 'web-java', Logo: lazy(async () => import('./web-java/logo.svg')), @@ -134,6 +134,12 @@ const guides: Readonly = Object.freeze([ Component: lazy(async () => import('./web-next-app-router/README.mdx')), metadata: webNextAppRouter, }, + { + id: 'web-outline', + Logo: lazy(async () => import('./web-outline/logo.svg')), + Component: lazy(async () => import('./web-outline/README.mdx')), + metadata: webOutline, + }, { id: 'web-php', Logo: lazy(async () => import('./web-php/logo.svg')), diff --git a/packages/console/src/assets/docs/guides/web-gpt-plugin/README.mdx b/packages/console/src/assets/docs/guides/web-gpt-plugin/README.mdx index a97a56ed6..143a299bd 100644 --- a/packages/console/src/assets/docs/guides/web-gpt-plugin/README.mdx +++ b/packages/console/src/assets/docs/guides/web-gpt-plugin/README.mdx @@ -11,9 +11,7 @@ import logtoSignInExperience from './assets/logto-sign-in-experience.png'; - + [ChatGPT plugins](https://openai.com/blog/chatgpt-plugins) are tools designed specifically for language models, and help ChatGPT access up-to-date information, run computations, or use third-party services. diff --git a/packages/console/src/assets/docs/guides/web-outline/README.mdx b/packages/console/src/assets/docs/guides/web-outline/README.mdx new file mode 100644 index 000000000..8386bda5b --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-outline/README.mdx @@ -0,0 +1,72 @@ +import UriInputField from '@/mdx-components-v2/UriInputField'; +import Steps from '@/mdx-components-v2/Steps'; +import Step from '@/mdx-components-v2/Step'; + +import logtoSignInExperience from './assets/logto-sign-in-experience.png'; +import outlineHome from './assets/outline-home.png'; +import outlineSignIn from './assets/outline-sign-in.png'; +import EnvironmentVariables from './components/EnvironmentVariables'; + + + + + +[Outline](https://github.com/outline/outline) serves as a knowledge base for growing teams. To get started, you need an Outline hosting environment with access to environment variables. + +Follow the steps outlined in the [Outline hosting guide](https://docs.getoutline.com/s/hosting/) until you reach the authentication configuration step. We'll leverage the Outline support of [OIDC-compatible authentication providers](https://docs.getoutline.com/s/hosting/doc/oidc-8CPBm6uC0I) in this guide. + + + + + +Add a redirect URI by replacing the origin to your outline host origin. + + + +For example, if your Outline is hosted at `https://outline.example.com`, the redirect URI should be `https://outline.example.com/auth/oidc.callback`. + +Don't forget to click the **Save** button. + + + + + +Refer to the following table for the necessary configuration details: + + + +Here's another table containing additional variables: + +| Outline Environment Variable | Description | +| ---------------------------- | ------------------------------ | +| OIDC_USERNAME_CLAIM | Set to `username` | +| OIDC_DISPLAY_NAME | Optional - customize as needed | +| OIDC_SCOPES | Keep default; no need to set | + + + + + +Since Outline requires user email to be provided, you need to configure email sign-in or a social sign-in that provides trustworthy email address, such as Google sign-in. + +See [Passwordless sign-in by adding connectors](https://docs.logto.io/docs/tutorials/get-started/passwordless-sign-in-by-adding-connectors/) to learn more about configuring passwordless sign-in in Logto. + + + + + +Start the Outline instance and access its endpoint. You should see a button in the center labeled "Continue with OpenID Connect"; it can be customized by setting the `OIDC_DISPLAY_NAME` environment variable. + +Outline sign-in page + +Click on the button, and you will be directed to the Logto sign-in experience. + +Logto sign-in experience + +If everything has been configured correctly, once you complete the sign-in or registration process in Logto, you will be redirected back to Outline. You can then see your personal information displayed in the bottom left corner of the page. + +Outline home + + + + diff --git a/packages/console/src/assets/docs/guides/web-outline/assets/logto-sign-in-experience.png b/packages/console/src/assets/docs/guides/web-outline/assets/logto-sign-in-experience.png new file mode 100644 index 000000000..d228bf314 Binary files /dev/null and b/packages/console/src/assets/docs/guides/web-outline/assets/logto-sign-in-experience.png differ diff --git a/packages/console/src/assets/docs/guides/web-outline/assets/outline-home.png b/packages/console/src/assets/docs/guides/web-outline/assets/outline-home.png new file mode 100644 index 000000000..5f78542c9 Binary files /dev/null and b/packages/console/src/assets/docs/guides/web-outline/assets/outline-home.png differ diff --git a/packages/console/src/assets/docs/guides/web-outline/assets/outline-sign-in.png b/packages/console/src/assets/docs/guides/web-outline/assets/outline-sign-in.png new file mode 100644 index 000000000..0d4f11143 Binary files /dev/null and b/packages/console/src/assets/docs/guides/web-outline/assets/outline-sign-in.png differ diff --git a/packages/console/src/assets/docs/guides/web-outline/components/EnvironmentVariables.tsx b/packages/console/src/assets/docs/guides/web-outline/components/EnvironmentVariables.tsx new file mode 100644 index 000000000..aff527ca0 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-outline/components/EnvironmentVariables.tsx @@ -0,0 +1,67 @@ +import { type SnakeCaseOidcConfig } from '@logto/schemas'; +import { useContext } from 'react'; +import useSWR from 'swr'; + +import { openIdProviderConfigPath } from '@/consts/oidc'; +import CopyToClipboard from '@/ds-components/CopyToClipboard'; +import { type RequestError } from '@/hooks/use-api'; +import { GuideContext } from '@/pages/Applications/components/GuideV2'; + +export default function EnvironmentVariables() { + const { + app: { id, secret }, + } = useContext(GuideContext); + const { data } = useSWR(openIdProviderConfigPath); + const authorizationEndpoint = data?.authorization_endpoint ?? '[LOADING]'; + const tokenEndpoint = data?.token_endpoint ?? '[LOADING]'; + const userinfoEndpoint = data?.userinfo_endpoint ?? '[LOADING]'; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Outline Environment VariableLogto Display NameValue
OIDC_CLIENT_IDApp ID + +
OIDC_CLIENT_SECRETApp secret + +
OIDC_AUTH_URIAuthorization endpoint + +
OIDC_TOKEN_URIToken endpoint + +
OIDC_USERINFO_URIUserinfo endpoint + +
+ ); +} diff --git a/packages/console/src/assets/docs/guides/web-outline/index.ts b/packages/console/src/assets/docs/guides/web-outline/index.ts new file mode 100644 index 000000000..40858f295 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-outline/index.ts @@ -0,0 +1,11 @@ +import { ApplicationType } from '@logto/schemas'; + +import { type GuideMetadata } from '../types'; + +const metadata: Readonly = Object.freeze({ + name: 'Outline', + description: 'Use Logto as an identity provider for Outline', + target: ApplicationType.Traditional, +}); + +export default metadata; diff --git a/packages/console/src/assets/docs/guides/web-outline/logo.svg b/packages/console/src/assets/docs/guides/web-outline/logo.svg new file mode 100644 index 000000000..eadcc1942 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-outline/logo.svg @@ -0,0 +1,5 @@ + + + diff --git a/packages/console/src/pages/Applications/components/GuideV2/index.module.scss b/packages/console/src/pages/Applications/components/GuideV2/index.module.scss index a5461f8e3..a206b9c7b 100644 --- a/packages/console/src/pages/Applications/components/GuideV2/index.module.scss +++ b/packages/console/src/pages/Applications/components/GuideV2/index.module.scss @@ -25,6 +25,29 @@ margin-block: _.unit(2); padding-inline-start: _.unit(1); } + + section table { + border-spacing: 0; + border: 1px solid var(--color-border); + font: var(--font-body-2); + + tr { + width: 100%; + } + + td, + th { + padding: _.unit(2) _.unit(4); + } + + thead { + font: var(--font-title-3); + } + + tbody td { + border-top: 1px solid var(--color-border); + } + } } } diff --git a/packages/console/src/pages/Applications/components/GuideV2/index.tsx b/packages/console/src/pages/Applications/components/GuideV2/index.tsx index 3da4f605e..9b59bd452 100644 --- a/packages/console/src/pages/Applications/components/GuideV2/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideV2/index.tsx @@ -39,8 +39,19 @@ type GuideContextType = { }; }; -// eslint-disable-next-line no-restricted-syntax -export const GuideContext = createContext({} as GuideContextType); +export const GuideContext = createContext({ + // The following `as` is for context initialization, they won't be used in production except for + // HMR. + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, no-restricted-syntax + metadata: {} as GuideMetadata, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, no-restricted-syntax + app: {} as Application, + endpoint: '', + redirectUris: [], + postLogoutRedirectUris: [], + isCompact: false, + sampleUrls: { origin: '', callback: '' }, +}); type Props = { guideId: string;