Compare commits

..

3 Commits

Author SHA1 Message Date
Marcelo Elizeche Landó
264ad31f50 refactor for bandit alerts 2026-04-30 02:39:29 -03:00
Marcelo Elizeche Landó
cced288ddb Add polling to shutdown.wait in lifecycle/worker_process.py 2026-04-30 01:35:06 -03:00
Marcelo Elizeche Landó
4aa323bc20 Add debug-attach to Makefile implementing Python 3.14 remote debugging interface 2026-04-30 00:12:01 -03:00
76 changed files with 1680 additions and 3104 deletions

View File

@@ -1,81 +0,0 @@
name: "Setup Node.js and NPM"
description: "Sets up Node.js with a specific NPM version via Corepack"
inputs:
working-directory:
description: "Path to the working directory containing the package.json file"
required: false
default: "."
dependencies:
required: false
description: "List of dependencies to setup"
default: "monorepo,working-directory"
node-version-file:
description: "Path to file containing the Node.js version"
required: false
default: "package.json"
cache-dependency-path:
description: "Path to dependency lock file for caching"
required: false
default: "package-lock.json"
cache:
description: "Package manager to cache"
default: "npm"
registry-url:
description: "npm registry URL"
default: "https://registry.npmjs.org"
runs:
using: "composite"
steps:
- name: Setup Node.js (Corepack bootstrap)
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: ${{ inputs.node-version-file }}
registry-url: ${{ inputs.registry-url }}
# The setup-node action will attempt to create a cache using a version of
# npm that may not be compatible with the range specified in package.json.
# This can be enabled **after** corepack is installed and the correct npm version is available.
package-manager-cache: false
- name: Install Corepack
working-directory: ${{ github.workspace}}
shell: bash
run: | #shell
node ./scripts/node/lint-runtime.mjs
node ./scripts/node/setup-corepack.mjs --force
corepack enable
- name: Lint Node.js and NPM versions
shell: bash
run: node ./scripts/node/lint-runtime.mjs
- name: Setup Node.js (Monorepo Root)
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: ${{ inputs.node-version-file }}
cache: ${{ inputs.cache }}
cache-dependency-path: ${{ inputs.cache-dependency-path }}
registry-url: ${{ inputs.registry-url }}
- name: Install monorepo dependencies
if: ${{ contains(inputs.dependencies, 'monorepo') }}
shell: bash
run: | #shell
node ./scripts/node/lint-lockfile.mjs
corepack npm ci
- name: Setup Node.js (Working Directory)
if: ${{ contains(inputs.dependencies, 'working-directory') }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: ${{ inputs.working-directory }}/${{ inputs.node-version-file }}
cache: ${{ inputs.cache }}
cache-dependency-path: ${{ inputs.working-directory }}/${{ inputs.cache-dependency-path }}
registry-url: ${{ inputs.registry-url }}
- name: Install working directory dependencies
if: ${{ contains(inputs.dependencies, 'working-directory') }}
shell: bash
run: | # shell
corepack install
echo "node version: $(node --version)"
echo "npm version: $(corepack npm --version)"
node ./scripts/node/lint-lockfile.mjs ${{ inputs.working-directory }}
corepack npm ci --prefix ${{ inputs.working-directory }}

View File

@@ -18,24 +18,19 @@ runs:
using: "composite"
steps:
- name: Cleanup apt
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies,
'python') }}
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
shell: bash
run: sudo apt-get remove --purge man-db
- name: Install apt deps
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies,
'python') }}
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
uses: gerlero/apt-install@f4fa5265092af9e750549565d28c99aec7189639
with:
packages: libpq-dev openssl libxmlsec1-dev pkg-config gettext krb5-multidev
libkrb5-dev heimdal-multidev libclang-dev krb5-kdc krb5-user
krb5-admin-server
packages: libpq-dev openssl libxmlsec1-dev pkg-config gettext krb5-multidev libkrb5-dev heimdal-multidev libclang-dev krb5-kdc krb5-user krb5-admin-server
update: true
upgrade: false
install-recommends: false
- name: Make space on disk
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies,
'python') }}
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
shell: bash
run: |
sudo mkdir -p /tmp/empty/
@@ -56,8 +51,7 @@ runs:
working-directory: ${{ inputs.working-directory }}
run: uv sync --all-extras --dev --frozen
- name: Setup rust (stable)
if: ${{ contains(inputs.dependencies, 'rust') && !contains(inputs.dependencies,
'rust-nightly') }}
if: ${{ contains(inputs.dependencies, 'rust') && !contains(inputs.dependencies, 'rust-nightly') }}
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1
with:
rustflags: ""
@@ -70,14 +64,30 @@ runs:
rustflags: ""
- name: Setup rust dependencies
if: ${{ contains(inputs.dependencies, 'rust') }}
uses: taiki-e/install-action@481c34c1cf3a84c68b5e46f4eccfc82af798415a # v2
uses: taiki-e/install-action@cf525cb33f51aca27cd6fa02034117ab963ff9f1 # v2
with:
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
- name: Setup node (root, web)
- name: Setup node (web)
if: ${{ contains(inputs.dependencies, 'node') }}
uses: ./.github/actions/setup-node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
working-directory: web
node-version-file: "${{ inputs.working-directory }}web/package.json"
cache: "npm"
cache-dependency-path: "${{ inputs.working-directory }}web/package-lock.json"
registry-url: "https://registry.npmjs.org"
- name: Setup node (root)
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: "${{ inputs.working-directory }}package.json"
cache: "npm"
cache-dependency-path: "${{ inputs.working-directory }}package-lock.json"
registry-url: "https://registry.npmjs.org"
- name: Install Node deps
if: ${{ contains(inputs.dependencies, 'node') }}
shell: bash
working-directory: ${{ inputs.working-directory }}
run: npm ci
- name: Setup go
if: ${{ contains(inputs.dependencies, 'go') }}
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v5
@@ -87,9 +97,7 @@ runs:
if: ${{ contains(inputs.dependencies, 'runtime') }}
uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
with:
key: docker-images-${{ runner.os }}-${{
hashFiles('.github/actions/setup/compose.yml', 'Makefile') }}-${{
inputs.postgresql_version }}
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
- name: Setup dependencies
if: ${{ contains(inputs.dependencies, 'runtime') }}
shell: bash
@@ -97,7 +105,7 @@ runs:
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/compose.yml up -d
corepack npm ci --prefix web
cd web && npm ci
- name: Generate config
if: ${{ contains(inputs.dependencies, 'python') }}
shell: uv run python {0}

View File

@@ -67,16 +67,6 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: ./.github/actions/setup-node
with:
working-directory: web
dependencies: "monorepo"
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- name: Generate API Clients
run: |
make gen-client-ts
- name: Build Docker Image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
id: push
@@ -91,8 +81,7 @@ jobs:
${{ steps.ev.outputs.imageBuildArgs }}
tags: ${{ steps.ev.outputs.imageTags }}
platforms: linux/${{ inputs.image_arch }}
cache-from: type=registry,ref=${{ steps.ev.outputs.attestImageNames
}}:buildcache-${{ inputs.image_arch }}
cache-from: type=registry,ref=${{ steps.ev.outputs.attestImageNames }}:buildcache-${{ inputs.image_arch }}
cache-to: ${{ steps.ev.outputs.cacheTo }}
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest

View File

@@ -1,65 +0,0 @@
---
name: API - Publish Typescript client
on:
push:
branches: [main]
paths:
- "schema.yml"
workflow_dispatch:
permissions:
# Required for NPM OIDC trusted publisher
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
token: ${{ steps.generate_token.outputs.token }}
- uses: ./.github/actions/setup-node
with:
working-directory: web
- name: Generate API Client
run: make gen-client-ts
- name: Publish package
working-directory: gen-ts-api/
run: |
npm i
npm publish --tag generated
- name: Upgrade /web
working-directory: web
run: |
export VERSION=`node -e 'import mod from "./gen-ts-api/package.json" with { type: "json" };console.log(mod.version);'`
npm i @goauthentik/api@$VERSION
- name: Upgrade /web/packages/sfe
working-directory: web/packages/sfe
run: |
export VERSION=`node -e 'import mod from "./gen-ts-api/package.json" with { type: "json" };console.log(mod.version);'`
npm i @goauthentik/api@$VERSION
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}
branch: update-web-api-client
commit-message: "web: bump API Client version"
title: "web: bump API Client version"
body: "web: bump API Client version"
delete-branch: true
signoff: true
# ID from https://api.github.com/users/authentik-automation[bot]
author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
labels: dependencies
- uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3
with:
token: ${{ steps.generate_token.outputs.token }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash

View File

@@ -22,19 +22,25 @@ jobs:
- prettier-check
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: ./.github/actions/setup-node
with:
working-directory: website
- name: Install Dependencies
working-directory: website/
run: npm ci
- name: Lint
run: corepack npm run ${{ matrix.command }} --prefix website
working-directory: website/
run: npm run ${{ matrix.command }}
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: website
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
name: Install Dependencies
run: npm ci
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v4
with:
path: |
@@ -48,7 +54,7 @@ jobs:
working-directory: website
env:
NODE_ENV: production
run: corepack npm run build -w api
run: npm run build -w api
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4
with:
name: api-docs
@@ -65,9 +71,11 @@ jobs:
with:
name: api-docs
path: website/api/build
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: website
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- name: Deploy Netlify (Production)
working-directory: website/api
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

View File

@@ -24,9 +24,14 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Setup authentik env
uses: ./.github/actions/setup
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: lifecycle/aws
node-version-file: lifecycle/aws/package.json
cache: "npm"
cache-dependency-path: lifecycle/aws/package-lock.json
- working-directory: lifecycle/aws/
run: |
npm ci
- name: Check changes have been applied
run: |
uv run make aws-cfn

View File

@@ -24,34 +24,46 @@ jobs:
- prettier-check
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: ./.github/actions/setup-node
with:
working-directory: website
- name: Install dependencies
working-directory: website/
run: npm ci
- name: Lint
run: corepack npm run ${{ matrix.command }} --prefix website
working-directory: website/
run: npm run ${{ matrix.command }}
build-docs:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: ./.github/actions/setup-node
name: Setup Node.js
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: website
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
name: Install Dependencies
run: npm ci
- name: Build Documentation via Docusaurus
run: corepack npm run build --prefix website
working-directory: website/
run: npm run build
build-integrations:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: website
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
name: Install Dependencies
run: npm ci
- name: Build Integrations via Docusaurus
run: corepack npm run build -w integrations --prefix website
working-directory: website/
run: npm run build -w integrations
build-container:
runs-on: ubuntu-latest
permissions:
@@ -92,9 +104,7 @@ jobs:
platforms: linux/amd64,linux/arm64
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' &&
'type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache,mode=max'
|| '' }}
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache,mode=max' || '' }}
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}

View File

@@ -73,8 +73,7 @@ jobs:
- name: generate API clients
run: make gen-clients
- name: ensure schema is up-to-date
run: git diff --exit-code -- schema.yml blueprints/schema.json
packages/client-go packages/client-rust packages/client-ts
run: git diff --exit-code -- schema.yml blueprints/schema.json packages/client-go packages/client-rust packages/client-ts
test-migrations:
runs-on: ubuntu-latest
steps:
@@ -92,8 +91,7 @@ jobs:
outputs:
seed: ${{ steps.seed.outputs.seed }}
test-migrations-from-stable:
name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }} - Run ${{
matrix.run_id }}/5
name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
runs-on: ubuntu-latest
timeout-minutes: 30
needs: test-make-seed
@@ -103,7 +101,7 @@ jobs:
psql:
- 14-alpine
- 18-alpine
run_id: [ 1, 2, 3, 4, 5 ]
run_id: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
@@ -111,13 +109,8 @@ jobs:
- name: checkout stable
run: |
set -e -o pipefail
cp -R .github ..
cp -R scripts ..
mkdir -p ../packages
cp -R packages/logger-js ../packages/logger-js
# Previous stable tag
prev_stable=$(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
# Current version family based on
@@ -125,13 +118,10 @@ jobs:
if [[ -n $current_version_family ]]; then
prev_stable="version/${current_version_family}"
fi
echo "::notice::Checking out ${prev_stable} as stable version..."
git checkout ${prev_stable}
rm -rf .github/ scripts/ packages/logger-js/
rm -rf .github/ scripts/
mv ../.github ../scripts .
mv ../packages/logger-js ./packages/
- name: Setup authentik env (stable)
uses: ./.github/actions/setup
with:
@@ -179,7 +169,7 @@ jobs:
psql:
- 14-alpine
- 18-alpine
run_id: [ 1, 2, 3, 4, 5 ]
run_id: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Setup authentik env
@@ -262,22 +252,19 @@ jobs:
COMPOSE_PROFILES: ${{ matrix.job.profiles }}
run: |
docker compose -f tests/e2e/compose.yml up -d --quiet-pull
- uses: ./.github/actions/setup-node
- id: cache-web
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v4
if: contains(matrix.job.profiles, 'selenium')
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json',
'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true' && contains(matrix.job.profiles,
'selenium')
if: steps.cache-web.outputs.cache-hit != 'true' && contains(matrix.job.profiles, 'selenium')
working-directory: web
run: |
corepack npm ci
corepack npm run build
corepack npm run build:sfe
npm ci
npm run build
npm run build:sfe
- name: run e2e
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
@@ -315,14 +302,14 @@ jobs:
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v4
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**',
'web/packages/sfe/src/**') }}-b
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web
run: |
corepack npm ci --prefix web
corepack npm run build --prefix web
corepack npm run build:sfe --prefix web
npm ci
npm run build
npm run build:sfe
- name: run conformance
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
@@ -388,9 +375,7 @@ jobs:
uses: ./.github/workflows/_reusable-docker-build.yml
secrets: inherit
with:
image_name: ${{ github.repository == 'goauthentik/authentik-internal' &&
'ghcr.io/goauthentik/internal-server' ||
'ghcr.io/goauthentik/dev-server' }}
image_name: ${{ github.repository == 'goauthentik/authentik-internal' && 'ghcr.io/goauthentik/internal-server' || 'ghcr.io/goauthentik/dev-server' }}
release: false
pr-comment:
needs:

View File

@@ -114,11 +114,8 @@ jobs:
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
platforms: linux/amd64,linux/arm64
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type
}}:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' &&
format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max',
matrix.type) || '' }}
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }}
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
@@ -139,8 +136,8 @@ jobs:
- ldap
- radius
- rac
goos: [ linux ]
goarch: [ amd64, arm64 ]
goos: [linux]
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
@@ -148,11 +145,16 @@ jobs:
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: web
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Build web
run: corepack npm run build-proxy --prefix web
working-directory: web/
run: |
npm ci
npm run build-proxy
- name: Build outpost
run: |
set -x

View File

@@ -15,30 +15,48 @@ on:
jobs:
lint:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
command:
- lint
- lint:lockfile
- tsc
- prettier-check
project:
- web
include:
- command: tsc
project: web
- command: lit-analyse
project: web
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: web
node-version-file: ${{ matrix.project }}/package.json
cache: "npm"
cache-dependency-path: ${{ matrix.project }}/package-lock.json
- working-directory: ${{ matrix.project }}/
run: |
npm ci
- name: Lint
run: corepack npm run lint --prefix web
- name: Check types
run: corepack npm run tsc --prefix web
- name: Check formatting
run: corepack npm run prettier-check --prefix web
- name: Lit analyse
run: corepack npm run lit-analyse --prefix web
working-directory: ${{ matrix.project }}/
run: npm run ${{ matrix.command }}
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: web
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
run: npm ci
- name: build
working-directory: web/
run: corepack npm run build
run: npm run build
ci-web-mark:
if: always()
needs:
@@ -55,9 +73,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: web
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
run: npm ci
- name: test
working-directory: web/
run: corepack npm run test || exit 0
run: npm run test || exit 0

View File

@@ -3,7 +3,7 @@ name: Packages - Publish NPM packages
on:
push:
branches: [ main ]
branches: [main]
paths:
- packages/tsconfig/**
- packages/eslint-config/**
@@ -35,19 +35,22 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
fetch-depth: 2
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: ${{ matrix.package }}
node-version-file: ${{ matrix.package }}/package.json
registry-url: "https://registry.npmjs.org"
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
with:
files: |
${{ matrix.package }}/package.json
- name: Install Dependencies
run: npm ci
- name: Publish package
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ${{ matrix.package }}
run: |
corepack npm ci
corepack npm run build
corepack npm publish
npm ci
npm run build
npm publish

View File

@@ -3,7 +3,7 @@ name: Release - On publish
on:
release:
types: [ published, created ]
types: [published, created]
jobs:
build-server:
@@ -87,9 +87,11 @@ jobs:
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: web
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
@@ -142,16 +144,22 @@ jobs:
- proxy
- ldap
- radius
goos: [ linux, darwin ]
goarch: [ amd64, arm64 ]
goos: [linux, darwin]
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: ./.github/actions/setup-node
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
working-directory: web
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Install web dependencies
working-directory: web/
run: |
npm ci
- name: Build web
working-directory: web/
run: |
@@ -167,10 +175,8 @@ jobs:
uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{
matrix.goarch }}
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{
matrix.goarch }}
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
tag: ${{ github.ref }}
upload-aws-cfn-template:
permissions:

2
.gitignore vendored
View File

@@ -14,8 +14,6 @@ media
# Node
node_modules
corepack.tgz
.corepack
.cspellcache
cspell-report.*

View File

@@ -106,9 +106,8 @@ migrate: ## Run the Authentik Django server's migrations
i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that require translation into files to send to a translation service
aws-cfn: node-install
corepack npm install --prefix lifecycle/aws
$(UV) run corepack npm run aws-cfn --prefix lifecycle/aws
aws-cfn:
cd lifecycle/aws && npm i && $(UV) run npm run aws-cfn
run-server: ## Run the main authentik server process
$(UV) run ak server
@@ -119,6 +118,9 @@ run-worker: ## Run the main authentik worker process
run-worker-watch: ## Run the authentik worker, with auto reloading
watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs --no-meta --notify -- $(UV) run ak worker
debug-attach: ## Attach pdb to a running authentik Python worker (PEP 768). PID=<pid> to pick; SUDO=1 on macOS.
$(UV) run python scripts/debug_attach.py
core-i18n-extract:
$(UV) run ak makemessages \
--add-location file \
@@ -129,7 +131,7 @@ core-i18n-extract:
--ignore website \
-l en
install: node-install web-install core-install ## Install all requires dependencies for `node`, `web` and `core`
install: node-install docs-install core-install ## Install all requires dependencies for `node`, `docs` and `core`
dev-drop-db:
$(eval pg_user := $(shell $(UV) run python -m authentik.lib.config postgresql.user 2>/dev/null))
@@ -233,46 +235,38 @@ gen-dev-config: ## Generate a local development config file
#########################
node-install: ## Install the necessary libraries to build Node.js packages
node ./scripts/node/setup-corepack.mjs
node ./scripts/node/lint-runtime.mjs
node ./scripts/node/lint-runtime.mjs
npm ci
npm ci --prefix web
#########################
## Web
#########################
web-install: ## Install the necessary libraries to build the Authentik UI
node ./scripts/node/lint-runtime.mjs web
corepack npm ci
corepack npm ci --prefix web
web-build: ## Build the Authentik UI
corepack npm run --prefix web build
web-build: node-install ## Build the Authentik UI
npm run --prefix web build
web: web-lint-fix web-lint web-check-compile ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it
web-test: ## Run tests for the Authentik UI
corepack npm run --prefix web test
npm run --prefix web test
web-watch: ## Build and watch the Authentik UI for changes, updating automatically
corepack npm run --prefix web watch
npm run --prefix web watch
web-storybook-watch: ## Build and run the storybook documentation server
corepack npm run --prefix web storybook
npm run --prefix web storybook
web-lint-fix:
corepack npm run --prefix web prettier
npm run --prefix web prettier
web-lint:
corepack npm run --prefix web lint
corepack npm run --prefix web lit-analyse
npm run --prefix web lint
npm run --prefix web lit-analyse
web-check-compile:
corepack npm run --prefix web tsc
npm run --prefix web tsc
web-i18n-extract:
corepack npm run --prefix web extract-locales
npm run --prefix web extract-locales
#########################
## Docs
@@ -280,40 +274,35 @@ web-i18n-extract:
docs: docs-lint-fix docs-build ## Automatically fix formatting issues in the Authentik docs source code, lint the code, and compile it
docs-install: node-install ## Install the necessary libraries to build the Authentik documentation
node ./scripts/node/lint-runtime.mjs
corepack npm ci
corepack npm ci --prefix website
docs-install:
npm ci --prefix website
docs-lint-fix: lint-spellcheck
corepack npm run --prefix website prettier
npm run --prefix website prettier
docs-build:
node ./scripts/node/lint-runtime.mjs website
corepack npm run --prefix website build
npm run --prefix website build
docs-watch: ## Build and watch the topics documentation
corepack npm run --prefix website start
npm run --prefix website start
integrations: docs-lint-fix integrations-build ## Fix formatting issues in the integrations source code, lint the code, and compile it
integrations-build:
corepack npm run --prefix website -w integrations build
npm run --prefix website -w integrations build
integrations-watch: ## Build and watch the Integrations documentation
corepack npm run --prefix website -w integrations start
npm run --prefix website -w integrations start
docs-api-build:
corepack npm run --prefix website -w api build
npm run --prefix website -w api build
docs-api-watch: ## Build and watch the API documentation
corepack npm run --prefix website -w api generate
corepack npm run --prefix website -w api start
npm run --prefix website -w api generate
npm run --prefix website -w api start
docs-api-clean: ## Clean generated API documentation
corepack npm run --prefix website -w api build:api:clean
npm run --prefix website -w api build:api:clean
#########################
## Docker

View File

@@ -1,73 +1,31 @@
"""authentik API Modelviewset tests"""
from collections.abc import Callable
from urllib.parse import urlencode
from django.test import TestCase
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from authentik.admin.api.version_history import VersionHistoryViewSet
from authentik.api.v3.urls import router
from authentik.core.tests.utils import RequestFactory, create_test_admin_user
from authentik.lib.generators import generate_id
from authentik.tenants.api.domains import DomainViewSet
from authentik.tenants.api.tenants import TenantViewSet
from authentik.tenants.utils import get_current_tenant
class TestModelViewSets(TestCase):
"""Test Viewset"""
def setUp(self):
self.user = create_test_admin_user()
self.factory = RequestFactory()
def viewset_tester_factory(test_viewset: type[ModelViewSet], full=True) -> dict[str, Callable]:
def viewset_tester_factory(test_viewset: type[ModelViewSet]) -> Callable:
"""Test Viewset"""
def test_attrs(self: TestModelViewSets) -> None:
"""Test attributes we require on all viewsets"""
self.assertIsNotNone(getattr(test_viewset, "ordering", None))
def tester(self: TestModelViewSets):
self.assertIsNotNone(getattr(test_viewset, "search_fields", None))
self.assertIsNotNone(getattr(test_viewset, "ordering", None))
filterset_class = getattr(test_viewset, "filterset_class", None)
if not filterset_class:
self.assertIsNotNone(getattr(test_viewset, "filterset_fields", None))
def test_ordering(self: TestModelViewSets) -> None:
"""Test that all ordering fields are correct"""
view = test_viewset.as_view({"get": "list"})
for ordering_field in test_viewset.ordering:
with self.subTest(ordering_field):
req = self.factory.get(
f"/?{urlencode({'ordering': ordering_field}, doseq=True)}", user=self.user
)
req.tenant = get_current_tenant()
res = view(req)
self.assertEqual(res.status_code, 200)
def test_search(self: TestModelViewSets) -> None:
"""Test that search fields are correct"""
view = test_viewset.as_view({"get": "list"})
req = self.factory.get(
f"/?{urlencode({'search': generate_id()}, doseq=True)}", user=self.user
)
req.tenant = get_current_tenant()
res = view(req)
self.assertEqual(res.status_code, 200)
cases = {
"attrs": test_attrs,
}
if full:
cases["ordering"] = test_ordering
cases["search"] = test_search
return cases
return tester
for _, viewset, _ in router.registry:
if not issubclass(viewset, ModelViewSet | ReadOnlyModelViewSet):
continue
full = viewset not in [VersionHistoryViewSet, DomainViewSet, TenantViewSet]
for test, case in viewset_tester_factory(viewset, full=full).items():
setattr(TestModelViewSets, f"test_viewset_{viewset.__name__}_{test}", case)
setattr(TestModelViewSets, f"test_viewset_{viewset.__name__}", viewset_tester_factory(viewset))

View File

@@ -20,16 +20,11 @@ class TestBrands(APITestCase):
def setUp(self):
super().setUp()
self.default_flags = {}
for flag in Flag.available(visibility="public"):
self.default_flags[flag().key] = flag.get()
Brand.objects.all().delete()
@property
def default_flags(self) -> dict[str, object]:
"""Get current public flags.
Some tests define temporary Flag subclasses, so this can't be cached in setUp.
"""
return {flag().key: flag.get() for flag in Flag.available(visibility="public")}
def test_current_brand(self):
"""Test Current brand API"""
brand = create_test_brand()

View File

@@ -47,8 +47,7 @@ class ApplicationEntitlementViewSet(UsedByMixin, ModelViewSet):
search_fields = [
"pbm_uuid",
"name",
"app__name",
"app__slug",
"app",
"attributes",
]
filterset_fields = [

View File

@@ -12,7 +12,7 @@
{% block head %}
<style data-id="static-styles">
:root {
--ak-global--background-image: url("{{ request.brand.branding_default_flow_background_url|iriencode|safe }}");
--ak-global--background-image: url("{{ request.brand.branding_default_flow_background_url }}");
}
</style>

View File

@@ -23,7 +23,7 @@
height: 100%;
}
body {
background-image: url("{{ flow_background_url|iriencode|safe }}");
background-image: url("{{ flow_background_url }}");
background-repeat: no-repeat;
background-size: cover;
}

View File

@@ -39,7 +39,7 @@
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
<style data-id="flow-css">
:root {
--ak-global--background-image: url("{{ flow_background_url|iriencode|safe }}");
--ak-global--background-image: url("{{ flow_background_url }}");
}
</style>
{% endblock %}

View File

@@ -1,14 +1,12 @@
"""stage view tests"""
from collections.abc import Callable
from unittest.mock import patch
from django.test import RequestFactory, TestCase
from django.urls import reverse
from authentik.core.tests.utils import RequestFactory as AuthentikRequestFactory
from authentik.core.tests.utils import create_test_flow
from authentik.flows.models import Flow, FlowStageBinding
from authentik.flows.models import FlowStageBinding
from authentik.flows.stage import StageView
from authentik.flows.views.executor import FlowExecutorView
from authentik.lib.utils.reflection import all_subclasses
@@ -44,46 +42,6 @@ class TestViews(TestCase):
"/static/dist/assets/images/flow_background.jpg",
)
def test_flow_interface_css_background_preserves_presigned_url_query(self):
"""Test flow CSS keeps signed URL query separators intact."""
flow = create_test_flow()
background_url = (
"https://s3.ca-central-1.amazonaws.com/example/media/public/background.png"
"?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential"
"&X-Amz-Signature=signature"
)
with patch.object(Flow, "background_url", return_value=background_url):
response = self.client.get(
reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
)
self.assertContains(
response,
f'--ak-global--background-image: url("{background_url}");',
html=False,
)
def test_flow_sfe_css_background_preserves_presigned_url_query(self):
"""Test SFE flow CSS keeps signed URL query separators intact."""
flow = create_test_flow()
background_url = (
"https://s3.ca-central-1.amazonaws.com/example/media/public/background.png"
"?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential"
"&X-Amz-Signature=signature"
)
with patch.object(Flow, "background_url", return_value=background_url):
response = self.client.get(
reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) + "?sfe"
)
self.assertContains(
response,
f'background-image: url("{background_url}");',
html=False,
)
def view_tester_factory(view_class: type[StageView]) -> Callable:
"""Test a form"""

View File

@@ -13,8 +13,8 @@
"cross-env": "^10.1.0"
},
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
"node": ">=20",
"npm": ">=11.6.2"
}
},
"node_modules/@epic-web/invariant": {

View File

@@ -11,20 +11,7 @@
"cross-env": "^10.1.0"
},
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
},
"devEngines": {
"runtime": {
"name": "node",
"onFail": "warn",
"version": ">=24"
},
"packageManager": {
"name": "npm",
"version": ">=11.10.1",
"onFail": "warn"
}
},
"packageManager": "npm@11.11.0+sha512.f36811c4aae1fde639527368ae44c571d050006a608d67a191f195a801a52637a312d259186254aa3a3799b05335b7390539cf28656d18f0591a1125ba35f973"
"node": ">=20",
"npm": ">=11.6.2"
}
}

View File

@@ -7,17 +7,6 @@ ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
ENV NODE_ENV=production
WORKDIR /work
RUN --mount=type=bind,target=/work/package.json,src=./package.json \
--mount=type=bind,target=/work/package-lock.json,src=./package-lock.json \
--mount=type=bind,target=/work/web/package.json,src=./web/package.json \
--mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \
--mount=type=bind,target=/work/scripts/node/,src=./scripts/node/ \
--mount=type=bind,target=/work/packages/logger-js/,src=./packages/logger-js/ \
node ./scripts/node/setup-corepack.mjs --force && \
node ./scripts/node/lint-runtime.mjs ./web
WORKDIR /work/web
# These files need to be copied and cannot be mounted as `npm ci` will build the client's typescript
@@ -29,7 +18,7 @@ RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
--mount=type=bind,target=/work/web/packages/sfe/package.json,src=./web/packages/sfe/package.json \
--mount=type=bind,target=/work/web/scripts,src=./web/scripts \
--mount=type=cache,id=npm-ak,sharing=shared,target=/root/.npm \
corepack npm ci
npm ci
COPY ./package.json /work
COPY ./web /work/web/

View File

@@ -10,22 +10,12 @@ WORKDIR /static
COPY ./packages /packages
COPY ./web/packages /static/packages
RUN --mount=type=bind,target=/static/package.json,src=./package.json \
--mount=type=bind,target=/static/package-lock.json,src=./package-lock.json \
--mount=type=bind,target=/static/web/package.json,src=./web/package.json \
--mount=type=bind,target=/static/web/package-lock.json,src=./web/package-lock.json \
--mount=type=bind,target=/static/scripts/node/,src=./scripts/node/ \
--mount=type=bind,target=/static/packages/logger-js/,src=./packages/logger-js/ \
node ./scripts/node/setup-corepack.mjs --force && \
node ./scripts/node/lint-runtime.mjs ./web
COPY package.json /
RUN --mount=type=bind,target=/static/package.json,src=./web/package.json \
--mount=type=bind,target=/static/package-lock.json,src=./web/package-lock.json \
--mount=type=bind,target=/static/scripts,src=./web/scripts \
--mount=type=cache,target=/root/.npm \
corepack npm ci
npm ci
COPY web .
RUN npm run build-proxy

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import faulthandler
import os
import random
import signal
@@ -76,6 +77,12 @@ def main(worker_id: int, socket_path: str):
signal.signal(signal.SIGINT, immediate_shutdown)
signal.signal(signal.SIGQUIT, immediate_shutdown)
signal.signal(signal.SIGTERM, graceful_shutdown)
# SIGUSR1 dumps every thread's traceback to stderr. Without this, the default
# action is "terminate", which kills the worker (and trips the Rust supervisor).
# Side-benefit: signal delivery wakes the eval loop, so `pdb -p` can attach to
# an otherwise-idle worker parked in a C-level syscall.
faulthandler.enable()
faulthandler.register(signal.SIGUSR1)
random.seed()
@@ -97,7 +104,11 @@ def main(worker_id: int, socket_path: str):
# Notify rust process that we are ready
os.kill(os.getppid(), signal.SIGUSR2)
shutdown.wait()
# Poll instead of waiting indefinitely so the main thread's eval loop ticks
# periodically — PEP 768's debugger pending hook is serviced on the main
# thread, and a permanent Event.wait() never returns to bytecode execution.
while not shutdown.wait(timeout=1.0):
pass
logger.info("Shutting down worker...")

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-30 00:27+0000\n"
"POT-Creation-Date: 2026-04-29 00:28+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -224,14 +224,6 @@ msgid ""
"providers are returned. When set to false, backchannel providers are excluded"
msgstr ""
#: authentik/core/api/users.py
msgid "Invalid password hash format. Must be a valid Django password hash."
msgstr ""
#: authentik/core/api/users.py
msgid "Cannot set both password and password_hash. Use only one."
msgstr ""
#: authentik/core/api/users.py
msgid "No leading or trailing slashes allowed."
msgstr ""

View File

@@ -19,7 +19,6 @@ Forti
Fortigate
Gatus
Gestionnaire
ghec
Gitea
Gravitee
Homarr

91
scripts/debug_attach.py Normal file
View File

@@ -0,0 +1,91 @@
"""Attach pdb to a running authentik Python worker via PEP 768."""
import os
import shutil
import subprocess # nosec B404 — needed to launch ps and pdb
import sys
PS_BIN = shutil.which("ps") or "/bin/ps"
def list_python_procs() -> list[tuple[int, int, str]]:
# argv is fully controlled, no shell, ps path resolved at startup.
out = subprocess.check_output([PS_BIN, "-eo", "pid,ppid,command"], text=True) # nosec B603
procs: list[tuple[int, int, str]] = []
for line in out.splitlines()[1:]:
try:
pid_s, ppid_s, cmd = line.split(None, 2)
except ValueError:
continue
if not pid_s.isdigit() or not ppid_s.isdigit():
continue
procs.append((int(pid_s), int(ppid_s), cmd))
return procs
def find_targets() -> list[tuple[int, str]]:
# Match any authentik Python process: dev_server / runserver via manage.py,
# gunicorn, dramatiq, or the worker_process supervisor. Go/Rust supervisors
# and the `uv run` / shell wrappers don't match these patterns.
needles = (
"manage.py",
"manage dev_server",
"manage runserver",
"gunicorn",
"dramatiq",
"lifecycle.worker_process",
"lifecycle/worker_process",
)
matches = [(p, pp, c) for p, pp, c in list_python_procs() if any(n in c for n in needles)]
matched_pids = {p for p, _, _ in matches}
parents_of_matches = {pp for _, pp, _ in matches if pp in matched_pids}
# A leaf is a match that isn't itself the parent of another match — this
# picks the dev_server reloader child or the gunicorn worker, and still
# includes single-process workers (which trivially have no child match).
leaves = [(p, c) for p, _, c in matches if p not in parents_of_matches]
return leaves
def attach(pid: int) -> int:
use_sudo = os.environ.get("SUDO") == "1"
cmd = [sys.executable, "-m", "pdb", "-p", str(pid)]
if use_sudo:
cmd = ["sudo", "-E", *cmd]
print(f"attaching pdb to pid {pid} (Ctrl-D or `quit` to detach)", file=sys.stderr)
# cmd is built from sys.executable plus a digits-only PID.
rc = subprocess.call(cmd) # nosec B603
if rc != 0 and not use_sudo and sys.platform == "darwin":
print(
"\nattach failed. On macOS task_for_pid is restricted; "
f"retry with: SUDO=1 make debug-attach PID={pid}",
file=sys.stderr,
)
return rc
def main() -> int:
env_pid = os.environ.get("PID")
if env_pid:
if not env_pid.isdigit():
print(f"PID={env_pid!r} is not numeric", file=sys.stderr)
return 2
return attach(int(env_pid))
targets = find_targets()
if not targets:
print(
"no gunicorn/dramatiq Python workers found — is `make run-server` "
"or `make run-worker` running?",
file=sys.stderr,
)
return 1
if len(targets) > 1:
print("multiple worker candidates — pick one with PID=<pid>:", file=sys.stderr)
for pid, cmd in targets:
print(f" {pid}\t{cmd[:120]}", file=sys.stderr)
return 1
return attach(targets[0][0])
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,278 +0,0 @@
#!/usr/bin/env node
/**
* @file Lints the package-lock.json file to ensure it is in sync with package.json.
*
* Usage:
* lint-lockfile [options] [directory]
*
* Options:
* --warn Report issues as warnings instead of failing. The lockfile is
* still regenerated on disk, but the process exits 0.
*
* Exit codes:
* 0 Lockfile is in sync (or --warn was passed)
* 1 Unexpected error
* 2 Lockfile drift detected
*/
/// <reference lib="esnext" />
import * as assert from "node:assert/strict";
import { findPackageJSON } from "node:module";
import { dirname } from "node:path";
import { isDeepStrictEqual, parseArgs } from "node:util";
import { ConsoleLogger } from "../../packages/logger-js/lib/node.js";
import { parseCWD, reportAndExit } from "./utils/commands.mjs";
import { corepack } from "./utils/corepack.mjs";
import { gitStatus } from "./utils/git.mjs";
import { findNPMPackage, loadJSON, npm, pluckDependencyFields } from "./utils/node.mjs";
//#region Constants
const logger = ConsoleLogger.prefix("lint:lockfile");
const { values: options, positionals } = parseArgs({
options: {
"warn": {
type: "boolean",
default: false,
description: "Report issues as warnings instead of failing",
},
"skip-git": {
type: "boolean",
default: !!process.env.CI,
description:
"Skip checking for uncommitted changes (use with --warn to ignore drift without reporting)",
},
},
allowPositionals: true,
});
const cwd = parseCWD(positionals);
const ignoredProperties = new Set([
// ---
"peer",
"engines",
"optional",
]);
//#region Utilities
/**
* @param {Record<string, unknown>} actual
* @param {Record<string, unknown>} expected
* @param {string[]} [prefix]
* @returns {Set<string>[]}
*/
function extractDiffedProperties(actual, expected, prefix = []) {
const a = actual ?? {};
const b = expected ?? {};
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
/** @type {Set<string>[]} */
const diffs = [];
for (const key of keys) {
const path = [...prefix, key];
const valA = a[key];
const valB = b[key];
if (
valA !== null &&
valB !== null &&
typeof valA === "object" &&
typeof valB === "object" &&
!Array.isArray(valA) &&
!Array.isArray(valB)
) {
// @ts-ignore
diffs.push(...extractDiffedProperties(valA, valB, path));
} else if (!isDeepStrictEqual(valA, valB)) {
diffs.push(new Set(path));
}
}
return diffs;
}
//#endregion
/**
* Exit code when lockfile drift is detected (distinct from general errors)
*/
const EXIT_DRIFT = 2;
/**
* @returns {Promise<string[]>} The list of issues detected.
*/
async function run() {
/** @type {string[]} */
const issues = [];
/**
* Records an issue. In strict mode, throws immediately.
* In warn mode, collects the message for later reporting.
*
* @param {boolean} ok
* @param {string} message
*/
const check = (ok, message) => {
if (ok) return;
if (options.warn) {
issues.push(message);
return;
}
assert.fail(message);
};
/**
* Checks deep equality of two values. In strict mode, throws if they are not equal.
* In warn mode, records an issue instead.
*
* @param {unknown} actual
* @param {unknown} expected
* @param {string} message
*/
const checkDeep = (actual, expected, message) => {
if (options.warn) {
if (!isDeepStrictEqual(actual, expected)) {
issues.push(message);
}
return;
}
assert.deepStrictEqual(actual, expected, message);
};
logger.info(`Linting lockfile integrity in: ${cwd}`);
// MARK: Locate files
const resolvedPath = import.meta.resolve(cwd);
const packageJSONPath = findPackageJSON(resolvedPath);
assert.ok(
packageJSONPath,
"Could not find package.json in the current directory or any parent directories",
);
const packageDir = dirname(packageJSONPath);
const { packageLockPath } = await findNPMPackage(packageDir);
const lockfileDir = dirname(packageLockPath);
const isWorkspace = lockfileDir !== packageDir;
const corepackVersion = await corepack`--version`().catch(() => null);
const useCorepack = !!corepackVersion;
logger.info(`corepack: ${corepackVersion || "disabled"}`);
const expected = {
lockfile: await loadJSON(packageLockPath),
package: await loadJSON(packageJSONPath).then(pluckDependencyFields),
};
logger.info(`package.json: ${packageJSONPath} (${expected.package.name})`);
logger.info(`package-lock.json: ${packageLockPath}${isWorkspace ? " (workspace root)" : ""}`);
// MARK: Uncommitted changes
if (options["skip-git"]) {
logger.warn("Skipping git status check");
} else {
const packageStatus = await gitStatus(packageJSONPath);
const lockfileStatus = await gitStatus(packageLockPath);
if (!packageStatus.available || !lockfileStatus.available) {
logger.warn("Git is not available; skipping uncommitted change detection.");
} else {
check(packageStatus.clean, `package.json has uncommitted changes: ${packageJSONPath}`);
check(
lockfileStatus.clean,
`package-lock.json has uncommitted changes: ${packageLockPath}`,
);
}
}
// MARK: Regenerate
const npmVersion = await npm`--version`({ useCorepack });
logger.info(`Detected npm version: ${npmVersion}`);
await npm`install --package-lock-only`({
cwd: lockfileDir,
useCorepack,
});
logger.info("npm install complete.");
const actual = {
lockfile: await loadJSON(packageLockPath),
package: await loadJSON(packageJSONPath).then(pluckDependencyFields),
};
// MARK: Compare
assert.deepStrictEqual(
actual.package,
expected.package,
`package.json was unexpectedly modified during lockfile check: ${packageJSONPath}`,
);
try {
checkDeep(
actual.lockfile,
expected.lockfile,
`package-lock.json is out of sync with package.json`,
);
} catch (error) {
if (!(error instanceof assert.AssertionError)) {
throw error;
}
// NPM versions <=11.10 has issues with deterministic lockfile generation,
// especially around optional peer dependencies.
const diffedProperties = extractDiffedProperties(actual.lockfile, expected.lockfile).filter(
(segments) => segments.isDisjointFrom(ignoredProperties),
);
if (diffedProperties.length) {
const formatted = diffedProperties
.map((segments) => Array.from(segments).join("."))
.join("\n");
throw new Error(`Lockfile drift detected:\n${formatted}`, { cause: error });
}
logger.warn(
"Permissible dependency differences detected. Run `npm install` to update the lockfile.",
);
}
return issues;
}
run()
.then((issues) => {
if (issues.length) {
logger.warn(`⚠️ ${issues.length} issue(s) detected:`);
for (const issue of issues) {
logger.warn(` - ${issue}`);
}
if (options.warn) {
logger.warn(
"The lockfile on disk has been regenerated. Review and commit the changes.",
);
process.exit(EXIT_DRIFT);
}
} else {
logger.info("✅ Lockfile is in sync.");
}
})
.catch((error) => reportAndExit(error, logger));

View File

@@ -1,114 +0,0 @@
#!/usr/bin/env node
/**
* @file Lints the installed Node.js and npm versions against the requirements specified in package.json.
*
* Usage:
* lint-node [options] [directory]
*
* Exit codes:
* 0 Versions are in sync
* 1 Version mismatch detected
*/
import * as assert from "node:assert/strict";
import { parseArgs } from "node:util";
import { ConsoleLogger } from "../../packages/logger-js/lib/node.js";
import { CommandError, parseCWD, reportAndExit } from "./utils/commands.mjs";
import { corepack } from "./utils/corepack.mjs";
import { resolveRepoRoot } from "./utils/git.mjs";
import { compareVersions, findNPMPackage, loadJSON, node, npm, parseRange } from "./utils/node.mjs";
const logger = ConsoleLogger.prefix("lint-runtime");
/**
* @param {string} start
*/
async function readRequirements(start) {
const { packageJSONPath } = await findNPMPackage(start);
logger.info(`Checking versions in ${packageJSONPath}`);
const packageJSONData = await loadJSON(packageJSONPath);
const nodeVersion = await node`--version`().then((output) => output.replace(/^v/, ""));
const requiredNpmVersion = packageJSONData.engines?.npm;
const requiredNodeVersion = packageJSONData.engines?.node;
return { nodeVersion, requiredNpmVersion, requiredNodeVersion };
}
async function main() {
const parsedArgs = parseArgs({
allowPositionals: true,
});
const cwd = parseCWD(parsedArgs.positionals);
const repoRoot = await resolveRepoRoot(cwd).catch(() => null);
logger.info(`cwd ${cwd}`);
logger.info(`repository ${repoRoot || "not found"}`);
const corepackVersion = await corepack`--version`().catch(() => null);
const useCorepack = !!corepackVersion;
logger.info(`corepack ${corepackVersion || "disabled"}`);
const npmVersion = await npm`--version`({ cwd, useCorepack })
.then((version) => {
logger.info(`npm${corepackVersion ? " (via Corepack)" : ""} ${version}`);
return version;
})
.catch((error) => {
if (error instanceof CommandError && corepackVersion) {
logger.warn(`Failed to read npm version via Corepack ${error.message}`);
logger.info(`Attempting to read npm version directly without Corepack...`);
// Corepack might be misconfigured or outdated.
// Attempting a second read without Corepack can help us distinguish
// between a general npm issue and a Corepack-specific one.
return npm`--version`({ cwd }).then((version) => {
logger.info(`npm (direct) ${version}`);
return version;
});
}
throw error;
});
const { nodeVersion, requiredNpmVersion, requiredNodeVersion } = await readRequirements(cwd);
logger.info(`node ${nodeVersion}`);
if (requiredNpmVersion) {
logger.info(`package.json npm ${requiredNpmVersion}`);
const { operator, version: required } = parseRange(requiredNpmVersion);
const result = compareVersions(npmVersion, required);
assert.ok(
operator === ">=" ? result >= 0 : result === 0,
`npm version ${npmVersion} does not satisfy required version ${requiredNpmVersion}`,
);
}
if (requiredNodeVersion) {
logger.info(`package.json node ${requiredNodeVersion}`);
const { operator, version: required } = parseRange(requiredNodeVersion);
const result = compareVersions(nodeVersion, required);
assert.ok(
operator === ">=" ? result >= 0 : result === 0,
`Node.js version ${nodeVersion} does not satisfy required version ${requiredNodeVersion}`,
);
}
}
main()
.then(() => {
logger.info("✅ Node.js and npm versions are in sync.");
})
.catch((error) => reportAndExit(error, logger));

View File

@@ -1,94 +0,0 @@
#!/usr/bin/env node
/**
* @file Downloads the latest corepack tarball from the npm registry.
*/
import * as fs from "node:fs/promises";
import { parseArgs } from "node:util";
import { ConsoleLogger } from "../../packages/logger-js/lib/node.js";
import { $, parseCWD, reportAndExit } from "./utils/commands.mjs";
import { corepack, pullLatestCorepack } from "./utils/corepack.mjs";
import { resolveRepoRoot } from "./utils/git.mjs";
import { findNPMPackage, loadJSON, npm } from "./utils/node.mjs";
const FALLBACK_NPM_VERSION = "11.11.0";
const logger = ConsoleLogger.prefix("setup-corepack");
async function main() {
const parsedArgs = parseArgs({
options: {
force: {
type: "boolean",
default: false,
description: "Force re-download of corepack even if a version is already installed",
},
},
allowPositionals: true,
});
const cwdArg = parseCWD(parsedArgs.positionals);
const repoRoot = await resolveRepoRoot(cwdArg).catch(() => null);
const cwd = repoRoot || cwdArg;
const npmVersion = await npm`--version`({ cwd });
logger.info(`npm ${npmVersion}`);
const corepackVersion = await corepack`--version`({ cwd }).catch(() => null);
logger.info(`corepack ${corepackVersion || "not found"}`);
if (corepackVersion && !parsedArgs.values.force) {
logger.info("Corepack is already installed, skipping download (use --force to override)");
return;
}
await pullLatestCorepack(cwd);
await npm`install --force -g corepack@latest`({ cwd });
logger.info("Corepack installed successfully");
const { packageJSONPath } = await findNPMPackage(cwd);
logger.info(`Checking versions in ${packageJSONPath}`);
const packageJSONData = await loadJSON(packageJSONPath);
const packageManager = packageJSONData.packageManager || `npm@${FALLBACK_NPM_VERSION}`;
await $`corepack install -g ${packageManager}`({ cwd });
logger.info(`Setting up Corepack to use ${packageManager}...`);
const writablePackageJSON = await fs.access(packageJSONPath, fs.constants.W_OK).then(
() => true,
() => false,
);
/**
* @type {string}
*/
let subcommand;
if (!writablePackageJSON) {
if (!packageJSONData.packageManager) {
throw new Error(
`package.json is not writable and does not specify a packageManager field. Was the package.json file mounted via Docker?`,
);
}
subcommand = "install -g";
} else {
logger.info("package.json is writable");
subcommand = "use";
}
await $`corepack ${subcommand} ${packageManager}`({ cwd });
logger.info("Corepack installed npm successfully");
}
main().catch((error) => reportAndExit(error, logger));

View File

@@ -1,116 +0,0 @@
/**
* Utility functions for running shell commands and handling their results.
*
* @import { ExecOptions } from "node:child_process"
*/
import { exec } from "node:child_process";
import { resolve, sep } from "node:path";
import { promisify } from "node:util";
import { ConsoleLogger } from "../../../packages/logger-js/lib/node.js";
const logger = ConsoleLogger.prefix("commands");
export class CommandError extends Error {
name = "CommandError";
/**
* @param {string} command
* @param {ErrorOptions & ExecOptions} options
*/
constructor(command, { cause, cwd, shell } = {}) {
const cwdInfo = cwd ? ` in directory ${cwd}` : "";
const shellInfo = shell ? ` using shell ${shell}` : "";
super(`Command failed: ${command}${cwdInfo}${shellInfo}`, { cause });
}
}
/**
* @param {string[]} positionals
* @returns {string} The resolved current working directory for the script
*/
export function parseCWD(positionals) {
// `INIT_CWD` is present only if the script is run via npm.
const initCWD = process.env.INIT_CWD || process.cwd();
const cwd = (positionals.length ? resolve(initCWD, positionals[0]) : initCWD) + sep;
return cwd;
}
const execAsync = promisify(exec);
/**
* @param {Awaited<ReturnType<typeof execAsync>>} result
*/
export const trimResult = (result) => String(result.stdout).trim();
/**
* @typedef {(strings: TemplateStringsArray, ...expressions: unknown[]) =>
* (options?: ExecOptions) => Promise<string>
* } CommandTag
*/
function createTag(prefix = "") {
/** @type {CommandTag} */
return (strings, ...expressions) => {
const command = (prefix ? prefix + " " : "") + String.raw(strings, ...expressions);
logger.debug(command);
return (options) =>
execAsync(command, options)
.then(trimResult)
.catch((cause) => {
throw new CommandError(command, { ...options, cause });
});
};
}
/**
* A tagged template function for running shell commands.
* @type {CommandTag & { bind(prefix: string): CommandTag }}
*/
export const $ = createTag();
/**
* @param {string} prefix
* @returns {CommandTag}
*/
$.bind = (prefix) => createTag(prefix);
/**
* Promisified version of {@linkcode exec} for easier async/await usage.
*
* @param {string} command The command to run, with space-separated arguments.
* @param {ExecOptions} [options] Optional execution options.
* @throws {CommandError} If the command fails to execute.
*/
export function $2(command, options) {
return execAsync(command, options)
.then(trimResult)
.catch((cause) => {
throw new CommandError(command, { ...options, cause });
});
}
/**
* Logs the given error and its cause (if any) and exits the process with a failure code.
* @param {unknown} error
* @param {typeof ConsoleLogger} logger
* @returns {never}
*/
export function reportAndExit(error, logger = ConsoleLogger) {
const message = error instanceof Error ? error.message : String(error);
const cause = error instanceof Error && error.cause instanceof Error ? error.cause : null;
logger.error(`${message}`);
if (cause) {
logger.error(`Caused by: ${cause.message}`);
}
process.exit(1);
}

View File

@@ -1,84 +0,0 @@
import * as crypto from "node:crypto";
import * as fs from "node:fs/promises";
import { join, relative } from "node:path";
import { ConsoleLogger } from "../../../packages/logger-js/lib/node.js";
import { $ } from "./commands.mjs";
const REGISTRY_URL = "https://registry.npmjs.org/corepack";
const OUTPUT_DIR = join(".corepack", "releases");
const OUTPUT_FILENAME = "latest.tgz";
export const corepack = $.bind("corepack");
/**
* Reads the installed Corepack version.
*
* @param {string} [cwd] The directory to run the command in.
* @returns {Promise<string | null>} The installed Corepack version
*/
export function readCorepackVersion(cwd = process.cwd()) {
return $`corepack --version`({ cwd });
}
const logger = ConsoleLogger.prefix("setup-corepack");
/**
* @param {string} baseDirectory
*/
export async function pullLatestCorepack(baseDirectory = process.cwd()) {
logger.info("Fetching corepack metadata from registry...");
const outputDir = join(baseDirectory, OUTPUT_DIR);
const outputPath = join(outputDir, OUTPUT_FILENAME);
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(1000 * 60) });
if (!res.ok) {
throw new Error(`Failed to fetch registry metadata: ${res.status} ${res.statusText}`);
}
const metadata = await res.json();
const latestVersion = metadata["dist-tags"].latest;
const versionData = metadata.versions[latestVersion];
const tarballUrl = versionData.dist.tarball;
const expectedIntegrity = versionData.dist.integrity;
logger.info(`Latest corepack version: ${latestVersion}`);
logger.info(`Tarball URL: ${tarballUrl}`);
logger.info(`Expected integrity: ${expectedIntegrity}`);
logger.info({ url: tarballUrl }, "Downloading tarball...");
const tarballRes = await fetch(tarballUrl, {
signal: AbortSignal.timeout(1000 * 60),
});
if (!tarballRes.ok) {
throw new Error(
`Failed to download tarball: ${tarballRes.status} ${tarballRes.statusText}`,
);
}
const tarballBuffer = Buffer.from(await tarballRes.arrayBuffer());
logger.info("Verifying integrity...");
const [algorithm, expectedHash] = expectedIntegrity.split("-");
const actualHash = crypto.createHash(algorithm).update(tarballBuffer).digest("base64");
if (actualHash !== expectedHash) {
throw new Error(
`Integrity mismatch!\n Expected: ${expectedHash}\n Actual: ${actualHash}`,
);
}
logger.info("Integrity verified.");
await fs.mkdir(outputDir, { recursive: true });
await fs.writeFile(outputPath, tarballBuffer);
logger.info(`Saved to ${relative(baseDirectory, outputPath)}`);
logger.info(`corepack@${latestVersion} (${expectedIntegrity})`);
}

View File

@@ -1,25 +0,0 @@
import { $ } from "./commands.mjs";
/**
* Checks whether the given file has uncommitted changes in git.
*
* @param {string} filePath
* @param {string} [cwd]
* @returns {Promise<{ clean: boolean, available: boolean }>}
*/
export async function gitStatus(filePath, cwd = process.cwd()) {
return $`git status --porcelain ${filePath}`({ cwd })
.then((output) => ({ clean: !output, available: true }))
.catch(() => ({ clean: false, available: false }));
}
/**
* Finds the root directory of the git repository containing the given directory.
*
* @param {string} cwd
* @returns {Promise<string>} The path to the git repository root.
* @throws {Error} If the command fails (e.g., not a git repository).
*/
export function resolveRepoRoot(cwd = process.cwd()) {
return $`git rev-parse --show-toplevel`({ cwd });
}

View File

@@ -1,175 +0,0 @@
/**
* Utility functions for working with npm packages and versions.
*
* @import { ExecOptions } from "node:child_process"
*/
import * as fs from "node:fs/promises";
import { dirname, join } from "node:path";
import { $ } from "./commands.mjs";
/**
* Find the nearest directory containing both package.json and package-lock.json,
* starting from the given directory and walking upward.
*
* @param {string} start The directory to start searching from.
* @returns {Promise<{ packageJSONPath: string, packageLockPath: string }>}
* @throws {Error} If no co-located package.json and package-lock.json are found.
*/
export async function findNPMPackage(start) {
let currentDir = start;
while (currentDir !== dirname(currentDir)) {
const packageJSONPath = join(currentDir, "package.json");
const packageLockPath = join(currentDir, "package-lock.json");
try {
await Promise.all([fs.access(packageJSONPath), fs.access(packageLockPath)]);
return {
packageJSONPath,
packageLockPath,
};
} catch {
// Continue searching up the directory tree
}
currentDir = dirname(currentDir);
}
throw new Error(`No co-located package.json and package-lock.json found above ${start}`);
}
/**
* @typedef {object} PackageJSON
* @property {string} name
* @property {string} version
* @property {Record<string, string>} [dependencies]
* @property {Record<string, string>} [devDependencies]
* @property {Record<string, string>} [peerDependencies]
* @property {Record<string, string>} [optionalDependencies]
* @property {Record<string, string>} [peerDependenciesMeta]
* @property {Record<string, string>} [engines]
* @property {Record<string, string>} [devEngines]
* @property {string} [packageManager]
*/
/**
* @param {string} jsonPath
* @returns {Promise<PackageJSON>}
*/
export function loadJSON(jsonPath) {
return fs
.readFile(jsonPath, "utf-8")
.then(JSON.parse)
.catch((cause) => {
throw new Error(`Failed to load JSON file at ${jsonPath}`, { cause });
});
}
const PackageJSONComparisionFields = /** @type {const} */ ([
"name",
"dependencies",
"devDependencies",
"optionalDependencies",
"peerDependencies",
"peerDependenciesMeta",
]);
/**
* @typedef {typeof PackageJSONComparisionFields[number]} PackageJSONComparisionField
*/
/**
* Extracts only the dependency fields from a package.json object for comparison purposes.
*
* @param {PackageJSON} data
* @returns {Pick<PackageJSON, PackageJSONComparisionField>}
*/
export function pluckDependencyFields(data) {
/**
* @type {Record<string, unknown>}
*/
const result = {};
for (const field of PackageJSONComparisionFields) {
if (data[field]) {
result[field] = data[field];
}
}
return /** @type {Pick<PackageJSON, PackageJSONComparisionField>} */ (result);
}
//#region Versioning
/**
* Compares two semantic version strings (e.g., "14.17.0").
*
* @param {string} a The first version string.
* @param {string} b The second version string.
* @returns {number}
*/
export function compareVersions(a, b) {
const pa = a.split(".").map(Number);
const pb = b.split(".").map(Number);
for (let i = 0; i < 3; i++) {
if (pa[i] > pb[i]) return 1;
if (pa[i] < pb[i]) return -1;
}
return 0;
}
/**
* Runs a Node.js command and returns its stdout output as a string.
*
* @param {TemplateStringsArray} strings
* @param {...unknown} expressions
* @returns {(options?: ExecOptions) => Promise<string>}
*/
export const node = $.bind("node");
/**
* @typedef {object} NPMCommandOptions
* @property {boolean} [useCorepack] Whether to prefix the command with "corepack " to use Corepack's shims.
* @returns {Promise<string>}
*/
/**
* Runs an npm command and returns its stdout output as a string.
*
* @param {TemplateStringsArray} strings
* @param {...unknown} expressions
* @returns {(options?: ExecOptions & NPMCommandOptions) => Promise<string>}
*/
export function npm(strings, ...expressions) {
const subcommand = String.raw(strings, ...expressions);
return ({ useCorepack, ...options } = {}) => {
const command = [useCorepack ? "corepack" : "", "npm", subcommand]
.filter(Boolean)
.join(" ");
return $`${command}`(options);
};
}
/**
* Parses a version range string, stripping any leading >= and normalizing to three parts.
* @param {string} range
* @returns {{ operator: ">=" | "=", version: string }}
*/
export function parseRange(range) {
const hasGte = range.startsWith(">=");
const raw = hasGte ? range.slice(2) : range;
const parts = raw.split(".").map(Number);
while (parts.length < 3) parts.push(0);
return {
operator: hasGte ? ">=" : "=",
version: parts.join("."),
};
}
//#endregion

View File

@@ -83,8 +83,7 @@ pub(crate) fn start(tasks: &mut Tasks) -> Result<Arc<Metrics>> {
"metrics",
router.clone(),
addr,
config::get().debug, /* Allow failure in case the server is running on the same
* machine, like in dev */
true, // Allow failure in case the server is running on the same machine, like in dev
)?;
}
@@ -93,8 +92,7 @@ pub(crate) fn start(tasks: &mut Tasks) -> Result<Arc<Metrics>> {
"metrics",
router,
unix::net::SocketAddr::from_pathname(socket_path())?,
config::get().debug, /* Allow failure in case the server is running on the same machine,
* like in dev */
true, // Allow failure in case the server is running on the same machine, like in dev
)?;
Ok(metrics)

View File

@@ -328,8 +328,8 @@ pub(crate) fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Workers>> {
"worker",
router.clone(),
addr,
config::get().debug, /* Allow failure in case the server is running on the same
* machine, like in dev. */
true, /* Allow failure in case the server is running on the same machine, like
* in dev. */
)?;
}
@@ -338,8 +338,7 @@ pub(crate) fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Workers>> {
"worker",
router,
unix::net::SocketAddr::from_pathname(socket_path())?,
config::get().debug, /* Allow failure in case the server is running on the same
* machine, like in dev. */
true, // Allow failure in case the server is running on the same machine, like in dev.
)?;
}

1884
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@
"format": "wireit",
"lint": "eslint --fix .",
"lint:imports": "knip --config scripts/knip.config.ts",
"lint:lockfile": "wireit",
"lint:types": "wireit",
"lint-check": "eslint --max-warnings 0 .",
"lit-analyse": "wireit",
@@ -267,6 +268,11 @@
"build-locales"
]
},
"lint:lockfile": {
"__comment": "The lockfile-lint package does not have an option to ensure resolved hashes are set everywhere",
"shell": true,
"command": "sh ./scripts/lint-lockfile.sh package-lock.json"
},
"lit-analyse": {
"command": "lit-analyzer src"
},
@@ -275,7 +281,8 @@
"dependencies": [
"lint",
"lint:types",
"lint:components"
"lint:components",
"lint:lockfile"
]
},
"storybook:build": {
@@ -296,7 +303,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
"npm": ">=11.6.2"
},
"devEngines": {
"runtime": {
@@ -306,11 +313,10 @@
},
"packageManager": {
"name": "npm",
"version": ">=11.10.1",
"version": "11.10.1",
"onFail": "warn"
}
},
"packageManager": "npm@11.11.0+sha512.f36811c4aae1fde639527368ae44c571d050006a608d67a191f195a801a52637a312d259186254aa3a3799b05335b7390539cf28656d18f0591a1125ba35f973",
"prettier": "@goauthentik/prettier-config",
"overrides": {
"@goauthentik/esbuild-plugin-live-reload": {
@@ -346,7 +352,6 @@
"rapidoc": {
"@apitools/openapi-parser": "0.0.37"
},
"tree-sitter": false,
"typescript-eslint": {
"typescript": "$typescript"
}

View File

@@ -52,6 +52,6 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
"npm": ">=11.6.2"
}
}

21
web/scripts/lint-lockfile.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
if ! command -v jq >/dev/null 2>&1 ; then
echo "This check requires the jq program be installed."
echo "To install jq, visit"
echo " https://jqlang.github.io/jq/"
exit 1
fi
CMD=$(jq -r '.packages | to_entries[] | select((.key | contains("node_modules")) and (.value | has("resolved") | not)) | .key' < "$1")
if [ -n "$CMD" ]; then
echo "ERROR package-lock.json entries missing 'resolved' field:"
echo ""
# Shellcheck erroneously believes that shell string substitution can be used here, but that
# feature lacks a "start of line" discriminator.
# shellcheck disable=SC2001
echo "$CMD" | sed 's/^/ /g'
echo ""
exit 1
fi

View File

@@ -34,7 +34,6 @@ export const MDXAnchor = ({
const nextURL = new URL(nextPathname, import.meta.env.AK_DOCS_URL);
// Remove trailing .md and .mdx, and trailing "index".
nextURL.pathname = nextURL.pathname.replace(/(index)?\.mdx?$/, "");
// eslint-disable-next-line react-hooks/immutability
href = nextURL.toString();
}

View File

@@ -5270,7 +5270,7 @@ neprojde, když jedna nebo obě z vybraných možností jsou rovny nebo nad prah
<target>Aktivovat</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target>Aktualizovat heslo uživatele <x id="0" equiv-text="${item.name || item.username}"/></target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -11007,22 +11007,6 @@ Vazby na skupiny/uživatele jsou kontrolovány vůči uživateli události.</tar
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5295,7 +5295,7 @@ Hier können nur Policies verwendet werden, da der Zugriff geprüft wird, bevor
<target>Aktivieren</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target><x id="0" equiv-text="${item.name || item.username}"/> - Passwort ändern.</target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -11040,22 +11040,6 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -4089,7 +4089,7 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Activate</source>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
<source>Set password</source>
@@ -9010,22 +9010,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5235,7 +5235,7 @@ El valor de este campo se compara con el atributo de pertenencia del usuario.</t
<target>Activar</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target>Actualizar la contraseña de <x id="0" equiv-text="${item.name || item.username}"/></target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -10965,22 +10965,6 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5398,7 +5398,7 @@ läpäisy estyy kun jompi kumpi tai molemmat vaihtoehdot ylittävät raja-arvon.
<target>Aktivoi</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target>Päivitä käyttäjän <x id="0" equiv-text="${item.name || item.username}"/> salasana</target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -11206,22 +11206,6 @@ Liitokset käyttäjiin/ryhmiin tarkistetaan tapahtuman käyttäjästä.</target>
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5388,7 +5388,7 @@ doesn't pass when either or both of the selected options are equal or above the
<target>Activer</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target>Mettre à jour le mot de passe de <x id="0" equiv-text="${item.name || item.username}"/></target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -11195,22 +11195,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5194,7 +5194,7 @@ doesn't pass when either or both of the selected options are equal or above the
<target>Attivare</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target>Aggiorna <x id="0" equiv-text="${item.name || item.username}"/> password</target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -10914,22 +10914,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5395,7 +5395,7 @@ doesn't pass when either or both of the selected options are equal or above the
<target>アクティブ化</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target><x id="0" equiv-text="${item.name || item.username}"/> のパスワードを更新</target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -11195,22 +11195,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -4988,7 +4988,7 @@ doesn't pass when either or both of the selected options are equal or above the
<target>활성화</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target><x id="0" equiv-text="${item.name || item.username}"/> 비밀번호 업데이트 </target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -10562,22 +10562,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -4804,7 +4804,7 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d
<target>Activeren</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
<source>Set password</source>
@@ -10246,22 +10246,6 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5004,7 +5004,7 @@ Można tu używać tylko zasad, ponieważ dostęp jest sprawdzany przed uwierzyt
<target>Aktywuj</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
<source>Set password</source>
@@ -10588,22 +10588,6 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5394,7 +5394,7 @@ Você só pode usar políticas aqui, pois o acesso é verificado antes de o usu
<target>Ativar</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target>Atualizar a senha de <x id="0" equiv-text="${item.name || item.username}"/></target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -11188,22 +11188,6 @@ por exemplo: <x id="0" equiv-text="&lt;code&gt;"/>oci://registry.domain.tld/path
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5052,7 +5052,7 @@ doesn't pass when either or both of the selected options are equal or above the
<target>Активировать</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
<source>Set password</source>
@@ -10674,22 +10674,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5051,7 +5051,7 @@ Belirlenen seçeneklerden biri veya her ikisi de eşiğe eşit veya eşiğin üz
<target>Etkinleştir</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
<source>Set password</source>
@@ -10663,22 +10663,6 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -5461,7 +5461,7 @@ doesn't pass when either or both of the selected options are equal or above the
<target>激活</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
<target>更新 <x id="0" equiv-text="${user.name || user.username}"/> 的密码</target>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
@@ -11467,22 +11467,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -4839,7 +4839,7 @@ doesn't pass when either or both of the selected options are equal or above the
<target>啟用</target>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${formatUserDisplayName(user)}"/>'s password</source>
<source>Update <x id="0" equiv-text="${user.name || user.username}"/>'s password</source>
</trans-unit>
<trans-unit id="sce8d867ca5f35304">
<source>Set password</source>
@@ -10299,22 +10299,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sf7aba95a8c43b7b1">
<source>Sets a custom EntityID/Issuer to override the authentik generated default.</source>
</trans-unit>
<trans-unit id="sa3a27a128ad87f31">
<source>Passwords</source>
</trans-unit>
<trans-unit id="s16d13ea527d7fe6b">
<source>Setting</source>
</trans-unit>
<trans-unit id="sfef81bb4077a56fd">
<source>Type a new password...</source>
</trans-unit>
<trans-unit id="sf9ec917e3e986bc1">
<source>When enabled, your username will be remembered on this device for future logins.</source>
</trans-unit>
<trans-unit id="form.submitting.no-entity">
<source><x id="0" equiv-text="${submittingVerb}"/>...</source>
<note from="lit-localize">The message shown while a form is being submitted, when no entity name is provided.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -4,23 +4,17 @@ ENV NODE_ENV=production
WORKDIR /work
# TODO: Use setup-corepack.mjs
RUN --mount=type=bind,target=/work/package.json,src=./package.json \
--mount=type=bind,target=/work/package-lock.json,src=./package-lock.json \
--mount=type=bind,target=/work/scripts/node/,src=./scripts/node/ \
--mount=type=bind,target=/work/packages/logger-js/,src=./packages/logger-js/ \
--mount=type=bind,target=/work/packages/tsconfig/,src=./packages/tsconfig/ \
--mount=type=bind,target=/work/packages/eslint-config/,src=./packages/eslint-config/ \
--mount=type=bind,target=/work/packages/prettier-config/,src=./packages/prettier-config/ \
--mount=type=bind,target=/work/website/package.json,src=./website/package.json \
--mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \
node ./scripts/node/setup-corepack.mjs --force && \
corepack npm ci \
node ./scripts/node/lint-runtime.mjs ./website
npm install --force -g corepack@latest && \
corepack install -g npm@11.11.0+sha512.f36811c4aae1fde639527368ae44c571d050006a608d67a191f195a801a52637a312d259186254aa3a3799b05335b7390539cf28656d18f0591a1125ba35f973 && \
corepack enable
WORKDIR /work/website
RUN --mount=type=bind,target=/work/package.json,src=./package.json \
--mount=type=bind,target=/work/package-lock.json,src=./package-lock.json \
--mount=type=bind,target=/work/scripts/node/,src=./scripts/node/ \
--mount=type=bind,target=/work/packages/logger-js/,src=./packages/logger-js/ \
--mount=type=bind,target=/work/packages/tsconfig/,src=./packages/tsconfig/ \
--mount=type=bind,target=/work/packages/eslint-config/,src=./packages/eslint-config/ \
--mount=type=bind,target=/work/packages/prettier-config/,src=./packages/prettier-config/ \
@@ -32,9 +26,7 @@ RUN --mount=type=bind,target=/work/package.json,src=./package.json \
--mount=type=bind,target=/work/website/integrations/package.json,src=./website/integrations/package.json \
--mount=type=bind,target=/work/website/docs/package.json,src=./website/docs/package.json \
--mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \
corepack npm ci --workspaces --include-workspace-root --prefix ./website
WORKDIR /work/website
corepack npm ci --workspaces --include-workspace-root
COPY ./website /work/website/
COPY ./blueprints /work/blueprints/
@@ -42,7 +34,7 @@ COPY ./schema.yml /work/
COPY ./lifecycle/container/compose.yml /work/lifecycle/container/
COPY ./SECURITY.md /work/
RUN corepack npm run build -w docs
RUN corepack npm run build
FROM docker.io/library/nginx:1.29-trixie@sha256:6e23479198b998e5e25921dff8455837c7636a67111a04a635cf1bb363d199dc
LABEL org.opencontainers.image.authors="Authentik Security Inc." \

View File

@@ -38,6 +38,6 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
"npm": ">=11.6.2"
}
}

View File

@@ -1,234 +0,0 @@
---
title: Integrate with GitHub Enterprise Managed Users
sidebar_label: GitHub Enterprise EMU
support_level: community
---
import TabItem from "@theme/TabItem";
import Tabs from "@theme/Tabs";
## What is GitHub Enterprise Managed Users
> With Enterprise Managed Users, you manage the lifecycle and authentication of your users on GitHub from an external identity management system, or IdP.
>
> -- https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/understanding-iam-for-enterprises/about-enterprise-managed-users
This guide configures authentik as the SAML identity provider and SCIM provider for GitHub Enterprise Cloud with Enterprise Managed Users (EMU). It applies to EMU enterprises hosted on GitHub.com and EMU enterprises with data residency on GHE.com.
## Preparation
The following placeholders are used in this guide:
- `github.com/enterprises/foo` is your GitHub.com EMU enterprise, where `foo` is the name of your enterprise.
- `foo.ghe.com` is your GHE.com EMU enterprise, where `foo` is the name of your enterprise.
- `authentik.company` is the FQDN of the authentik installation.
- `GitHub Users` is an application entitlement used for standard GitHub users.
- `GitHub Admins` is an application entitlement used for GitHub enterprise administrators.
:::info
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
SCIM must be configured for this integration. GitHub matches the SAML identity to the SCIM identity by comparing the SAML `NameID` value with the SCIM `userName` value. The mappings below use the `github_emu_username` user attribute when it exists, and fall back to the authentik username.
Use the values for your EMU deployment when configuring authentik:
<Tabs
groupId="github-emu-deployment"
defaultValue="github"
values={[
{label: 'GitHub.com', value: 'github'},
{label: 'GHE.com', value: 'ghec'},
]}>
<TabItem value="github">
| Setting | Value |
| ------------ | ------------------------------------------------- |
| **ACS URL** | `https://github.com/enterprises/foo/saml/consume` |
| **Audience** | `https://github.com/enterprises/foo` |
| **Issuer** | `https://github.com/enterprises/foo` |
| **SCIM URL** | `https://api.github.com/scim/v2/enterprises/foo` |
</TabItem>
<TabItem value="ghec">
| Setting | Value |
| ------------ | -------------------------------------------------- |
| **ACS URL** | `https://foo.ghe.com/enterprises/foo/saml/consume` |
| **Audience** | `https://foo.ghe.com/enterprises/foo` |
| **Issuer** | `https://foo.ghe.com/enterprises/foo` |
| **SCIM URL** | `https://api.foo.ghe.com/scim/v2/enterprises/foo` |
</TabItem>
</Tabs>
## authentik configuration
To support the integration of GitHub Enterprise EMU with authentik, you need to create property mappings, an application/provider pair, application entitlements, and a SCIM provider.
### Create property mappings in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Customization** > **Property Mappings** and click **Create**.
3. Create the following **SAML Provider Property Mapping**s:
- **Name**: `GitHub EMU username`
- **SAML Attribute Name**: `http://schemas.goauthentik.io/2021/02/saml/username`
- **Expression**:
```python
return request.user.attributes.get("github_emu_username", request.user.username)
```
- **Name**: `GitHub EMU full name`
- **SAML Attribute Name**: `full_name`
- **Expression**:
```python
return request.user.name
```
- **Name**: `GitHub EMU emails`
- **SAML Attribute Name**: `emails`
- **Expression**:
```python
if request.user.email:
yield request.user.email
```
4. Create a **SCIM Provider Mapping** with the following settings:
- **Name**: `GitHub EMU user`
- **Expression**:
The supported `roles` values are documented in [GitHub Enterprise Cloud's SCIM API documentation](https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/scim#provision-a-scim-enterprise-user).
```python
username = request.user.attributes.get("github_emu_username", request.user.username)
formatted = request.user.name or username
given_name = formatted
family_name = " "
if " " in formatted:
given_name, _, family_name = formatted.partition(" ")
emails = []
if request.user.email:
emails.append(
{
"value": request.user.email,
"type": "work",
"primary": True,
}
)
entitlement_names = {
entitlement.name
for entitlement in request.user.app_entitlements(provider.application)
}
roles = []
if "GitHub Admins" in entitlement_names:
roles.append({"value": "enterprise_owner", "primary": True})
elif "GitHub Users" in entitlement_names:
roles.append({"value": "user", "primary": True})
return {
"userName": username,
"externalId": str(request.user.uid),
"name": {
"formatted": formatted,
"givenName": given_name,
"familyName": family_name,
},
"displayName": formatted,
"active": request.user.is_active,
"emails": emails,
"roles": roles,
}
```
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set **ACS URL** to the ACS URL for your EMU deployment.
- Set **Audience** to the audience value for your EMU deployment.
- Set **Issuer** to the issuer value for your EMU deployment.
- Set **Service Provider Binding** to `Post`.
- Under **Advanced protocol settings**:
- Add the `GitHub EMU full name` and `GitHub EMU emails` property mappings.
- Set **NameID Property Mapping** to `GitHub EMU username`.
- Set **Default NameID Policy** to `urn:oasis:names:tc:SAML:2.0:nameid-format:persistent`.
- Select an available **Signing certificate**. Download this certificate because it is required later.
- Enable **Sign assertion** and **Sign response**.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page. If you add the SCIM provider as a backchannel provider later, only users who can view this application are synchronized.
3. Click **Submit** to save the new application and provider.
### Create application entitlements
1. In the authentik Admin interface, open the GitHub EMU application that you created.
2. Click the **Application entitlements** tab.
3. Create two entitlements named `GitHub Users` and `GitHub Admins`.
4. Open each entitlement and bind the users or groups that should receive it.
## GitHub configuration
When GitHub provisions your managed enterprise, GitHub sends an email inviting you to reset the password for the setup user. The setup user has the username `foo_admin`, cannot be linked with SSO, and is the emergency account that can bypass SSO requirements.
### Create the SCIM token
1. Log in as the setup user.
2. Navigate to the personal access tokens page:
- GitHub.com: `https://github.com/settings/tokens`
- GHE.com: `https://foo.ghe.com/settings/tokens`
3. Generate a new classic personal access token with the `scim:enterprise` scope.
4. Copy the token. This value is used in the authentik SCIM provider.
### Configure SAML in GitHub
1. Log in as the setup user.
2. Navigate to your enterprise.
3. Click **Identity provider**.
4. Under **Identity Provider**, click **Single sign-on configuration**.
5. Under **Open SCIM Configuration**, select **Enable open SCIM configuration**.
6. Under **SAML single sign-on**, select **Add SAML configuration**.
7. Configure the following settings:
- **Sign on URL**: enter the **SSO URL (Redirect)** from the SAML provider that you created in authentik.
- **Issuer**: enter the **Issuer** that you configured in authentik.
- **Public certificate**: paste the full signing certificate that you downloaded from authentik.
- **Signature method** and **Digest method**: select the methods that match the authentik SAML provider settings.
8. Click **Test SAML configuration**.
9. After the test succeeds, click **Save SAML settings**.
10. Save the SAML recovery codes that GitHub provides.
![Screenshot showing populated GitHub Enterprise Cloud EMU SAML settings](ghec_emu_settings.png)
### Create a SCIM provider in authentik
1. In the authentik Admin interface, navigate to **Applications** > **Providers** and click **Create**.
2. Select **SCIM Provider** as the provider type and click **Next**.
3. Configure the following settings:
- **Name**: provide a descriptive name.
- **URL**: enter the SCIM URL for your EMU deployment.
- **Token**: paste the GitHub personal access token that you created earlier.
- **User Property Mappings**: remove `authentik default SCIM Mapping: User`, then add the `GitHub EMU user` mapping that you created earlier.
- **Group Property Mappings**: keep `authentik default SCIM Mapping: Group` selected.
4. Click **Finish**.
5. Navigate to **Applications** > **Applications** and open the GitHub EMU application.
6. Add the SCIM provider to **Backchannel Providers**.
7. Click **Update**.
## Configuration verification
To confirm that authentik is properly configured with GitHub Enterprise EMU, assign a test user to the `GitHub Users` entitlement and ensure that the user can view the application in authentik.
Open the SCIM provider and click **Run sync again**. After the sync completes, confirm that the user is provisioned in GitHub. Then, log in to GitHub as the test user and confirm that GitHub redirects the user to authentik for SAML authentication.
## Resources
- [GitHub Enterprise Cloud: configuring SAML single sign-on for Enterprise Managed Users](https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/configuring-authentication-for-enterprise-managed-users/configuring-saml-single-sign-on-for-enterprise-managed-users)
- [GitHub Enterprise Cloud: configuring SCIM provisioning for Enterprise Managed Users](https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/provisioning-user-accounts-with-scim/configuring-scim-provisioning-for-users)
- [GitHub Enterprise Cloud: REST API endpoints for SCIM](https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/scim)

View File

@@ -1,75 +0,0 @@
---
title: Integrate with GitHub Enterprise Cloud
sidebar_label: GitHub Enterprise Cloud
support_level: community
---
## What is GitHub Enterprise Cloud
> GitHub Enterprise Cloud is a plan for large businesses or teams who collaborate on GitHub.com.
>
> -- https://docs.github.com/en/enterprise-cloud@latest/get-started/learning-about-github/githubs-plans
This guide configures SAML SSO for a GitHub Enterprise Cloud organization.
:::info
For GitHub Enterprise Cloud with Enterprise Managed Users, see the [GitHub Enterprise EMU](../ghec-emu/) integration guide.
:::
## Preparation
The following placeholders are used in this guide:
- `github.com/orgs/foo` is your GitHub organization, where `foo` is the name of your organization.
- `authentik.company` is the FQDN of the authentik installation.
:::info
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
## authentik configuration
To support the integration of GitHub Enterprise Cloud with authentik, you need to create an application/provider pair in authentik.
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set **ACS URL** to `https://github.com/orgs/foo/saml/consume`.
- Set **Audience** to `https://github.com/orgs/foo`.
- Set **Issuer** to `https://github.com/orgs/foo`.
- Set **Service Provider Binding** to `Post`.
- Under **Advanced protocol settings**, select an available **Signing certificate**. Download this certificate because it is required later.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
## GitHub configuration
1. Log in to GitHub as an organization owner.
2. Navigate to your organization at `https://github.com/foo`.
3. Click **Settings**.
4. In the left sidebar, under **Security**, click **Authentication security**.
5. Under **SAML single sign-on**, select **Enable SAML authentication**.
6. Configure the following settings:
- **Sign on URL**: enter the **SSO URL (Redirect)** from the SAML provider that you created in authentik.
- **Issuer**: enter the **Issuer** that you configured in authentik.
- **Public certificate**: paste the full signing certificate that you downloaded from authentik.
- **Signature method** and **Digest method**: select the methods that match the authentik SAML provider settings.
7. Click **Test SAML configuration**.
8. After the test succeeds, click **Save**.
![Screenshot showing populated GitHub organization SAML settings](ghorg_saml_settings.png)
This enables SAML as an authentication option. To require SAML for all organization members, visit `https://github.com/orgs/foo/sso`, sign in with SAML, then return to **Authentication security** and select **Require SAML SSO authentication for all members of the foo organization**.
## Configuration verification
To confirm that authentik is properly configured with GitHub Enterprise Cloud, log out of GitHub and then access a resource in the organization. GitHub should prompt you to authenticate with SAML through authentik.
## Resources
- [GitHub Enterprise Cloud: managing SAML single sign-on for your organization](https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-saml-single-sign-on-for-your-organization)

View File

@@ -1,142 +0,0 @@
---
title: Integrate with GitHub Enterprise Server
sidebar_label: GitHub Enterprise Server
support_level: community
---
## What is GitHub Enterprise Server
> GitHub Enterprise Server is a self-hosted platform for software development within your enterprise.
>
> -- https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server
## Preparation
The following placeholders are used in this guide:
- `github.company` is the FQDN of your GitHub Enterprise Server installation.
- `authentik.company` is the FQDN of the authentik installation.
- `GitHub Users` is an application entitlement used for standard GitHub Enterprise Server users.
- `GitHub Admins` is an application entitlement used for GitHub Enterprise Server administrators.
:::info
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
## authentik configuration
To support the integration of GitHub Enterprise Server with authentik, you need to create an application/provider pair in authentik. If you want to use SCIM provisioning, you also need to create application entitlements and a SCIM property mapping.
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set **ACS URL** to `https://github.company/saml/consume`.
- Set **Audience** to `https://github.company`.
- Set **Issuer** to `https://github.company`.
- Set **Service Provider Binding** to `Post`.
- Under **Advanced protocol settings**:
- Select an available **Signing certificate**. Download this certificate because it is required later.
- Set **NameID Property Mapping** to `authentik default SAML Mapping: Username`.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page. If you add the SCIM provider as a backchannel provider later, only users who can view this application are synchronized.
3. Click **Submit** to save the new application and provider.
### Create application entitlements
1. In the authentik Admin interface, open the GitHub Enterprise Server application that you created.
2. Click the **Application entitlements** tab.
3. Create two entitlements named `GitHub Users` and `GitHub Admins`.
4. Open each entitlement and bind the users or groups that should receive it.
### Create a SCIM property mapping
1. In the authentik Admin interface, navigate to **Customization** > **Property Mappings** and click **Create**.
2. Select **SCIM Provider Mapping** and click **Next**.
3. Create a mapping for GitHub roles:
- **Name**: `GitHub roles`
- **Expression**:
The supported `roles` values are documented in [GitHub Enterprise Server's SCIM API documentation](https://docs.github.com/en/enterprise-server@latest/rest/enterprise-admin/scim#provision-a-scim-enterprise-user).
```python
entitlement_names = {
entitlement.name
for entitlement in request.user.app_entitlements(provider.application)
}
roles = []
if "GitHub Admins" in entitlement_names:
roles.append({"value": "enterprise_owner", "primary": True})
elif "GitHub Users" in entitlement_names:
roles.append({"value": "user", "primary": True})
return {
"roles": roles,
}
```
4. Click **Finish**.
## GitHub Enterprise Server configuration
### Create the SCIM token
1. Log in to GitHub Enterprise Server with the administrator account that you use for SCIM provisioning.
2. Navigate to `https://github.company/settings/tokens`.
3. Generate a new classic personal access token with the `scim:enterprise` scope.
4. Copy the token. This value is used in the authentik SCIM provider.
### Configure SAML
1. Navigate to the GitHub Enterprise Server Management Console at `https://github.company:8443`.
2. Sign in as an administrator.
3. Go to **Authentication**.
4. Configure the following settings:
- Select **SAML**.
- **Sign on URL**: enter the **SSO URL (Redirect)** from the SAML provider that you created in authentik.
- **Issuer**: enter the **Issuer** that you configured in authentik.
- **Signature method** and **Digest method**: select the methods that match the authentik SAML provider settings.
- **Validation certificate**: upload the signing certificate that you downloaded from authentik.
- If you plan to use SCIM, select **Allow creation of accounts with built-in authentication** and **Disable administrator demotion/promotion**.
- In the **User attributes** section, do not configure a different username attribute unless it returns the same value as the SCIM `userName` attribute.
5. Click **Save settings** and wait for the changes to apply.
![Screenshot showing populated GitHub Enterprise Server SAML settings](ghes_saml_settings.png)
### Enable SCIM
1. Log in to GitHub Enterprise Server with an administrator account.
2. Open **Enterprise settings**.
3. In the left sidebar, click **Settings** > **Authentication security**.
4. Select **Enable SCIM configuration**.
5. Click **Save**.
### Create a SCIM provider in authentik
1. In the authentik Admin interface, navigate to **Applications** > **Providers** and click **Create**.
2. Select **SCIM Provider** as the provider type and click **Next**.
3. Configure the following settings:
- **Name**: provide a descriptive name.
- **URL**: `https://github.company/api/v3/scim/v2`
- **Token**: paste the GitHub personal access token that you created earlier.
- **User Property Mappings**: keep `authentik default SCIM Mapping: User` selected, then add the `GitHub roles` mapping that you created earlier.
- **Group Property Mappings**: keep `authentik default SCIM Mapping: Group` selected.
4. Click **Finish**.
5. Navigate to **Applications** > **Applications** and open the GitHub Enterprise Server application.
6. Add the SCIM provider to **Backchannel Providers**.
7. Click **Update**.
## Configuration verification
To confirm that authentik is properly configured with GitHub Enterprise Server, assign a test user to the `GitHub Users` entitlement and ensure that the user can view the application in authentik.
Open the SCIM provider and click **Run sync again**. After the sync completes, confirm that the user is provisioned in GitHub Enterprise Server. Then, log in to GitHub Enterprise Server as the test user and confirm that GitHub redirects the user to authentik for SAML authentication.
## Resources
- [GitHub Enterprise Server: configuring SAML single sign-on for your enterprise](https://docs.github.com/en/enterprise-server@latest/admin/managing-iam/using-saml-for-enterprise-iam/configuring-saml-single-sign-on-for-your-enterprise)
- [GitHub Enterprise Server: REST API endpoints for SCIM](https://docs.github.com/en/enterprise-server@latest/rest/enterprise-admin/scim)

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -0,0 +1,67 @@
---
title: Integrate with GitHub Enterprise Cloud
sidebar_label: GitHub Enterprise Cloud
support_level: community
---
## What is GitHub Enterprise Cloud
> GitHub is a complete developer platform to build, scale, and deliver secure software. Businesses use our suite of products to support the entire software development lifecycle, increasing development velocity and improving code quality.
>
> -- https://docs.github.com/en/enterprise-cloud@latest/admin/overview/about-github-for-enterprises
:::info
GitHub Enterprise Cloud EMU (Enterprise Managed Users) are not compatible with authentik. GitHub currently only permits SAML/OIDC for EMU organizations with Okta and/or Microsoft Entra ID (Azure AD).
:::
## Preparation
The following placeholders are used in this guide:
- `github.com/enterprises/foo` is your GitHub organization, where `foo` is the name of your enterprise.
- `authentik.company` is the FQDN of the authentik installation.
:::info
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
## authentik configuration
To support the integration of GitHub Enterprise Cloud with authentik, you need to create an application/provider pair in authentik.
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set the **ACS URL** to `https://github.com/enterprises/foo/saml/consume`.
- Set the **Audience** to `https://github.com/enterprises/foo`.
- Set the **Issuer** to `https://github.com/enterprises/foo`.
- Set the **Service Provider Binding** to `Post`.
- Under **Advanced protocol settings**, select an available **Signing certificate**. It is advised to download this certificate as it will be required later. It can be found under **System** > **Certificates** in the Admin Interface.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
## GitHub Configuration
Navigate to your enterprise settings by clicking your GitHub user portrait in the top right of GitHub.com, then select `Your enterprises` and click `Settings` for the enterprise you wish to configure.
In the left-hand navigation, within the `Settings` section, click `Authentication security`.
On this page:
- Select the `Require SAML authentication` checkbox.
- In `Sign on URL`, type `https://authentik.company/application/saml/<application_slug>/sso/binding/redirect/`
- For `Issuer`, type `https://github.com/enterprises/foo` or the `Audience` you set in authentik
- For `Public certificate`, paste the _full_ signing certificate into this field.
- Verify that the `Signature method` and `Digest method` match your SAML provider settings in authentik.
![Screenshot showing populated GitHub enterprise SAML settings](ghec_saml_settings.png)
Once these fields are populated, you can use the `Test SAML configuration` button to test the authentication flow. If the flow completes successfully, you will see a green tick next to the Test button.
Scroll down to hit the `Save` button below.

View File

@@ -0,0 +1,130 @@
---
title: Integrate with GitHub Enterprise Cloud - Enterprise Managed Users
sidebar_label: GitHub Enterprise Cloud EMU
support_level: community
---
## What is GitHub Enterprise Cloud - Enterprise Managed Users
> With Enterprise Managed Users, you manage the lifecycle and authentication of your users on GitHub from an external identity management system, or IdP:
>
> - Your IdP provisions new user accounts on GitHub, with access to your enterprise.
> - Users must authenticate on your IdP to access your enterprise's resources on GitHub.
> - You control usernames, profile data, organization membership, and repository access from your IdP.
> - If your enterprise uses OIDC SSO, GitHub will validate access to your enterprise and its resources using your IdP's Conditional Access Policy (CAP). See "About support for your IdP's Conditional Access Policy."
> - Managed user accounts cannot create public content or collaborate outside your enterprise. See "Abilities and restrictions of managed user accounts."
>
> -- https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/understanding-iam-for-enterprises/about-enterprise-managed-users
## Preparation
The following placeholders are used in this guide:
- `github.com/enterprises/foo` is your GitHub organization, where `foo` is the name of your enterprise
- `authentik.company` is the FQDN of the authentik installation.
- `GitHub Users` is an application entitlement used for standard GitHub Enterprise Cloud EMU users.
- `GitHub Admins` is an application entitlement used for GitHub enterprise administrators.
:::info
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
## authentik configuration
To support the integration of GitHub Enterprise Cloud EMU with authentik, you need to create an application/provider pair in authentik.
:::info
In order to use GitHub Enterprise Cloud EMU, SCIM must also be set up.
:::
:::info
GitHub will create usernames for your EMU users based on the SAML `NameID` property, which must also match SCIM's `_userName_` attribute.
:::
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set the **ACS URL** to `https://github.com/enterprises/foo/saml/consume`.
- Set the **Audience** to `https://github.com/enterprises/foo`.
- Set the **Issuer** to `https://github.com/enterprises/foo`.
- Set the **Service Provider Binding** to `Post`.
- Under **Advanced protocol settings**, select an available **Signing certificate**. It is advised to download this certificate as it will be required later. It can be found under **System** > **Certificates** in the Admin Interface.
- Under **NameID Property Mapping**, set **NameID Property Mapping** to be based on the `Email` field.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page. If you add the SCIM provider as a backchannel provider later, only users who can view this application will be synchronized.
3. Click **Submit** to save the new application and provider.
**Create the user and administrator entitlements**
In the authentik Admin interface, open the GitHub EMU application that you just created, click the **Application entitlements** tab, and create two entitlements named `GitHub Users` and `GitHub Admins`.
After creating the entitlements, open each entitlement and bind the users or groups that should receive it.
## GitHub SAML Configuration
When your EMU is provisioned by GitHub, you will receive an email inviting you to reset the password of your 'setup user'. This user cannot be linked with SSO and is an emergency access account, as it will be the only account that can bypass SSO requirements.
Before enabling SAML, go to your [Personal access tokens](https://github.com/settings/tokens) on your EMU setup user and Generate a new _personal access token (classic)_. This should have a descriptive note like `SCIM Token`. It is advisable to set this to not expire. For scopes, select only _admin:enterprise_ and click _Generate token_.
Copy the resulting token to a safe location.
After you have set a password for this account and generated your SCIM token, navigate to your enterprise settings by clicking your GitHub user portrait in the top right of GitHub.com, select `Your enterprise`, click the `Settings` link, and then click `Authentication security`.
On this page:
- Select the `Require SAML authentication` checkbox.
- In `Sign on URL`, input the _SSO URL (Redirect)_ entry from the SAML provider you created.
- For `Issuer`, input the `Issuer` you set in authentik.
- For `Public certificate`, paste the _full_ signing certificate into this field.
- Verify that the `Signature method` and `Digest method` match your SAML provider settings in authentik.
![Screenshot showing populated GitHub enterprise SAML settings](ghec_emu_settings.png)
Once these fields are populated, you can use the `Test SAML configuration` button to test the authentication flow. If the flow completes successfully, you will see a green tick next to the Test button.
Scroll down to hit the `Save SAML settings` button below.
You will now be prompted to save your SAML recovery codes. These will be necessary if you need to disable or change your SAML settings, so keep them safe!
## SCIM Provider
Before we create a SCIM provider, we also have to create a new Property Mapping. In authentik, go to _Customization_, then _Property Mappings_. Here, click _Create_, select _SCIM Provider Mapping_. Name the mapping something memorable and paste the following code in the _Expression_ field:
```python
entitlement_names = {
entitlement.name
for entitlement in request.user.app_entitlements(provider.application)
}
roles = []
# Edit this if statement if you need to add more GitHub roles.
# Valid roles include:
# user, guest_collaborator, enterprise_owner, billing_manager
if "GitHub Admins" in entitlement_names:
roles.append({'value': 'enterprise_owner', 'primary': True})
elif "GitHub Users" in entitlement_names:
roles.append({'value': 'user', 'primary': True})
return {
"roles": roles,
}
```
If you renamed either entitlement, make sure that you update the code above to match.
Create a new SCIM provider with the following parameters:
- URL: `https://api.github.com/scim/v2/enterprises/foo/` (Replacing `foo` with your Enterprise slug.)
- Token: Paste the token provided from GitHub here.
- In the _Attribute mapping_ section, de-select the `authentik default SCIM Mapping: User` mapping by selecting it on the right-hand side and clicking the left-facing single chevron.
- Select the property mapping you created in the previous step and add it by clicking the right-facing single chevron.
- You can leave the _Group Property Mappings_ as is.
- Click _Finish_.
Go back to your GitHub EMU Application created in the first step and add your new SCIM provider in the _Backchannel Providers_ field, then click the _Update_ button.
You should now be ready to assign users or groups to your _GitHub Users_ and _GitHub Admins_ application entitlements. Use application bindings or policies to limit which users can view the application and are synchronized by SCIM, and use the entitlements to assign the corresponding GitHub SCIM role values. If you do not see your users being provisioned, go to your SCIM provider and click the _Run sync again_ option. A few seconds later, you should see results of the SCIM sync.

View File

@@ -0,0 +1,118 @@
---
title: Integrate with GitHub Enterprise Server
sidebar_label: GitHub Enterprise Server
support_level: community
---
## What is GitHub Enterprise Server
> GitHub Enterprise Server is a self-hosted platform for software development within your enterprise. Your team can use GitHub Enterprise Server to build and ship software using Git version control, powerful APIs, productivity and collaboration tools, and integrations. Developers familiar with GitHub.com can onboard and contribute seamlessly using familiar features and workflows.
>
> -- https://docs.github.com/en/enterprise-server@3.5/admin/overview/about-github-enterprise-server
## Preparation
The following placeholders are used in this guide:
- `https://github.company` is your GitHub Enterprise Server installation
- `authentik.company` is the FQDN of the authentik installation.
- `GitHub Users` is an application entitlement used for standard GitHub Enterprise Server users.
- `GitHub Admins` is an application entitlement used for GitHub Enterprise Server administrators.
:::info
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
## authentik configuration
To support the integration of GitHub Enterprise Server with authentik, you need to create an application/provider pair in authentik.
:::info
In order to use GitHub Enterprise Server, SCIM must also be set up.
:::
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set the **ACS URL** to `https://github.company/saml/consume`.
- Set the **Audience** and **Issuer** to `https://github.company`.
- Set the **Service Provider Binding** to `Post`.
- Under **Advanced protocol settings**, select an available **Signing certificate**. It is advised to download this certificate as it will be required later. It can be found under **System** > **Certificates** in the Admin Interface.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page. If you add the SCIM provider as a backchannel provider later, only users who can view this application will be synchronized.
3. Click **Submit** to save the new application and provider.
### Create the user and administrator entitlements
In the authentik Admin interface, open the GitHub Enterprise Server application that you just created, click the **Application entitlements** tab, and create two entitlements named `GitHub Users` and `GitHub Admins`.
After creating the entitlements, open each entitlement and bind the users or groups that should receive it.
## SAML Configuration
If you plan to use SCIM (available from GHES 3.14.0), create a first administrator user on your instance and go to your personal access tokens at `https://github.company/settings/tokens/new`, click _Generate new token_, and then click _Generate new token (classic)_. Your token should have a descriptive name and, ideally, no expiration date. For permission scopes, you need to select _admin:enterprise_. Click _Generate token_ and store the resulting token in a safe location.
To enable SAML, navigate to your appliance maintenance settings. These are found at `https://github.company:8443`. Here, sign in with an administrator user and go to the Authentication section.
On this page:
- Select the _SAML_ option.
- In _Sign on URL_, input your _SSO URL (Redirect)_ from authentik.
- For _Issuer_, use the _Audience_ you set in authentik.
- Verify that the _Signature method_ and _Digest method_ match your SAML provider settings in authentik.
- For _Validation certificate_, upload the signing certificate you downloaded after creating the provider.
- If you plan to enable SCIM, select _Allow creation of accounts with built-in authentication_ and _Disable administrator demotion/promotion_ options. These are selected so you can use your administrator user as an emergency non-SSO account, as well as create machine users, and to ensure users are not promoted outside your IdP.
- In the _User attributes_ section, enter `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` in the _Username_ field to ensure the emails become normalized into usernames in GitHub.
- Press Save settings on the left-hand side and wait for the changes to apply.
![Screenshot showing populated GitHub Enterprise Server SAML settings](ghes_saml_settings.png)
Once the appliance has saved the settings and reloaded the services, you should be able to navigate to your instance URL at `https://github.company` and sign in with SAML.
## SCIM Configuration
This section only applies if you completed the steps above to prepare the instance for SCIM enablement.
After enabling SAML, log into your initial administrator account again. Click the user portrait in the top right, click _Enterprise settings_, click _Settings_ in the left-hand sidebar, and then click _Authentication security_. On this page, check _Enable SCIM configuration_ and press _Save_. After that, you should see a message reading _SCIM Enabled_.
Before we create a SCIM provider, we have to create a new Property Mapping. In authentik, go to _Customization_, then _Property Mappings_. Here, click _Create_, select _SCIM Provider Mapping_. Name the mapping something memorable and paste the following code in the _Expression_ field:
```python
entitlement_names = {
entitlement.name
for entitlement in request.user.app_entitlements(provider.application)
}
roles = []
# Edit this if statement if you need to add more GitHub roles.
# Valid roles include:
# user, guest_collaborator, enterprise_owner, billing_manager
if "GitHub Admins" in entitlement_names:
roles.append({'value': 'enterprise_owner', 'primary': True})
elif "GitHub Users" in entitlement_names:
roles.append({'value': 'user', 'primary': True})
return {
"roles": roles,
}
```
If you renamed either entitlement, make sure that you update the code above to match.
Create a new SCIM provider with the following parameters:
- URL: `https://github.company/api/v3/scim/v2`
- Token: Paste the token you generated earlier here.
- In the _Attribute mapping_ section, de-select the `authentik default SCIM Mapping: User` mapping from the _User Property Mappings_ by selecting it on the right-hand side and clicking the left-facing single chevron.
- Select the property mapping you created in the previous step and add it by clicking the right-facing single chevron.
- Ensure that `authentik default SCIM Mapping: Group` is the only one active in the _Group Property Mappings_.
- Click _Finish_.
Go back to your GitHub Enterprise Server Application created in the first step and add your new SCIM provider in the _Backchannel Providers_ field, then click the _Update_ button.
You should now be ready to assign users or groups to your _GitHub Users_ and _GitHub Admins_ application entitlements. Use application bindings or policies to limit which users can view the application and are synchronized by SCIM, and use the entitlements to assign the corresponding GitHub SCIM role values. If you do not see your users being provisioned, go to your SCIM provider and click the _Run sync again_ option. A few seconds later, you should see results of the SCIM sync.

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -0,0 +1,65 @@
---
title: Integrate with GitHub Organization
sidebar_label: GitHub Organization
support_level: community
---
## What is a GitHub Organization
> Organizations are shared accounts where businesses and open-source projects can collaborate across many projects at once, with sophisticated security and administrative features.
>
> -- https://docs.github.com/en/organizations/collaborating-with-groups-in-organizations/about-organizations
## Preparation
The following placeholders are used in this guide:
- `github.com/orgs/foo` is your GitHub organization, where `foo` is the name of your GitHub organization.
- `authentik.company` is the FQDN of the authentik installation.
:::info
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
## authentik configuration
To support the integration of GitHub Organization with authentik, you need to create an application/provider pair in authentik.
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings. Take note of the **slug** as it will be required later.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Set the **ACS URL** to `https://github.com/orgs/foo/saml/consume`.
- Set the **Audience** to `https://github.com/orgs/foo`.
- Set the **Issuer** to `https://github.com/orgs/foo`.
- Set the **Service Provider Binding** to `Post`.
- Under **Advanced protocol settings**, select an available **Signing certificate**. It is advised to download this certificate as it will be required later. It can be found under **System** > **Certificates** in the Admin Interface.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
## GitHub Configuration
Navigate to your organization settings by going to your organization page at https://github.com/foo, then click Settings.
In the left-hand navigation, scroll down to the Security section and click `Authentication security`.
On this page:
- Select the `Enable SAML authentication` checkbox.
- In `sign-on URL`, type `https://authentik.company/application/saml/<application_slug>/sso/binding/redirect/`
- For `Issuer`, type `https://github.com/orgs/foo` or the `Audience` you set in authentik
- For `Public certificate`, paste the _full_ signing certificate into this field.
- Verify that the `Signature method` and `Digest method` match your SAML provider settings in authentik.
Once these fields are populated, you can use the `Test SAML configuration` button to test the authentication flow. If the flow completes successfully, you will see a green tick next to the Test button.
Scroll down to hit the `Save` button below.
![Screenshot showing populated GitHub organization SAML settings](ghorg_saml_settings.png)
This enables SAML as an authentication _option_. If you want to _require_ SAML for your organization, visit your SSO url at `https://github.com/orgs/foo/sso` and sign in. Once signed in, you can navigate back to the `Authentication security` page and check `Require SAML SSO authentication for all members of the foo organization.`

View File

@@ -31,6 +31,6 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
"npm": ">=11.6.2"
}
}

View File

@@ -10,18 +10,4 @@
/integrations/* /:splat 301!
#endregion
#region GitHub integration renames
/development/github-enterprise-cloud /development/ghec 301!
/development/github-enterprise-cloud/ /development/ghec/ 301!
/development/github-organization /development/ghec 301!
/development/github-organization/ /development/ghec/ 301!
/development/github-enterprise-emu /development/ghec-emu 301!
/development/github-enterprise-emu/ /development/ghec-emu/ 301!
/development/github-enterprise-server /development/ghes 301!
/development/github-enterprise-server/ /development/ghes/ 301!
#endregion
/networking/cloudflare-access /security/cloudflare-access 301!

View File

@@ -7,6 +7,7 @@
"": {
"name": "@goauthentik/docs",
"version": "0.0.0",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
"vendored/*",
@@ -202,7 +203,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
"npm": ">=11.6.2"
}
},
"docusaurus-theme": {
@@ -246,7 +247,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
"npm": ">=11.6.2"
}
},
"node_modules/@algolia/abtesting": {

View File

@@ -9,7 +9,9 @@
"build:integrations": "npm run build -w integrations",
"check-types": "tsc -b",
"docusaurus": "docusaurus",
"preinstall": "npm ci --prefix ..",
"lint": "eslint --fix .",
"lint:lockfile": "echo 'Skipping lockfile linting'",
"lint-check": "eslint --max-warnings 0 .",
"prettier": "prettier --write .",
"prettier-check": "prettier --check .",