Compare commits

..

2 Commits

Author SHA1 Message Date
dbd5ae90eb web: Remove escape. 2025-06-27 00:18:12 +02:00
40363a2142 web: Flesh out import clean up. 2025-06-26 23:52:54 +02:00
755 changed files with 7872 additions and 19739 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2025.6.3
current_version = 2025.6.2
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?

View File

@ -78,13 +78,13 @@ updates:
patterns:
- "@goauthentik/*"
- package-ecosystem: npm
directory: "/docs"
directory: "/website"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
commit-message:
prefix: "docs:"
prefix: "website:"
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 docs`)
- [ ] The documentation has been formatted (`make website`)

View File

@ -38,8 +38,6 @@ jobs:
# Needed for attestation
id-token: write
attestations: write
# Needed for checkout
contents: read
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3.6.0

View File

@ -1,83 +0,0 @@
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

@ -9,7 +9,6 @@ on:
jobs:
test-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false

View File

@ -247,13 +247,11 @@ jobs:
# Needed for attestation
id-token: write
attestations: write
# Needed for checkout
contents: read
needs: ci-core-mark
uses: ./.github/workflows/_reusable-docker-build.yaml
secrets: inherit
with:
image_name: ${{ github.repository == 'goauthentik/authentik-internal' && 'ghcr.io/goauthentik/internal-server' || 'ghcr.io/goauthentik/dev-server' }}
image_name: ghcr.io/goauthentik/dev-server
release: false
pr-comment:
needs:

View File

@ -24,8 +24,8 @@ jobs:
run: |
# Create folder structure for go embeds
mkdir -p web/dist
mkdir -p docs/help
touch web/dist/test docs/help/test
mkdir -p website/help
touch web/dist/test website/help/test
- name: Generate API
run: make gen-client-go
- name: golangci-lint
@ -59,7 +59,6 @@ jobs:
with:
jobs: ${{ toJSON(needs) }}
build-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
timeout-minutes: 120
needs:
- ci-outpost-mark

View File

@ -1,4 +1,4 @@
name: authentik-ci-docs
name: authentik-ci-website
on:
push:
@ -18,49 +18,51 @@ jobs:
fail-fast: false
matrix:
command:
- lint:lockfile
- prettier-check
steps:
- uses: actions/checkout@v4
- name: Install dependencies
working-directory: docs/
- working-directory: website/
run: npm ci
- name: Lint
working-directory: docs/
working-directory: website/
run: npm run ${{ matrix.command }}
build-topics:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: docs/package.json
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: docs/package-lock.json
- working-directory: docs/
name: Install Dependencies
cache-dependency-path: website/package-lock.json
- working-directory: website/
run: npm ci
- name: Build Documentation via Docusaurus
working-directory: docs/
run: npm run build
build-integrations:
- name: test
working-directory: website/
run: npm test
build:
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: docs/package.json
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: docs/package-lock.json
- working-directory: docs/
name: Install Dependencies
cache-dependency-path: website/package-lock.json
- working-directory: website/
run: npm ci
- name: Build Integrations via Docusaurus
working-directory: docs/
run: npm run build -w integrations
- name: build
working-directory: website/
run: npm run ${{ matrix.job }}
build-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
permissions:
# Needed to upload container images to ghcr.io
@ -95,7 +97,7 @@ jobs:
uses: docker/build-push-action@v6
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: docs/Dockerfile
file: website/Dockerfile
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
platforms: linux/amd64,linux/arm64
context: .
@ -108,16 +110,15 @@ jobs:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
ci-docs-mark:
ci-website-mark:
if: always()
needs:
- lint
- build-topics
- build-integrations
- test
- build
- build-container
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
allowed-skips: ${{ github.repository == 'goauthentik/authentik-internal' && 'build-container' || '[]' }}

View File

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

View File

@ -1,21 +0,0 @@
name: "authentik-repo-mirror-cleanup"
on:
workflow_dispatch:
jobs:
to_internal:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: BeryJu/repository-mirroring-action@5cf300935bc2e068f73ea69bcc411a8a997208eb
with:
target_repo_url: git@github.com:goauthentik/authentik-internal.git
ssh_private_key: ${{ secrets.GH_MIRROR_KEY }}
args: --tags --force --prune
env:
MIRROR_KEY: ${{ secrets.GH_MIRROR_KEY }}

View File

@ -11,10 +11,11 @@ jobs:
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: BeryJu/repository-mirroring-action@5cf300935bc2e068f73ea69bcc411a8a997208eb
uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url: git@github.com:goauthentik/authentik-internal.git
ssh_private_key: ${{ secrets.GH_MIRROR_KEY }}
args: --tags --force
target_repo_url:
git@github.com:goauthentik/authentik-internal.git
ssh_private_key:
${{ secrets.GH_MIRROR_KEY }}
env:
MIRROR_KEY: ${{ secrets.GH_MIRROR_KEY }}

View File

@ -16,7 +16,6 @@ env:
jobs:
compile:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- id: generate_token

View File

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

44
.vscode/tasks.json vendored
View File

@ -4,7 +4,12 @@
{
"label": "authentik/core: make",
"command": "uv",
"args": ["run", "make", "lint-fix", "lint"],
"args": [
"run",
"make",
"lint-fix",
"lint"
],
"presentation": {
"panel": "new"
},
@ -13,7 +18,11 @@
{
"label": "authentik/core: run",
"command": "uv",
"args": ["run", "ak", "server"],
"args": [
"run",
"ak",
"server"
],
"group": "build",
"presentation": {
"panel": "dedicated",
@ -23,13 +32,17 @@
{
"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",
@ -39,19 +52,26 @@
{
"label": "authentik: install",
"command": "make",
"args": ["install", "-j4"],
"args": [
"install",
"-j4"
],
"group": "build"
},
{
"label": "authentik/docs: make",
"label": "authentik/website: make",
"command": "make",
"args": ["docs"],
"args": [
"website"
],
"group": "build"
},
{
"label": "authentik/docs: watch",
"label": "authentik/website: watch",
"command": "make",
"args": ["docs-watch"],
"args": [
"website-watch"
],
"group": "build",
"presentation": {
"panel": "dedicated",
@ -61,7 +81,11 @@
{
"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
docs/ @goauthentik/docs
website/ @goauthentik/docs
CODE_OF_CONDUCT.md @goauthentik/docs
# Security
SECURITY.md @goauthentik/security @goauthentik/docs
docs/security/ @goauthentik/security @goauthentik/docs
website/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 ./docs /work/docs/
COPY ./website /work/website/
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
RUN npm run build && \
@ -75,7 +75,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.7.17 AS uv
FROM ghcr.io/astral-sh/uv:0.7.15 AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base

View File

@ -1,7 +1,7 @@
Copyright (c) 2023 Jens Langhammer
Portions of this software are licensed as follows:
* All content residing under the "docs/" directory of this repository is licensed under "Creative Commons: CC BY-SA 4.0 license".
* All content residing under the "website/" 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 docs
.PHONY: gen dev-reset all clean test web website
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 docs \
--ignore website \
-l en
install: web-install docs-install core-install ## Install all requires dependencies for `web`, `docs` and `core`
install: web-install website-install core-install ## Install all requires dependencies for `web`, `website` 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
#########################
## Docs
## Website
#########################
docs: docs-lint-fix docs-build ## Automatically fix formatting issues in the Authentik docs source code, lint the code, and compile it
website: website-lint-fix website-build ## Automatically fix formatting issues in the Authentik website/docs source code, lint the code, and compile it
docs-install:
npm ci --prefix docs
website-install:
cd website && npm ci
docs-lint-fix: lint-codespell
npm run prettier --prefix docs
website-lint-fix: lint-codespell
cd website && npm run prettier
docs-build:
npm run build --prefix docs
website-build:
cd website && npm run build
docs-watch: ## Build and watch the documentation website, updating automatically
npm run watch --prefix docs
website-watch: ## Build and watch the documentation website, updating automatically
cd website && npm run watch
#########################
## Docker

View File

@ -2,7 +2,7 @@
from os import environ
__version__ = "2025.6.3"
__version__ = "2025.6.2"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -5,6 +5,7 @@ from collections.abc import Callable
from django.apps import apps
from django.test import TestCase
from authentik.blueprints.v1.importer import is_model_allowed
from authentik.lib.models import SerializerModel
from authentik.providers.oauth2.models import RefreshToken
@ -21,13 +22,10 @@ def serializer_tester_factory(test_model: type[SerializerModel]) -> Callable:
return
model_class = test_model()
self.assertTrue(isinstance(model_class, SerializerModel))
# Models that have subclasses don't have to have a serializer
if len(test_model.__subclasses__()) > 0:
return
self.assertIsNotNone(model_class.serializer)
if model_class.serializer.Meta().model == RefreshToken:
return
self.assertTrue(issubclass(test_model, model_class.serializer.Meta().model))
self.assertEqual(model_class.serializer.Meta().model, test_model)
return tester
@ -36,6 +34,6 @@ for app in apps.get_app_configs():
if not app.label.startswith("authentik"):
continue
for model in app.get_models():
if not issubclass(model, SerializerModel):
if not is_model_allowed(model):
continue
setattr(TestModels, f"test_{app.label}_{model.__name__}", serializer_tester_factory(model))

View File

@ -1082,12 +1082,6 @@ class AuthenticatedSession(SerializerModel):
user = models.ForeignKey(User, on_delete=models.CASCADE)
@property
def serializer(self) -> type[Serializer]:
from authentik.core.api.authenticated_sessions import AuthenticatedSessionSerializer
return AuthenticatedSessionSerializer
class Meta:
verbose_name = _("Authenticated Session")
verbose_name_plural = _("Authenticated Sessions")

View File

@ -6,7 +6,7 @@ from djangoql.ast import Name
from djangoql.exceptions import DjangoQLError
from djangoql.queryset import apply_search
from djangoql.schema import DjangoQLSchema
from rest_framework.filters import BaseFilterBackend, SearchFilter
from rest_framework.filters import SearchFilter
from rest_framework.request import Request
from structlog.stdlib import get_logger
@ -39,21 +39,19 @@ class BaseSchema(DjangoQLSchema):
return super().resolve_name(name)
class QLSearch(BaseFilterBackend):
class QLSearch(SearchFilter):
"""rest_framework search filter which uses DjangoQL"""
def __init__(self):
super().__init__()
self._fallback = SearchFilter()
@property
def enabled(self):
return apps.get_app_config("authentik_enterprise").enabled()
def get_search_terms(self, request: Request) -> str:
"""Search terms are set by a ?search=... query parameter,
and may be comma and/or whitespace delimited."""
params = request.query_params.get("search", "")
def get_search_terms(self, request) -> str:
"""
Search terms are set by a ?search=... query parameter,
and may be comma and/or whitespace delimited.
"""
params = request.query_params.get(self.search_param, "")
params = params.replace("\x00", "") # strip null characters
return params
@ -72,9 +70,9 @@ class QLSearch(BaseFilterBackend):
search_query = self.get_search_terms(request)
schema = self.get_schema(request, view)
if len(search_query) == 0 or not self.enabled:
return self._fallback.filter_queryset(request, queryset, view)
return super().filter_queryset(request, queryset, view)
try:
return apply_search(queryset, search_query, schema=schema)
except DjangoQLError as exc:
LOGGER.debug("Failed to parse search expression", exc=exc)
return self._fallback.filter_queryset(request, queryset, view)
return super().filter_queryset(request, queryset, view)

View File

@ -57,7 +57,7 @@ class QLTest(APITestCase):
)
self.assertEqual(res.status_code, 200)
content = loads(res.content)
self.assertEqual(content["pagination"]["count"], 1)
self.assertGreaterEqual(content["pagination"]["count"], 1)
self.assertEqual(content["results"][0]["username"], self.user.username)
def test_search_json(self):

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 ../../docs/topics/install-config/configuration/configuration.mdx Configuration documentation} for more information.
# @see {@link ../../website/docs/install-config/configuration/configuration.mdx Configuration documentation} for more information.
postgresql:
host: localhost

View File

@ -66,10 +66,7 @@ class RACClientConsumer(AsyncWebsocketConsumer):
def init_outpost_connection(self):
"""Initialize guac connection settings"""
self.token = (
ConnectionToken.filter_not_expired(
token=self.scope["url_route"]["kwargs"]["token"],
session__session__session_key=self.scope["session"].session_key,
)
ConnectionToken.filter_not_expired(token=self.scope["url_route"]["kwargs"]["token"])
.select_related("endpoint", "provider", "session", "session__user")
.first()
)

View File

@ -87,22 +87,3 @@ class TestRACViews(APITestCase):
)
body = loads(flow_response.content)
self.assertEqual(body["component"], "ak-stage-access-denied")
def test_different_session(self):
"""Test request"""
self.client.force_login(self.user)
response = self.client.get(
reverse(
"authentik_providers_rac:start",
kwargs={"app": self.app.slug, "endpoint": str(self.endpoint.pk)},
)
)
self.assertEqual(response.status_code, 302)
flow_response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
body = loads(flow_response.content)
next_url = body["to"]
self.client.logout()
final_response = self.client.get(next_url)
self.assertEqual(final_response.url, reverse("authentik_core:if-user"))

View File

@ -68,10 +68,7 @@ class RACInterface(InterfaceView):
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
# Early sanity check to ensure token still exists
token = ConnectionToken.filter_not_expired(
token=self.kwargs["token"],
session__session__session_key=request.session.session_key,
).first()
token = ConnectionToken.filter_not_expired(token=self.kwargs["token"]).first()
if not token:
return redirect("authentik_core:if-user")
self.token = token

File diff suppressed because one or more lines are too long

View File

@ -27,6 +27,7 @@
</table>
</td>
</tr>
<td>
{% endblock %}
{% block sub_content %}

View File

@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json",
"type": "object",
"title": "authentik 2025.6.3 Blueprint schema",
"title": "authentik 2025.6.2 Blueprint schema",
"required": [
"version",
"entries"

View File

@ -31,7 +31,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.3}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
restart: unless-stopped
command: server
environment:
@ -55,7 +55,7 @@ services:
redis:
condition: service_healthy
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.3}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
restart: unless-stopped
command: worker
environment:

View File

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

View File

@ -1,20 +0,0 @@
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,18 +0,0 @@
---
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.

View File

@ -1,15 +0,0 @@
---
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

@ -1,17 +0,0 @@
---
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.

View File

@ -1,33 +0,0 @@
---
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

@ -1,13 +0,0 @@
---
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

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

View File

@ -1,161 +0,0 @@
/**
* @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

@ -1,11 +0,0 @@
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,16 +0,0 @@
---
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.

View File

@ -1,30 +0,0 @@
[[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"

View File

@ -1,24 +0,0 @@
{
"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": "*"
}
}

View File

@ -1,65 +0,0 @@
/**
* @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

@ -1,25 +0,0 @@
---
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

@ -1,75 +0,0 @@
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

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

View File

@ -1,245 +0,0 @@
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

@ -1,44 +0,0 @@
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

@ -1,70 +0,0 @@
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

@ -1,127 +0,0 @@
.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;
}
}

View File

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

View File

@ -1,37 +0,0 @@
/// <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;
}

View File

@ -1,34 +0,0 @@
/**
* @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,97 +0,0 @@
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

@ -1,76 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,33 +0,0 @@
.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

@ -1,81 +0,0 @@
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

@ -1,85 +0,0 @@
/**
* @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

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

View File

@ -1,21 +0,0 @@
/**
* @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

@ -1,23 +0,0 @@
{
"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

@ -1,65 +0,0 @@
/* 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

@ -1,69 +0,0 @@
/**
* @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

@ -1,5 +0,0 @@
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

@ -1,35 +0,0 @@
/**
* @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

@ -1,26 +0,0 @@
/// <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

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

View File

@ -1,44 +0,0 @@
/**
* @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"];
}
}

View File

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

View File

@ -1,7 +0,0 @@
.theme-doc-sidebar-item-category-level-1 .menu__list-item-collapsible {
border-top: 0.5px solid;
border-top-color: var(--ifm-category-color, var(--ifm-menu-color-background-active));
border-radius: 0;
font-weight: 600;
padding-block: 0.25em;
}

View File

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

View File

@ -1,87 +0,0 @@
/**
* @file Docusaurus Integrations config.
*
* @import { Config } from "@docusaurus/types";
* @import { UserThemeConfig, UserThemeConfigExtra } from "@goauthentik/docusaurus-config";
* @import { Options as DocsPluginOptions } from "@docusaurus/plugin-content-docs";
*/
import { createDocusaurusConfig } from "@goauthentik/docusaurus-config";
import { CommonConfig, CommonDocsPluginOptions } from "@goauthentik/docusaurus-theme/config";
import { remarkLinkRewrite } from "@goauthentik/docusaurus-theme/remark";
import { GlobExcludeDefault } from "@docusaurus/utils";
import { deepmerge } from "deepmerge-ts";
import { createRequire } from "node:module";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
const require = createRequire(import.meta.url);
const __dirname = fileURLToPath(new URL(".", import.meta.url));
//#region Configuration
/**
* Documentation site configuration for Docusaurus.
* @satisfies {Partial<Config>}
*/
const config = {
staticDirectories: [
// ---
resolve(__dirname, "..", "static"),
"static",
],
themes: ["@goauthentik/docusaurus-theme"],
themeConfig: /** @type {UserThemeConfig & UserThemeConfigExtra} */ ({
navbarReplacements: {
INTEGRATIONS_URL: "/",
},
algolia: {
externalUrlRegex: /^(?:https?:\/\/)(integrations|api).?(goauthentik.io)/.source,
},
}),
plugins: [
[
"@docusaurus/theme-classic",
{
customCss: [
"./custom.css",
require.resolve("@goauthentik/docusaurus-config/css/index.css"),
],
},
],
//#region Documentation
[
"@docusaurus/plugin-content-docs",
deepmerge(
CommonDocsPluginOptions,
/** @type {DocsPluginOptions} */ ({
id: "docs",
routeBasePath: "/",
path: ".",
exclude: [...GlobExcludeDefault],
include: ["**/*.mdx", "**/*.md"],
sidebarPath: "./sidebar.mjs",
showLastUpdateTime: false,
editUrl:
"https://github.com/goauthentik/authentik/edit/main/docs/topics/integrations/",
//#region Docs Plugins
beforeDefaultRemarkPlugins: [
remarkLinkRewrite([
// ---
["/api", "https://api.goauthentik.io"],
["/docs", "https://docs.goauthentik.io"],
]),
],
}),
),
],
],
};
export default /** @type {Config} */ (deepmerge(CommonConfig, createDocusaurusConfig(config)));

View File

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

View File

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

View File

@ -1,164 +0,0 @@
---
title: Integrate with Omada Controller
sidebar_label: Omada Controller
support_level: community
---
## What is Omada Controller
> Omada Controller is a software platform used to centrally manage and monitor Omada networking devices like access points, switches, and routers. It provides a single interface for configuring, managing, and monitoring these devices, offering centralized control over your entire Omada network.
>
> -- https://www.omadanetworks.com/
## Preparation
The following placeholders are used in this guide:
- `authentik.company` is the FQDN of the authentik installation.
:::note
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.
:::
## authentik configuration
To support the integration of Omada Controller with authentik, you need to create property mappings, a group, and an application/provider pair in authentik.
### Create property mappings in authentik
1. Log in to authentik as an administrator, and open the authentik Admin interface.
2. Navigate to **Customization** > **Property Mappings**, click **Create**, select **SAML Provider Property Mappings**, and click **Next**.
3. Configure the first mapping for the user's _given name_ (first name):
- **Name**: `givenname`
- **SAML Attribute Name**: `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname`
- **Friendly Name**: Leave blank
- **Expression**:
```python
return request.user.name.split(" ", 1)[0]
```
4. Click **Finish** to save. Then, repeat the process to create a mapping for the user's _surname_:
- **Name**: `surname`
- **SAML Attribute Name**: `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname`
- **Friendly Name**: Leave blank
- **Expression**:
```python
return request.user.name.split(" ", 1)[-1]
```
5. Click **Finish** to save. Then, repeat the process to create a mapping for the user's _group memberships_:
- **Name**: `usergroup_name`
- **SAML Attribute Name**: `usergroup_name`
- **Friendly Name**: Leave blank
- **Expression**:
```python
for group in user.ak_groups.all():
yield group.name
```
6. Click **Finish** to save. Finally, repeat the process to create a mapping for the user's _username_:
- **Name**: `username`
- **SAML Attribute Name**: `username`
- **Friendly Name**: Leave blank
- **Expression**:
```python
return request.user.username
```
7. Click **Finish**.
### Create a group in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Directory** > **Groups** and click **Create**.
3. Set a name for the group (e.g. `Omada-admins`) and click **Create**.
4. Click the name of the newly created group, then switch to the **Users** tab.
5. Click **Add existing user**, select the user who needs Omada Controller administrator access, and click **Add**.
### Create an application and provider in authentik
1. Log in to authentik as an administrator, and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, and then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- Note the application slug, it will be required when filling out the **Identity provider SSO URL** later on.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- **ACS URL**:
- For Cloud Controllers: `https://aps1-omada-account.tplinkcloud.com/sso/saml/login/`
- For Software/Hardware Controllers: `https://<controller_ip_address>:8043/sso/saml/login`
- **Issuer**:
- For Cloud Controllers: `https://omada.tplinkcloud.com/`
- For Software and Hardware Controllers: `https://<controller_ip_address>:8043`
- Set the **Service Provider Binding** to `Post`.
- Under **Advanced protocol settings**:
- Set an available signing certificate.
- Set **NameID Property Mapping** to `authentik default SAML Mapping: UPN`
- Under **Property mappings**:
- Select only the following **User Property Mappings**:
- `authentik default SAML Mapping: Email`
- `authentik default SAML Mapping: Name`
- `authentik default SAML Mapping: UPN`
- `givenname`
- `surname`
- `usergroup_name`
- `username`
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
### Copy the metadata URL
1. Log into authentik as an administrator, and open the authentik Admin interface.
2. Navigate to **Applications** > **Providers** and click on the name of the newly created Omada Controller provider.
3. Under **Metadata**, click the **Copy Download URL**. This metadata URL will be required in the next section.
## Omada Controller configuration
1. Log in to the Omada Controller.
2. Navigate to **Global View** > **Settings** > **SAML SSO**, and then click **Add New SAML Connection**.
3. Set **Identity Provider Name** to `authentik`.
4. Select `Metadata URL` as the **Configuration Method**, and then paste the metadata URL that you copied from authentik.
5. Click **Load Info**, and then click **Send**.
6. In the **Actions** column, click on the **Details** button next to the newly created authentik SAML connection.
7. Take note of the **Entity ID**, **Omada ID**, **Resource ID**, and then click **OK**. These values will be required in the next section.
8. At the top right of the page, click **Go To SAML Role**, and then **Add New SAML Role**.
9. Set the desired **SAML Role Name**, **Role**, **User Type**, and **Privileges** for the new SAML role. The **SAML Role Name** must match the name of the previously created authentik group.
10. Click **Create**.
## Encoding default relay state
The default relay state is generated by Base64-encoding a combination of the **Resource ID** and **Omada ID**, separated by an underscore (`_`).
You can generate the relay state value using one of the following methods:
### Linux and macOS
```bash
echo -n '<Resource_ID>_<Omada_ID>' | base64 --wrap=0
```
### Windows (PowerShell):
```powershell
[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('<Resource_ID>_<Omada_ID>'))
```
## Reconfigure authentik provider
1. Log in to authentik as an administrator, and open the authentik Admin interface.
2. Navigate to **Applications** > **Providers** and click the **Edit** icon next to the newly created Omada Controller provider.
3. Set **Issuer** to the **Entity ID** value from Omada Controller.
4. Under **Advanced protocol settings**, set **Default relay state** to the encoded value from the previous section.
5. Click **Update** to save your changes.
## Configuration verification
To verify that authentik is correctly integrated with Omada Controller, first log out of Omada Controller. Log in to authentik and click on the Omada Controller application in the application dashboard, and you should then be redirected to the Omada Controller dashboard.
## Resources
- [Omada Networks Documentation - How to Configure SAML SSO on Omada Controller](https://www.omadanetworks.com/de/support/faq/4406/#_Toc193896083)

View File

@ -1,22 +0,0 @@
{
"name": "@goauthentik/integration-docs",
"version": "0.0.0",
"license": "MIT",
"private": true,
"scripts": {
"build": "run-s build:types build:docusaurus",
"build:docusaurus": "docusaurus build",
"build:types": "tsc -b .",
"deploy": "docusaurus deploy",
"docusaurus": "docusaurus",
"serve": "docusaurus serve",
"start": "docusaurus start",
"test": "node --test"
},
"dependencies": {
"@goauthentik/docusaurus-theme": "*"
},
"engines": {
"node": ">=24"
}
}

View File

@ -1,49 +0,0 @@
/**
* @file Sidebar configuration for the authentik integrations.
*
* @import { SidebarItemConfig } from "@docusaurus/plugin-content-docs/src/sidebars/types.js"
*/
/**
* @type {ReadonlyArray<readonly [string, string]>}
*/
const categories = [
["chat-communication-collaboration", "Chat, Communication & Collaboration"],
["device-management", "Device Management"],
["cloud-providers", "Cloud Providers"],
["dashboards", "Dashboards"],
["development", "Development"],
["documentation", "Documentation"],
["hypervisors-orchestrators", "Hypervisors / Orchestrators"],
["infrastructure", "Infrastructure"],
["networking", "Networking"],
["media", "Media"],
["miscellaneous", "Miscellaneous"],
["monitoring", "Monitoring"],
["platforms", "Platforms"],
["security", "Security"],
];
export default /** @type {SidebarItemConfig} */
({
integrations: [
{
type: "doc",
id: "index",
},
{
type: "doc",
id: "applications",
},
...categories.map(([dirName, label]) => ({
type: "category",
label,
items: [
{
type: "autogenerated",
dirName,
},
],
})),
],
});

View File

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

View File

@ -1,13 +0,0 @@
/**
* @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.
*/
/// <reference types="@docusaurus/plugin-content-docs" />
/// <reference types="@docusaurus/theme-classic" />

View File

@ -1,101 +0,0 @@
{
"name": "@goauthentik/docs",
"version": "0.0.0",
"license": "MIT",
"private": true,
"scripts": {
"build": "run-s build:types build:docusaurus",
"build:docusaurus": "npm run build -w topics",
"build:types": "tsc -b",
"docusaurus": "docusaurus",
"lint": "eslint --fix .",
"lint-check": "eslint --max-warnings 0 .",
"prettier": "prettier --write .",
"prettier-check": "prettier --check .",
"start": "npm start -w topics"
},
"dependencies": {
"@docusaurus/core": "^3.8.1",
"@docusaurus/faster": "^3.8.1",
"@docusaurus/module-type-aliases": "^3.8.1",
"@docusaurus/plugin-client-redirects": "^3.8.1",
"@docusaurus/plugin-content-docs": "^3.8.1",
"@docusaurus/preset-classic": "^3.8.1",
"@docusaurus/remark-plugin-npm2yarn": "^3.8.1",
"@docusaurus/theme-common": "^3.8.1",
"@docusaurus/theme-mermaid": "^3.8.1",
"@docusaurus/tsconfig": "^3.8.1",
"@docusaurus/types": "^3.8.1",
"@eslint/js": "^9.29.0",
"@goauthentik/docusaurus-config": "^2.1.1",
"@goauthentik/docusaurus-theme": "*",
"@goauthentik/eslint-config": "^1.0.5",
"@goauthentik/prettier-config": "^2.0.1",
"@goauthentik/tsconfig": "^1.0.4",
"@mdx-js/react": "^3.1.0",
"@reduxjs/toolkit": "^1.7.1",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/lodash": "^4.17.18",
"@types/node": "^24.0.3",
"@types/pako": "^2.0.3",
"@types/pidusage": "^2.0.5",
"@types/postman-collection": "^3.5.11",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/semver": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^8.34.1",
"@typescript-eslint/parser": "^8.34.1",
"clsx": "^2.1.1",
"cross-env": "^7.0.3",
"docusaurus-plugin-openapi-docs": "^4.4.0",
"docusaurus-theme-openapi-docs": "^4.4.0",
"eslint": "^9.29.0",
"fast-glob": "^3.3.3",
"netlify-cli": "^22.2.1",
"netlify-plugin-cache": "^1.0.3",
"npm-run-all": "^4.1.5",
"pako": "^2.1.0",
"pidtree": "^0.6.0",
"pidusage": "^4.0.1",
"postcss": "^8.5.6",
"prettier": "^3.5.3",
"prettier-plugin-packagejson": "^2.5.15",
"prism-react-renderer": "^2.4.1",
"react": "^18.3.1",
"react-before-after-slider-component": "^1.1.8",
"react-dom": "^18.3.1",
"react-redux": "^7.2.0",
"remark-directive": "^4.0.0",
"remark-github": "^12.0.0",
"semver": "^7.7.2",
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.0"
},
"optionalDependencies": {
"@rspack/binding-darwin-arm64": "1.3.15",
"@rspack/binding-linux-arm64-gnu": "1.3.15",
"@rspack/binding-linux-x64-gnu": "1.3.15",
"@swc/core-darwin-arm64": "1.12.7",
"@swc/core-linux-arm64-gnu": "1.12.7",
"@swc/core-linux-x64-gnu": "1.12.7",
"@swc/html-darwin-arm64": "1.12.7",
"@swc/html-linux-arm64-gnu": "1.12.7",
"@swc/html-linux-x64-gnu": "1.12.7",
"lightningcss-darwin-arm64": "1.30.1",
"lightningcss-linux-arm64-gnu": "1.30.1",
"lightningcss-linux-x64-gnu": "1.30.1"
},
"engines": {
"node": ">=24"
},
"workspaces": [
"api",
"docusaurus-theme",
"integrations",
"topics"
],
"prettier": "@goauthentik/prettier-config",
"overrides": {
"@rspack/core": "1.4.0-rc.0"
}
}

View File

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

View File

@ -1,92 +0,0 @@
/**
* @file Docusaurus Documentation config.
*
* @import { Config } from "@docusaurus/types";
* @import { UserThemeConfig, UserThemeConfigExtra } from "@goauthentik/docusaurus-config";
* @import { Options as DocsPluginOptions } from "@docusaurus/plugin-content-docs";
* @import { ReleasesPluginOptions } from "@goauthentik/docusaurus-theme/releases/plugin"
*/
import { createDocusaurusConfig } from "@goauthentik/docusaurus-config";
import { CommonConfig, CommonDocsPluginOptions } from "@goauthentik/docusaurus-theme/config";
import { remarkLinkRewrite } from "@goauthentik/docusaurus-theme/remark";
import { GlobExcludeDefault } from "@docusaurus/utils";
import { deepmerge } from "deepmerge-ts";
import { createRequire } from "node:module";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const require = createRequire(import.meta.url);
/**
* Documentation site configuration for Docusaurus.
* @satisfies {Partial<Config>}
*/
const config = {
staticDirectories: [
// ---
resolve(__dirname, "..", "static"),
"static",
],
themes: ["@goauthentik/docusaurus-theme"],
themeConfig: /** @type {UserThemeConfig & UserThemeConfigExtra} */ ({
navbarReplacements: {
DOCS_URL: "/",
},
algolia: {
externalUrlRegex: /^(?:https?:\/\/)(integrations|api).?(goauthentik.io)/.source,
},
}),
plugins: [
[
"@docusaurus/theme-classic",
{
customCss: require.resolve("@goauthentik/docusaurus-config/css/index.css"),
},
],
[
"@goauthentik/docusaurus-theme/releases/plugin",
/** @type {ReleasesPluginOptions} */ ({
docsDirectory: __dirname,
}),
],
//#region Documentation
[
"@docusaurus/plugin-content-docs",
deepmerge(
CommonDocsPluginOptions,
/** @type {DocsPluginOptions} */ ({
id: "docs",
routeBasePath: "/",
path: ".",
exclude: [...GlobExcludeDefault],
include: ["**/*.mdx", "**/*.md"],
sidebarPath: "./sidebar.mjs",
showLastUpdateTime: false,
editUrl: "https://github.com/goauthentik/authentik/edit/main/docs/",
//#region Docs Plugins
beforeDefaultRemarkPlugins: [
remarkLinkRewrite([
// ---
["/docs", ""],
["/api", "https://api.goauthentik.io"],
["/integrations", "https://integrations.goauthentik.io"],
]),
],
}),
),
],
],
};
export default /** @type {Config} */ (deepmerge(CommonConfig, createDocusaurusConfig(config)));

View File

@ -1,46 +0,0 @@
[[plugins]]
package = "netlify-plugin-cache"
[plugins.inputs]
paths = [".docusaurus", ".cache", 'node_modules/.cache']
[[plugins]]
package = "netlify-plugin-debug-cache"
[build]
base = "docs"
package = "topics"
command = "npm run build -w topics"
publish = "topics/build"
[dev]
command = "npm start"
targetPort = 3000
publish = "topics/build"
[context.production.environment]
NODE_ENV = "production"
[context.dev.environment]
NODE_ENV = "development"
# Migration from docs to separate directory
[[redirects]]
from = "/docs/integrations/*"
to = "https://integrations.goauthentik.io/:splat"
status = 302
[[redirects]]
from = "/integrations/*"
to = "https://integrations.goauthentik.io/:splat"
status = 302
[[redirects]]
from = "/docs/*"
to = "/:splat"
status = 302
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"

View File

@ -1,22 +0,0 @@
{
"name": "@goauthentik/docs-topics",
"version": "0.0.0",
"license": "MIT",
"private": true,
"scripts": {
"build": "run-s build:types build:docusaurus",
"build:docusaurus": "docusaurus build",
"build:types": "tsc -b .",
"deploy": "docusaurus deploy",
"docusaurus": "docusaurus",
"serve": "docusaurus serve",
"start": "docusaurus start",
"test": "node --test"
},
"dependencies": {
"@goauthentik/docusaurus-theme": "*"
},
"engines": {
"node": ">=24"
}
}

View File

@ -1,27 +0,0 @@
# CVE-2025-52553
_Reported by [SPIEGEL-Verlag](https://gruppe.spiegel.de)_
## Insufficient Session verification for Remote Access Control endpoint access
### Summary
After authorizing access to a RAC endpoint, authentik creates a token which is used for a single connection and is sent to the client in the URL. This token is intended to only be valid for the session of the user who authorized the connection, however this check is currently missing.
### Patches
authentik 2025.4.3 and 2025.6.3 fix this issue.
### Impact
When for example using RAC during a screenshare, a malicious user could access the same session by copying the URL from the shown browser.
### Workarounds
As a workaround it is recommended to decrease the duration a token is valid for (in the RAC Provider settings, set **Connection expiry** to `minutes=5` for example). We also recommend enabling the option **Delete authorization on disconnect**.
### For more information
If you have any questions or comments about this advisory:
- Email us at [security@goauthentik.io](mailto:security@goauthentik.io).

View File

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

View File

@ -1,27 +0,0 @@
// @file TSConfig used by the docs package during development.
//
// @remarks
// While this configuration will influence the IDE experience,
// Docusaurus will instead use an internal configuration to build the site.
//
// @see https://docusaurus.io/docs/typescript-support
{
"extends": "./tsconfig.base.json",
"files": [],
"references": [
{
"path": "./docusaurus-theme"
},
{
"path": "./api"
},
{
"path": "./integrations"
},
{
"path": "./topics"
}
]
}

2
go.mod
View File

@ -29,7 +29,7 @@ require (
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025063.1
goauthentik.io/api/v3 v3.2025062.6
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.15.0

4
go.sum
View File

@ -298,8 +298,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025063.1 h1:zvKhZTESgMY/SNiLuTs7G0YleBnev1v7+S9Xd6PZ9bc=
goauthentik.io/api/v3 v3.2025063.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2025062.6 h1:rlChhGP2vJufYCaTMb4sbRBEE1p2uL5T4HzMqF1AJ4A=
goauthentik.io/api/v3 v3.2025062.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@ -33,4 +33,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion())
}
const VERSION = "2025.6.3"
const VERSION = "2025.6.2"

View File

@ -26,7 +26,7 @@ Parameters:
Description: authentik Docker image
AuthentikVersion:
Type: String
Default: 2025.6.3
Default: 2025.6.2
Description: authentik Docker image tag
AuthentikServerCPU:
Type: Number

Binary file not shown.

View File

@ -11,18 +11,18 @@
# Nicola Mersi, 2024
# tmassimi, 2024
# Marc Schmitt, 2024
# albanobattistella <albanobattistella@gmail.com>, 2024
# Matteo Piccina <altermatte@gmail.com>, 2025
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
# albanobattistella <albanobattistella@gmail.com>, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-25 00:10+0000\n"
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: albanobattistella <albanobattistella@gmail.com>, 2025\n"
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -116,7 +116,7 @@ msgstr "Certificato Web utilizzato dal server Web authentik Core."
#: authentik/brands/models.py
msgid "Certificates used for client authentication."
msgstr "Certificati utilizzati per l'autenticazione del client."
msgstr ""
#: authentik/brands/models.py
msgid "Brand"
@ -130,6 +130,10 @@ msgstr "Brands"
msgid "User does not have access to application."
msgstr "L'utente non ha accesso all'applicazione."
#: authentik/core/api/devices.py
msgid "Extra description not available"
msgstr "Descrizione extra non disponibile"
#: authentik/core/api/groups.py
msgid "Cannot set group as parent of itself."
msgstr "Impossibile impostare il gruppo come padre di se stesso."
@ -290,15 +294,15 @@ msgid ""
msgstr ""
"Collegamento a un utente con indirizzo email identico. Può avere "
"implicazioni sulla sicurezza quando una fonte non convalida gli indirizzi "
"email."
"e-mail."
#: authentik/core/models.py
msgid ""
"Use the user's email address, but deny enrollment when the email address "
"already exists."
msgstr ""
"Usa l'indirizzo email dell'utente, ma nega l'iscrizione quando l'indirizzo "
"email esiste già."
"Usa l'indirizzo e-mail dell'utente, ma nega l'iscrizione quando l'indirizzo "
"e-mail esiste già."
#: authentik/core/models.py
msgid ""
@ -678,29 +682,26 @@ msgid ""
"option has a higher priority than the `client_certificate` option on "
"`Brand`."
msgstr ""
"Configura le autorità di certificazione per convalidare il certificato. "
"Questa opzione ha una priorità maggiore rispetto all'opzione "
"`client_certificate` su `Brand`."
#: authentik/enterprise/stages/mtls/models.py
msgid "Mutual TLS Stage"
msgstr "Fase di TLS reciproca"
msgstr ""
#: authentik/enterprise/stages/mtls/models.py
msgid "Mutual TLS Stages"
msgstr "Fasi di TLS reciproche"
msgstr ""
#: authentik/enterprise/stages/mtls/models.py
msgid "Permissions to pass Certificates for outposts."
msgstr " Permessi di trasmissione dei Certificati per gli avamposti."
msgstr ""
#: authentik/enterprise/stages/mtls/stage.py
msgid "Certificate required but no certificate was given."
msgstr " Il certificato è stato richiesto ma non è stato consegnato."
msgstr ""
#: authentik/enterprise/stages/mtls/stage.py
msgid "No user found for certificate."
msgstr "Nessun utente trovato per il certificato."
msgstr ""
#: authentik/enterprise/stages/source/models.py
msgid ""
@ -833,14 +834,6 @@ msgstr ""
"Definisci a quale gruppo di utenti deve essere inviata e mostrata questa "
"notifica. Se lasciato vuoto, la notifica non verrà inviata."
#: authentik/events/models.py
msgid ""
"When enabled, notification will be sent to user the user that triggered the "
"event.When destination_group is configured, notification is sent to both."
msgstr ""
"Se abilitata, la notifica verrà inviata all'utente che ha attivato l'evento."
" Se destination_group è configurato, la notifica verrà inviata a entrambi."
#: authentik/events/models.py
msgid "Notification Rule"
msgstr "Regola di notifica"
@ -1057,16 +1050,16 @@ msgstr "Avvio della sincronizzazione completa del provider"
#: authentik/lib/sync/outgoing/tasks.py
msgid "Syncing users"
msgstr "Sincronizzazione degli utenti"
msgstr ""
#: authentik/lib/sync/outgoing/tasks.py
msgid "Syncing groups"
msgstr "Sincronizzazione dei gruppi"
msgstr ""
#: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format
msgid "Syncing page {page} of {object_type}"
msgstr "Sincronizzazione della pagina {page} di {object_type}"
msgid "Syncing page {page} of groups"
msgstr "Sincronizzando pagina {page} dei gruppi"
#: authentik/lib/sync/outgoing/tasks.py
msgid "Dropping mutating request due to dry run"
@ -2468,10 +2461,6 @@ msgstr "Gruppo di aggiunta DN"
msgid "Consider Objects matching this filter to be Users."
msgstr "Considerare gli oggetti corrispondenti a questo filtro come Utenti."
#: authentik/sources/ldap/models.py
msgid "Attribute which matches the value of `group_membership_field`."
msgstr "Attributo che corrisponde al valore di `group_membership_field`."
#: authentik/sources/ldap/models.py
msgid "Field which contains members of a group."
msgstr "Campo che contiene i membri di un gruppo."
@ -2513,8 +2502,6 @@ msgid ""
"Delete authentik users and groups which were previously supplied by this "
"source, but are now missing from it."
msgstr ""
"Elimina gli utenti e i gruppi authentik precedentemente forniti da questa "
"fonte, ma che ora mancano."
#: authentik/sources/ldap/models.py
msgid "LDAP Source"
@ -2536,8 +2523,6 @@ msgstr "Mappature delle proprietà della sorgente LDAP"
msgid ""
"Unique ID used while checking if this object still exists in the directory."
msgstr ""
"ID univoco utilizzato per verificare se questo oggetto esiste ancora nella "
"directory."
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connection"
@ -2935,7 +2920,7 @@ msgstr "Connessioni sorgente SAML di gruppo"
#: authentik/sources/saml/views.py
#, python-brace-format
msgid "Continue to {source_name}"
msgstr "Continua su {source_name}"
msgstr ""
#: authentik/sources/scim/models.py
msgid "SCIM Source"
@ -3003,8 +2988,8 @@ msgstr "Fasi di configurazione dell'autenticatore email"
#: authentik/stages/email/stage.py
msgid "Exception occurred while rendering E-mail template"
msgstr ""
"Si è verificata un'eccezione durante la visualizzazione del modello di posta"
" elettronica"
"Eccezione verificatasi durante la visualizzazione del modello di posta "
"elettronica"
#: authentik/stages/authenticator_email/models.py
msgid "Email Device"
@ -3043,7 +3028,7 @@ msgid ""
" "
msgstr ""
"\n"
" Codice MFA via email.\n"
" Codice MFA via e-mail.\n"
" "
#: authentik/stages/authenticator_email/templates/email/email_otp.html
@ -3069,7 +3054,7 @@ msgid ""
"Email MFA code\n"
msgstr ""
"\n"
"Codice email MFA\n"
"Codice e-mail MFA\n"
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
#, python-format
@ -3336,7 +3321,7 @@ msgstr "Consensi utente"
#: authentik/stages/consent/stage.py
msgid "Invalid consent token, re-showing prompt"
msgstr "Token di consenso non valido, viene nuovamente visualizzato il prompt"
msgstr ""
#: authentik/stages/deny/models.py
msgid "Deny Stage"
@ -3356,11 +3341,11 @@ msgstr "Fasi fittizie"
#: authentik/stages/email/flow.py
msgid "Continue to confirm this email address."
msgstr "Continua per confermare questo indirizzo email."
msgstr ""
#: authentik/stages/email/flow.py
msgid "Link was already used, please request a new link."
msgstr "Il collegamento è già stato utilizzato. Richiedine uno nuovo."
msgstr ""
#: authentik/stages/email/models.py
msgid "Password Reset"
@ -3380,7 +3365,7 @@ msgstr "Fase email"
#: authentik/stages/email/models.py
msgid "Email Stages"
msgstr "Fasi email"
msgstr "Fasi Email"
#: authentik/stages/email/stage.py
msgid "Successfully verified Email."
@ -3482,7 +3467,7 @@ msgid ""
" "
msgstr ""
"\n"
" Se non hai richiesto una modifica della password, ignora questa email. Il link sopra è valido per %(expires)s.\n"
" Se non hai richiesto una modifica della password, ignora questa e-mail. Il link sopra è valido per %(expires)s.\n"
" "
#: authentik/stages/email/templates/email/password_reset.txt
@ -3500,11 +3485,11 @@ msgid ""
"If you did not request a password change, please ignore this email. The link above is valid for %(expires)s.\n"
msgstr ""
"\n"
"Se non hai richiesto una modifica della password, ignora questa email. Il link sopra è valido per %(expires)s.\n"
"Se non hai richiesto una modifica della password, ignora questa e-mail. Il link sopra è valido per %(expires)s.\n"
#: authentik/stages/email/templates/email/setup.html
msgid "authentik Test-Email"
msgstr "email di prova di authentik"
msgstr "e-mail di prova di authentik"
#: authentik/stages/email/templates/email/setup.html
msgid ""
@ -3513,7 +3498,7 @@ msgid ""
" "
msgstr ""
"\n"
" Questa è un'email di prova per informarti che hai configurato correttamente le email di authentik.\n"
" Questa è un'e-mail di prova per informarti che hai configurato correttamente le e-mail di authentik.\n"
" "
#: authentik/stages/email/templates/email/setup.txt
@ -3522,7 +3507,7 @@ msgid ""
"This is a test email to inform you, that you've successfully configured authentik emails.\n"
msgstr ""
"\n"
"Questa è un'email di prova per informarti che hai configurato correttamente le email di authentik.\n"
"Questa è un'e-mail di prova per informarti che hai configurato correttamente le e-mail di authentik.\n"
#: authentik/stages/identification/api.py
msgid "When no user fields are selected, at least one source must be selected"
@ -3725,7 +3710,7 @@ msgstr ""
#: authentik/stages/prompt/models.py
msgid "Email: Text field with Email type."
msgstr "Email: Campo di testo con il tipo di email."
msgstr "E-mail: Campo di testo con il tipo di e-mail."
#: authentik/stages/prompt/models.py
msgid ""
@ -3880,6 +3865,10 @@ msgstr "Fasi di accesso utente"
msgid "No Pending user to login."
msgstr "Nessun utente in attesa di accesso."
#: authentik/stages/user_login/stage.py
msgid "Successfully logged in!"
msgstr "Accesso effettuato!"
#: authentik/stages/user_logout/models.py
msgid "User Logout Stage"
msgstr "Fase di disconnessione dell'utente"

Binary file not shown.

View File

@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-25 00:10+0000\n"
"POT-Creation-Date: 2025-06-04 00:12+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2025\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
@ -118,6 +118,10 @@ msgstr "品牌"
msgid "User does not have access to application."
msgstr "用户没有访问此应用程序的权限。"
#: authentik/core/api/devices.py
msgid "Extra description not available"
msgstr "额外描述不可用"
#: authentik/core/api/groups.py
msgid "Cannot set group as parent of itself."
msgstr "无法设置组自身为父级。"
@ -771,12 +775,6 @@ msgid ""
"If left empty, Notification won't ben sent."
msgstr "定义此通知应该发送到哪些用户组。如果留空,则不会发送通知。"
#: authentik/events/models.py
msgid ""
"When enabled, notification will be sent to user the user that triggered the "
"event.When destination_group is configured, notification is sent to both."
msgstr "启用时,通知会被发送到触发事件的用户。当配置了 destination_group 时,通知也会同时发送到对应组。"
#: authentik/events/models.py
msgid "Notification Rule"
msgstr "通知规则"

Binary file not shown.

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