diff --git a/website/Dockerfile b/website/Dockerfile index 735dcf079f..207f338c75 100644 --- a/website/Dockerfile +++ b/website/Dockerfile @@ -23,4 +23,4 @@ RUN npm run build FROM docker.io/library/nginx:1.29.0 -COPY --from=docs-builder /work/website/build /usr/share/nginx/html +COPY --from=docs-builder /work/website/docs/build /usr/share/nginx/html diff --git a/website/api/docusaurus.config.esm.mjs b/website/api/docusaurus.config.esm.mjs index bcee153337..9843eac991 100644 --- a/website/api/docusaurus.config.esm.mjs +++ b/website/api/docusaurus.config.esm.mjs @@ -2,6 +2,7 @@ * @file Docusaurus config. * * @import { UserThemeConfig, UserThemeConfigExtra } from "@goauthentik/docusaurus-config"; + * @import { AKReleasesPluginOptions } from "@goauthentik/docusaurus-theme/releases/plugin" * @import * as OpenApiPlugin from "docusaurus-plugin-openapi-docs"; * @import {Options as PresetOptions} from '@docusaurus/preset-classic'; */ @@ -12,10 +13,12 @@ import { basename, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { createDocusaurusConfig } from "@goauthentik/docusaurus-config"; +import { prepareReleaseEnvironment } from "@goauthentik/docusaurus-theme/releases/utils"; import { remarkLinkRewrite } from "@goauthentik/docusaurus-theme/remark"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); const require = createRequire(import.meta.url); +const releaseEnvironment = prepareReleaseEnvironment(); const rootStaticDirectory = resolve(__dirname, "..", "static"); const authentikModulePath = resolve(__dirname, "..", ".."); @@ -67,6 +70,7 @@ export default createDocusaurusConfig({ theme: { customCss: [require.resolve("@goauthentik/docusaurus-config/css/index.css")], }, + pages: false, docs: { routeBasePath: "/", path: ".", @@ -103,6 +107,13 @@ export default createDocusaurusConfig({ //#region Plugins plugins: [ + [ + "@goauthentik/docusaurus-theme/releases/plugin", + /** @type {AKReleasesPluginOptions} */ ({ + docsDirectory: __dirname, + environment: releaseEnvironment, + }), + ], [ "docusaurus-plugin-openapi-docs", { diff --git a/website/api/sidebar.mjs b/website/api/sidebar.mjs index f67eb0e207..0eaef48673 100644 --- a/website/api/sidebar.mjs +++ b/website/api/sidebar.mjs @@ -11,7 +11,9 @@ import "./ensure-reference-sidebar.mjs"; // @ts-ignore - Allows for project-wide type checking when partially building docs. import apiReference from "./reference/sidebar"; -const DOCS_URL = process.env.DOCS_URL || "https://docs.goauthentik.io"; +import { prepareReleaseEnvironment } from "@goauthentik/docusaurus-theme/releases/utils"; + +const releaseEnvironment = prepareReleaseEnvironment(); /** * @type {SidebarItemConfig} @@ -21,7 +23,7 @@ const sidebar = { { type: "link", label: "← Back to Developer Docs", - href: new URL("/developer-docs", DOCS_URL).href, + href: new URL("/developer-docs", releaseEnvironment.preReleaseOrigin).href, className: "navbar-sidebar__upwards", }, { diff --git a/website/api/src/theme/DocSidebarItem/Link/index.tsx b/website/api/src/theme/DocSidebarItem/Link/index.tsx index 924300693e..85e839ac36 100644 --- a/website/api/src/theme/DocSidebarItem/Link/index.tsx +++ b/website/api/src/theme/DocSidebarItem/Link/index.tsx @@ -1,5 +1,7 @@ import "./styles.css"; +import { useCachedVersionPluginData } from "@goauthentik/docusaurus-theme/components/VersionPicker/utils.ts"; + import isInternalUrl from "@docusaurus/isInternalUrl"; import Link from "@docusaurus/Link"; import { isActiveSidebarItem } from "@docusaurus/plugin-content-docs/client"; @@ -7,16 +9,7 @@ import { ThemeClassNames } from "@docusaurus/theme-common"; import type { Props } from "@theme/DocSidebarItem/Link"; import IconExternalLink from "@theme/Icon/ExternalLink"; import clsx from "clsx"; -import React from "react"; - -const docsURL = new URL(process.env.DOCS_URL || "https://docs.goauthentik.io"); -function isInternalUrlOrDocsUrl(url: string) { - if (isInternalUrl(url)) return true; - - const inputURL = new URL(url); - - return inputURL.origin === docsURL.origin; -} +import React, { useMemo } from "react"; const DocSidebarItemLink: React.FC = ({ item, @@ -29,7 +22,18 @@ const DocSidebarItemLink: React.FC = ({ }) => { const { href, label, className, autoAddBaseUrl } = item; const isActive = isActiveSidebarItem(item, activePath); - const internalLink = isInternalUrlOrDocsUrl(href); + const versionPluginData = useCachedVersionPluginData(); + const apiReferenceOrigin = versionPluginData?.env.apiReferenceOrigin; + + const internalLink = useMemo(() => { + if (isInternalUrl(href)) return true; + + if (!apiReferenceOrigin) return false; + + const inputURL = new URL(href); + + return inputURL.origin === apiReferenceOrigin; + }, [href, apiReferenceOrigin]); return (
  • ; -} - -export default Home; diff --git a/website/docs/sidebar.mjs b/website/docs/sidebar.mjs index 9bc16b65cf..03d0171ccd 100644 --- a/website/docs/sidebar.mjs +++ b/website/docs/sidebar.mjs @@ -10,11 +10,13 @@ import { fileURLToPath } from "node:url"; import { collectReleaseFiles, createReleaseSidebarEntries, + prepareReleaseEnvironment, } from "@goauthentik/docusaurus-theme/releases/utils"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); const releases = collectReleaseFiles(path.join(__dirname)); +const releaseEnvironment = prepareReleaseEnvironment(); /** * @type {SidebarItemConfig[]} @@ -640,7 +642,7 @@ const items = [ items: [ { type: "link", - href: "https://api.goauthentik.io", + href: releaseEnvironment.apiReferenceOrigin, label: "API Overview", className: "api-overview", }, diff --git a/website/docusaurus-theme/components/VersionPicker/VersionDropdown.tsx b/website/docusaurus-theme/components/VersionPicker/VersionDropdown.tsx index e4689e977a..66b8431486 100644 --- a/website/docusaurus-theme/components/VersionPicker/VersionDropdown.tsx +++ b/website/docusaurus-theme/components/VersionPicker/VersionDropdown.tsx @@ -4,16 +4,16 @@ import { createVersionURL, parseBranchSemVer } from "#components/VersionPicker/u import clsx from "clsx"; import React, { memo } from "react"; +import { AKReleasesPluginEnvironment } from "releases/utils.mjs"; export interface VersionDropdownProps { /** * The hostname of the client. */ hostname: string | null; - /** - * The branch of the documentation. - */ - branch?: string; + + environment: AKReleasesPluginEnvironment; + /** * The available versions of the documentation. * @@ -25,7 +25,8 @@ export interface VersionDropdownProps { /** * A dropdown that shows the available versions of the documentation. */ -export const VersionDropdown = memo(({ branch, releases }) => { +export const VersionDropdown = memo(({ environment, releases }) => { + const { branch, preReleaseOrigin } = environment; const parsedSemVer = parseBranchSemVer(branch); const currentLabel = parsedSemVer || "Pre-release"; @@ -50,7 +51,7 @@ export const VersionDropdown = memo(({ branch, releases }) <>
  • (({ branch, releases }) ) : null} {visibleReleases.map((semVer, idx) => { - const label = semVer; + let label = semVer; - // TODO: Flesh this out after we settle on versioning strategy. - // if (idx === 0) { - // label += " (Current Release)"; - // } + if (idx === 0) { + label += " (Current Release)"; + } return (
  • diff --git a/website/docusaurus-theme/components/VersionPicker/VersionPickerLoader.tsx b/website/docusaurus-theme/components/VersionPicker/VersionPickerLoader.tsx index 053d39ff3a..4db18c936a 100644 --- a/website/docusaurus-theme/components/VersionPicker/VersionPickerLoader.tsx +++ b/website/docusaurus-theme/components/VersionPicker/VersionPickerLoader.tsx @@ -1,10 +1,10 @@ -import { LocalhostAliases, ProductionURL, useHostname } from "#components/VersionPicker/utils.ts"; +import { useHostname } from "#components/VersionPicker/utils.ts"; import { VersionDropdown } from "#components/VersionPicker/VersionDropdown.tsx"; import { AKReleasesPluginData } from "@goauthentik/docusaurus-theme/releases/plugin"; import useIsBrowser from "@docusaurus/useIsBrowser"; -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useState } from "react"; export interface VersionPickerLoaderProps { pluginData: AKReleasesPluginData; @@ -18,24 +18,18 @@ export interface VersionPickerLoaderProps { * @client */ export const VersionPickerLoader: React.FC = ({ pluginData }) => { + const { preReleaseOrigin } = pluginData.env; + const [releases, setReleases] = useState(pluginData.releases); const browser = useIsBrowser(); const hostname = useHostname(); - const prereleaseOrigin = useMemo(() => { - if (browser && LocalhostAliases.has(window.location.hostname)) { - return window.location.origin; - } - - return ProductionURL.href; - }, [browser]); - useEffect(() => { - if (!browser || !prereleaseOrigin) return; + if (!browser || !preReleaseOrigin) return; const controller = new AbortController(); - const updateURL = new URL(pluginData.publicPath, prereleaseOrigin); + const updateURL = new URL(pluginData.publicPath, preReleaseOrigin); fetch(updateURL, { signal: controller.signal, @@ -64,7 +58,7 @@ export const VersionPickerLoader: React.FC = ({ plugin // eslint-disable-next-line consistent-return return () => controller.abort("unmount"); - }, [browser, pluginData.publicPath, prereleaseOrigin]); + }, [browser, pluginData.publicPath, preReleaseOrigin]); - return ; + return ; }; diff --git a/website/docusaurus-theme/components/VersionPicker/index.tsx b/website/docusaurus-theme/components/VersionPicker/index.tsx index 992570fae6..f03cff934f 100644 --- a/website/docusaurus-theme/components/VersionPicker/index.tsx +++ b/website/docusaurus-theme/components/VersionPicker/index.tsx @@ -1,9 +1,5 @@ -import { useHostname } from "#components/VersionPicker/utils.ts"; -import { VersionDropdown } from "#components/VersionPicker/VersionDropdown.tsx"; - -import { AKReleasesPluginData } from "@goauthentik/docusaurus-theme/releases/plugin"; - -import { usePluginData } from "@docusaurus/useGlobalData"; +import { useVersionPluginData } from "#components/VersionPicker/utils.ts"; +import { VersionPickerLoader } from "#components/VersionPicker/VersionPickerLoader.tsx"; /** * A component that shows the available versions of the documentation. @@ -11,21 +7,9 @@ import { usePluginData } from "@docusaurus/useGlobalData"; * @see {@linkcode VersionPickerLoader} for the data-fetching component. */ export const VersionPicker: React.FC = () => { - const hostname = useHostname(); + const pluginData = useVersionPluginData(); - const pluginData = usePluginData("ak-releases-plugin", undefined) as - | AKReleasesPluginData - | undefined; + if (!pluginData) return null; - if (!pluginData?.releases.length) return null; - - // return ; - - return ( - - ); + return ; }; diff --git a/website/docusaurus-theme/components/VersionPicker/utils.ts b/website/docusaurus-theme/components/VersionPicker/utils.ts index eb5cabe325..62e3fc4090 100644 --- a/website/docusaurus-theme/components/VersionPicker/utils.ts +++ b/website/docusaurus-theme/components/VersionPicker/utils.ts @@ -1,9 +1,10 @@ +import type { AKReleasesPluginData } from "@goauthentik/docusaurus-theme/releases/plugin"; + +import { usePluginData } from "@docusaurus/useGlobalData"; import useIsBrowser from "@docusaurus/useIsBrowser"; import { useMemo } from "react"; import { coerce } from "semver"; -export const ProductionURL = new URL("https://docs.goauthentik.io"); - export const LocalhostAliases: ReadonlySet = new Set(["localhost", "127.0.0.1"]); /** @@ -15,20 +16,6 @@ export function createVersionURL(semver: string): string { return `https://${subdomain}.goauthentik.io`; } -/** - * Predicate to determine if a hostname appears to be a prerelease origin. - */ -export function isPrerelease(hostname: string | null): boolean { - if (!hostname) return false; - - if (hostname === ProductionURL.hostname) return true; - if (hostname.endsWith(".netlify.app")) return true; - - if (LocalhostAliases.has(hostname)) return true; - - return false; -} - /** * Given a hostname, parse the semver from the subdomain. */ @@ -66,16 +53,40 @@ export function useHostname() { return hostname; } -export function usePrereleaseOrigin() { - const browser = useIsBrowser(); +export function useCachedVersionPluginData(): AKReleasesPluginData | null { + const pluginData = usePluginData("ak-releases-plugin", undefined) as + | AKReleasesPluginData + | undefined; - const prereleaseOrigin = useMemo(() => { - if (browser && LocalhostAliases.has(window.location.hostname)) { - return window.location.origin; - } - - return ProductionURL.href; - }, [browser]); - - return prereleaseOrigin; + return pluginData ?? null; +} + +function preferredPreReleaseOrigin(browser: boolean, fallback: string): string { + if (browser && LocalhostAliases.has(window.location.hostname)) { + return window.location.origin; + } + + return fallback; +} + +export function useVersionPluginData(): AKReleasesPluginData | null { + const browser = useIsBrowser(); + const cachedPluginData = useCachedVersionPluginData(); + + return useMemo(() => { + if (!cachedPluginData) return null; + + const preReleaseOrigin = preferredPreReleaseOrigin( + browser, + cachedPluginData.env.preReleaseOrigin, + ); + + return { + ...cachedPluginData, + env: { + ...cachedPluginData.env, + preReleaseOrigin, + }, + }; + }, [browser, cachedPluginData]); } diff --git a/website/docusaurus-theme/releases/plugin.mjs b/website/docusaurus-theme/releases/plugin.mjs index 4369d15859..c43c362474 100644 --- a/website/docusaurus-theme/releases/plugin.mjs +++ b/website/docusaurus-theme/releases/plugin.mjs @@ -3,41 +3,50 @@ * @file Docusaurus releases plugin. * * @import { LoadContext, Plugin } from "@docusaurus/types" + * @import { AKReleasesPluginEnvironment } from "./utils.mjs" */ import * as fs from "node:fs/promises"; import * as path from "node:path"; -import { collectReleaseFiles } from "./utils.mjs"; +import { collectReleaseFiles, prepareReleaseEnvironment } from "./utils.mjs"; const PLUGIN_NAME = "ak-releases-plugin"; const RELEASES_FILENAME = "releases.gen.json"; /** - * @typedef {object} ReleasesPluginOptions + * @typedef {object} AKReleasesPluginOptions * @property {string} docsDirectory The path to the documentation directory. + * @property {AKReleasesPluginEnvironment} [environment] Optional environment variables overrides. */ /** * @typedef {object} AKReleasesPluginData - * @property {string} [branch] - * @property {string} publicPath The URL to the plugin's public directory. - * @property {string[]} releases The available versions of the documentation. + * @property {string} publicPath URL to the plugin's public directory. + * @property {string[]} releases Available versions of the documentation. + * @property {AKReleasesPluginEnvironment} env Environment variables */ /** * @param {LoadContext} loadContext - * @param {ReleasesPluginOptions} options + * @param {AKReleasesPluginOptions} options * @returns {Promise>} */ -async function akReleasesPlugin(loadContext, { docsDirectory }) { +async function akReleasesPlugin(loadContext, options) { return { name: PLUGIN_NAME, async loadContent() { console.log(`🚀 ${PLUGIN_NAME} loaded`); - const releases = collectReleaseFiles(docsDirectory).map((release) => release.name); + const environment = { + ...prepareReleaseEnvironment(), + ...options.environment, + }; + + const releases = collectReleaseFiles(options.docsDirectory).map( + (release) => release.name, + ); const outputPath = path.join(loadContext.siteDir, "static", RELEASES_FILENAME); @@ -49,11 +58,13 @@ async function akReleasesPlugin(loadContext, { docsDirectory }) { * @type {AKReleasesPluginData} */ const content = { - branch: process.env.BRANCH, releases, publicPath: path.join("/", RELEASES_FILENAME), + env: environment, }; + content.publicPath; + return content; }, diff --git a/website/docusaurus-theme/releases/utils.mjs b/website/docusaurus-theme/releases/utils.mjs index 425f02d7d4..09619e5016 100644 --- a/website/docusaurus-theme/releases/utils.mjs +++ b/website/docusaurus-theme/releases/utils.mjs @@ -69,3 +69,26 @@ export function createReleaseSidebarEntries(releaseFiles) { return sidebarEntries; } + +/** + * @typedef {object} AKReleasesPluginEnvironment + * @property {string} [branch] The current branch name, if available. + * e.g. "main" `version-${year}.${month}`, "feature-branch" + * @property {string} currentReleaseOrigin The URL to the current release documentation. + * @property {string} preReleaseOrigin The URL to the pre-release documentation. + * @property {string} apiReferenceOrigin The URL to the API reference documentation. + */ + +/** + * Prepare the environment variables for the releases plugin. + * + * @returns {AKReleasesPluginEnvironment} + */ +export function prepareReleaseEnvironment() { + return { + branch: process.env.BRANCH, + currentReleaseOrigin: process.env.CURRENT_RELEASE_ORIGIN || "https://docs.goauthentik.io", + preReleaseOrigin: process.env.PRE_RELEASE_ORIGIN || "https://next.goauthentik.io", + apiReferenceOrigin: process.env.API_REFERENCE_ORIGIN || "https://api.goauthentik.io", + }; +} diff --git a/website/netlify.toml b/website/netlify.toml new file mode 100644 index 0000000000..d4ace1c902 --- /dev/null +++ b/website/netlify.toml @@ -0,0 +1,1016 @@ +[[plugins]] + package = "netlify-plugin-cache" + +[plugins.inputs] + paths = [".docusaurus", ".cache", 'node_modules/.cache'] + +[[plugins]] + package = "netlify-plugin-debug-cache" + +[build] + base = "website" + package = "docs" + command = "npm run build -w docs" + publish = "docs/build" + +[dev] + command = "npm start" + targetPort = 3000 + +[context.production.environment] + NODE_ENV = "production" + +[context.dev.environment] + NODE_ENV = "development" + +[[headers]] + for = "/*" +[headers.values] + X-Frame-Options = "DENY" + +[[headers]] + for = "/releases.gen.json" +[headers.values] + Access-Control-Allow-Origin = "*" + Access-Control-Allow-Headers = "*" + Cache-Control = "public, max-age=3600" + +# Split integrations to separate deploy +[[redirects]] + from = "/integrations/*" + to = "https://integrations.goauthentik.io/:splat" + status = 302 + +# Fix path concatenation when moving across versioned domains +[[redirects]] + from = "/docs/*" + to = "/:splat" + status = 302 + +# Split Property Mappings docs between Providers and Sources +[[redirects]] + from = "/property-mappings/" + to = "/providers/property-mappings/" + status = 302 + +[[redirects]] + from = "/property-mappings/expression" + to = "/providers/property-mappings/expression" + status = 302 + +[[redirects]] + from = "/add-secure-apps/flows-stages/flow/layouts" + to = "/add-secure-apps/flows-stages/flow/executors/if-flow" + status = 302 + +[[redirects]] + from = "/customize/brands" + to = "/customize/branding" + status = 302 + +[[redirects]] + from = "/customize/interfaces/admin/customization" + to = "/customize/interfaces/admin" + status = 302 + +[[redirects]] + from = "/customize/interfaces/user/customization" + to = "/customize/interfaces/user" + status = 302 + +[[redirects]] + from = "/customize/interfaces/flow/customization" + to = "/customize/interfaces/flow" + status = 302 + +# Migration to new structure with script Sept 2025 +[[redirects]] + from = "/advanced/tenancy" + to = "/sys-mgmt/tenancy" + status = 302 + force = true + +[[redirects]] + from = "/core/applications" + to = "/add-secure-apps/applications/" + status = 302 + +[[redirects]] + from = "/applications/" + to = "/add-secure-apps/applications/" + status = 302 + force = true + +[[redirects]] + from = "/applications/manage_apps" + to = "/add-secure-apps/applications/manage_apps" + status = 302 + force = true + +[[redirects]] + from = "/core/brands" + to = "/customize/brands" + status = 302 + force = true + +[[redirects]] + from = "/core/certificates" + to = "/sys-mgmt/certificates" + status = 302 + force = true + +[[redirects]] + from = "/core/geoip" + to = "/install-config/geoip" + status = 302 + force = true + +[[redirects]] + from = "/core/settings" + to = "/sys-mgmt/settings" + status = 302 + force = true + +[[redirects]] + from = "/events/" + to = "/sys-mgmt/events/" + status = 302 + force = true + +[[redirects]] + from = "/events/notifications" + to = "/sys-mgmt/events/notifications" + status = 302 + force = true + +[[redirects]] + from = "/events/transports" + to = "/sys-mgmt/events/transports" + status = 302 + force = true + +[[redirects]] + from = "/flow/context/" + to = "/add-secure-apps/flows-stages/flow/context/" + status = 302 + force = true + +[[redirects]] + from = "/flow/examples/flows" + to = "/add-secure-apps/flows-stages/flow/examples/flows" + status = 302 + force = true + +[[redirects]] + from = "/flow/examples/snippets" + to = "/add-secure-apps/flows-stages/flow/examples/snippets" + status = 302 + force = true + +[[redirects]] + from = "/flow/executors/headless" + to = "/add-secure-apps/flows-stages/flow/executors/headless" + status = 302 + force = true + +[[redirects]] + from = "/flow/executors/if-flow" + to = "/add-secure-apps/flows-stages/flow/executors/if-flow" + status = 302 + force = true + +[[redirects]] + from = "/flow/executors/sfe" + to = "/add-secure-apps/flows-stages/flow/executors/sfe" + status = 302 + force = true + +[[redirects]] + from = "/flow/executors/user-settings" + to = "/add-secure-apps/flows-stages/flow/executors/user-settings" + status = 302 + force = true + +[[redirects]] + from = "/flow/" + to = "/add-secure-apps/flows-stages/flow/" + status = 302 + force = true + +[[redirects]] + from = "/flow/inspector" + to = "/add-secure-apps/flows-stages/flow/inspector" + status = 302 + force = true + +[[redirects]] + from = "/flow/layouts" + to = "/add-secure-apps/flows-stages/flow/layouts" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/authenticator_duo/" + to = "/add-secure-apps/flows-stages/stages//authenticator_duo/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/authenticator_sms/" + to = "/add-secure-apps/flows-stages/stages/authenticator_sms/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/authenticator_static/" + to = "/add-secure-apps/flows-stages/stages/authenticator_static/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/authenticator_totp/" + to = "/add-secure-apps/flows-stages/stages/authenticator_totp/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/authenticator_validate/" + to = "/add-secure-apps/flows-stages/stages/authenticator_validate/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/authenticator_webauthn/" + to = "/add-secure-apps/flows-stages/stages/authenticator_webauthn/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/captcha/" + to = "/add-secure-apps/flows-stages/stages/captcha/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/deny" + to = "/add-secure-apps/flows-stages/stages/deny" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/email/x" + to = "/add-secure-apps/flows-stages/stages/email/x" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/identification/" + to = "/add-secure-apps/flows-stages/stages/identification/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/" + to = "/add-secure-apps/flows-stages/stages/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/invitation/" + to = "/add-secure-apps/flows-stages/stages/invitation/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/password/" + to = "/add-secure-apps/flows-stages/stages/password/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/prompt/" + to = "/add-secure-apps/flows-stages/stages/prompt/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/source/" + to = "/add-secure-apps/flows-stages/stages/source/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/user_delete" + to = "/add-secure-apps/flows-stages/stages/user_delete" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/user_login/" + to = "/add-secure-apps/flows-stages/stages/user_login/" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/user_logout" + to = "/add-secure-apps/flows-stages/stages/user_logout" + status = 302 + force = true + +[[redirects]] + from = "/flow/stages/user_write" + to = "/add-secure-apps/flows-stages/stages/user_write" + status = 302 + force = true + +[[redirects]] + from = "/installation/air-gapped" + to = "/install-config/air-gapped" + status = 302 + force = true + +[[redirects]] + from = "/installation/automated-install" + to = "/install-config/automated-install" + status = 302 + force = true + +[[redirects]] + from = "/installation/beta" + to = "/install-config/beta" + status = 302 + force = true + +[[redirects]] + from = "/installation/configuration" + to = "/install-config/configuration/configuration" + status = 302 + force = true + +[[redirects]] + from = "/installation/docker-compose" + to = "/install-config/install/docker-compose" + status = 302 + force = true + +[[redirects]] + from = "/installation/x" + to = "/install-config/x" + status = 302 + force = true + +[[redirects]] + from = "/installation/kubernetes" + to = "/install-config/install/kubernetes" + status = 302 + force = true + +[[redirects]] + from = "/installation/monitoring" + to = "/sys-mgmt/ops/monitoring" + status = 302 + force = true + +[[redirects]] + from = "/installation/reverse-proxy" + to = "/install-config/reverse-proxy" + status = 302 + force = true + +[[redirects]] + from = "/installation/storage-s3" + to = "/install-config/storage-s3" + status = 302 + force = true + +[[redirects]] + from = "/installation/upgrade" + to = "/install-config/upgrade" + status = 302 + force = true + +[[redirects]] + from = "/interfaces/_global/customcss" + to = "/customize/interfaces/_global/customcss" + status = 302 + force = true + +[[redirects]] + from = "/interfaces/_global/global" + to = "/customize/interfaces/_global/global" + status = 302 + force = true + +[[redirects]] + from = "/interfaces/admin/customization" + to = "/customize/interfaces/admin/customization" + status = 302 + force = true + +[[redirects]] + from = "/interfaces/flow/customization" + to = "/customize/interfaces/flow/customization" + status = 302 + force = true + +[[redirects]] + from = "/interfaces/user/customization" + to = "/customize/interfaces/user/customization" + status = 302 + force = true + +[[redirects]] + from = "/outposts/_config" + to = "/add-secure-apps/outposts/_config" + status = 302 + force = true + +[[redirects]] + from = "/outposts/embedded/embedded" + to = "/add-secure-apps/outposts/embedded/embedded" + status = 302 + force = true + +[[redirects]] + from = "/outposts/x" + to = "/add-secure-apps/outposts/x" + status = 302 + force = true + +[[redirects]] + from = "/outposts/integrations/docker" + to = "/add-secure-apps/outposts/integrations/docker" + status = 302 + force = true + +[[redirects]] + from = "/outposts/integrations/kubernetes" + to = "/add-secure-apps/outposts/integrations/kubernetes" + status = 302 + force = true + +[[redirects]] + from = "/outposts/manual-deploy-docker-compose" + to = "/add-secure-apps/outposts/manual-deploy-docker-compose" + status = 302 + force = true + +[[redirects]] + from = "/outposts/manual-deploy-kubernetes" + to = "/add-secure-apps/outposts/manual-deploy-kubernetes" + status = 302 + force = true + +[[redirects]] + from = "/outposts/upgrading" + to = "/add-secure-apps/outposts/upgrading" + status = 302 + force = true + +[[redirects]] + from = "/policies/expression" + to = "/customize/policies/expression" + status = 302 + force = true + +[[redirects]] + from = "/policies/" + to = "/customize/policies/" + status = 302 + force = true + +[[redirects]] + from = "/policies/working_with_policies/unique_email" + to = "/customize/policies/expression/unique_email" + status = 302 + force = true + +[[redirects]] + from = "/customize/policies/working_with_policies/unique_email" + to = "/customize/policies/expression/unique_email" + status = 302 + force = true + +[[redirects]] + from = "/policies/working_with_policies/whitelist_email" + to = "/customize/policies/expression/whitelist_email" + status = 302 + force = true + +[[redirects]] + from = "/customize/policies/working_with_policies/whitelist_email" + to = "/customize/policies/expression/whitelist_email" + status = 302 + force = true + +[[redirects]] + from = "/policies/working_with_policies/working_with_policies" + to = "/customize/policies/working_with_policies/working_with_policies" + status = 302 + force = true + +[[redirects]] + from = "/providers/entra/add-entra-provider" + to = "/add-secure-apps/providers/entra/add-entra-provider" + status = 302 + force = true + +[[redirects]] + from = "/providers/entra/" + to = "/add-secure-apps/providers/entra/" + status = 302 + force = true + +[[redirects]] + from = "/providers/entra/setup-entra" + to = "/add-secure-apps/providers/entra/setup-entra" + status = 302 + force = true + +[[redirects]] + from = "/providers/gws/add-gws-provider" + to = "/add-secure-apps/providers/gws/add-gws-provider" + status = 302 + force = true + +[[redirects]] + from = "/providers/gws/" + to = "/add-secure-apps/providers/gws/" + status = 302 + force = true + +[[redirects]] + from = "/providers/gws/setup-gws" + to = "/add-secure-apps/providers/gws/setup-gws" + status = 302 + force = true + +[[redirects]] + from = "/providers/x" + to = "/add-secure-apps/providers/x" + status = 302 + force = true + +[[redirects]] + from = "/providers/ldap/generic_setup" + to = "/add-secure-apps/providers/ldap/generic_setup" + status = 302 + force = true + +[[redirects]] + from = "/providers/ldap/" + to = "/add-secure-apps/providers/ldap/" + status = 302 + force = true + +[[redirects]] + from = "/providers/oauth2/client_credentials" + to = "/add-secure-apps/providers/oauth2/client_credentials" + status = 302 + force = true + +[[redirects]] + from = "/providers/oauth2/device_code" + to = "/add-secure-apps/providers/oauth2/device_code" + status = 302 + force = true + +[[redirects]] + from = "/providers/oauth2/" + to = "/add-secure-apps/providers/oauth2/" + status = 302 + force = true + +[[redirects]] + from = "/providers/property-mappings/expression" + to = "/add-secure-apps/providers/property-mappings/expression" + status = 302 + force = true + +[[redirects]] + from = "/providers/property-mappings/" + to = "/add-secure-apps/providers/property-mappings/" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/__placeholders" + to = "/add-secure-apps/providers/proxy/__placeholders" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/_caddy_standalone" + to = "/add-secure-apps/providers/proxy/_caddy_standalone" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/_envoy_istio" + to = "/add-secure-apps/providers/proxy/_envoy_istio" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/_nginx_ingress" + to = "/add-secure-apps/providers/proxy/_nginx_ingress" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/_nginx_proxy_manager" + to = "/add-secure-apps/providers/proxy/_nginx_proxy_manager" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/_nginx_standalone" + to = "/add-secure-apps/providers/proxy/_nginx_standalone" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/_traefik_compose" + to = "/add-secure-apps/providers/proxy/_traefik_compose" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/_traefik_ingress" + to = "/add-secure-apps/providers/proxy/_traefik_ingress" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/_traefik_standalone" + to = "/add-secure-apps/providers/proxy/_traefik_standalone" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/custom_headers" + to = "/add-secure-apps/providers/proxy/custom_headers" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/forward_auth" + to = "/add-secure-apps/providers/proxy/forward_auth" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/header_authentication" + to = "/add-secure-apps/providers/proxy/header_authentication" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/" + to = "/add-secure-apps/providers/proxy/" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/server_caddy" + to = "/add-secure-apps/providers/proxy/server_caddy" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/server_envoy" + to = "/add-secure-apps/providers/proxy/server_envoy" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/server_nginx" + to = "/add-secure-apps/providers/proxy/server_nginx" + status = 302 + force = true + +[[redirects]] + from = "/providers/proxy/server_traefik" + to = "/add-secure-apps/providers/proxy/server_traefik" + status = 302 + force = true + +[[redirects]] + from = "/providers/rac/how-to-rac" + to = "/add-secure-apps/providers/rac/how-to-rac" + status = 302 + force = true + +[[redirects]] + from = "/providers/rac/" + to = "/add-secure-apps/providers/rac/" + status = 302 + force = true + +[[redirects]] + from = "/providers/radius/" + to = "/add-secure-apps/providers/radius/" + status = 302 + force = true + +[[redirects]] + from = "/providers/saml/" + to = "/add-secure-apps/providers/saml/" + status = 302 + force = true + +[[redirects]] + from = "/providers/scim/" + to = "/add-secure-apps/providers/scim/" + status = 302 + force = true + +[[redirects]] + from = "/security/2023-06-cure53" + to = "/security/audits-and-certs/2023-06-cure53" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2022-23555" + to = "/security/cves/CVE-2022-23555" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2022-46145" + to = "/security/cves/CVE-2022-46145" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2022-46172" + to = "/security/cves/CVE-2022-46172" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2024-42490" + to = "/security/cves/CVE-2024-42490" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2023-26481" + to = "/security/cves/CVE-2023-26481" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2023-36456" + to = "/security/cves/CVE-2023-36456" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2023-39522" + to = "/security/cves/CVE-2023-39522" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2023-48228" + to = "/security/cves/CVE-2023-48228" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2024-21637" + to = "/security/cves/CVE-2024-21637" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2024-23647" + to = "/security/cves/CVE-2024-23647" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2024-37905" + to = "/security/cves/CVE-2024-37905" + status = 302 + force = true + +[[redirects]] + from = "/security/CVE-2024-38371" + to = "/security/cves/CVE-2024-38371" + status = 302 + force = true + +[[redirects]] + from = "/security/GHSA-rjvp-29xq-f62w" + to = "/security/cves/GHSA-rjvp-29xq-f62w" + status = 302 + force = true + +[[redirects]] + from = "/sources/active-directory/" + to = "/users-sources/sources/directory-sync/active-directory/" + status = 302 + force = true + +[[redirects]] + from = "/sources/apple/" + to = "/users-sources/sources/social-logins/apple/" + status = 302 + force = true + +[[redirects]] + from = "/sources/azure-ad/" + to = "/users-sources/sources/social-logins/azure-ad/" + status = 302 + force = true + +[[redirects]] + from = "/sources/discord/" + to = "/users-sources/sources/social-logins/discord/" + status = 302 + force = true + +[[redirects]] + from = "/sources/facebook/" + to = "/users-sources/sources/social-logins/facebook/" + status = 302 + force = true + +[[redirects]] + from = "/sources/freeipa/" + to = "/users-sources/sources/directory-sync/freeipa/" + status = 302 + force = true + +[[redirects]] + from = "/sources/github/" + to = "/users-sources/sources/social-logins/github/" + status = 302 + force = true + +[[redirects]] + from = "/sources/google/" + to = "/users-sources/sources/social-logins/google/" + status = 302 + force = true + +[[redirects]] + from = "/sources/" + to = "/users-sources/sources/" + status = 302 + force = true + +[[redirects]] + from = "/sources/ldap/" + to = "/users-sources/sources/protocols/ldap/" + status = 302 + force = true + +[[redirects]] + from = "/sources/mailcow/" + to = "/users-sources/sources/social-logins/mailcow/" + status = 302 + force = true + +[[redirects]] + from = "/sources/oauth/" + to = "/users-sources/sources/protocols/oauth/" + status = 302 + force = true + +[[redirects]] + from = "/sources/plex/" + to = "/users-sources/sources/social-logins/plex/" + status = 302 + force = true + +[[redirects]] + from = "/sources/property-mappings/expressions" + to = "/users-sources/sources/property-mappings/expressions" + status = 302 + force = true + +[[redirects]] + from = "/sources/property-mappings/" + to = "/users-sources/sources/property-mappings/" + status = 302 + force = true + +[[redirects]] + from = "/sources/saml/" + to = "/users-sources/sources/protocols/saml/" + status = 302 + force = true + +[[redirects]] + from = "/sources/scim/" + to = "/users-sources/sources/protocols/scim/" + status = 302 + force = true + +[[redirects]] + from = "/sources/twitch/" + to = "/users-sources/sources/social-logins/twitch/" + status = 302 + force = true + +[[redirects]] + from = "/sources/twitter/" + to = "/users-sources/sources/social-logins/twitter/" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/access-control/x" + to = "/users-sources/access-control/x" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/access-control/manage_permissions" + to = "/users-sources/access-control/manage_permissions" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/access-control/permissions" + to = "/users-sources/access-control/permissions" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/groups/group_ref" + to = "/users-sources/groups/group_ref" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/groups/x" + to = "/users-sources/groups/x" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/groups/manage_groups" + to = "/users-sources/groups/manage_groups" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/roles/x" + to = "/users-sources/roles/" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/roles/manage_roles" + to = "/users-sources/roles/manage_roles" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/user/x" + to = "/users-sources/user/x" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/user/invitations" + to = "/users-sources/user/invitations" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/user/user_basic_operations" + to = "/users-sources/user/user_basic_operations" + status = 302 + force = true + +[[redirects]] + from = "/user-group-role/user/user_ref" + to = "/users-sources/user/user_ref" + status = 302 + force = true + +[[redirects]] + from = "/schema.yaml" + to = "/schema.yml" + status = 302 + force = true + + # Moved GeoIP and S3 under System Management/user_basic_operations + +[[redirects]] + from = "/install-config/storage-s3" + to = "/sys-mgmt/ops/storage-s3" + status = 302 + force = true + +[[redirects]] + from = "/install-config/geoip" + to = "/sys-mgmt/ops/geoip" + status = 302 + force = true diff --git a/website/package-lock.json b/website/package-lock.json index 51b5563988..c7130ea23c 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -23,6 +23,7 @@ "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "eslint": "^9.33.0", + "netlify-plugin-cache": "^1.0.3", "npm-run-all": "^4.1.5", "prettier": "^3.6.2", "prettier-plugin-packagejson": "^2.5.19", @@ -19633,6 +19634,12 @@ "node": ">= 10" } }, + "node_modules/netlify-plugin-cache": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/netlify-plugin-cache/-/netlify-plugin-cache-1.0.3.tgz", + "integrity": "sha512-CTOwNWrTOP59T6y6unxQNnp1WX702v2R/faR5peSH94ebrYfyY4zT5IsRcIiHKq57jXeyCrhy0GLuTN8ktzuQg==", + "license": "MIT" + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", diff --git a/website/package.json b/website/package.json index 95505f5d96..abe7a6e1bc 100644 --- a/website/package.json +++ b/website/package.json @@ -26,6 +26,7 @@ "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "eslint": "^9.33.0", + "netlify-plugin-cache": "^1.0.3", "npm-run-all": "^4.1.5", "prettier": "^3.6.2", "prettier-plugin-packagejson": "^2.5.19",