Compare commits

...

3 Commits

Author SHA1 Message Date
95d6ffe898 website: Update actions. 2025-07-01 21:54:00 +02:00
8a5f755605 docs: Flesh out Netlify. 2025-07-01 21:53:59 +02:00
582812b3ec website: Flesh out docs split.
website: Copy files during build.

website: Allow for mixed env builds.

website: Reduce build size.

website: Expose build.

website: Add build memory debugging.

WIP: Disable broken links check to compare memory usage.

website: Update deps.

website: Clean up API paths.

website: Flesh out 3.8 fixes.

Format.

website: Update ignore paths.

Website: Clean up integrations build.

website: Fix paths.

website: Optimize remark.

website: Update deps.

website: Format.

website: Remove linking.

website: Fix paths.

wip: Attempt API only build.

Prep.

Migrate render to runtime. Tidy sidebar.

Clean up templates.

docs: Move directory. WIP

docs: Flesh out split.

website: Fix issue where routes have collisions.
2025-07-01 21:53:19 +02:00
712 changed files with 18952 additions and 5827 deletions

View File

@ -78,13 +78,13 @@ updates:
patterns:
- "@goauthentik/*"
- package-ecosystem: npm
directory: "/website"
directory: "/docs"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
commit-message:
prefix: "website:"
prefix: "docs:"
labels:
- dependencies
groups:

View File

@ -31,4 +31,4 @@ If changes to the frontend have been made
If applicable
- [ ] The documentation has been updated
- [ ] The documentation has been formatted (`make website`)
- [ ] The documentation has been formatted (`make docs`)

83
.github/workflows/ci-api-docs.yml vendored Normal file
View File

@ -0,0 +1,83 @@
name: authentik-ci-api-docs
on:
push:
branches:
- main
- next
- version-*
pull_request:
branches:
- main
- version-*
jobs:
lint:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
command:
- prettier-check
steps:
- uses: actions/checkout@v4
- name: Install Dependencies
working-directory: docs/
run: npm ci
- name: Lint
working-directory: docs/
run: npm run ${{ matrix.command }}
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: docs/package.json
cache: "npm"
cache-dependency-path: docs/package-lock.json
- working-directory: docs/
name: Install Dependencies
run: npm ci
- name: Build API Docs via Docusaurus
working-directory: docs
run: npm run build -w api
- uses: actions/upload-artifact@v4
with:
name: api-docs
path: docs/api/build
deploy:
runs-on: ubuntu-latest
needs:
- lint
- build
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: api-docs
path: docs/api/build
- uses: actions/setup-node@v4
with:
node-version-file: docs/package.json
cache: "npm"
cache-dependency-path: docs/package-lock.json
- working-directory: docs/
name: Install Dependencies
run: npm ci
- name: Deploy Netlify (Production)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
NETLIFY_SITE_ID: authentik-api-docs.netlify.app
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
working-directory: docs/api
run: npx netlify deploy --no-build --prod
- name: Deploy Netlify (Preview)
if: github.event_name == 'pull_request' || github.ref != 'refs/heads/main'
env:
NETLIFY_SITE_ID: authentik-api-docs.netlify.app
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
working-directory: docs/api
run: npx netlify deploy --no-build --alias=deploy-preview-${{ github.event.number }}

View File

@ -1,4 +1,4 @@
name: authentik-ci-website
name: authentik-ci-docs
on:
push:
@ -18,50 +18,47 @@ jobs:
fail-fast: false
matrix:
command:
- lint:lockfile
- prettier-check
steps:
- uses: actions/checkout@v4
- working-directory: website/
- name: Install dependencies
working-directory: docs/
run: npm ci
- name: Lint
working-directory: website/
working-directory: docs/
run: npm run ${{ matrix.command }}
test:
build-topics:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: website/package.json
node-version-file: docs/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
cache-dependency-path: docs/package-lock.json
- working-directory: docs/
name: Install Dependencies
run: npm ci
- name: test
working-directory: website/
run: npm test
build:
- name: Build Documentation via Docusaurus
working-directory: docs/
run: npm run build
build-integrations:
runs-on: ubuntu-latest
name: ${{ matrix.job }}
strategy:
fail-fast: false
matrix:
job:
- build
- build:integrations
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: website/package.json
node-version-file: docs/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
cache-dependency-path: docs/package-lock.json
- working-directory: docs/
name: Install Dependencies
run: npm ci
- name: build
working-directory: website/
run: npm run ${{ matrix.job }}
- name: Build Integrations via Docusaurus
working-directory: docs/
run: npm run build -w integrations
build-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
@ -98,7 +95,7 @@ jobs:
uses: docker/build-push-action@v6
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: website/Dockerfile
file: docs/Dockerfile
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
platforms: linux/amd64,linux/arm64
context: .
@ -111,12 +108,12 @@ jobs:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
ci-website-mark:
ci-docs-mark:
if: always()
needs:
- lint
- test
- build
- build-topics
- build-integrations
- build-container
runs-on: ubuntu-latest
steps:

View File

@ -24,8 +24,8 @@ jobs:
run: |
# Create folder structure for go embeds
mkdir -p web/dist
mkdir -p website/help
touch web/dist/test website/help/test
mkdir -p docs/help
touch web/dist/test docs/help/test
- name: Generate API
run: make gen-client-go
- name: golangci-lint

View File

@ -52,7 +52,7 @@ jobs:
uses: docker/build-push-action@v6
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: website/Dockerfile
file: docs/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
context: .

View File

@ -10,7 +10,7 @@ coverage
dist
out
.docusaurus
website/docs/developer-docs/api/**/*
docs/api/reference
## Environment
*.env

44
.vscode/tasks.json vendored
View File

@ -4,12 +4,7 @@
{
"label": "authentik/core: make",
"command": "uv",
"args": [
"run",
"make",
"lint-fix",
"lint"
],
"args": ["run", "make", "lint-fix", "lint"],
"presentation": {
"panel": "new"
},
@ -18,11 +13,7 @@
{
"label": "authentik/core: run",
"command": "uv",
"args": [
"run",
"ak",
"server"
],
"args": ["run", "ak", "server"],
"group": "build",
"presentation": {
"panel": "dedicated",
@ -32,17 +23,13 @@
{
"label": "authentik/web: make",
"command": "make",
"args": [
"web"
],
"args": ["web"],
"group": "build"
},
{
"label": "authentik/web: watch",
"command": "make",
"args": [
"web-watch"
],
"args": ["web-watch"],
"group": "build",
"presentation": {
"panel": "dedicated",
@ -52,26 +39,19 @@
{
"label": "authentik: install",
"command": "make",
"args": [
"install",
"-j4"
],
"args": ["install", "-j4"],
"group": "build"
},
{
"label": "authentik/website: make",
"label": "authentik/docs: make",
"command": "make",
"args": [
"website"
],
"args": ["docs"],
"group": "build"
},
{
"label": "authentik/website: watch",
"label": "authentik/docs: watch",
"command": "make",
"args": [
"website-watch"
],
"args": ["docs-watch"],
"group": "build",
"presentation": {
"panel": "dedicated",
@ -81,11 +61,7 @@
{
"label": "authentik/api: generate",
"command": "uv",
"args": [
"run",
"make",
"gen"
],
"args": ["run", "make", "gen"],
"group": "build"
}
]

View File

@ -32,8 +32,8 @@ tests/wdio/ @goauthentik/frontend
locale/ @goauthentik/backend @goauthentik/frontend
web/xliff/ @goauthentik/backend @goauthentik/frontend
# Docs & Website
website/ @goauthentik/docs
docs/ @goauthentik/docs
CODE_OF_CONDUCT.md @goauthentik/docs
# Security
SECURITY.md @goauthentik/security @goauthentik/docs
website/docs/security/ @goauthentik/security @goauthentik/docs
docs/security/ @goauthentik/security @goauthentik/docs

View File

@ -18,7 +18,7 @@ RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
COPY ./package.json /work
COPY ./web /work/web/
COPY ./website /work/website/
COPY ./docs /work/docs/
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
RUN npm run build && \

View File

@ -1,7 +1,7 @@
Copyright (c) 2023 Jens Langhammer
Portions of this software are licensed as follows:
* All content residing under the "website/" directory of this repository is licensed under "Creative Commons: CC BY-SA 4.0 license".
* All content residing under the "docs/" directory of this repository is licensed under "Creative Commons: CC BY-SA 4.0 license".
* All content that resides under the "authentik/enterprise/" directory of this repository, if that directory exists, is licensed under the license defined in "authentik/enterprise/LICENSE".
* All client-side JavaScript (when served directly or after being compiled, arranged, augmented, or combined), is licensed under the "MIT Expat" license.
* All third party components incorporated into the authentik are licensed under the original license provided by the owner of the applicable component.

View File

@ -1,4 +1,4 @@
.PHONY: gen dev-reset all clean test web website
.PHONY: gen dev-reset all clean test web docs
SHELL := /usr/bin/env bash
.SHELLFLAGS += ${SHELLFLAGS} -e -o pipefail
@ -70,10 +70,10 @@ core-i18n-extract:
--ignore internal \
--ignore ${GEN_API_TS} \
--ignore ${GEN_API_GO} \
--ignore website \
--ignore docs \
-l en
install: web-install website-install core-install ## Install all requires dependencies for `web`, `website` and `core`
install: web-install docs-install core-install ## Install all requires dependencies for `web`, `docs` and `core`
dev-drop-db:
dropdb -U ${pg_user} -h ${pg_host} ${pg_name}
@ -221,22 +221,22 @@ web-i18n-extract:
cd web && npm run extract-locales
#########################
## Website
## Docs
#########################
website: website-lint-fix website-build ## Automatically fix formatting issues in the Authentik website/docs source code, lint the code, and compile it
docs: docs-lint-fix docs-build ## Automatically fix formatting issues in the Authentik docs source code, lint the code, and compile it
website-install:
cd website && npm ci
docs-install:
npm ci --prefix docs
website-lint-fix: lint-codespell
cd website && npm run prettier
docs-lint-fix: lint-codespell
npm run prettier --prefix docs
website-build:
cd website && npm run build
docs-build:
npm run build --prefix docs
website-watch: ## Build and watch the documentation website, updating automatically
cd website && npm run watch
docs-watch: ## Build and watch the documentation website, updating automatically
npm run watch --prefix docs
#########################
## Docker

View File

@ -8,12 +8,12 @@
# make gen-dev-config
# ```
#
# You may edit the generated file to override the configuration below.
# You may edit the generated file to override the configuration below.
#
# When making modifying the default configuration file,
# When making modifying the default configuration file,
# ensure that the corresponding documentation is updated to match.
#
# @see {@link ../../website/docs/install-config/configuration/configuration.mdx Configuration documentation} for more information.
# @see {@link ../../docs/topics/install-config/configuration/configuration.mdx Configuration documentation} for more information.
postgresql:
host: localhost

9
docs/.browserslistrc Normal file
View File

@ -0,0 +1,9 @@
[production]
> 0.2%
not dead
not op_mini all
[development]
last 1 chrome version
last 1 firefox version
last 1 safari version

View File

@ -2,6 +2,8 @@
/node_modules
# Production
/api/build
/integrations/build
/build
/out
/help
@ -9,6 +11,7 @@
# Generated files
.docusaurus
.cache-loader
cache-output.json
# Misc
.DS_Store
@ -24,7 +27,8 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
static/docker-compose.yml
static/schema.yml
static/releases.gen.json
docs/developer-docs/api/reference/**
**/static/docker-compose.yml
**/static/schema.yml
**/static/releases.gen.json
api/reference
!integrations/**/media

20
docs/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM --platform=${BUILDPLATFORM} docker.io/library/node:24-slim AS docs-builder
ENV NODE_ENV=production
WORKDIR /work/docs
COPY ./docs/package.json ./docs/package-lock.json /work/docs/
RUN npm ci --include=dev
COPY ./docs /work/docs/
COPY ./blueprints /work/blueprints/
COPY ./schema.yml /work/
COPY ./SECURITY.md /work/
RUN npm run build
FROM docker.io/library/nginx:1.29.0
COPY --from=docs-builder /work/docs/topics/build /usr/share/nginx/html

View File

@ -1,11 +1,11 @@
# authentik documentation source
This directory contains the source files for the [authentik technical documentation](https://docs.goauthentik.io/docs?utm_source=github) and the [authentik integration guides](https://docs.goauthentik.io/integrations?utm_source=github).
This directory contains the source files for the [authentik technical documentation](https://docs.goauthentik.io/docs?utm_source=github) and the [authentik integration guides](https://integrations.goauthentik.io?utm_source=github).
Contributions are welcome! Please refer to our [contributor guidelines](https://docs.goauthentik.io/docs/developer-docs?utm_source=github) for details about contributing code or docs.
Contributions are welcome! Please refer to our [contributor guidelines](https://docs.goauthentik.io/developer-docs?utm_source=github) for details about contributing code or docs.
For instructions to set up your local environment for building docs locally, refer to our [Docs development environment](https://docs.goauthentik.io/docs/developer-docs/setup/website-dev-environment?utm_source=github) page.
For instructions to set up your local environment for building docs locally, refer to our [Docs development environment](https://docs.goauthentik.io/developer-docs/setup/website-dev-environment?utm_source=github) page.
For instructions for writing the docs and then testing in your local build, plus tips on writing, links to our Style Guide and templates, see the [Writing documentation guide](https://docs.goauthentik.io/docs/developer-docs/docs/writing-documentation?utm_source=github).
For instructions for writing the docs and then testing in your local build, plus tips on writing, links to our Style Guide and templates, see the [Writing documentation guide](https://docs.goauthentik.io/developer-docs/docs/writing-documentation?utm_source=github).
To ensure a smooth review process, we encourage you to build the documentation locally to preview and test your documentation contributions. Be sure to test locally before opening a pull request. Let us know if you have any questions or want help with any part of the process.

View File

@ -0,0 +1,18 @@
---
title: Authentication
sidebar_position: 1
---
For any of the token-based methods, set the `Authorization` header to `Bearer <token>`.
### Session
When authenticating with a flow, you'll get an authenticated Session cookie, that can be used for authentication. Keep in mind that in this context, a CSRF header is also required.
### API Token
Users can create tokens to authenticate as any user with a static key, which can optionally be expiring and auto-rotate.
### JWT Token
OAuth2 clients can request the scope `goauthentik.io/api`, which allows their OAuth Access token to be used to authenticate to the API.

15
docs/api/clients.mdx Normal file
View File

@ -0,0 +1,15 @@
---
title: API Client Overview
---
import DocCardList from "@theme/DocCardList";
These API clients are officially supported and maintained.
:::info
These API clients are primarily built around creating/updating/deleting configuration objects in authentik, and in most cases can **not** be used to implemented SSO into your application.
:::
<DocCardList />

View File

@ -0,0 +1,17 @@
---
title: Go API Client
sidebar_label: Golang
description: A Golang client for the authentik API.
---
The [Go API client](https://pkg.go.dev/goauthentik.io/api/v3) is generated using the [OpenAPI Generator](https://openapi-generator.tech/) and the [OpenAPI v3 schema](https://docs.goauthentik.io/schema.yml).
```bash
go get goauthentik.io/api/v3
```
## Building the Go Client
The Go client is used by the Outpost to communicate with the backend authentik server. To build the go client, run `make gen-client-go`.
The generated files are stored in `/gen-go-api` in the root of the repository.

33
docs/api/clients/node.md Normal file
View File

@ -0,0 +1,33 @@
---
title: Node.js API Client
sidebar_label: Node.js
description: A TypeScript client for the authentik API.
---
The [Node.js API client](https://www.npmjs.com/package/@goauthentik/api) is generated using the [OpenAPI Generator](https://openapi-generator.tech/) and the [OpenAPI v3 schema](https://docs.goauthentik.io/schema.yml).
```bash npm2yarn
npm install @goauthentik/api
```
## Usage
```ts
import { AdminApi, Configuration } from "@goauthentik/api";
const config = new Configuration({
basePath: "authentik.company/api/v3",
});
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
```
## Building the Node.js Client
The web client is used by the web-interface and web-FlowExecutor to communicate with authentik. To build the client, run `make gen-client-ts`.
Since the client is normally distributed as an npm package, running `make gen-client-ts` will overwrite the locally installed client with the newly built one.
:::caution
Running `npm i` in the `/web` folder after using `make gen-client-ts` will overwrite the custom client and revert to the upstream client.
:::

View File

@ -0,0 +1,13 @@
---
title: Python API Client
sidebar_label: Python
description: A Python client for the authentik API.
---
The [Python API client](https://pypi.org/project/authentik-client/) is generated using the [OpenAPI Generator](https://openapi-generator.tech/) and the [OpenAPI v3 schema](https://docs.goauthentik.io/schema.yml).
```bash
pip install authentik-client
# Or
uv pip install authentik-client
```

View File

@ -0,0 +1 @@
module.exports = import("./docusaurus.config.esm.mjs").then(($) => $.default);

View File

@ -0,0 +1,161 @@
/**
* @file Docusaurus config.
*
* @import { Config } from "@docusaurus/types";
* @import { UserThemeConfig, UserThemeConfigExtra } from "@goauthentik/docusaurus-config";
* @import { Options as DocsPluginOptions } from "@docusaurus/plugin-content-docs";
* @import * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
*/
import { createDocusaurusConfig } from "@goauthentik/docusaurus-config";
import { remarkLinkRewrite } from "@goauthentik/docusaurus-theme/remark";
import { GlobExcludeDefault } from "@docusaurus/utils";
import { createApiPageMD } from "docusaurus-plugin-openapi-docs/lib/markdown/index.js";
import { cp } from "node:fs/promises";
import { createRequire } from "node:module";
import { basename, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { gzip } from "pako";
const require = createRequire(import.meta.url);
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const rootStaticDirectory = resolve(__dirname, "..", "static");
//#region Copy static files
const authentikModulePath = resolve(__dirname, "..", "..");
const files = [
resolve(authentikModulePath, "docker-compose.yml"),
resolve(authentikModulePath, "schema.yml"),
];
await Promise.all(
files.map((file) => {
const fileName = basename(file);
const destPath = resolve(rootStaticDirectory, fileName);
return cp(file, destPath, {
recursive: true,
});
}),
);
//#endregion
//#region Configuration
//#region Configuration
/**
* Documentation site configuration for Docusaurus.
* @satisfies {Partial<Config>}
*/
const config = {
staticDirectories: [
// ---
"static",
rootStaticDirectory,
],
onBrokenAnchors: "ignore",
onBrokenLinks: "ignore",
onBrokenMarkdownLinks: "ignore",
onDuplicateRoutes: "ignore",
themes: ["@docusaurus/theme-mermaid", "docusaurus-theme-openapi-docs"],
themeConfig: /** @type {UserThemeConfig & UserThemeConfigExtra} */ ({
navbarReplacements: {
DOCS_URL: "/",
},
docs: {
sidebar: {
hideable: true,
},
},
}),
plugins: [
[
"@docusaurus/theme-classic",
{
customCss: require.resolve("@goauthentik/docusaurus-config/css/index.css"),
},
],
//#region Docs Content Plugin
[
"@docusaurus/plugin-content-docs",
/** @type {DocsPluginOptions} */ ({
showLastUpdateAuthor: false,
showLastUpdateTime: false,
numberPrefixParser: false,
id: "docs",
routeBasePath: "/",
path: ".",
exclude: [...GlobExcludeDefault],
include: ["**/*.mdx", "**/*.md"],
sidebarPath: "./sidebar.mjs",
docItemComponent: "@theme/ApiItem",
beforeDefaultRemarkPlugins: [
remarkLinkRewrite([
// ---
["/integrations", "https://integrations.goauthentik.io"],
["/docs", "https://docs.goauthentik.io"],
]),
],
}),
],
//#endregion
//#region OpenAPI Docs Plugin
[
"docusaurus-plugin-openapi-docs",
{
id: "open-api-docs",
docsPluginId: "docs",
config: {
authentik: /** @type {OpenApiPlugin.Options} */ ({
specPath: resolve("..", "..", "schema.yml"),
outputDir: "./reference",
hideSendButton: true,
disableCompression: true,
sidebarOptions: {
groupPathsBy: "tag",
},
template: "src/templates/api.mustache",
markdownGenerators: {
createApiPageMD: (pageData) => {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
info,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
postman,
...coreAPI
} = pageData.api;
return [
createApiPageMD(pageData),
`export const api = "${btoa(
String.fromCharCode(
...gzip(JSON.stringify(coreAPI), {
level: 9,
}),
),
)}";`,
].join("\n");
},
},
}),
},
},
],
],
//#endregion
};
//#endregion
export default createDocusaurusConfig(config);

View File

@ -0,0 +1,11 @@
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
try {
require.resolve("#reference/sidebar");
} catch (_error) {
console.error(
"\n⛔ API Reference sidebar not found.\n\nRun `npm run build:api` to generate files.",
);
process.exit(1);
}

View File

@ -1,17 +1,11 @@
import { DefaultIgnorePatterns, createESLintPackageConfig } from "@goauthentik/eslint-config";
// @ts-check
/**
* ESLint configuration for authentik's monorepo.
*/
const ESLintConfig = createESLintPackageConfig({
export default createESLintPackageConfig({
ignorePatterns: [
// ---
...DefaultIgnorePatterns,
".docusaurus/",
"./build",
"./reference",
],
});
export default ESLintConfig;

View File

@ -1,8 +1,9 @@
---
title: Flow executor (backend)
sidebar_position: 2
---
A big focus of authentik is the flows system, which allows you to combine and build complex conditional processes using stages and policies. Normally, these flows are automatically executed in the browser using authentik's [standard browser-based flow executor (/if/flows)](../../add-secure-apps/flows-stages/flow/executors/if-flow.md).
A big focus of authentik is the flows system, which allows you to combine and build complex conditional processes using stages and policies. Normally, these flows are automatically executed in the browser using authentik's [standard browser-based flow executor (/if/flows)](/docs/add-secure-apps/flows-stages/flow/executors/if-flow).
However, any flow can be executed via an API from anywhere, in fact that is what every flow executor does. With a few requests you can execute flows from anywhere, and integrate authentik even better.
@ -64,7 +65,7 @@ Depending on the flow, you'll either get a 200 Response with another challenge,
Depending also on the stage, a response might take longer to be returned (especially with the Duo Authenticator validation).
To see the data layout for every stage possible, see the [API Browser](./reference/flows-executor-get)
<!-- To see the data layout for every stage possible, see the [API Browser](./reference/flows-executor-get) -->
## Result

16
docs/api/index.md Normal file
View File

@ -0,0 +1,16 @@
---
title: API Overview
sidebar_label: Overview
---
Our API reference documentation is generated from the [OpenAPI v3 schema](https://docs.goauthentik.io/schema.yml).
You can also access your installation's own, instance-specific API Browser. Starting with 2021.3.5, every authentik instance has a built-in API browser, which can be accessed at <code>https://<em>authentik.company</em>/api/v3/</code>.
To generate an API client you can use the OpenAPI v3 schema at <code>https://<em>authentik.company</em>/api/v3/schema/</code>.
## Making schema changes
Some backend changes might require new/different fields or remove other fields. To create a new schema after changing a Serializer, run `make gen-build`.
This will update the `schema.yml` file in the root of the repository.

30
docs/api/netlify.toml Normal file
View File

@ -0,0 +1,30 @@
[[plugins]]
package = "netlify-plugin-cache"
[plugins.inputs]
paths = [".docusaurus", ".cache", 'node_modules/.cache']
[[plugins]]
package = "netlify-plugin-debug-cache"
[build]
base = "docs"
package = "api"
command = "npm run build -w api"
publish = "api/build"
[dev]
command = "npm start"
targetPort = 3000
publish = "api/build"
[context.production.environment]
NODE_ENV = "production"
[context.dev.environment]
NODE_ENV = "development"
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"

24
docs/api/package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "@goauthentik/api-docs",
"version": "0.0.0",
"description": "API Documentation",
"license": "MIT",
"private": true,
"scripts": {
"build": "run-s build:api build:types build:docusaurus",
"build:api": "docusaurus gen-api-docs all",
"build:docusaurus": "docusaurus build",
"build:types": "tsc -b .",
"deploy": "docusaurus deploy",
"docusaurus": "docusaurus",
"serve": "docusaurus serve",
"start": "docusaurus start",
"swizzle": "docusaurus swizzle"
},
"imports": {
"#reference/sidebar": "./reference/sidebar.ts"
},
"dependencies": {
"@goauthentik/docusaurus-theme": "*"
}
}

65
docs/api/sidebar.mjs Normal file
View File

@ -0,0 +1,65 @@
/**
* @file Sidebar configuration for documentation entries.
*
* @import { SidebarItemConfig } from "@docusaurus/plugin-content-docs/src/sidebars/types.js"
*/
import "./ensure-reference-sidebar.mjs";
// No file extensions for Docusaurus's automatic resolution.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @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";
/**
* @type {SidebarItemConfig}
*/
const sidebar = {
reference: [
{
type: "link",
label: "← Back to Developer Docs",
href: new URL("/developer-docs", DOCS_URL).href,
className: "navbar-sidebar__upwards",
},
{
type: "doc",
label: "API Overview",
className: "api-overview",
id: "index",
},
{
type: "category",
label: "Clients",
collapsed: false,
collapsible: false,
link: {
type: "doc",
id: "clients",
},
items: [
{
type: "autogenerated",
dirName: "clients",
},
],
},
{
type: "category",
label: "API Reference",
className: "api-reference",
collapsed: false,
collapsible: false,
link: {
type: "doc",
id: apiReference[0].id,
},
items: apiReference.slice(1),
},
],
};
export default sidebar;

View File

@ -0,0 +1,25 @@
---
id: {{{id}}}
title: "{{{title}}}"
description: "{{{frontMatter.description}}}"
{{^api}}
sidebar_label: Introduction
sidebar_position: 0
{{/api}}
hide_title: true
{{#api}}
hide_table_of_contents: true
{{/api}}
{{#json}}
api: true
{{/json}}
{{#api.method}}
sidebar_class_name: "{{{api.method}}} api-method"
{{/api.method}}
{{#infoPath}}
info_path: {{{infoPath}}}
{{/infoPath}}
hide_send_button: true
---
{{{markdown}}}

View File

@ -0,0 +1,75 @@
import { useDoc } from "@docusaurus/plugin-content-docs/client";
import { useWindowSize } from "@docusaurus/theme-common";
import type { Props } from "@theme/ApiItem/Layout";
import ContentVisibility from "@theme/ContentVisibility";
import DocBreadcrumbs from "@theme/DocBreadcrumbs";
import DocItemContent from "@theme/DocItem/Content";
import DocItemFooter from "@theme/DocItem/Footer";
import DocItemPaginator from "@theme/DocItem/Paginator";
import DocItemTOCDesktop from "@theme/DocItem/TOC/Desktop";
import DocItemTOCMobile from "@theme/DocItem/TOC/Mobile";
import DocVersionBadge from "@theme/DocVersionBadge";
import DocVersionBanner from "@theme/DocVersionBanner";
import clsx from "clsx";
import React, { type JSX } from "react";
import styles from "./styles.module.css";
/**
* Decide if the toc should be rendered, on mobile or desktop viewports
*/
function useDocTOC() {
const { frontMatter, toc } = useDoc();
const windowSize = useWindowSize();
const hidden = frontMatter.hide_table_of_contents;
const canRender = !hidden && toc.length > 0;
const mobile = canRender ? <DocItemTOCMobile /> : undefined;
const desktop =
canRender && (windowSize === "desktop" || windowSize === "ssr") ? (
<DocItemTOCDesktop />
) : undefined;
return {
hidden,
mobile,
desktop,
};
}
export default function DocItemLayout({ children }: Props): JSX.Element {
const docTOC = useDocTOC();
const { metadata, frontMatter } = useDoc() as DocContextValue;
const { api, schema } = frontMatter;
return (
<div className="row">
<div className={clsx("col", !docTOC.hidden && styles.docItemCol)}>
<ContentVisibility metadata={metadata} />
<DocVersionBanner />
<div className={styles.docItemContainer}>
<article>
<DocBreadcrumbs />
<DocVersionBadge />
{docTOC.mobile}
<DocItemContent>{children}</DocItemContent>
<div className="row">
<div className={clsx("col", api || schema ? "col--7" : "col--12")}>
<DocItemFooter />
</div>
</div>
</article>
<div className="row">
<div className={clsx("col", api || schema ? "col--7" : "col--12")}>
<DocItemPaginator />
</div>
</div>
</div>
</div>
{docTOC.desktop ? <div className="col col--3">{docTOC.desktop}</div> : null}
</div>
);
}

View File

@ -0,0 +1,10 @@
.docItemContainer header + *,
.docItemContainer article > *:first-child {
margin-top: 0;
}
@media (min-width: 997px) {
.docItemCol {
max-width: 75% !important;
}
}

View File

@ -0,0 +1,245 @@
import BrowserOnly from "@docusaurus/BrowserOnly";
import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
import { DocProvider } from "@docusaurus/plugin-content-docs/client";
import { HtmlClassNameProvider } from "@docusaurus/theme-common";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import useIsBrowser from "@docusaurus/useIsBrowser";
import type { ApiExplorerProps } from "@theme/APIExplorer";
import { createAuth } from "@theme/ApiExplorer/Authorization/slice";
import { createPersistanceMiddleware } from "@theme/ApiExplorer/persistanceMiddleware";
import DocItemLayout from "@theme/ApiItem/Layout";
import CodeBlock from "@theme/CodeBlock";
import DocItemMetadata from "@theme/DocItem/Metadata";
import SkeletonLoader from "@theme/SkeletonLoader";
import clsx from "clsx";
import { ParameterObject, ServerObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
import type { ApiItem as ApiItemType } from "docusaurus-plugin-openapi-docs/src/types";
import type { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types";
import { ungzip } from "pako";
import React from "react";
import { Provider } from "react-redux";
import { APIStore, createStoreWithState, createStoreWithoutState } from "./store";
let ApiExplorer: React.FC<ApiExplorerProps> = () => <div />;
if (ExecutionEnvironment.canUseDOM) {
// @ts-expect-error - Dynamic import
ApiExplorer = await import("@theme/ApiExplorer").then((mod) => mod.default);
}
function base64ToUint8Array(base64: string) {
const binary = atob(base64);
const len = binary.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
function decodeAPI(encodedAPI: string): ApiItemType | null {
try {
return JSON.parse(
ungzip(base64ToUint8Array(encodedAPI), {
to: "string",
}),
);
} catch (_error) {
return null;
}
}
interface APIItemSchemeProps {
content: PropDocContent;
}
const APIItemScheme: React.FC<APIItemSchemeProps> = (props) => {
const MDXComponent = props.content;
const docHtmlClassName = `docs-doc-id-${props.content.metadata.id}`;
const { frontMatter } = MDXComponent;
const { sample } = frontMatter;
return (
<DocProvider content={props.content}>
<HtmlClassNameProvider className={docHtmlClassName}>
<DocItemMetadata />
<DocItemLayout>
<div className={clsx("row", "theme-api-markdown")}>
<div className="col col--7 openapi-left-panel__container schema">
<MDXComponent />
</div>
<div className="col col--5 openapi-right-panel__container">
{sample ? (
<CodeBlock language="json" title={`${frontMatter.title}`}>
{JSON.stringify(sample, null, 2)}
</CodeBlock>
) : null}
</div>
</div>
</DocItemLayout>
</HtmlClassNameProvider>
</DocProvider>
);
};
interface APIItemAPIProps {
content: PropDocContent;
api: ApiItemType;
}
const APIItemAPI: React.FC<APIItemAPIProps> = ({ content: MDXComponent, api }) => {
const docHtmlClassName = `docs-doc-id-${MDXComponent.metadata.id}`;
const frontMatter = MDXComponent.frontMatter;
const { siteConfig } = useDocusaurusContext();
const themeConfig = siteConfig.themeConfig as ThemeConfig;
const options = themeConfig.api;
const isBrowser = useIsBrowser();
// Regex for 2XX status
const statusRegex = new RegExp("(20[0-9]|2[1-9][0-9])");
let store: APIStore;
const persistanceMiddleware = createPersistanceMiddleware(options);
// Init store for SSR
if (!isBrowser) {
store = createStoreWithoutState({}, [persistanceMiddleware]);
} else {
// Init store for CSR to hydrate components
// Create list of only 2XX response content types to create request samples from
const acceptArrayInit: string[][] = [];
for (const [code, content] of Object.entries(api.responses ?? [])) {
if (statusRegex.test(code)) {
acceptArrayInit.push(Object.keys(content.content ?? {}));
}
}
const acceptArray = acceptArrayInit.flat();
const content = api.requestBody?.content ?? {};
const contentTypeArray = Object.keys(content);
const servers = api.servers ?? [];
const params = {
path: [] as ParameterObject[],
query: [] as ParameterObject[],
header: [] as ParameterObject[],
cookie: [] as ParameterObject[],
};
api.parameters?.forEach((param: { in: "path" | "query" | "header" | "cookie" }) => {
const paramType = param.in;
const paramsArray: ParameterObject[] = params[paramType];
paramsArray.push(param as ParameterObject);
});
const auth = createAuth({
security: api.security,
securitySchemes: api.securitySchemes,
options,
});
const server = window?.sessionStorage.getItem("server");
const serverObject = (JSON.parse(server!) as ServerObject) ?? {};
store = createStoreWithState(
{
accept: {
value: acceptArray[0],
options: acceptArray,
},
contentType: {
value: contentTypeArray[0],
options: contentTypeArray,
},
server: {
value: serverObject.url ? serverObject : undefined,
options: servers,
},
response: { value: undefined },
body: { type: "empty" },
params,
auth,
},
[persistanceMiddleware],
);
}
return (
<DocProvider content={MDXComponent}>
<HtmlClassNameProvider className={docHtmlClassName}>
<DocItemMetadata />
<DocItemLayout>
<Provider store={store}>
<div className={clsx("row", "theme-api-markdown")}>
<div className="col col--7 openapi-left-panel__container">
<MDXComponent />
</div>
<div className="col col--5 openapi-right-panel__container">
<BrowserOnly fallback={<SkeletonLoader size="lg" />}>
{() => {
return (
<ApiExplorer
item={api}
infoPath={frontMatter.info_path}
/>
);
}}
</BrowserOnly>
</div>
</div>
</Provider>
</DocItemLayout>
</HtmlClassNameProvider>
</DocProvider>
);
};
interface APIItemProps {
content: PropDocContent;
}
const ApiItem: React.FC<APIItemProps> = ({ content: MDXComponent }) => {
const frontMatter = MDXComponent.frontMatter;
if (frontMatter.schema) {
return <APIItemScheme content={MDXComponent} />;
}
if (!MDXComponent.api) {
// Non-API docs
return (
<DocProvider content={MDXComponent}>
<HtmlClassNameProvider className={`docs-doc-id-${MDXComponent.metadata.id}`}>
<DocItemMetadata />
<DocItemLayout>
<div className="row">
<div className="col col--12 markdown">
<MDXComponent />
</div>
</div>
</DocItemLayout>
</HtmlClassNameProvider>
</DocProvider>
);
}
return (
<BrowserOnly fallback={<SkeletonLoader size="lg" />}>
{() => {
const api = decodeAPI(MDXComponent.api!);
if (!api) {
console.error("Failed to decode API", frontMatter);
throw new Error("Failed to decode API");
}
return <APIItemAPI content={MDXComponent} api={api} />;
}}
</BrowserOnly>
);
};
export default ApiItem;

View File

@ -0,0 +1,44 @@
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { Middleware } from "@reduxjs/toolkit";
import accept from "@theme/ApiExplorer/Accept/slice";
import auth from "@theme/ApiExplorer/Authorization/slice";
import body from "@theme/ApiExplorer/Body/slice";
import contentType from "@theme/ApiExplorer/ContentType/slice";
import params from "@theme/ApiExplorer/ParamOptions/slice";
import response from "@theme/ApiExplorer/Response/slice";
import server from "@theme/ApiExplorer/Server/slice";
const rootReducer = combineReducers({
accept,
contentType,
response,
server,
body,
params,
auth,
});
export type RootState = ReturnType<typeof rootReducer>;
export function createStoreWithState(preloadedState: RootState, middlewares: Middleware[]) {
return configureStore({
reducer: rootReducer,
preloadedState,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(...middlewares),
});
}
export type APIStore = ReturnType<typeof createStoreWithState>;
export function createStoreWithoutState(
preloadedState: Partial<RootState>,
middlewares: Middleware[],
) {
return configureStore({
reducer: rootReducer,
preloadedState,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(...middlewares),
});
}
export type AppDispatch = ReturnType<typeof createStoreWithState>["dispatch"];

View File

@ -0,0 +1,70 @@
import Link from "@docusaurus/Link";
import isInternalUrl from "@docusaurus/isInternalUrl";
import { isActiveSidebarItem } from "@docusaurus/plugin-content-docs/client";
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";
import "./styles.css";
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;
}
const DocSidebarItemLink: React.FC<Props> = ({
item,
onItemClick,
activePath,
level,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
index,
...props
}) => {
const { href, label, className, autoAddBaseUrl } = item;
const isActive = isActiveSidebarItem(item, activePath);
const internalLink = isInternalUrlOrDocsUrl(href);
return (
<li
className={clsx(
ThemeClassNames.docs.docSidebarItemLink,
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
"menu__list-item",
className,
)}
key={label}
>
<Link
className={clsx("menu__link", {
"menu__link--external": !internalLink,
"menu__link--active": isActive,
})}
autoAddBaseUrl={autoAddBaseUrl}
aria-current={isActive ? "page" : undefined}
to={href}
{...(internalLink && {
onClick: onItemClick ? () => onItemClick(item) : undefined,
})}
{...props}
>
{item.className?.includes("api-method") ? (
<div className="badge-container">
<span role="img" className="badge method" />
</div>
) : null}
{label}
{!internalLink && <IconExternalLink />}
</Link>
</li>
);
};
export default DocSidebarItemLink;

View File

@ -0,0 +1,127 @@
.theme-layout-main {
--doc-sidebar-width: 400px;
}
.navbar-sidebar__upwards {
.menu__link {
font-size: var(--ifm-h6-font-size);
font-weight: var(--ifm-font-weight-bold);
color: var(--ifm-color-info-light);
padding-block: calc(var(--ifm-spacing-vertical) / 1.5);
}
}
.theme-doc-sidebar-item-category.api-reference {
> .menu__list-item-collapsible {
font-weight: 900;
font-size: var(--ifm-h3-font-size);
}
.menu__list {
padding-left: 0;
}
.theme-doc-sidebar-item-category-level-2 .menu__list-item-collapsible {
font-size: var(--ifm-h4-font-size);
font-weight: bold;
text-transform: capitalize;
}
}
.menu__link.menu__link--external {
align-items: center;
}
.menu__list-item.api-method {
.badge-container {
flex: 0 0 auto;
display: flex;
width: 100%;
justify-content: end;
}
> .menu__link {
--menu-border-width: 2px;
color: var(--menu-item-contrast-foreground, red);
background-color: var(--menu-item-background-color, transparent);
flex-flow: column;
font-family: var(--ifm-font-family-monospace);
font-weight: 600;
gap: 0.25em;
padding-inline-end: 0.25em;
word-break: break-all;
align-items: start;
border-radius: 0;
margin-inline-end: calc(var(--ifm-menu-link-padding-horizontal) / 2);
font-size: var(--ifm-h6-font-size);
letter-spacing: 0.015em;
text-rendering: optimizelegibility;
position: relative;
box-shadow: var(--ifm-global-shadow-lw);
&::before {
position: absolute;
left: 0;
top: 0;
width: var(--menu-border-width);
height: 100%;
display: block;
z-index: 1;
background-color: var(--ifm-badge-color, var(--ifm-color-primary));
content: "";
transition: width 0.2s var(--ifm-transition-timing-default);
}
&:hover,
&.menu__link--active {
--menu-border-width: 6px;
}
}
&.get {
--method-label: "GET";
--menu-item-contrast-foreground: var(--ifm-color-content);
--menu-item-background-color: var(--ifm-card-background-color);
--ifm-badge-color: var(--ifm-color-primary-light);
}
&.post {
--method-label: "POST";
--menu-item-contrast-foreground: var(--ifm-color-success-contrast-foreground);
--menu-item-background-color: var(--ifm-color-success-contrast-background);
--ifm-badge-color: var(--ifm-color-success-lightest);
}
&.put {
--method-label: "PUT";
--menu-item-contrast-foreground: var(--ifm-color-info-contrast-foreground);
--menu-item-background-color: var(--ifm-color-info-contrast-background);
--ifm-badge-color: var(--ifm-color-info-lightest);
}
&.patch {
--method-label: "PATCH";
--menu-item-contrast-foreground: var(--ifm-color-warning-contrast-foreground);
--menu-item-background-color: var(--ifm-color-warning-contrast-background);
--ifm-badge-color: var(--ifm-color-warning-lightest);
}
&.delete {
--method-label: "DELETE";
--menu-item-contrast-foreground: var(--ifm-color-danger-contrast-foreground);
--menu-item-background-color: var(--ifm-color-danger-contrast-background);
--ifm-badge-color: var(--ifm-color-danger-lightest);
}
}
.badge.method {
position: relative;
flex: 0 0 auto;
user-select: none;
&::before {
content: var(--method-label, "METHOD");
display: block;
}
}

8
docs/api/tsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.base.json",
"references": [
{
"path": "../docusaurus-theme"
}
]
}

37
docs/api/types/api-plugin.d.ts vendored Normal file
View File

@ -0,0 +1,37 @@
/// <reference types="docusaurus-theme-openapi-docs" />
/// <reference types="docusaurus-plugin-openapi-docs" />
declare module "@docusaurus/plugin-content-docs/src/sidebars/types" {
export * from "@docusaurus/plugin-content-docs/src/sidebars/types.ts";
}
declare module "@theme/RequestSchema";
declare module "@theme/ParamsDetails";
declare module "@theme/StatusCodes";
declare module "@theme/OperationTabs";
declare module "@theme/SkeletonLoader" {
import { FC } from "react";
const SkeletonLoader: FC<{ size: "sm" | "md" | "lg" }>;
export default SkeletonLoader;
}
declare module "@theme/APIExplorer" {
import { FC } from "react";
export interface ApiExplorerProps {
item: unknown;
infoPath: unknown;
}
const ApiExplorer: FC<ApiExplorerProps>;
export default ApiExplorer;
}
declare module "@theme/ApiExplorer/persistanceMiddleware" {
import { Middleware } from "@reduxjs/toolkit";
import type { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types";
export const createPersistanceMiddleware: (options: ThemeConfig["api"]) => Middleware;
}

34
docs/api/types/globals.d.ts vendored Normal file
View File

@ -0,0 +1,34 @@
/**
* @file Supplemental type definitions for Docusaurus.
*
* @remarks
*
* Docusaurus uses an unconventional module resolution strategy, which can lead to
* issues when using TypeScript.
*
* The types in this file are intended to expose less visible types to TypeScript's
* project references, allowing for better type checking and autocompletion.
*/
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference types="@docusaurus/plugin-content-docs" />
/// <reference types="@docusaurus/theme-classic" />
import type { PropDocContent as BasePropDocContent } from "@docusaurus/plugin-content-docs";
import type { DocContextValue as BaseDocContextValue } from "@docusaurus/plugin-content-docs/client";
declare global {
export interface APIDocFrontMatter {
readonly info_path?: string;
readonly api?: string;
readonly schema?: boolean;
readonly sample?: unknown;
}
export interface PropDocContent extends BasePropDocContent {
readonly api?: string;
frontMatter: APIDocFrontMatter & BasePropDocContent["frontMatter"];
}
export interface DocContextValue extends BaseDocContextValue {
frontMatter: APIDocFrontMatter & BasePropDocContent["frontMatter"];
}
}

View File

@ -1,5 +1,7 @@
---
title: Websocket API
sidebar_position: 3
sidebar_label: Websockets
---
authentik has two different WebSocket endpoints, one is used for web-based clients to get real-time updates, and the other is used for outposts to report their healthiness.

View File

@ -1,4 +1,5 @@
import { SupportLevelToLabel, isSupportLevel } from "@site/remark/support-directive.mjs";
import { SupportLevelToLabel, isSupportLevel } from "#remark/support-directive.mjs";
import React from "react";
export interface SupportBadgeProps {

View File

@ -0,0 +1,97 @@
import {
createVersionURL,
isPrerelease,
parseHostnameSemVer,
} from "#components/VersionPicker/utils.ts";
import clsx from "clsx";
import React, { memo } from "react";
import "./styles.css";
export interface VersionDropdownProps {
/**
* The hostname of the client.
*/
hostname: string | null;
/**
* The origin of the prerelease documentation.
*
* @format url
*/
prereleaseOrigin: string;
/**
* The available versions of the documentation.
*
* @format semver
*/
releases: string[];
}
/**
* A dropdown that shows the available versions of the documentation.
*/
export const VersionDropdown = memo<VersionDropdownProps>(
({ hostname, prereleaseOrigin, releases }) => {
const prerelease = isPrerelease(hostname);
const parsedSemVer = !prerelease ? parseHostnameSemVer(hostname) : null;
const currentLabel = parsedSemVer || "Pre-Release";
const endIndex = parsedSemVer ? releases.indexOf(parsedSemVer) : -1;
const visibleReleases = releases.slice(0, endIndex === -1 ? 3 : endIndex + 3);
return (
<li className="navbar__item dropdown dropdown--hoverable dropdown--right ak-version-selector">
<div
aria-haspopup="true"
aria-expanded="false"
role="button"
className="navbar__link menu__link"
>
Version: {currentLabel}
</div>
<ul className="dropdown__menu menu__list-item--collapsed">
{!prerelease ? (
<li>
<a
href={prereleaseOrigin}
target="_blank"
rel="noopener noreferrer"
className="dropdown__link menu__link"
>
Pre-Release
</a>
</li>
) : null}
{visibleReleases.map((semVer, idx) => {
const label = semVer;
// TODO: Flesh this out after we settle on versioning strategy.
// if (idx === 0) {
// label += " (Current Release)";
// }
return (
<li key={idx}>
<a
href={createVersionURL(semVer)}
target="_blank"
rel="noopener noreferrer"
className={clsx("dropdown__link menu__link", {
"menu__link--active": semVer === currentLabel,
})}
>
{label}
</a>
</li>
);
})}
</ul>
</li>
);
},
);

View File

@ -0,0 +1,76 @@
import { VersionDropdown } from "#components/VersionPicker/VersionDropdown.tsx";
import { LocalhostAliases, ProductionURL, useHostname } from "#components/VersionPicker/utils.ts";
import { AKReleasesPluginData } from "@goauthentik/docusaurus-theme/releases/plugin";
import useIsBrowser from "@docusaurus/useIsBrowser";
import React, { useEffect, useMemo, useState } from "react";
export interface VersionPickerLoaderProps {
pluginData: AKReleasesPluginData;
}
/**
* A data-fetching component that loads available versions of the documentation.
*
* @see {@linkcode VersionPicker} for the component.
* @see {@linkcode AKReleasesPluginData} for the plugin data.
* @client
*/
export const VersionPickerLoader: React.FC<VersionPickerLoaderProps> = ({ pluginData }) => {
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;
const controller = new AbortController();
const updateURL = new URL(pluginData.publicPath, prereleaseOrigin);
fetch(updateURL, {
signal: controller.signal,
})
.then((response) => {
if (!response.ok) {
throw new Error(`Failed to fetch new releases: ${response.status}`);
}
return response.json();
})
.then((data: unknown) => {
// We're extra cautious here to be ready if the API shape ever changes.
if (!data) throw new Error("Failed to parse releases");
if (!Array.isArray(data)) throw new Error("Releases must be an array");
if (!data.every((item) => typeof item === "string"))
throw new Error("Releases must be an array of strings");
setReleases(data);
})
.catch((error) => {
console.warn(`Failed to fetch new releases: ${error}`);
});
// eslint-disable-next-line consistent-return
return () => controller.abort("unmount");
}, [browser, pluginData.publicPath, prereleaseOrigin]);
return (
<VersionDropdown
hostname={hostname}
prereleaseOrigin={prereleaseOrigin}
releases={releases}
/>
);
};

View File

@ -0,0 +1,32 @@
import { VersionDropdown } from "#components/VersionPicker/VersionDropdown.tsx";
import { useHostname, usePrereleaseOrigin } from "#components/VersionPicker/utils.ts";
import { AKReleasesPluginData } from "@goauthentik/docusaurus-theme/releases/plugin";
import { usePluginData } from "@docusaurus/useGlobalData";
/**
* A component that shows the available versions of the documentation.
*
* @see {@linkcode VersionPickerLoader} for the data-fetching component.
*/
export const VersionPicker: React.FC = () => {
const hostname = useHostname();
const prereleaseOrigin = usePrereleaseOrigin();
const pluginData = usePluginData("ak-releases-plugin", undefined) as
| AKReleasesPluginData
| undefined;
if (!pluginData?.releases.length) return null;
// return <VersionPickerLoader pluginData={pluginData} />;
return (
<VersionDropdown
hostname={hostname}
prereleaseOrigin={prereleaseOrigin}
releases={pluginData.releases}
/>
);
};

View File

@ -0,0 +1,33 @@
.theme-doc-sidebar-menu {
--ak-version-selector-padding: calc(var(--ifm-spacing-vertical) / 2);
.dropdown.ak-version-selector {
width: calc(100% - (var(--ifm-spacing-horizontal) / 2));
border-block-end: var(--ifm-hr-height) solid var(--ifm-color-emphasis-200);
padding-block-start: calc(var(--ak-version-selector-padding) / 2);
padding-block-end: var(--ak-version-selector-padding);
margin-block-end: var(--ak-version-selector-padding);
&:has(+ .navbar-sidebar__upwards) {
margin-block-end: 0;
}
.navbar__link.menu__link {
display: flex;
width: 100%;
justify-content: space-between;
font-weight: var(--ifm-font-weight-semibold);
&::after {
color: var(--ifm-color-emphasis-400);
filter: var(--ifm-menu-link-sublist-icon-filter);
}
}
.dropdown__menu {
background: var(--ifm-dropdown-background-color);
box-shadow: var(--ifm-global-shadow-lw);
border: 1px solid var(--ifm-color-emphasis-200);
}
}
}

View File

@ -0,0 +1,81 @@
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<string> = new Set(["localhost", "127.0.0.1"]);
/**
* Given a semver, create the URL for the version.
*/
export function createVersionURL(semver: string): string {
const subdomain = `version-${semver.replace(".", "-")}`;
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.
*/
export function parseHostnameSemVer(hostname: string | null): string | null {
if (!hostname) return null;
const [, possibleSemVer] = hostname.match(/version-(.+)\.goauthentik\.io/) || [];
if (!possibleSemVer) return null;
const formattedSemVer = possibleSemVer.replace("-", ".");
if (!coerce(formattedSemVer)) return null;
return formattedSemVer;
}
export function useHostname() {
const browser = useIsBrowser();
const hostname = useMemo(() => {
if (!browser) return null;
const searchParams = new URLSearchParams(window.location.search);
// Query parameter used for debugging.
// Note that this doesn't synchronize with Docusaurus's router state.
const subdomain = searchParams.get("version");
if (subdomain) return subdomain;
return window.location.hostname;
}, [browser]);
return hostname;
}
export function usePrereleaseOrigin() {
const browser = useIsBrowser();
const prereleaseOrigin = useMemo(() => {
if (browser && LocalhostAliases.has(window.location.hostname)) {
return window.location.origin;
}
return ProductionURL.href;
}, [browser]);
return prereleaseOrigin;
}

View File

@ -0,0 +1,85 @@
/**
* @file Docusaurus config.
*
* @import { Config } from "@docusaurus/types";
* @import { UserThemeConfig, UserThemeConfigExtra } from "@goauthentik/docusaurus-config";
* @import { Options as DocsPluginOptions } from "@docusaurus/plugin-content-docs";
* @import { BuildUrlValues } from "remark-github";
*/
import {
remarkEnterpriseDirective,
remarkPreviewDirective,
remarkSupportDirective,
remarkVersionDirective,
} from "#remark";
import remarkNPM2Yarn from "@docusaurus/remark-plugin-npm2yarn";
import remarkDirective from "remark-directive";
import remarkGithub, { defaultBuildUrl } from "remark-github";
//#region Common configuration
/**
* @satisfies {DocsPluginOptions}
*/
export const CommonDocsPluginOptions = {
id: "docs",
routeBasePath: "/",
path: "docs",
sidebarPath: "./docs/sidebar.mjs",
showLastUpdateTime: false,
editUrl: "https://github.com/goauthentik/authentik/edit/main/docs/",
//#region Docs Plugins
beforeDefaultRemarkPlugins: [
remarkDirective,
remarkVersionDirective,
remarkEnterpriseDirective,
remarkPreviewDirective,
remarkSupportDirective,
],
remarkPlugins: [
[remarkNPM2Yarn, { sync: true }],
[
remarkGithub,
{
repository: "goauthentik/authentik",
/**
* @param {BuildUrlValues} values
*/
buildUrl: (values) => {
// Only replace issues and PR links
return values.type === "issue" || values.type === "mention"
? defaultBuildUrl(values)
: false;
},
},
],
],
};
/**
* Documentation site configuration for Docusaurus.
* @satisfies {Partial<Config>}
*/
export const CommonConfig = {
themes: ["@docusaurus/theme-mermaid"],
themeConfig: /** @type {UserThemeConfig & UserThemeConfigExtra} */ ({
algolia: {
appId: "36ROD0O0FV",
apiKey: "727db511300ca9aec5425645bbbddfb5",
indexName: "goauthentik",
},
}),
plugins: [
[
"@docusaurus/plugin-google-gtag",
{
trackingID: ["G-9MVR9WZFZH"],
anonymizeIP: true,
},
],
],
};

View File

@ -0,0 +1,3 @@
import { createESLintPackageConfig } from "@goauthentik/eslint-config";
export default createESLintPackageConfig();

View File

@ -0,0 +1,21 @@
/**
* @file Docusaurus theme plugin.
* @import { Plugin } from "@docusaurus/types";
*/
/**
* @returns {Plugin<void>}
*/
export default function docusaurusThemeAuthentik() {
return {
name: "docusaurus-theme-authentik",
getThemePath() {
return "./theme";
},
getTypeScriptThemePath() {
return "./theme";
},
};
}

View File

@ -0,0 +1,23 @@
{
"name": "@goauthentik/docusaurus-theme",
"version": "0.0.0",
"license": "MIT",
"private": true,
"type": "module",
"exports": {
"./package.json": "./package.json",
".": "./index.js",
"./config": "./config.js",
"./remark": "./remark/index.mjs",
"./components/*": "./components/*",
"./releases/plugin": "./releases/plugin.mjs",
"./releases/utils": "./releases/utils.mjs"
},
"imports": {
"#remark": "./remark/index.mjs",
"#remark/*": "./remark/*",
"#components/*": "./components/*",
"#hooks/*": "./hooks/*",
"#theme/*": "./theme/*"
}
}

View File

@ -0,0 +1,65 @@
/* eslint-disable no-console */
/**
* @file Docusaurus releases plugin.
*
* @import { LoadContext, Plugin } from "@docusaurus/types"
*/
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { collectReleaseFiles } from "./utils.mjs";
const PLUGIN_NAME = "ak-releases-plugin";
const RELEASES_FILENAME = "releases.gen.json";
/**
* @typedef {object} ReleasesPluginOptions
* @property {string} docsDirectory The path to the documentation directory.
*/
/**
* @typedef {object} AKReleasesPluginData
* @property {string} publicPath The URL to the plugin's public directory.
* @property {string[]} releases The available versions of the documentation.
*/
/**
* @param {LoadContext} loadContext
* @param {ReleasesPluginOptions} options
* @returns {Promise<Plugin<AKReleasesPluginData>>}
*/
async function akReleasesPlugin(loadContext, { docsDirectory }) {
return {
name: PLUGIN_NAME,
async loadContent() {
console.log(`🚀 ${PLUGIN_NAME} loaded`);
const releases = collectReleaseFiles(docsDirectory).map((release) => release.name);
const outputPath = path.join(loadContext.siteDir, "static", RELEASES_FILENAME);
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, JSON.stringify(releases, null, 2), "utf-8");
console.log(`${RELEASES_FILENAME} generated`);
/**
* @type {AKReleasesPluginData}
*/
const content = {
releases,
publicPath: path.join("/", RELEASES_FILENAME),
};
return content;
},
contentLoaded({ content, actions }) {
const { setGlobalData } = actions;
setGlobalData(content);
},
};
}
export default akReleasesPlugin;

View File

@ -0,0 +1,69 @@
/**
* @file Docusaurus release utils.
*
* @import { SidebarItemConfig } from "@docusaurus/plugin-content-docs/src/sidebars/types.js"
*/
import FastGlob from "fast-glob";
import * as path from "node:path";
import { coerce } from "semver";
/**
*
* @param {string} releasesParentDirectory
* @returns {FastGlob.Entry[]}
*/
export function collectReleaseFiles(releasesParentDirectory) {
const releaseFiles = FastGlob.sync("releases/**/v*.{md,mdx}", {
cwd: releasesParentDirectory,
onlyFiles: true,
objectMode: true,
})
.map((fileEntry) => {
return {
...fileEntry,
path: fileEntry.path.replace(/\.mdx?$/, ""),
name: fileEntry.name.replace(/^v/, "").replace(/\.mdx?$/, ""),
};
})
.sort((a, b) => {
const aSemVer = coerce(a.name);
const bSemVer = coerce(b.name);
if (aSemVer && bSemVer) {
return bSemVer.compare(aSemVer);
}
return b.name.localeCompare(a.name);
});
return releaseFiles;
}
export const SUPPORTED_RELEASE_COUNT = 3;
/**
*
* @param {FastGlob.Entry[]} releaseFiles
*/
export function createReleaseSidebarEntries(releaseFiles) {
/**
* @type {SidebarItemConfig[]}
*/
let sidebarEntries = releaseFiles.map((fileEntry) => {
return path.join(fileEntry.path);
});
if (releaseFiles.length > SUPPORTED_RELEASE_COUNT) {
// Then we add the rest of the releases as a category.
sidebarEntries = [
...sidebarEntries.slice(0, SUPPORTED_RELEASE_COUNT),
{
type: "category",
label: "Previous versions",
items: sidebarEntries.slice(SUPPORTED_RELEASE_COUNT),
},
];
}
return sidebarEntries;
}

View File

@ -2,6 +2,7 @@
* @file Remark plugin to transform `ak-enterprise` directives into badges.
*
* @import { Root } from "mdast";
* @import {} from "mdast-util-directive";
*/
import { h } from "hastscript";
import { SKIP, visit } from "unist-util-visit";
@ -9,7 +10,7 @@ import { SKIP, visit } from "unist-util-visit";
/**
* MDAST plugin to transform `ak-enterprise` directives into badges.
*/
function remarkEnterpriseDirective() {
export function remarkEnterpriseDirective() {
/**
* @param {Root} tree The MDAST tree to transform.
*/

View File

@ -0,0 +1,5 @@
export * from "./enterprise-directive.mjs";
export * from "./link-rewrite-directive.mjs";
export * from "./preview-directive.mjs";
export * from "./support-directive.mjs";
export * from "./version-directive.mjs";

View File

@ -0,0 +1,35 @@
/**
* @import { Root } from "mdast";
*/
import { SKIP, visit } from "unist-util-visit";
/**
* @typedef {[pattern: string | RegExp, replacement: string]} Rewrite
*/
/**
* Remark plugin to transform relative links to docs to absolute URLs
* @param {Iterable<[string, string]>} rewrites Map of urls to rewrite where the key is the prefix to check for and the value is the domain to add
*/
export function remarkLinkRewrite(rewrites) {
const map = new Map(rewrites);
return () => {
/**
* @param {Root} tree The MDAST tree to transform.
*/
return (tree) => {
visit(tree, "link", (node) => {
for (const [pattern, replacement] of map) {
if (!node.url.startsWith(pattern)) continue;
node.url = node.url.replace(pattern, replacement);
}
return SKIP;
});
};
};
}
export default remarkLinkRewrite;

View File

@ -2,6 +2,7 @@
* @file Remark plugin to transform `ak-preview` directives into preview badges.
*
* @import { Root } from "mdast";
* @import {} from "mdast-util-directive";
*/
import { h } from "hastscript";
import { SKIP, visit } from "unist-util-visit";
@ -9,7 +10,7 @@ import { SKIP, visit } from "unist-util-visit";
/**
* MDAST plugin to transform `ak-preview` directives into preview badges.
*/
function remarkPreviewDirective() {
export function remarkPreviewDirective() {
/**
* @param {Root} tree The MDAST tree to transform.
*/

View File

@ -2,6 +2,7 @@
* @file Remark plugin to transform `ak-support` directives into support level badges.
*
* @import { Root } from "mdast";
* @import {} from "mdast-util-directive";
*/
import { h } from "hastscript";
import { SKIP, visit } from "unist-util-visit";
@ -36,7 +37,7 @@ export function isSupportLevel(input) {
/**
* MDAST plugin to transform `ak-support` directives into preview badges.
*/
function remarkSupportDirective() {
export function remarkSupportDirective() {
/**
* @param {Root} tree The MDAST tree to transform.
*/

View File

@ -2,6 +2,7 @@
* @file Remark plugin to transform `ak-version` directives into version badges.
*
* @import { Root } from "mdast";
* @import {} from "mdast-util-directive";
*/
import { h } from "hastscript";
import { coerce } from "semver";
@ -22,7 +23,7 @@ import { SKIP, visit } from "unist-util-visit";
* # Feature Foobar <span class="badge badge--version">authentik: v1.2.3+</span>
* ```
*/
function remarkVersionDirective() {
export function remarkVersionDirective() {
/**
* @param {Root} tree The MDAST tree to transform.
*/

View File

@ -1,4 +1,3 @@
/// <reference types="@docusaurus/plugin-content-docs" />
/**
* @file Swizzled DocItemContent component.
*
@ -8,11 +7,13 @@
* the content of a documentation page. However, it also adds support for
* support badges, and Authentik version badges.
*/
import { SupportBadge } from "#components/SupportBadge.tsx";
import { VersionBadge } from "#components/VersionBadge.tsx";
import { useSyntheticTitle } from "#hooks/title.ts";
import { useDoc } from "@docusaurus/plugin-content-docs/client";
import { ThemeClassNames } from "@docusaurus/theme-common";
import { SupportBadge } from "@site/src/components/SupportBadge";
import { VersionBadge } from "@site/src/components/VersionBadge";
import { useSyntheticTitle } from "@site/src/hooks/title";
import type { Props } from "@theme/DocItem/Content";
import Heading from "@theme/Heading";
import MDXContent from "@theme/MDXContent";
@ -57,6 +58,22 @@ function useBadgeLinterEffect() {
}, [hide_title, id]);
}
interface BadgesProps {
badges: JSX.Element[];
}
const BadgeGroup: React.FC<BadgesProps> = ({ badges }) => {
if (!badges.length) return null;
return (
<p className="badge-group">
{badges.map((badge, index) => (
<React.Fragment key={index}>{badge}</React.Fragment>
))}
</p>
);
};
const DocItemContent: React.FC<Props> = ({ children }) => {
const syntheticTitle = useSyntheticTitle();
const { frontMatter, metadata, contentTitle } = useDoc();
@ -116,20 +133,4 @@ const DocItemContent: React.FC<Props> = ({ children }) => {
);
};
interface BadgesProps {
badges: JSX.Element[];
}
const BadgeGroup: React.FC<BadgesProps> = ({ badges }) => {
if (!badges.length) return null;
return (
<p className="badge-group">
{badges.map((badge, index) => (
<React.Fragment key={index}>{badge}</React.Fragment>
))}
</p>
);
};
export default DocItemContent;

View File

@ -0,0 +1,26 @@
/// <reference types="@docusaurus/plugin-content-docs" />
import { VersionPicker } from "#components/VersionPicker/index.tsx";
import {
DocSidebarItemsExpandedStateProvider,
useVisibleSidebarItems,
} from "@docusaurus/plugin-content-docs/client";
import DocSidebarItem from "@theme/DocSidebarItem";
import type { Props as DocSidebarItemsProps } from "@theme/DocSidebarItems";
import { memo } from "react";
const DocSidebarItems = ({ items, ...props }: DocSidebarItemsProps): JSX.Element => {
const visibleItems = useVisibleSidebarItems(items, props.activePath);
const includeVersionPicker = props.level === 1 && !props.activePath.startsWith("/integrations");
return (
<DocSidebarItemsExpandedStateProvider>
{includeVersionPicker ? <VersionPicker /> : null}
{visibleItems.map((item, index) => (
<DocSidebarItem key={index} item={item} index={index} {...props} />
))}
</DocSidebarItemsExpandedStateProvider>
);
};
export default memo(DocSidebarItems);

View File

@ -0,0 +1,3 @@
{
"extends": "../tsconfig.base.json"
}

View File

@ -10,17 +10,11 @@
* project references, allowing for better type checking and autocompletion.
*/
declare module "@docusaurus/plugin-content-docs-types" {
export * from "@docusaurus/plugin-content-docs";
export * from "@docusaurus/plugin-content-docs/src/types.ts";
export * from "@docusaurus/plugin-content-docs/src/sidebars/types.ts";
}
declare module "@docusaurus/plugin-content-docs/src/sidebars/types" {
export * from "@docusaurus/plugin-content-docs/src/sidebars/types.ts";
}
declare module "@docusaurus/plugin-content-docs/client" {
export * from "@docusaurus/plugin-content-docs/lib/client/doc.js";
export * from "@docusaurus/plugin-content-docs/lib/client/docSidebarItemsExpandedState.js";
export * from "@docusaurus/plugin-content-docs/lib/client/docsUtils.js";
import { DocContextValue as BaseDocContextValue } from "@docusaurus/plugin-content-docs/lib/client/doc.js";
import { DocFrontMatter as BaseDocFrontMatter } from "@docusaurus/plugin-content-docs";

View File

@ -0,0 +1,44 @@
/**
* @file Supplemental type definitions for Docusaurus.
*
* @remarks
*
* Docusaurus uses an unconventional module resolution strategy, which can lead to
* issues when using TypeScript.
*
* The types in this file are intended to expose less visible types to TypeScript's
* project references, allowing for better type checking and autocompletion.
*/
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference types="@docusaurus/plugin-content-docs" />
/// <reference types="@docusaurus/theme-classic" />
import type { PropDocContent as BasePropDocContent } from "@docusaurus/plugin-content-docs";
import type { DocContextValue as BaseDocContextValue } from "@docusaurus/plugin-content-docs/client";
declare global {
/**
* @monkeypatch
*/
export interface DocFrontMatter {
support_level?: string;
authentik_version?: string;
authentik_preview: boolean;
authentik_enterprise: boolean;
}
export interface APIDocFrontMatter {
readonly info_path?: string;
readonly api?: string;
readonly schema?: boolean;
readonly sample?: unknown;
}
export interface PropDocContent extends BasePropDocContent {
readonly api?: string;
frontMatter: APIDocFrontMatter & BasePropDocContent["frontMatter"];
}
export interface DocContextValue extends BaseDocContextValue {
frontMatter: DocFrontMatter & APIDocFrontMatter & BasePropDocContent["frontMatter"];
}
}

11
docs/eslint.config.mjs Normal file
View File

@ -0,0 +1,11 @@
import { DefaultIgnorePatterns, createESLintPackageConfig } from "@goauthentik/eslint-config";
export default createESLintPackageConfig({
ignorePatterns: [
// ---
...DefaultIgnorePatterns,
"**/.docusaurus/",
"**/build",
"**/reference",
],
});

View File

@ -1,9 +1,11 @@
---
title: Integrate with Applications
sidebar_label: Applications
sidebar_position: 2
---
import SupportBadge from "@site/src/components/SupportBadge";
import SupportBadge from "@goauthentik/docusaurus-theme/components/SupportBadge.tsx";
import DocCardList from "@theme/DocCardList";
# Applications
@ -23,14 +25,12 @@ All documented app integrations will have one of these badges:
<a id="add-new"></a>
To add documentation for a new application (with support level Community or Vendor), please use the integration template [`service.md`](https://github.com/goauthentik/authentik/blob/main/website/integrations/template/service.md) file from our GitHub repo. You can download the template file using the following command:
To add documentation for a new application (with support level Community or Vendor), please use the integration template [`service.md`](https://github.com/goauthentik/authentik/blob/main/docs/topics/integrations/template/service.md) file from our GitHub repo. You can download the template file using the following command:
```shell
wget https://raw.githubusercontent.com/goauthentik/authentik/main/website/integrations/template/service.md
wget https://raw.githubusercontent.com/goauthentik/authentik/main/docs/topics/integrations/template/service.md
```
Don't forget to edit the `website/sidebars/integrations.mjs` file to add your new integration to the appropriate section in the lefthand navigation pane.
## Integration categories
<DocCardList />

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -19,7 +19,7 @@ The following placeholders are used in this guide:
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
For additional information about integrating with Slack, refer to their [documentation](https://slack.com/help/articles/205168057-Custom-SAML-single-sign-on).
For additional information about integrating with Slack, refer to their [documentation](https://slack.com/help/topics/205168057-Custom-SAML-single-sign-on).
## authentik configuration

Some files were not shown because too many files have changed in this diff Show More