Compare commits
8 Commits
docusaurus
...
enterprise
Author | SHA1 | Date | |
---|---|---|---|
42a99e3672 | |||
4e501f2fbf | |||
1cca629464 | |||
4efdc3113e | |||
5a9b0f7b7a | |||
395ccc5af1 | |||
c8ac4fcdd6 | |||
53c36394e9 |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@ -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:
|
||||
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -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`)
|
||||
|
83
.github/workflows/ci-api-docs.yml
vendored
83
.github/workflows/ci-api-docs.yml
vendored
@ -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 }}
|
4
.github/workflows/ci-outpost.yml
vendored
4
.github/workflows/ci-outpost.yml
vendored
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
name: authentik-ci-docs
|
||||
name: authentik-ci-website
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -18,47 +18,50 @@ 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
|
||||
@ -95,7 +98,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,12 +111,12 @@ 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:
|
2
.github/workflows/release-publish.yml
vendored
2
.github/workflows/release-publish.yml
vendored
@ -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: .
|
||||
|
@ -10,7 +10,7 @@ coverage
|
||||
dist
|
||||
out
|
||||
.docusaurus
|
||||
docs/api/reference
|
||||
website/docs/developer-docs/api/**/*
|
||||
|
||||
## Environment
|
||||
*.env
|
||||
|
44
.vscode/tasks.json
vendored
44
.vscode/tasks.json
vendored
@ -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"
|
||||
}
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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 && \
|
||||
|
2
LICENSE
2
LICENSE
@ -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.
|
||||
|
26
Makefile
26
Makefile
@ -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
|
||||
|
@ -43,6 +43,7 @@ from authentik.core.models import (
|
||||
)
|
||||
from authentik.enterprise.license import LicenseKey
|
||||
from authentik.enterprise.models import LicenseUsage
|
||||
from authentik.enterprise.providers.apple_psso.models import AppleNonce
|
||||
from authentik.enterprise.providers.google_workspace.models import (
|
||||
GoogleWorkspaceProviderGroup,
|
||||
GoogleWorkspaceProviderUser,
|
||||
@ -135,6 +136,7 @@ def excluded_models() -> list[type[Model]]:
|
||||
EndpointDeviceConnection,
|
||||
DeviceToken,
|
||||
StreamEvent,
|
||||
AppleNonce,
|
||||
)
|
||||
|
||||
|
||||
|
32
authentik/enterprise/providers/apple_psso/api/providers.py
Normal file
32
authentik/enterprise/providers/apple_psso/api/providers.py
Normal file
@ -0,0 +1,32 @@
|
||||
"""Apple Platform SSO Provider API Views"""
|
||||
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.enterprise.api import EnterpriseRequiredMixin
|
||||
from authentik.enterprise.providers.apple_psso.models import ApplePlatformSSOProvider
|
||||
|
||||
|
||||
class ApplePlatformSSOProviderSerializer(EnterpriseRequiredMixin, ProviderSerializer):
|
||||
"""ApplePlatformSSOProvider Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = ApplePlatformSSOProvider
|
||||
fields = [
|
||||
"pk",
|
||||
"name",
|
||||
]
|
||||
extra_kwargs = {}
|
||||
|
||||
|
||||
class ApplePlatformSSOProviderViewSet(UsedByMixin, ModelViewSet):
|
||||
"""ApplePlatformSSOProvider Viewset"""
|
||||
|
||||
queryset = ApplePlatformSSOProvider.objects.all()
|
||||
serializer_class = ApplePlatformSSOProviderSerializer
|
||||
filterset_fields = [
|
||||
"name",
|
||||
]
|
||||
search_fields = ["name"]
|
||||
ordering = ["name"]
|
13
authentik/enterprise/providers/apple_psso/apps.py
Normal file
13
authentik/enterprise/providers/apple_psso/apps.py
Normal file
@ -0,0 +1,13 @@
|
||||
from authentik.enterprise.apps import EnterpriseConfig
|
||||
|
||||
|
||||
class AuthentikEnterpriseProviderApplePSSOConfig(EnterpriseConfig):
|
||||
|
||||
name = "authentik.enterprise.providers.apple_psso"
|
||||
label = "authentik_providers_apple_psso"
|
||||
verbose_name = "authentik Enterprise.Providers.Apple Platform SSO"
|
||||
default = True
|
||||
mountpoints = {
|
||||
"authentik.enterprise.providers.apple_psso.urls": "endpoint/apple/sso/",
|
||||
"authentik.enterprise.providers.apple_psso.urls_root": "",
|
||||
}
|
118
authentik/enterprise/providers/apple_psso/http.py
Normal file
118
authentik/enterprise/providers/apple_psso/http.py
Normal file
@ -0,0 +1,118 @@
|
||||
from base64 import urlsafe_b64encode
|
||||
from json import dumps
|
||||
from secrets import token_bytes
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
|
||||
from django.http import HttpResponse
|
||||
from jwcrypto.common import base64url_decode, base64url_encode
|
||||
|
||||
from authentik.enterprise.providers.apple_psso.models import AppleDevice
|
||||
|
||||
|
||||
def length_prefixed(data: bytes) -> bytes:
|
||||
length = len(data)
|
||||
return length.to_bytes(4, "big") + data
|
||||
|
||||
|
||||
def build_apu(public_key: ec.EllipticCurvePublicKey):
|
||||
# X9.63 representation: 0x04 || X || Y
|
||||
public_numbers = public_key.public_numbers()
|
||||
|
||||
x_bytes = public_numbers.x.to_bytes(32, "big")
|
||||
y_bytes = public_numbers.y.to_bytes(32, "big")
|
||||
|
||||
x963 = bytes([0x04]) + x_bytes + y_bytes
|
||||
|
||||
result = length_prefixed(b"APPLE") + length_prefixed(x963)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def encrypt_token_with_a256_gcm(body: dict, device_encryption_key: str, apv: bytes) -> str:
|
||||
ephemeral_key = ec.generate_private_key(curve=ec.SECP256R1())
|
||||
device_public_key = serialization.load_pem_public_key(
|
||||
device_encryption_key.encode(), backend=default_backend()
|
||||
)
|
||||
|
||||
shared_secret_z = ephemeral_key.exchange(ec.ECDH(), device_public_key)
|
||||
|
||||
apu = build_apu(ephemeral_key.public_key())
|
||||
|
||||
jwe_header = {
|
||||
"enc": "A256GCM",
|
||||
"kid": "ephemeralKey",
|
||||
"epk": {
|
||||
"x": base64url_encode(
|
||||
ephemeral_key.public_key().public_numbers().x.to_bytes(32, "big")
|
||||
),
|
||||
"y": base64url_encode(
|
||||
ephemeral_key.public_key().public_numbers().y.to_bytes(32, "big")
|
||||
),
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
},
|
||||
"typ": "platformsso-login-response+jwt",
|
||||
"alg": "ECDH-ES",
|
||||
"apu": base64url_encode(apu),
|
||||
"apv": base64url_encode(apv),
|
||||
}
|
||||
|
||||
party_u_info = length_prefixed(apu)
|
||||
party_v_info = length_prefixed(apv)
|
||||
supp_pub_info = (256).to_bytes(4, "big")
|
||||
|
||||
other_info = length_prefixed(b"A256GCM") + party_u_info + party_v_info + supp_pub_info
|
||||
|
||||
ckdf = ConcatKDFHash(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=32,
|
||||
otherinfo=other_info,
|
||||
)
|
||||
|
||||
derived_key = ckdf.derive(shared_secret_z)
|
||||
|
||||
nonce = token_bytes(12)
|
||||
|
||||
header_json = dumps(jwe_header, separators=(",", ":")).encode()
|
||||
aad = urlsafe_b64encode(header_json).rstrip(b"=")
|
||||
|
||||
aesgcm = AESGCM(derived_key)
|
||||
ciphertext = aesgcm.encrypt(nonce, dumps(body).encode(), aad)
|
||||
|
||||
ciphertext_body = ciphertext[:-16]
|
||||
tag = ciphertext[-16:]
|
||||
|
||||
# base64url encoding
|
||||
protected_b64 = urlsafe_b64encode(header_json).rstrip(b"=")
|
||||
iv_b64 = urlsafe_b64encode(nonce).rstrip(b"=")
|
||||
ciphertext_b64 = urlsafe_b64encode(ciphertext_body).rstrip(b"=")
|
||||
tag_b64 = urlsafe_b64encode(tag).rstrip(b"=")
|
||||
|
||||
jwe_compact = b".".join(
|
||||
[
|
||||
protected_b64,
|
||||
b"",
|
||||
iv_b64,
|
||||
ciphertext_b64,
|
||||
tag_b64,
|
||||
]
|
||||
)
|
||||
return jwe_compact.decode()
|
||||
|
||||
|
||||
class JWEResponse(HttpResponse):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: dict,
|
||||
device: AppleDevice,
|
||||
apv: str,
|
||||
):
|
||||
super().__init__(
|
||||
content=encrypt_token_with_a256_gcm(data, device.encryption_key, base64url_decode(apv)),
|
||||
content_type="application/platformsso-login-response+jwt",
|
||||
)
|
@ -0,0 +1,36 @@
|
||||
# Generated by Django 5.1.11 on 2025-06-28 00:12
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_oauth2", "0028_migrate_session"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ApplePlatformSSOProvider",
|
||||
fields=[
|
||||
(
|
||||
"oauth2provider_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_providers_oauth2.oauth2provider",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("authentik_providers_oauth2.oauth2provider",),
|
||||
),
|
||||
]
|
@ -0,0 +1,94 @@
|
||||
# Generated by Django 5.1.11 on 2025-06-28 15:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_apple_psso", "0001_initial"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="AppleDevice",
|
||||
fields=[
|
||||
(
|
||||
"endpoint_uuid",
|
||||
models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False),
|
||||
),
|
||||
("signing_key", models.TextField()),
|
||||
("encryption_key", models.TextField()),
|
||||
("key_exchange_key", models.TextField()),
|
||||
("sign_key_id", models.TextField()),
|
||||
("enc_key_id", models.TextField()),
|
||||
("creation_time", models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"provider",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="authentik_providers_apple_psso.appleplatformssoprovider",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="AppleDeviceUser",
|
||||
fields=[
|
||||
("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
("signing_key", models.TextField()),
|
||||
("encryption_key", models.TextField()),
|
||||
("sign_key_id", models.TextField()),
|
||||
("enc_key_id", models.TextField()),
|
||||
(
|
||||
"device",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="authentik_providers_apple_psso.appledevice",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="appledevice",
|
||||
name="users",
|
||||
field=models.ManyToManyField(
|
||||
through="authentik_providers_apple_psso.AppleDeviceUser",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="AppleNonce",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||
),
|
||||
),
|
||||
("expires", models.DateTimeField(default=None, null=True)),
|
||||
("expiring", models.BooleanField(default=True)),
|
||||
("nonce", models.TextField()),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
"indexes": [
|
||||
models.Index(fields=["expires"], name="authentik_p_expires_47d534_idx"),
|
||||
models.Index(fields=["expiring"], name="authentik_p_expirin_87253e_idx"),
|
||||
models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_p_expirin_20a7c9_idx"
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.1.11 on 2025-06-28 22:18
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"authentik_providers_apple_psso",
|
||||
"0002_appledevice_appledeviceuser_appledevice_users_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="appledeviceuser",
|
||||
old_name="sign_key_id",
|
||||
new_name="enclave_key_id",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="appledeviceuser",
|
||||
old_name="signing_key",
|
||||
new_name="secure_enclave_key",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="appledeviceuser",
|
||||
name="enc_key_id",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="appledeviceuser",
|
||||
name="encryption_key",
|
||||
),
|
||||
]
|
85
authentik/enterprise/providers/apple_psso/models.py
Normal file
85
authentik/enterprise/providers/apple_psso/models.py
Normal file
@ -0,0 +1,85 @@
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.serializers import Serializer
|
||||
|
||||
from authentik.core.models import ExpiringModel, User
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.providers.oauth2.models import (
|
||||
ClientTypes,
|
||||
IssuerMode,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
RedirectURIMatchingMode,
|
||||
ScopeMapping,
|
||||
)
|
||||
|
||||
|
||||
class ApplePlatformSSOProvider(OAuth2Provider):
|
||||
"""Integrate with Apple Platform SSO"""
|
||||
|
||||
def set_oauth_defaults(self):
|
||||
"""Ensure all OAuth2-related settings are correct"""
|
||||
self.issuer_mode = IssuerMode.PER_PROVIDER
|
||||
self.client_type = ClientTypes.PUBLIC
|
||||
self.signing_key = CertificateKeyPair.objects.get(name="authentik Self-signed Certificate")
|
||||
self.include_claims_in_id_token = True
|
||||
scopes = ScopeMapping.objects.filter(
|
||||
managed__in=[
|
||||
"goauthentik.io/providers/oauth2/scope-openid",
|
||||
"goauthentik.io/providers/oauth2/scope-profile",
|
||||
"goauthentik.io/providers/oauth2/scope-email",
|
||||
"goauthentik.io/providers/oauth2/scope-offline_access",
|
||||
"goauthentik.io/providers/oauth2/scope-authentik_api",
|
||||
]
|
||||
)
|
||||
self.property_mappings.add(*list(scopes))
|
||||
self.redirect_uris = [
|
||||
RedirectURI(RedirectURIMatchingMode.STRICT, "io.goauthentik.endpoint:/oauth2redirect"),
|
||||
]
|
||||
|
||||
@property
|
||||
def component(self) -> str:
|
||||
return "ak-provider-apple-psso-form"
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[Serializer]:
|
||||
from authentik.enterprise.providers.apple_psso.api.providers import (
|
||||
ApplePlatformSSOProviderSerializer,
|
||||
)
|
||||
|
||||
return ApplePlatformSSOProviderSerializer
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Apple Platform SSO Provider")
|
||||
verbose_name_plural = _("Apple Platform SSO Providers")
|
||||
|
||||
|
||||
class AppleDevice(models.Model):
|
||||
|
||||
endpoint_uuid = models.UUIDField(default=uuid4, primary_key=True)
|
||||
|
||||
signing_key = models.TextField()
|
||||
encryption_key = models.TextField()
|
||||
key_exchange_key = models.TextField()
|
||||
sign_key_id = models.TextField()
|
||||
enc_key_id = models.TextField()
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
provider = models.ForeignKey(ApplePlatformSSOProvider, on_delete=models.CASCADE)
|
||||
users = models.ManyToManyField(User, through="AppleDeviceUser")
|
||||
|
||||
|
||||
class AppleDeviceUser(models.Model):
|
||||
|
||||
uuid = models.UUIDField(default=uuid4, primary_key=True)
|
||||
|
||||
device = models.ForeignKey(AppleDevice, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
|
||||
secure_enclave_key = models.TextField()
|
||||
enclave_key_id = models.TextField()
|
||||
|
||||
|
||||
class AppleNonce(ExpiringModel):
|
||||
nonce = models.TextField()
|
15
authentik/enterprise/providers/apple_psso/urls.py
Normal file
15
authentik/enterprise/providers/apple_psso/urls.py
Normal file
@ -0,0 +1,15 @@
|
||||
from django.urls import path
|
||||
|
||||
from authentik.enterprise.providers.apple_psso.views.nonce import NonceView
|
||||
from authentik.enterprise.providers.apple_psso.views.register import (
|
||||
RegisterDeviceView,
|
||||
RegisterUserView,
|
||||
)
|
||||
from authentik.enterprise.providers.apple_psso.views.token import TokenView
|
||||
|
||||
urlpatterns = [
|
||||
path("token/", TokenView.as_view(), name="token"),
|
||||
path("nonce/", NonceView.as_view(), name="nonce"),
|
||||
path("register/device/", RegisterDeviceView.as_view(), name="register-device"),
|
||||
path("register/user/", RegisterUserView.as_view(), name="register-user"),
|
||||
]
|
7
authentik/enterprise/providers/apple_psso/urls_root.py
Normal file
7
authentik/enterprise/providers/apple_psso/urls_root.py
Normal file
@ -0,0 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
from authentik.enterprise.providers.apple_psso.views.site_association import AppleAppSiteAssociation
|
||||
|
||||
urlpatterns = [
|
||||
path(".well-known/apple-app-site-association", AppleAppSiteAssociation.as_view(), name="asa"),
|
||||
]
|
25
authentik/enterprise/providers/apple_psso/views/nonce.py
Normal file
25
authentik/enterprise/providers/apple_psso/views/nonce.py
Normal file
@ -0,0 +1,25 @@
|
||||
from base64 import b64encode
|
||||
from datetime import timedelta
|
||||
from secrets import token_bytes
|
||||
|
||||
from django.http import HttpRequest, JsonResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import now
|
||||
from django.views import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from authentik.enterprise.providers.apple_psso.models import AppleNonce
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class NonceView(View):
|
||||
|
||||
def post(self, request: HttpRequest, *args, **kwargs):
|
||||
nonce = AppleNonce.objects.create(
|
||||
nonce=b64encode(token_bytes(32)).decode(), expires=now() + timedelta(minutes=5)
|
||||
)
|
||||
return JsonResponse(
|
||||
{
|
||||
"Nonce": nonce.nonce,
|
||||
}
|
||||
)
|
92
authentik/enterprise/providers/apple_psso/views/register.py
Normal file
92
authentik/enterprise/providers/apple_psso/views/register.py
Normal file
@ -0,0 +1,92 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.authentication import BaseAuthentication
|
||||
from rest_framework.fields import CharField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from authentik.api.authentication import TokenAuthentication
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.core.models import User
|
||||
from authentik.enterprise.providers.apple_psso.models import (
|
||||
AppleDevice,
|
||||
AppleDeviceUser,
|
||||
ApplePlatformSSOProvider,
|
||||
)
|
||||
from authentik.lib.generators import generate_key
|
||||
|
||||
|
||||
class DeviceRegisterAuth(BaseAuthentication):
|
||||
def authenticate(self, request):
|
||||
# very temporary, lol
|
||||
return (User(), None)
|
||||
|
||||
|
||||
class RegisterDeviceView(APIView):
|
||||
|
||||
class DeviceRegistration(PassiveSerializer):
|
||||
|
||||
device_uuid = CharField()
|
||||
client_id = CharField()
|
||||
device_signing_key = CharField()
|
||||
device_encryption_key = CharField()
|
||||
sign_key_id = CharField()
|
||||
enc_key_id = CharField()
|
||||
|
||||
permission_classes = []
|
||||
pagination_class = None
|
||||
filter_backends = []
|
||||
serializer_class = DeviceRegistration
|
||||
authentication_classes = [DeviceRegisterAuth, TokenAuthentication]
|
||||
|
||||
def post(self, request: Request) -> Response:
|
||||
data = self.DeviceRegistration(data=request.data)
|
||||
data.is_valid(raise_exception=True)
|
||||
provider = get_object_or_404(
|
||||
ApplePlatformSSOProvider, client_id=data.validated_data["client_id"]
|
||||
)
|
||||
AppleDevice.objects.update_or_create(
|
||||
endpoint_uuid=data.validated_data["device_uuid"],
|
||||
defaults={
|
||||
"signing_key": data.validated_data["device_signing_key"],
|
||||
"encryption_key": data.validated_data["device_encryption_key"],
|
||||
"sign_key_id": data.validated_data["sign_key_id"],
|
||||
"enc_key_id": data.validated_data["enc_key_id"],
|
||||
"key_exchange_key": generate_key(),
|
||||
"provider": provider,
|
||||
},
|
||||
)
|
||||
return Response()
|
||||
|
||||
|
||||
class RegisterUserView(APIView):
|
||||
|
||||
class UserRegistration(PassiveSerializer):
|
||||
|
||||
device_uuid = CharField()
|
||||
user_secure_enclave_key = CharField()
|
||||
enclave_key_id = CharField()
|
||||
|
||||
permission_classes = []
|
||||
pagination_class = None
|
||||
filter_backends = []
|
||||
serializer_class = UserRegistration
|
||||
authentication_classes = [TokenAuthentication]
|
||||
|
||||
def post(self, request: Request) -> Response:
|
||||
data = self.UserRegistration(data=request.data)
|
||||
data.is_valid(raise_exception=True)
|
||||
device = get_object_or_404(AppleDevice, endpoint_uuid=data.validated_data["device_uuid"])
|
||||
AppleDeviceUser.objects.update_or_create(
|
||||
device=device,
|
||||
user=request.user,
|
||||
defaults={
|
||||
"secure_enclave_key": data.validated_data["user_secure_enclave_key"],
|
||||
"enclave_key_id": data.validated_data["enclave_key_id"],
|
||||
},
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"username": request.user.username,
|
||||
}
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.views import View
|
||||
|
||||
|
||||
class AppleAppSiteAssociation(View):
|
||||
def get(self, request: HttpRequest) -> HttpResponse:
|
||||
return JsonResponse(
|
||||
{
|
||||
"authsrv": {
|
||||
"apps": [
|
||||
"232G855Y8N.io.goauthentik.endpoint",
|
||||
"232G855Y8N.io.goauthentik.endpoint.psso",
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
140
authentik/enterprise/providers/apple_psso/views/token.py
Normal file
140
authentik/enterprise/providers/apple_psso/views/token.py
Normal file
@ -0,0 +1,140 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import now
|
||||
from django.views import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from jwt import PyJWT, decode
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import AuthenticatedSession, Session, User
|
||||
from authentik.core.sessions import SessionStore
|
||||
from authentik.enterprise.providers.apple_psso.http import JWEResponse
|
||||
from authentik.enterprise.providers.apple_psso.models import (
|
||||
AppleDevice,
|
||||
AppleDeviceUser,
|
||||
AppleNonce,
|
||||
ApplePlatformSSOProvider,
|
||||
)
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.events.signals import SESSION_LOGIN_EVENT
|
||||
from authentik.providers.oauth2.constants import TOKEN_TYPE
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import RefreshToken
|
||||
from authentik.root.middleware import SessionMiddleware
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TokenView(View):
|
||||
|
||||
device: AppleDevice
|
||||
provider: ApplePlatformSSOProvider
|
||||
|
||||
def post(self, request: HttpRequest) -> HttpResponse:
|
||||
version = request.POST.get("platform_sso_version")
|
||||
assertion = request.POST.get("assertion", request.POST.get("request"))
|
||||
if not assertion:
|
||||
return HttpResponse(status=400)
|
||||
|
||||
decode_unvalidated = PyJWT().decode_complete(assertion, options={"verify_signature": False})
|
||||
LOGGER.debug(decode_unvalidated["header"])
|
||||
expected_kid = decode_unvalidated["header"]["kid"]
|
||||
|
||||
self.device = AppleDevice.objects.filter(sign_key_id=expected_kid).first()
|
||||
if not self.device:
|
||||
raise Http404
|
||||
self.provider = self.device.provider
|
||||
|
||||
# Properly decode the JWT with the key from the device
|
||||
decoded = decode(
|
||||
assertion, self.device.signing_key, algorithms=["ES256"], options={"verify_aud": False}
|
||||
)
|
||||
LOGGER.debug(decoded)
|
||||
|
||||
LOGGER.debug("got device", device=self.device)
|
||||
|
||||
# Check that the nonce hasn't been used before
|
||||
nonce = AppleNonce.objects.filter(nonce=decoded["request_nonce"]).first()
|
||||
if not nonce:
|
||||
return HttpResponse(status=400)
|
||||
nonce.delete()
|
||||
|
||||
handler_func = (
|
||||
f"handle_v{version}_{decode_unvalidated["header"]["typ"]}".replace("-", "_")
|
||||
.replace("+", "_")
|
||||
.replace(".", "_")
|
||||
)
|
||||
handler = getattr(self, handler_func, None)
|
||||
if not handler:
|
||||
LOGGER.debug("Handler not found", handler=handler_func)
|
||||
return HttpResponse(status=400)
|
||||
LOGGER.debug("sending to handler", handler=handler_func)
|
||||
return handler(decoded)
|
||||
|
||||
def validate_device_user_response(self, assertion: str) -> tuple[AppleDeviceUser, dict] | None:
|
||||
"""Decode an embedded assertion and validate it by looking up the matching device user"""
|
||||
decode_unvalidated = PyJWT().decode_complete(assertion, options={"verify_signature": False})
|
||||
expected_kid = decode_unvalidated["header"]["kid"]
|
||||
|
||||
device_user = AppleDeviceUser.objects.filter(
|
||||
device=self.device, enclave_key_id=expected_kid
|
||||
).first()
|
||||
if not device_user:
|
||||
return None
|
||||
return device_user, decode(
|
||||
assertion,
|
||||
device_user.secure_enclave_key,
|
||||
audience="apple-platform-sso",
|
||||
algorithms=["ES256"],
|
||||
)
|
||||
|
||||
def create_auth_session(self, user: User):
|
||||
event = Event.new(EventAction.LOGIN).from_http(self.request, user=user)
|
||||
store = SessionStore()
|
||||
store[SESSION_LOGIN_EVENT] = event
|
||||
store.save()
|
||||
session = Session.objects.filter(session_key=store.session_key).first()
|
||||
AuthenticatedSession.objects.create(session=session, user=user)
|
||||
session = SessionMiddleware.encode_session(store.session_key, user)
|
||||
return session
|
||||
|
||||
def handle_v1_0_platformsso_login_request_jwt(self, decoded: dict):
|
||||
user = None
|
||||
if decoded["grant_type"] == "urn:ietf:params:oauth:grant-type:jwt-bearer":
|
||||
# Decode and validate inner assertion
|
||||
user, inner = self.validate_device_user_response(decoded["assertion"])
|
||||
if inner["nonce"] != decoded["nonce"]:
|
||||
LOGGER.warning("Mis-matched nonce to outer assertion")
|
||||
raise ValidationError("Invalid request")
|
||||
|
||||
refresh_token = RefreshToken(
|
||||
user=user.user,
|
||||
scope=decoded["scope"],
|
||||
expires=now() + timedelta(hours=8),
|
||||
provider=self.provider,
|
||||
auth_time=now(),
|
||||
session=None,
|
||||
)
|
||||
id_token = IDToken.new(
|
||||
self.provider,
|
||||
refresh_token,
|
||||
self.request,
|
||||
)
|
||||
id_token.nonce = decoded["nonce"]
|
||||
refresh_token.id_token = id_token
|
||||
refresh_token.save()
|
||||
return JWEResponse(
|
||||
{
|
||||
"refresh_token": refresh_token.token,
|
||||
"refresh_token_expires_in": int((refresh_token.expires - now()).total_seconds()),
|
||||
"id_token": refresh_token.id_token.to_jwt(self.provider),
|
||||
"token_type": TOKEN_TYPE,
|
||||
"session_key": self.create_auth_session(user.user),
|
||||
},
|
||||
device=self.device,
|
||||
apv=decoded["jwe_crypto"]["apv"],
|
||||
)
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -15,6 +15,7 @@ CELERY_BEAT_SCHEDULE = {
|
||||
TENANT_APPS = [
|
||||
"authentik.enterprise.audit",
|
||||
"authentik.enterprise.policies.unique_password",
|
||||
"authentik.enterprise.providers.apple_psso",
|
||||
"authentik.enterprise.providers.google_workspace",
|
||||
"authentik.enterprise.providers.microsoft_entra",
|
||||
"authentik.enterprise.providers.ssf",
|
||||
|
@ -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
|
||||
|
@ -555,6 +555,8 @@ class TokenView(View):
|
||||
|
||||
provider: OAuth2Provider | None = None
|
||||
params: TokenParams | None = None
|
||||
params_class = TokenParams
|
||||
provider_class = OAuth2Provider
|
||||
|
||||
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
@ -574,12 +576,14 @@ class TokenView(View):
|
||||
op="authentik.providers.oauth2.post.parse",
|
||||
):
|
||||
client_id, client_secret = extract_client_auth(request)
|
||||
self.provider = OAuth2Provider.objects.filter(client_id=client_id).first()
|
||||
self.provider = self.provider_class.objects.filter(client_id=client_id).first()
|
||||
if not self.provider:
|
||||
LOGGER.warning("OAuth2Provider does not exist", client_id=client_id)
|
||||
raise TokenError("invalid_client")
|
||||
CTX_AUTH_VIA.set("oauth_client_secret")
|
||||
self.params = TokenParams.parse(request, self.provider, client_id, client_secret)
|
||||
self.params = self.params_class.parse(
|
||||
request, self.provider, client_id, client_secret
|
||||
)
|
||||
|
||||
with start_span(
|
||||
op="authentik.providers.oauth2.post.response",
|
||||
|
@ -61,6 +61,22 @@ class SessionMiddleware(UpstreamSessionMiddleware):
|
||||
pass
|
||||
return session_key
|
||||
|
||||
@staticmethod
|
||||
def encode_session(session_key: str, user: User):
|
||||
payload = {
|
||||
"sid": session_key,
|
||||
"iss": "authentik",
|
||||
"sub": "anonymous",
|
||||
"authenticated": user.is_authenticated,
|
||||
"acr": ACR_AUTHENTIK_SESSION,
|
||||
}
|
||||
if user.is_authenticated:
|
||||
payload["sub"] = user.uid
|
||||
value = encode(payload=payload, key=SIGNING_HASH)
|
||||
if settings.TEST:
|
||||
value = session_key
|
||||
return value
|
||||
|
||||
def process_request(self, request: HttpRequest):
|
||||
raw_session = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
|
||||
session_key = SessionMiddleware.decode_session_key(raw_session)
|
||||
@ -117,21 +133,9 @@ class SessionMiddleware(UpstreamSessionMiddleware):
|
||||
"request completed. The user may have logged "
|
||||
"out in a concurrent request, for example."
|
||||
) from None
|
||||
payload = {
|
||||
"sid": request.session.session_key,
|
||||
"iss": "authentik",
|
||||
"sub": "anonymous",
|
||||
"authenticated": request.user.is_authenticated,
|
||||
"acr": ACR_AUTHENTIK_SESSION,
|
||||
}
|
||||
if request.user.is_authenticated:
|
||||
payload["sub"] = request.user.uid
|
||||
value = encode(payload=payload, key=SIGNING_HASH)
|
||||
if settings.TEST:
|
||||
value = request.session.session_key
|
||||
response.set_cookie(
|
||||
settings.SESSION_COOKIE_NAME,
|
||||
value,
|
||||
SessionMiddleware.encode_session(request.session.session_key, request.user),
|
||||
max_age=max_age,
|
||||
expires=expires,
|
||||
domain=settings.SESSION_COOKIE_DOMAIN,
|
||||
|
File diff suppressed because one or more lines are too long
@ -496,6 +496,46 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model",
|
||||
"identifiers"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
"const": "authentik_providers_apple_psso.appleplatformssoprovider"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"absent",
|
||||
"created",
|
||||
"must_created",
|
||||
"present"
|
||||
],
|
||||
"default": "present"
|
||||
},
|
||||
"conditions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/$defs/model_authentik_providers_apple_psso.appleplatformssoprovider_permissions"
|
||||
},
|
||||
"attrs": {
|
||||
"$ref": "#/$defs/model_authentik_providers_apple_psso.appleplatformssoprovider"
|
||||
},
|
||||
"identifiers": {
|
||||
"$ref": "#/$defs/model_authentik_providers_apple_psso.appleplatformssoprovider"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -5028,6 +5068,22 @@
|
||||
"authentik_policies_unique_password.delete_userpasswordhistory",
|
||||
"authentik_policies_unique_password.view_uniquepasswordpolicy",
|
||||
"authentik_policies_unique_password.view_userpasswordhistory",
|
||||
"authentik_providers_apple_psso.add_appledevice",
|
||||
"authentik_providers_apple_psso.add_appledeviceuser",
|
||||
"authentik_providers_apple_psso.add_applenonce",
|
||||
"authentik_providers_apple_psso.add_appleplatformssoprovider",
|
||||
"authentik_providers_apple_psso.change_appledevice",
|
||||
"authentik_providers_apple_psso.change_appledeviceuser",
|
||||
"authentik_providers_apple_psso.change_applenonce",
|
||||
"authentik_providers_apple_psso.change_appleplatformssoprovider",
|
||||
"authentik_providers_apple_psso.delete_appledevice",
|
||||
"authentik_providers_apple_psso.delete_appledeviceuser",
|
||||
"authentik_providers_apple_psso.delete_applenonce",
|
||||
"authentik_providers_apple_psso.delete_appleplatformssoprovider",
|
||||
"authentik_providers_apple_psso.view_appledevice",
|
||||
"authentik_providers_apple_psso.view_appledeviceuser",
|
||||
"authentik_providers_apple_psso.view_applenonce",
|
||||
"authentik_providers_apple_psso.view_appleplatformssoprovider",
|
||||
"authentik_providers_google_workspace.add_googleworkspaceprovider",
|
||||
"authentik_providers_google_workspace.add_googleworkspaceprovidergroup",
|
||||
"authentik_providers_google_workspace.add_googleworkspaceprovidermapping",
|
||||
@ -5599,6 +5655,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"model_authentik_providers_apple_psso.appleplatformssoprovider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Name"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"model_authentik_providers_apple_psso.appleplatformssoprovider_permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permission"
|
||||
],
|
||||
"properties": {
|
||||
"permission": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"add_appleplatformssoprovider",
|
||||
"change_appleplatformssoprovider",
|
||||
"delete_appleplatformssoprovider",
|
||||
"view_appleplatformssoprovider"
|
||||
]
|
||||
},
|
||||
"user": {
|
||||
"type": "integer"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"model_authentik_providers_google_workspace.googleworkspaceprovider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -7342,6 +7435,7 @@
|
||||
"authentik.enterprise",
|
||||
"authentik.enterprise.audit",
|
||||
"authentik.enterprise.policies.unique_password",
|
||||
"authentik.enterprise.providers.apple_psso",
|
||||
"authentik.enterprise.providers.google_workspace",
|
||||
"authentik.enterprise.providers.microsoft_entra",
|
||||
"authentik.enterprise.providers.ssf",
|
||||
@ -7452,6 +7546,7 @@
|
||||
"authentik_core.token",
|
||||
"authentik_enterprise.license",
|
||||
"authentik_policies_unique_password.uniquepasswordpolicy",
|
||||
"authentik_providers_apple_psso.appleplatformssoprovider",
|
||||
"authentik_providers_google_workspace.googleworkspaceprovider",
|
||||
"authentik_providers_google_workspace.googleworkspaceprovidermapping",
|
||||
"authentik_providers_microsoft_entra.microsoftentraprovider",
|
||||
@ -9674,6 +9769,22 @@
|
||||
"authentik_policies_unique_password.delete_userpasswordhistory",
|
||||
"authentik_policies_unique_password.view_uniquepasswordpolicy",
|
||||
"authentik_policies_unique_password.view_userpasswordhistory",
|
||||
"authentik_providers_apple_psso.add_appledevice",
|
||||
"authentik_providers_apple_psso.add_appledeviceuser",
|
||||
"authentik_providers_apple_psso.add_applenonce",
|
||||
"authentik_providers_apple_psso.add_appleplatformssoprovider",
|
||||
"authentik_providers_apple_psso.change_appledevice",
|
||||
"authentik_providers_apple_psso.change_appledeviceuser",
|
||||
"authentik_providers_apple_psso.change_applenonce",
|
||||
"authentik_providers_apple_psso.change_appleplatformssoprovider",
|
||||
"authentik_providers_apple_psso.delete_appledevice",
|
||||
"authentik_providers_apple_psso.delete_appledeviceuser",
|
||||
"authentik_providers_apple_psso.delete_applenonce",
|
||||
"authentik_providers_apple_psso.delete_appleplatformssoprovider",
|
||||
"authentik_providers_apple_psso.view_appledevice",
|
||||
"authentik_providers_apple_psso.view_appledeviceuser",
|
||||
"authentik_providers_apple_psso.view_applenonce",
|
||||
"authentik_providers_apple_psso.view_appleplatformssoprovider",
|
||||
"authentik_providers_google_workspace.add_googleworkspaceprovider",
|
||||
"authentik_providers_google_workspace.add_googleworkspaceprovidergroup",
|
||||
"authentik_providers_google_workspace.add_googleworkspaceprovidermapping",
|
||||
|
@ -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
|
@ -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
|
@ -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.
|
@ -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 />
|
@ -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.
|
@ -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.
|
||||
:::
|
@ -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
|
||||
```
|
@ -1 +0,0 @@
|
||||
module.exports = import("./docusaurus.config.esm.mjs").then(($) => $.default);
|
@ -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);
|
@ -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);
|
||||
}
|
@ -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.
|
@ -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"
|
@ -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": "*"
|
||||
}
|
||||
}
|
@ -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;
|
@ -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}}}
|
@ -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>
|
||||
);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
.docItemContainer header + *,
|
||||
.docItemContainer article > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 997px) {
|
||||
.docItemCol {
|
||||
max-width: 75% !important;
|
||||
}
|
||||
}
|
@ -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;
|
@ -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"];
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"references": [
|
||||
{
|
||||
"path": "../docusaurus-theme"
|
||||
}
|
||||
]
|
||||
}
|
37
docs/api/types/api-plugin.d.ts
vendored
37
docs/api/types/api-plugin.d.ts
vendored
@ -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;
|
||||
}
|
34
docs/api/types/globals.d.ts
vendored
34
docs/api/types/globals.d.ts
vendored
@ -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"];
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
},
|
||||
);
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
import { createESLintPackageConfig } from "@goauthentik/eslint-config";
|
||||
|
||||
export default createESLintPackageConfig();
|
@ -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";
|
||||
},
|
||||
};
|
||||
}
|
@ -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/*"
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
||||
}
|
@ -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";
|
@ -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;
|
@ -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);
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json"
|
||||
}
|
44
docs/docusaurus-theme/types/globals.d.ts
vendored
44
docs/docusaurus-theme/types/globals.d.ts
vendored
@ -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"];
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { DefaultIgnorePatterns, createESLintPackageConfig } from "@goauthentik/eslint-config";
|
||||
|
||||
export default createESLintPackageConfig({
|
||||
ignorePatterns: [
|
||||
// ---
|
||||
...DefaultIgnorePatterns,
|
||||
"**/.docusaurus/",
|
||||
"**/build",
|
||||
"**/reference",
|
||||
],
|
||||
});
|
@ -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;
|
||||
}
|
@ -1 +0,0 @@
|
||||
module.exports = import("./docusaurus.config.esm.mjs").then(($) => $.default);
|
@ -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)));
|
@ -1,10 +0,0 @@
|
||||
import { DefaultIgnorePatterns, createESLintPackageConfig } from "@goauthentik/eslint-config";
|
||||
|
||||
export default createESLintPackageConfig({
|
||||
ignorePatterns: [
|
||||
// ---
|
||||
...DefaultIgnorePatterns,
|
||||
".docusaurus/",
|
||||
"./build",
|
||||
],
|
||||
});
|
@ -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"
|
@ -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"
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
],
|
||||
})),
|
||||
],
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"references": [
|
||||
{
|
||||
"path": "../docusaurus-theme"
|
||||
}
|
||||
]
|
||||
}
|
13
docs/integrations/types/globals.d.ts
vendored
13
docs/integrations/types/globals.d.ts
vendored
@ -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" />
|
@ -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"
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
module.exports = import("./docusaurus.config.esm.mjs").then(($) => $.default);
|
@ -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)));
|
@ -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"
|
@ -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"
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"references": [
|
||||
{
|
||||
"path": "../docusaurus-theme"
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -23,8 +23,8 @@ export const DefaultIgnorePatterns = [
|
||||
"**/out",
|
||||
"**/dist",
|
||||
"**/.wireit",
|
||||
"docs/**/build/**",
|
||||
"docs/.docusaurus/**",
|
||||
"website/build/**",
|
||||
"website/.docusaurus/**",
|
||||
"**/node_modules",
|
||||
"**/coverage",
|
||||
"**/storybook-static",
|
||||
|
@ -43,7 +43,7 @@ dependencies = [
|
||||
"jwcrypto==1.5.6",
|
||||
"kubernetes==33.1.0",
|
||||
"ldap3==2.9.1",
|
||||
"lxml==6.0.0",
|
||||
"lxml==5.4.0",
|
||||
"msgraph-sdk==1.35.0",
|
||||
"opencontainers==0.0.14",
|
||||
"packaging==25.0",
|
||||
@ -144,9 +144,7 @@ skip = [
|
||||
"**/web/out",
|
||||
"./web/storybook-static",
|
||||
"./web/custom-elements.json",
|
||||
"./docs/build",
|
||||
"./docs/api/build",
|
||||
"./docs/integrations/build",
|
||||
"./website/build",
|
||||
"./gen-ts-api",
|
||||
"./gen-py-api",
|
||||
"./gen-go-api",
|
||||
@ -201,7 +199,7 @@ omit = [
|
||||
"*/migrations/*",
|
||||
"*/management/commands/*",
|
||||
"*/apps.py",
|
||||
"docs/",
|
||||
"website/",
|
||||
]
|
||||
|
||||
[tool.coverage.report]
|
||||
|
16
schema.yml
16
schema.yml
@ -24864,6 +24864,7 @@ paths:
|
||||
- authentik_policies_password.passwordpolicy
|
||||
- authentik_policies_reputation.reputationpolicy
|
||||
- authentik_policies_unique_password.uniquepasswordpolicy
|
||||
- authentik_providers_apple_psso.appleplatformssoprovider
|
||||
- authentik_providers_google_workspace.googleworkspaceprovider
|
||||
- authentik_providers_google_workspace.googleworkspaceprovidermapping
|
||||
- authentik_providers_ldap.ldapprovider
|
||||
@ -25113,6 +25114,7 @@ paths:
|
||||
- authentik_policies_password.passwordpolicy
|
||||
- authentik_policies_reputation.reputationpolicy
|
||||
- authentik_policies_unique_password.uniquepasswordpolicy
|
||||
- authentik_providers_apple_psso.appleplatformssoprovider
|
||||
- authentik_providers_google_workspace.googleworkspaceprovider
|
||||
- authentik_providers_google_workspace.googleworkspaceprovidermapping
|
||||
- authentik_providers_ldap.ldapprovider
|
||||
@ -41212,6 +41214,7 @@ components:
|
||||
- authentik.enterprise
|
||||
- authentik.enterprise.audit
|
||||
- authentik.enterprise.policies.unique_password
|
||||
- authentik.enterprise.providers.apple_psso
|
||||
- authentik.enterprise.providers.google_workspace
|
||||
- authentik.enterprise.providers.microsoft_entra
|
||||
- authentik.enterprise.providers.ssf
|
||||
@ -41258,6 +41261,15 @@ components:
|
||||
- redirect_uri
|
||||
- scope
|
||||
- state
|
||||
ApplePlatformSSOProviderRequest:
|
||||
type: object
|
||||
description: ApplePlatformSSOProvider Serializer
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
required:
|
||||
- name
|
||||
Application:
|
||||
type: object
|
||||
description: Application Serializer
|
||||
@ -48714,6 +48726,7 @@ components:
|
||||
- authentik_core.token
|
||||
- authentik_enterprise.license
|
||||
- authentik_policies_unique_password.uniquepasswordpolicy
|
||||
- authentik_providers_apple_psso.appleplatformssoprovider
|
||||
- authentik_providers_google_workspace.googleworkspaceprovider
|
||||
- authentik_providers_google_workspace.googleworkspaceprovidermapping
|
||||
- authentik_providers_microsoft_entra.microsoftentraprovider
|
||||
@ -56659,6 +56672,7 @@ components:
|
||||
type: string
|
||||
ProviderModelEnum:
|
||||
enum:
|
||||
- authentik_providers_apple_psso.appleplatformssoprovider
|
||||
- authentik_providers_google_workspace.googleworkspaceprovider
|
||||
- authentik_providers_ldap.ldapprovider
|
||||
- authentik_providers_microsoft_entra.microsoftentraprovider
|
||||
@ -61871,6 +61885,7 @@ components:
|
||||
- worker_id
|
||||
modelRequest:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/ApplePlatformSSOProviderRequest'
|
||||
- $ref: '#/components/schemas/GoogleWorkspaceProviderRequest'
|
||||
- $ref: '#/components/schemas/LDAPProviderRequest'
|
||||
- $ref: '#/components/schemas/MicrosoftEntraProviderRequest'
|
||||
@ -61884,6 +61899,7 @@ components:
|
||||
discriminator:
|
||||
propertyName: provider_model
|
||||
mapping:
|
||||
authentik_providers_apple_psso.appleplatformssoprovider: '#/components/schemas/ApplePlatformSSOProviderRequest'
|
||||
authentik_providers_google_workspace.googleworkspaceprovider: '#/components/schemas/GoogleWorkspaceProviderRequest'
|
||||
authentik_providers_ldap.ldapprovider: '#/components/schemas/LDAPProviderRequest'
|
||||
authentik_providers_microsoft_entra.microsoftentraprovider: '#/components/schemas/MicrosoftEntraProviderRequest'
|
||||
|
35
uv.lock
generated
35
uv.lock
generated
@ -305,7 +305,7 @@ requires-dist = [
|
||||
{ name = "jwcrypto", specifier = "==1.5.6" },
|
||||
{ name = "kubernetes", specifier = "==33.1.0" },
|
||||
{ name = "ldap3", specifier = "==2.9.1" },
|
||||
{ name = "lxml", specifier = "==6.0.0" },
|
||||
{ name = "lxml", specifier = "==5.4.0" },
|
||||
{ name = "msgraph-sdk", specifier = "==1.35.0" },
|
||||
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
|
||||
{ name = "packaging", specifier = "==25.0" },
|
||||
@ -1824,22 +1824,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "6.0.0"
|
||||
version = "5.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372, upload-time = "2025-06-26T16:26:39.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940, upload-time = "2025-06-26T16:26:41.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329, upload-time = "2025-06-26T16:26:44.669Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931, upload-time = "2025-06-26T16:26:47.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467, upload-time = "2025-06-26T16:26:49.998Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601, upload-time = "2025-06-26T16:26:52.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227, upload-time = "2025-06-26T16:26:55.054Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637, upload-time = "2025-06-26T16:26:57.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430, upload-time = "2025-06-26T16:27:00.031Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896, upload-time = "2025-06-26T16:27:04.251Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132, upload-time = "2025-06-26T16:27:06.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642, upload-time = "2025-06-26T16:27:09.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/cb/2ba1e9dd953415f58548506fa5549a7f373ae55e80c61c9041b7fd09a38a/lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", size = 8110086, upload-time = "2025-04-23T01:46:52.218Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/3e/6602a4dca3ae344e8609914d6ab22e52ce42e3e1638c10967568c5c1450d/lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", size = 4404613, upload-time = "2025-04-23T01:46:55.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/72/bf00988477d3bb452bef9436e45aeea82bb40cdfb4684b83c967c53909c7/lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", size = 5012008, upload-time = "2025-04-23T01:46:57.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/1f/93e42d93e9e7a44b2d3354c462cd784dbaaf350f7976b5d7c3f85d68d1b1/lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", size = 4760915, upload-time = "2025-04-23T01:47:00.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/0b/363009390d0b461cf9976a499e83b68f792e4c32ecef092f3f9ef9c4ba54/lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", size = 5283890, upload-time = "2025-04-23T01:47:04.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/dc/6056c332f9378ab476c88e301e6549a0454dbee8f0ae16847414f0eccb74/lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", size = 4812644, upload-time = "2025-04-23T01:47:07.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/8a/f8c66bbb23ecb9048a46a5ef9b495fd23f7543df642dabeebcb2eeb66592/lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", size = 4921817, upload-time = "2025-04-23T01:47:10.317Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/57/2e537083c3f381f83d05d9b176f0d838a9e8961f7ed8ddce3f0217179ce3/lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", size = 4753916, upload-time = "2025-04-23T01:47:12.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/80/ea8c4072109a350848f1157ce83ccd9439601274035cd045ac31f47f3417/lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", size = 5289274, upload-time = "2025-04-23T01:47:15.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/47/c4be287c48cdc304483457878a3f22999098b9a95f455e3c4bda7ec7fc72/lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", size = 4874757, upload-time = "2025-04-23T01:47:19.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/04/6ef935dc74e729932e39478e44d8cfe6a83550552eaa072b7c05f6f22488/lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", size = 4947028, upload-time = "2025-04-23T01:47:22.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/f9/c33fc8daa373ef8a7daddb53175289024512b6619bc9de36d77dca3df44b/lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", size = 4834487, upload-time = "2025-04-23T01:47:25.513Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/30/fc92bb595bcb878311e01b418b57d13900f84c2b94f6eca9e5073ea756e6/lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", size = 5381688, upload-time = "2025-04-23T01:47:28.454Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/d1/3ba7bd978ce28bba8e3da2c2e9d5ae3f8f521ad3f0ca6ea4788d086ba00d/lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", size = 5242043, upload-time = "2025-04-23T01:47:31.208Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/cd/95fa2201041a610c4d08ddaf31d43b98ecc4b1d74b1e7245b1abdab443cb/lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", size = 5021569, upload-time = "2025-04-23T01:47:33.805Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/a6/31da006fead660b9512d08d23d31e93ad3477dd47cc42e3285f143443176/lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", size = 3485270, upload-time = "2025-04-23T01:47:36.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/14/c115516c62a7d2499781d2d3d7215218c0731b2c940753bf9f9b7b73924d/lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", size = 3814606, upload-time = "2025-04-23T01:47:39.028Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -39,10 +39,8 @@ const pluginName = "mdx-plugin";
|
||||
* @returns {Plugin}
|
||||
*/
|
||||
export function mdxPlugin({ root }) {
|
||||
const prefix = "~docs";
|
||||
|
||||
// TODO: Replace with `resolvePackage` after NPM Workspaces support is added.
|
||||
const docsPackageRoot = path.resolve(MonoRepoRoot, "docs");
|
||||
const docsPackageRoot = path.resolve(MonoRepoRoot, "website");
|
||||
|
||||
/**
|
||||
* @param {PluginBuild} build
|
||||
@ -56,8 +54,7 @@ export function mdxPlugin({ root }) {
|
||||
if (!args.path.startsWith("~")) return args;
|
||||
|
||||
return {
|
||||
path: path.join(docsPackageRoot, "topics", args.path.slice(prefix.length)),
|
||||
|
||||
path: path.resolve(docsPackageRoot, args.path.slice(1)),
|
||||
pluginName,
|
||||
};
|
||||
}
|
||||
@ -77,7 +74,7 @@ export function mdxPlugin({ root }) {
|
||||
|
||||
const publicPath = path.resolve(
|
||||
"/",
|
||||
path.relative(path.join(root, "docs", "topics"), data.path),
|
||||
path.relative(path.join(root, "website"), data.path),
|
||||
);
|
||||
const publicDirectory = path.dirname(publicPath);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user