Compare commits

..

1 Commits

Author SHA1 Message Date
a4f92f5e30 Prep for monorepo use.
web: Update config.

Flesh out build.

Fix issue surrounding build.

Fix paths.

Update workspaces.

Fix build steps.

Apply linter. Temporarily remove problem rules.

Add ignorefile. Prep for formatting.

Lint website.

Lint web, repo packages.

Refine Prettier usage. Fix imports.

Tidy build.

Move node ignore files.

Remove unused.

Update job. Fix lint step.

Build before compiling.

Use root for paths.

Fix issues surrounding import references, types, package names.

Fix build paths.

Tidy.

Enforce prefix.

Apply prefixes to imports.

Enable linter, compiler, etc.

Fix references. Update names.

Mark optional.

Revise mounts. Fix build order.

Update package.json.

Ignore all docusaurus.

Fix paths, types.

Clean up build steps, names.

Fix paths.

website: Fix nested paragraphs build warning.

web: Enforce module resolution.

Use consistent LTS version.

Track Node version.

Use default resolution.

Test main entrypoint.

Fix Node v20 compatibility.

Add task names.

WIP: Fix styles.
2025-04-17 02:46:10 +02:00
264 changed files with 48348 additions and 53506 deletions

View File

@ -10,6 +10,9 @@ insert_final_newline = true
[*.html]
indent_size = 2
[schemas/*.json]
indent_size = 2
[*.{yaml,yml}]
indent_size = 2

View File

@ -28,9 +28,9 @@ runs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: web/package.json
node-version-file: package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
cache-dependency-path: package-lock.json
- name: Setup go
uses: actions/setup-go@v5
with:
@ -44,7 +44,7 @@ runs:
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/docker-compose.yml up -d
cd web && npm ci
npm ci
- name: Generate config
shell: uv run python {0}
run: |

View File

@ -1,5 +1,5 @@
# Re-usable workflow for a single-architecture build
name: Single-arch Container build
name: "Single-arch Container build"
on:
workflow_call:
@ -42,7 +42,7 @@ jobs:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3.6.0
- uses: docker/setup-buildx-action@v3
- name: prepare variables
- name: Prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
@ -64,12 +64,12 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: make empty clients
- name: Make empty clients
if: ${{ inputs.release }}
run: |
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
- name: generate ts client
- name: Generate TypeScript API Client
if: ${{ !inputs.release }}
run: make gen-client-ts
- name: Build Docker Image

View File

@ -1,5 +1,5 @@
# Re-usable workflow for a multi-architecture build
name: Multi-arch container build
name: "Multi-arch container build"
on:
workflow_call:
@ -49,7 +49,7 @@ jobs:
shouldPush: ${{ steps.ev.outputs.shouldPush }}
steps:
- uses: actions/checkout@v4
- name: prepare variables
- name: Prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
@ -69,7 +69,7 @@ jobs:
tag: ${{ fromJson(needs.get-tags.outputs.tags) }}
steps:
- uses: actions/checkout@v4
- name: prepare variables
- name: Prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:

View File

@ -1,4 +1,5 @@
name: authentik-api-py-publish
name: "Python API Publish"
on:
push:
branches: [main]
@ -7,6 +8,7 @@ on:
workflow_dispatch:
jobs:
build:
name: "Build and Publish"
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
permissions:
@ -30,7 +32,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- name: Generate API Client
- name: Generate Python API Client
run: make gen-client-py
- name: Publish package
working-directory: gen-py-api/

View File

@ -1,4 +1,4 @@
name: authentik-api-ts-publish
name: "TypeScript API Publish"
on:
push:
branches: [main]
@ -7,6 +7,7 @@ on:
workflow_dispatch:
jobs:
build:
name: "Build and Publish"
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
@ -20,9 +21,9 @@ jobs:
token: ${{ steps.generate_token.outputs.token }}
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
node-version-file: package.json
registry-url: "https://registry.npmjs.org"
- name: Generate API Client
- name: Generate TypeScript API Client
run: make gen-client-ts
- name: Publish package
working-directory: gen-ts-api/

View File

@ -1,4 +1,4 @@
name: authentik-ci-aws-cfn
name: "authentik CI AWS CloudFormation"
on:
push:
@ -18,6 +18,7 @@ env:
jobs:
check-changes-applied:
name: "Check changes applied"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -36,6 +37,7 @@ jobs:
uv run make aws-cfn
git diff --exit-code
ci-aws-cfn-mark:
name: "CI AWS CloudFormation Mark"
if: always()
needs:
- check-changes-applied

View File

@ -1,5 +1,5 @@
---
name: authentik-ci-main-daily
name: "authentik CI Main Daily"
on:
workflow_dispatch:
@ -9,6 +9,7 @@ on:
jobs:
test-container:
name: "Test Container ${{ matrix.version }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false

View File

@ -1,5 +1,5 @@
---
name: authentik-ci-main
name: "authentik CI Main"
on:
push:
@ -19,6 +19,7 @@ env:
jobs:
lint:
name: "Lint"
strategy:
fail-fast: false
matrix:
@ -33,9 +34,10 @@ jobs:
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- name: run job
- name: Run job ${{ matrix.job }}
run: uv run make ci-${{ matrix.job }}
test-migrations:
name: "Test Migrations"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -44,6 +46,7 @@ jobs:
- name: run migrations
run: uv run python -m lifecycle.migrate
test-make-seed:
name: "Test Make Seed"
runs-on: ubuntu-latest
steps:
- id: seed
@ -52,7 +55,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: 20
needs: test-make-seed
@ -67,7 +70,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: checkout stable
- name: Checkout Stable
run: |
# Copy current, latest config to local
# Temporarly comment the .github backup while migrating to uv
@ -84,9 +87,9 @@ jobs:
with:
postgresql_version: ${{ matrix.psql }}
continue-on-error: true
- name: run migrations to stable
- name: Run migrations to stable
run: poetry run python -m lifecycle.migrate
- name: checkout current code
- name: Checkout current code
run: |
set -x
git fetch
@ -97,10 +100,10 @@ jobs:
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
- name: migrate to latest
- name: Migrate to latest
run: |
uv run python -m lifecycle.migrate
- name: run tests
- name: Run tests
env:
# Test in the main database that we just migrated from the previous stable version
AUTHENTIK_POSTGRESQL__TEST__NAME: authentik
@ -110,7 +113,7 @@ jobs:
run: |
uv run make ci-test
test-unittest:
name: test-unittest - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
name: "Unit tests - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5"
runs-on: ubuntu-latest
timeout-minutes: 20
needs: test-make-seed
@ -146,6 +149,7 @@ jobs:
file: unittest.xml
token: ${{ secrets.CODECOV_TOKEN }}
test-integration:
name: "Integration tests"
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@ -154,7 +158,7 @@ jobs:
uses: ./.github/actions/setup
- name: Create k8s Kind Cluster
uses: helm/kind-action@v1.12.0
- name: run integration
- name: Run integration
run: |
uv run coverage run manage.py test tests/integration
uv run coverage xml
@ -170,49 +174,50 @@ jobs:
file: unittest.xml
token: ${{ secrets.CODECOV_TOKEN }}
test-e2e:
name: test-e2e (${{ matrix.job.name }})
name: "Test E2E (${{ matrix.job.name }})"
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
job:
- name: proxy
- name: Proxy Provider
glob: tests/e2e/test_provider_proxy*
- name: oauth
- name: OAuth2 Provider
glob: tests/e2e/test_provider_oauth2* tests/e2e/test_source_oauth*
- name: oauth-oidc
- name: OIDC Provider
glob: tests/e2e/test_provider_oidc*
- name: saml
- name: SAML Provider
glob: tests/e2e/test_provider_saml* tests/e2e/test_source_saml*
- name: ldap
- name: LDAP Provider
glob: tests/e2e/test_provider_ldap* tests/e2e/test_source_ldap*
- name: radius
- name: RADIUS Provider
glob: tests/e2e/test_provider_radius*
- name: scim
- name: SCIM Source
glob: tests/e2e/test_source_scim*
- name: flows
- name: Flows
glob: tests/e2e/test_flows*
steps:
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Setup e2e env (chrome, etc)
- name: Setup E2E env (chrome, etc)
run: |
docker compose -f tests/e2e/docker-compose.yml up -d --quiet-pull
- id: cache-web
uses: actions/cache@v4
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
- name: prepare web ui
key: ${{ runner.os }}-web-${{ hashFiles('./package-lock.json', 'web/src/**') }}
- name: Prepare Web UI
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web
run: |
npm ci
make -C .. gen-client-ts
npm run build
- name: run e2e
make gen-client-ts
npm run build -w @goauthentik/web
npm run typecheck
- name: Run E2E tests
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
uv run coverage xml
@ -228,6 +233,7 @@ jobs:
file: unittest.xml
token: ${{ secrets.CODECOV_TOKEN }}
ci-core-mark:
name: "CI Core Mark"
if: always()
needs:
- lint
@ -242,6 +248,7 @@ jobs:
with:
jobs: ${{ toJSON(needs) }}
build:
name: "Build"
permissions:
# Needed to upload container images to ghcr.io
packages: write
@ -255,6 +262,7 @@ jobs:
image_name: ghcr.io/goauthentik/dev-server
release: false
pr-comment:
name: "PR Comment"
needs:
- build
runs-on: ubuntu-latest
@ -267,7 +275,7 @@ jobs:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: prepare variables
- name: Prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:

View File

@ -1,5 +1,5 @@
---
name: authentik-ci-outpost
name: "authentik CI Outpost"
on:
push:
@ -14,6 +14,7 @@ on:
jobs:
lint-golint:
name: "Lint Go"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -26,7 +27,7 @@ jobs:
mkdir -p web/dist
mkdir -p website/help
touch web/dist/test website/help/test
- name: Generate API
- name: Generate Go API Client
run: make gen-client-go
- name: golangci-lint
uses: golangci/golangci-lint-action@v7
@ -35,6 +36,7 @@ jobs:
args: --timeout 5000s --verbose
skip-cache: true
test-unittest:
name: "Unit Test Go"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -43,12 +45,13 @@ jobs:
go-version-file: "go.mod"
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Generate API
- name: Generate Go API Client
run: make gen-client-go
- name: Go unittests
run: |
go test -timeout 0 -v -race -coverprofile=coverage.out -covermode=atomic -cover ./...
ci-outpost-mark:
name: "CI Outpost Mark"
if: always()
needs:
- lint-golint
@ -59,6 +62,7 @@ jobs:
with:
jobs: ${{ toJSON(needs) }}
build-container:
name: "Build Container"
timeout-minutes: 120
needs:
- ci-outpost-mark
@ -85,7 +89,7 @@ jobs:
uses: docker/setup-qemu-action@v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: prepare variables
- name: Prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
@ -99,7 +103,7 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate API
- name: Generate Go API Client
run: make gen-client-go
- name: Build Docker Image
id: push
@ -122,6 +126,7 @@ jobs:
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
build-binary:
name: "Build Binary"
timeout-minutes: 120
needs:
- ci-outpost-mark
@ -140,21 +145,22 @@ jobs:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: "npm"
cache-dependency-path: package-lock.json
- name: Install Node.js dependencies
run: npm ci
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Generate API
- name: Generate Go API Client
run: make gen-client-go
- name: Build web
working-directory: web/
run: |
npm ci
npm run build-proxy
npm run build-proxy -w @goauthentik/web
- name: Build outpost
run: |
set -x

View File

@ -1,4 +1,4 @@
name: authentik-ci-web
name: CI Web UI
on:
push:
@ -13,54 +13,50 @@ 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@v4
- uses: actions/setup-node@v4
with:
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: Generate API
run: make gen-client-ts
- name: Lint
working-directory: ${{ matrix.project }}/
run: npm run ${{ matrix.command }}
build:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
node-version-file: package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
cache-dependency-path: package-lock.json
- name: Install Node.js dependencies
run: npm ci
- name: Generate API
- name: Generate TypeScript API
run: make gen-client-ts
- name: Build
run: |
npm run build -w @goauthentik/web
- name: Type check
run: |
npm run typecheck
- name: Lint
run: |
npm run lint -w @goauthentik/web
npm run lint:lockfile -w @goauthentik/web
npm run lit-analyse -w @goauthentik/web
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: "npm"
cache-dependency-path: package-lock.json
- name: Install Node.js dependencies
run: npm ci
- name: Generate TypeScript API
run: make gen-client-ts
- name: build
working-directory: web/
run: npm run build
run: |
npm run build -w @goauthentik/web
npm run typecheck
ci-web-mark:
name: CI Web Mark
if: always()
needs:
- build
@ -71,6 +67,7 @@ jobs:
with:
jobs: ${{ toJSON(needs) }}
test:
name: Test
needs:
- ci-web-mark
runs-on: ubuntu-latest
@ -78,13 +75,12 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
node-version-file: package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
cache-dependency-path: package-lock.json
- name: Install Node.js dependencies
run: npm ci
- name: Generate API
- name: Generate TypeScript API
run: make gen-client-ts
- name: test
working-directory: web/
run: npm run test || exit 0
- name: Test Web UI
run: npm run test -w @goauthentik/web || exit 0

View File

@ -1,4 +1,4 @@
name: authentik-ci-website
name: CI Docs Website
on:
push:
@ -13,55 +13,59 @@ on:
jobs:
lint:
name: "Lint"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
command:
- lint:lockfile
- prettier-check
steps:
- uses: actions/checkout@v4
- working-directory: website/
run: npm ci
- name: Lint
working-directory: website/
run: npm run ${{ matrix.command }}
- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: "npm"
cache-dependency-path: package-lock.json
- name: Install Node.js dependencies
run: |
npm ci
- name: Generate TypeScript API
run: make gen-client-ts
- name: Lint Docs
run: |
npm run lint:prettier:check
npm run lint:lockfile -w @goauthentik/docs
test:
name: "Test Docs"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: website/package.json
node-version-file: package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
run: npm ci
- name: test
working-directory: website/
run: npm test
cache-dependency-path: package-lock.json
- name: Install Node.js dependencies
run: |
npm ci
- name: Generate TypeScript API
run: make gen-client-ts
- name: Test Docs
run: |
npm run test -w @goauthentik/docs
build:
name: "Build Docs"
runs-on: ubuntu-latest
name: ${{ matrix.job }}
strategy:
fail-fast: false
matrix:
job:
- build
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: website/package.json
node-version-file: package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
cache-dependency-path: package-lock.json
- name: Install Node.js dependencies
run: npm ci
- name: build
working-directory: website/
run: npm run ${{ matrix.job }}
- name: Build
run: |
npm run build -w @goauthentik/docs
ci-website-mark:
name: "CI Website Mark"
if: always()
needs:
- lint

View File

@ -10,7 +10,7 @@ on:
jobs:
analyze:
name: Analyze
name: "Analyze"
runs-on: ubuntu-latest
permissions:
actions: read

View File

@ -1,4 +1,4 @@
name: authentik-gen-update-webauthn-mds
name: "authentik CI Update WebAuthn MDS"
on:
workflow_dispatch:
schedule:
@ -11,6 +11,7 @@ env:
jobs:
build:
name: "Update WebAuthn MDS"
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:

View File

@ -1,6 +1,6 @@
---
# See https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
name: Cleanup cache after PR is closed
name: "Post-PR Closed Cache Cleanup"
on:
pull_request:
types:
@ -12,6 +12,7 @@ permissions:
jobs:
cleanup:
name: "Cleanup Cache"
runs-on: ubuntu-latest
steps:
- name: Check out code

View File

@ -1,4 +1,4 @@
name: ghcr-retention
name: "authentik GHCR Retention Policy"
on:
# schedule:
@ -8,7 +8,7 @@ on:
jobs:
clean-ghcr:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
name: Delete old unused container images
name: "Delete old unused container images"
runs-on: ubuntu-latest
steps:
- id: generate_token

View File

@ -1,5 +1,5 @@
---
name: authentik-compress-images
name: "authentik CI Image Compression"
on:
push:
@ -20,7 +20,7 @@ on:
jobs:
compress:
name: compress
name: "Compress Docker images"
runs-on: ubuntu-latest
# Don't run on forks. Token will not be available. Will run on main and open a PR anyway
if: |

View File

@ -1,4 +1,4 @@
name: authentik-publish-source-docs
name: "authentik Publish Source Docs"
on:
push:
@ -12,6 +12,7 @@ env:
jobs:
publish-source-docs:
name: "Publish"
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
timeout-minutes: 120
@ -19,11 +20,11 @@ jobs:
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- name: generate docs
- name: Generate docs
run: |
uv run make migrate
uv run ak build_source_docs
- name: Publish
- name: Deploy to Netlify
uses: netlify/actions/cli@master
with:
args: deploy --dir=source_docs --prod

View File

@ -1,4 +1,4 @@
name: authentik-on-release-next-branch
name: "authentik on Release Next Branch"
on:
schedule:
@ -11,6 +11,7 @@ permissions:
jobs:
update-next:
name: "Update Next Branch"
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
environment: internal-production

View File

@ -1,5 +1,5 @@
---
name: authentik-on-release
name: "Release publish"
on:
release:
@ -7,6 +7,7 @@ on:
jobs:
build-server:
name: "Build server"
uses: ./.github/workflows/_reusable-docker-build.yaml
secrets: inherit
permissions:
@ -21,6 +22,7 @@ jobs:
registry_dockerhub: true
registry_ghcr: true
build-outpost:
name: "Build outpost"
runs-on: ubuntu-latest
permissions:
# Needed to upload container images to ghcr.io
@ -45,14 +47,14 @@ jobs:
uses: docker/setup-qemu-action@v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: prepare variables
- name: Prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with:
image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }}
- name: make empty clients
- name: Make empty clients
run: |
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
@ -85,6 +87,7 @@ jobs:
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
build-outpost-binary:
name: "Build outpost binary"
timeout-minutes: 120
runs-on: ubuntu-latest
permissions:
@ -106,14 +109,13 @@ jobs:
go-version-file: "go.mod"
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
node-version-file: package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
cache-dependency-path: package-lock.json
- name: Build web
working-directory: web/
run: |
npm ci
npm run build-proxy
npm run build-proxy -w @goauthentik/web
- name: Build outpost
run: |
set -x
@ -129,6 +131,7 @@ jobs:
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
tag: ${{ github.ref }}
upload-aws-cfn-template:
name: "Upload AWS CloudFormation template"
permissions:
# Needed for AWS login
id-token: write
@ -150,6 +153,7 @@ jobs:
aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml
aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml
test-release:
name: "Test release"
needs:
- build-server
- build-outpost
@ -166,6 +170,7 @@ jobs:
docker compose start postgresql redis
docker compose run -u root server test-all
sentry-release:
name: "Sentry release"
needs:
- build-server
- build-outpost
@ -173,7 +178,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: prepare variables
- name: Prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:

View File

@ -1,5 +1,5 @@
---
name: authentik-on-tag
name: "authentik on Tag Release"
on:
push:
@ -8,7 +8,7 @@ on:
jobs:
build:
name: Create Release from Tag
name: "Create Release from Tag"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -20,7 +20,7 @@ jobs:
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: prepare variables
- name: Prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:

View File

@ -1,13 +1,15 @@
name: "authentik-repo-mirror"
name: "authentik Repository Mirror"
on: [push, delete]
jobs:
to_internal:
name: "Mirror to internal repository"
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
name: "Checkout repository"
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}

View File

@ -1,4 +1,4 @@
name: "authentik-repo-stale"
name: "authentik Repository Stale Issues"
on:
schedule:
@ -11,6 +11,7 @@ permissions:
jobs:
stale:
name: "Stale Issues"
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:

View File

@ -1,4 +1,4 @@
name: authentik-semgrep
name: "authentik CI Semgrep"
on:
workflow_dispatch: {}
pull_request: {}
@ -13,7 +13,7 @@ on:
- cron: '12 15 * * *'
jobs:
semgrep:
name: semgrep/ci
name: "semgrep/ci"
runs-on: ubuntu-latest
permissions:
contents: read

View File

@ -1,4 +1,4 @@
name: authentik-translation-advice
name: "authentik Translations Advice"
on:
pull_request:
@ -16,6 +16,7 @@ permissions:
jobs:
post-comment:
name: "Post Comment"
runs-on: ubuntu-latest
steps:
- name: Find Comment

View File

@ -1,5 +1,5 @@
---
name: authentik-translate-extract-compile
name: "authentik Extract & Compile Translations"
on:
schedule:
- cron: "0 0 * * *" # every day at midnight
@ -16,6 +16,7 @@ env:
jobs:
compile:
name: "Compile Translations"
runs-on: ubuntu-latest
steps:
- id: generate_token
@ -32,15 +33,20 @@ jobs:
if: ${{ github.event_name == 'pull_request' }}
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Generate API
- name: Generate TypeScript API
run: make gen-client-ts
- name: run extract
- name: Extract Translations
run: |
uv run make i18n-extract
- name: run compile
- name: Build Docs Site
run: npm run build-bundled -w @goauthentik/docs
- name: Build Web UI
run: npm run build -w @goauthentik/web
- name: Type check
run: npm run typecheck
- name: Compile Messages
run: |
uv run ak compilemessages
make web-check-compile
- name: Create Pull Request
if: ${{ github.event_name != 'pull_request' }}
uses: peter-evans/create-pull-request@v7

View File

@ -1,6 +1,6 @@
# Rename transifex pull requests to have a correct naming
# Also enables auto squash-merge
name: authentik-translation-transifex-rename
name: "authentik Translations Transifex PR Rename"
on:
pull_request:
@ -12,6 +12,7 @@ permissions:
jobs:
rename_pr:
name: "Rename PR"
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}}
steps:

23
.gitignore vendored
View File

@ -217,3 +217,26 @@ source_docs/
### Docker ###
docker-compose.override.yml
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
node_modules/
tsconfig.tsbuildinfo
# Wireit's cache
.wireit
custom-elements.json
### Development ###
.drafts

View File

@ -4,12 +4,16 @@
**/LICENSE
authentik/stages/**/*
authentik/sources/**/*
schemas/**/*
blueprints/**/*
## Build asset directories
coverage
dist
out
.docusaurus
.wireit
website/docs/developer-docs/api/**/*
## Environment
@ -32,14 +36,15 @@ coverage
# Templates
# TODO: Rename affected files to *.template.* or similar.
authentik/**/*.html
*.html
*.mdx
*.md
## Import order matters
poly.ts
src/locale-codes.ts
src/locales/
web/src/poly.ts
web/src/locale-codes.ts
web/src/locales/
# Storybook
storybook-static/

View File

@ -17,6 +17,6 @@
"ms-python.vscode-pylance",
"redhat.vscode-yaml",
"Tobermory.es6-string-html",
"unifiedjs.vscode-mdx",
"unifiedjs.vscode-mdx"
]
}

72
.vscode/settings.json vendored
View File

@ -16,7 +16,7 @@
],
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index",
"typescript.tsdk": "./web/node_modules/typescript/lib",
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"yaml.schemas": {
"./blueprints/schema.json": "blueprints/**/*.yaml"
@ -30,7 +30,71 @@
}
],
"go.testFlags": ["-count=1"],
"github-actions.workflows.pinned.workflows": [
".github/workflows/ci-main.yml"
]
"github-actions.workflows.pinned.workflows": [".github/workflows/ci-main.yml"],
"eslint.useFlatConfig": true,
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"*.mjs": "*.d.mts",
"*.cjs": "*.d.cts",
"package.json": "package-lock.json, yarn.lock, .yarnrc, .yarnrc.yml, .yarn, .nvmrc, .node-version",
"tsconfig.json": "tsconfig.*.json, jsconfig.json",
"Dockerfile": "*.Dockerfile"
},
"search.exclude": {
"**/node_modules": true,
"**/*.code-search": true,
"**/dist": true,
"**/out": true,
"**/package-lock.json": true
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[shellscript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[django-html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.removeUnusedImports": "explicit"
},
// We use Prettier for formatting, but specifying these settings
// will ensure that VS Code's IntelliSense doesn't autocomplete unformatted code.
"javascript.format.semicolons": "insert",
"typescript.format.semicolons": "insert",
"javascript.preferences.quoteStyle": "double",
"typescript.preferences.quoteStyle": "double",
"github.copilot.enable": {
"*": true,
"plaintext": true,
"markdown": true,
"scminput": false,
"csv": false,
"json": true,
"yaml": true
}
}

40
.vscode/tasks.json vendored
View File

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

View File

@ -1,48 +1,31 @@
# syntax=docker/dockerfile:1
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS website-builder
# Stage 1 Web UI and Documentation build
ENV NODE_ENV=production
WORKDIR /work/website
RUN --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 \
--mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \
npm ci --include=dev
COPY ./website /work/website/
COPY ./blueprints /work/blueprints/
COPY ./schema.yml /work/
COPY ./SECURITY.md /work/
RUN npm run build-bundled
# Stage 2: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS web-builder
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
ENV NODE_ENV=production
WORKDIR /work/web
WORKDIR /work
RUN --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/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-web,sharing=shared,target=/root/.npm \
npm ci --include=dev
COPY ./package.json ./package.json
COPY ./package-lock.json ./package-lock.json
COPY ./packages ./packages
COPY ./web ./web
COPY ./website ./website
COPY ./package.json /work
COPY ./web /work/web/
COPY ./website /work/website/
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
COPY ./gen-ts-api ./gen-ts-api
COPY ./blueprints ./blueprints
COPY ./schema.yml ./schema.yml
COPY ./SECURITY.md ./SECURITY.md
RUN npm run build
RUN --mount=type=cache,target=/root/.npm npm ci --include=dev
RUN npm run build-bundled -w @goauthentik/docs
RUN npm run build -w @goauthentik/web
# Stage 2: Build go proxy
# Stage 3: Build go proxy
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
ARG TARGETOS
@ -79,7 +62,8 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \
go build -o /go/authentik ./cmd/server
# Stage 4: MaxMind GeoIP
# Stage 3: MaxMind GeoIP
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
@ -93,9 +77,10 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
mkdir -p /usr/share/GeoIP && \
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 5: Download uv
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.6.14 AS uv
# Stage 6: Base python image
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base
ENV VENV_PATH="/ak-root/.venv" \
@ -109,7 +94,7 @@ WORKDIR /ak-root/
COPY --from=uv /uv /uvx /bin/
# Stage 7: Python dependencies
# Stage 6: Python dependencies
FROM python-base AS python-deps
ARG TARGETARCH
@ -144,7 +129,7 @@ RUN --mount=type=bind,target=pyproject.toml,src=pyproject.toml \
--mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-install-project --no-dev
# Stage 8: Run
# Stage 7: Run
FROM python-base AS final-image
ARG VERSION
@ -189,7 +174,7 @@ COPY --from=go-builder /go/authentik /bin/authentik
COPY --from=python-deps /ak-root/.venv /ak-root/.venv
COPY --from=web-builder /work/web/dist/ /web/dist/
COPY --from=web-builder /work/web/authentik/ /web/authentik/
COPY --from=website-builder /work/website/build/ /website/help/
COPY --from=web-builder /work/website/build/ /website/help/
COPY --from=geoip /usr/share/GeoIP /geoip
USER 1000

107
Makefile
View File

@ -36,6 +36,13 @@ test: ## Run the server tests and produce a coverage report (locally)
uv run coverage html
uv run coverage report
node-check-compile: ## Check and compile the TypeScript source code
npm run typecheck
node-lint-fix: ## Lint and automatically fix errors in the javascript source code
lint-codespell
npm run lint:fix
lint-fix: lint-codespell ## Lint and automatically fix errors in the python source code. Reports spelling errors.
uv run black $(PY_SOURCES)
uv run ruff check --fix $(PY_SOURCES)
@ -47,9 +54,6 @@ lint: ## Lint the python and golang sources
uv run bandit -c pyproject.toml -r $(PY_SOURCES)
golangci-lint run -v
core-install:
uv sync --frozen
migrate: ## Run the Authentik Django server's migrations
uv run python -m lifecycle.migrate
@ -72,7 +76,9 @@ core-i18n-extract:
--ignore website \
-l en
install: web-install website-install core-install ## Install all requires dependencies for `web`, `website` and `core`
install: ## Install all requires dependencies for `web`, `website` and `core`
npm ci
uv sync --frozen
dev-drop-db:
dropdb -U ${pg_user} -h ${pg_host} ${pg_name}
@ -94,6 +100,7 @@ gen-build: ## Extract the schema from the database
AUTHENTIK_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
uv run ak make_blueprint_schema > blueprints/schema.json
AUTHENTIK_DEBUG=true \
AUTHENTIK_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
@ -101,19 +108,24 @@ gen-build: ## Extract the schema from the database
gen-changelog: ## (Release) generate the changelog based from the commits since the last tag
git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md
npx prettier --write changelog.md
gen-diff: ## (Release) generate the changelog diff between the current schema and the last tag
git show $(shell git describe --tags $(shell git rev-list --tags --max-count=1)):schema.yml > old_schema.yml
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-diff:2.1.0-beta.8 \
--markdown /local/diff.md \
/local/old_schema.yml /local/schema.yml
rm old_schema.yml
sed -i 's/{/{/g' diff.md
sed -i 's/}/}/g' diff.md
npx prettier --write diff.md
gen-clean-ts: ## Remove generated API client for Typescript
@ -133,46 +145,57 @@ gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescri
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
-i /local/schema.yml \
-g typescript-fetch \
-o /local/${GEN_API_TS} \
-c /local/scripts/api-ts-config.yaml \
--input-spec /local/schema.yml \
--generator-name typescript-fetch \
--output /local/${GEN_API_TS} \
--config /local/scripts/api-ts-config.yaml \
--additional-properties=npmVersion=${NPM_VERSION} \
--git-repo-id authentik \
--git-user-id goauthentik
mkdir -p web/node_modules/@goauthentik/api
cd ./${GEN_API_TS} && npm i
\cp -rf ./${GEN_API_TS}/* web/node_modules/@goauthentik/api
npm install
gen-client-py: gen-clean-py ## Build and install the authentik API for Python
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
-i /local/schema.yml \
-g python \
-o /local/${GEN_API_PY} \
-c /local/scripts/api-py-config.yaml \
--input-spec /local/schema.yml \
--generator-name python \
--output /local/${GEN_API_PY} \
--config /local/scripts/api-py-config.yaml \
--additional-properties=packageVersion=${NPM_VERSION} \
--git-repo-id authentik \
--git-user-id goauthentik
pip install ./${GEN_API_PY}
gen-client-go: gen-clean-go ## Build and install the authentik API for Golang
mkdir -p ./${GEN_API_GO} ./${GEN_API_GO}/templates
wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml -O ./${GEN_API_GO}/config.yaml
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache -O ./${GEN_API_GO}/templates/README.mustache
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache -O ./${GEN_API_GO}/templates/go.mod.mustache
wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml \
-O ./${GEN_API_GO}/config.yaml
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache \
-O ./${GEN_API_GO}/templates/README.mustache
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache \
-O ./${GEN_API_GO}/templates/go.mod.mustache
cp schema.yml ./${GEN_API_GO}/
docker run \
--rm -v ${PWD}/${GEN_API_GO}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \
-i /local/schema.yml \
-g go \
-o /local/ \
-c /local/config.yaml
--input-spec /local/schema.yml \
--generator-name go \
--output /local/ \
--config /local/config.yaml
go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO}
rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/
gen-dev-config: ## Generate a local development config file
@ -184,56 +207,38 @@ gen: gen-build gen-client-ts
## Web
#########################
web-build: web-install ## Build the Authentik UI
cd web && npm run 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-install: ## Install the necessary libraries to build the Authentik UI
cd web && npm ci
web: web-lint-fix web-lint node-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
cd web && npm run test
npm run test -w @goauthentik/web
web-watch: ## Build and watch the Authentik UI for changes, updating automatically
rm -rf web/dist/
mkdir web/dist/
touch web/dist/.gitkeep
cd web && npm run watch
npm run watch -w @goauthentik/web
web-storybook-watch: ## Build and run the storybook documentation server
cd web && npm run storybook
npm run storybook -w @goauthentik/web
web-lint-fix:
cd web && npm run prettier
npm run prettier -w @goauthentik/web
web-lint:
cd web && npm run lint
cd web && npm run lit-analyse
web-check-compile:
cd web && npm run tsc
npm run lint -w @goauthentik/web
npm run lit-analyse -w @goauthentik/web
web-i18n-extract:
cd web && npm run extract-locales
npm run extract-locales -w @goauthentik/web
#########################
## Website
#########################
website: website-lint-fix website-build ## Automatically fix formatting issues in the Authentik website/docs source code, lint the code, and compile it
website-install:
cd website && npm ci
website-lint-fix: lint-codespell
cd website && npm run prettier
website: node-lint-fix website-build ## Automatically fix formatting issues in the Authentik website/docs source code, lint the code, and compile it
website-build:
cd website && npm run build
npm run build -w @goauthentik/docs
website-watch: ## Build and watch the documentation website, updating automatically
cd website && npm run watch
npm run watch -w @goauthentik/docs
#########################
## Docker

View File

@ -2,20 +2,22 @@
{% get_current_language as LANGUAGE_CODE %}
<script>
window.authentik = {
locale: "{{ LANGUAGE_CODE }}",
config: JSON.parse('{{ config_json|escapejs }}'),
brand: JSON.parse('{{ brand_json|escapejs }}'),
versionFamily: "{{ version_family }}",
versionSubdomain: "{{ version_subdomain }}",
build: "{{ build }}",
api: {
base: "{{ base_url }}",
relBase: "{{ base_url_rel }}",
},
};
window.authentik = {
locale: "{{ LANGUAGE_CODE }}",
config: JSON.parse("{{ config_json|escapejs }}" || "{}"),
brand: JSON.parse("{{ brand_json|escapejs }}" || "{}"),
versionFamily: "{{ version_family }}",
versionSubdomain: "{{ version_subdomain }}",
build: "{{ build }}",
api: {
base: "{{ base_url }}",
relBase: "{{ base_url_rel }}",
},
};
{% if messages %}
window.addEventListener("DOMContentLoaded", function () {
{% for message in messages %}
{% for message in messages %}
window.dispatchEvent(
new CustomEvent("ak-message", {
bubbles: true,
@ -26,6 +28,7 @@
},
}),
);
{% endfor %}
{% endfor %}
});
{% endif %}
</script>

View File

@ -2,31 +2,79 @@
{% load i18n %}
{% load authentik_core %}
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
{# Darkreader breaks the site regardless of theme as its not compatible with webcomponents, and we default to a dark theme based on preferred colour-scheme #}
<meta name="darkreader-lock">
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<style>{{ brand.branding_custom_css }}</style>
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
{% block head %}
{% endblock %}
<meta name="sentry-trace" content="{{ sentry_trace }}" />
</head>
<body>
{% block body %}
{% endblock %}
{% block scripts %}
{% endblock %}
</body>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
{% comment %}
Darkreader breaks the site regardless of theme as its not compatible with webcomponents, and we
default to a dark theme based on preferred colour-scheme
{% endcomment %}
<meta name="darkreader-lock" />
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon_url }}" />
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}" />
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}" />
<style data-test-id="color-scheme">
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark light;
}
}
@media (prefers-color-scheme: light) {
:root {
color-scheme: light dark;
}
}
</style>
<style data-test-id="custom-branding-css">
{{ brand.branding_custom_css }}
</style>
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
<script
src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}"
type="module"
></script>
{% block head %}
{% endblock %}
<meta name="sentry-trace" content="{{ sentry_trace }}" />
</head>
<body>
{% block body %}{% endblock %}
{% block scripts %}{% endblock %}
<noscript>
<style>
body {
font-family: var(--ak-font-family-base), sans-serif;
}
</style>
<h1>
JavaScript is required to use
{% trans title|default:brand.branding_title %}
</h1>
<p>
Please enable JavaScript in your browser settings and reload the page. If you are using a
browser extension that blocks JavaScript, please disable it for this site.
</p>
</noscript>
</body>
</html>

View File

@ -4,14 +4,16 @@
{% block head %}
<script src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
{% include "base/header_js.html" %}
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-interface-admin>
<ak-loading></ak-loading>
<ak-loading></ak-loading>
</ak-interface-admin>
{% endblock %}

View File

@ -13,9 +13,14 @@
{% block card %}
<form method="POST" class="pf-c-form">
<p>{% trans message %}</p>
<a id="ak-back-home" href="{% url 'authentik_core:root-redirect' %}" class="pf-c-button pf-m-primary">
{% trans 'Go home' %}
</a>
<p>{% trans message %}</p>
<a
id="ak-back-home"
href="{% url 'authentik_core:root-redirect' %}"
class="pf-c-button pf-m-primary"
>
{% trans 'Go home' %}
</a>
</form>
{% endblock %}

View File

@ -4,14 +4,17 @@
{% block head %}
<script src="{% versioned_script 'dist/user/UserInterface-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)" />
{% include "base/header_js.html" %}
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-interface-user>
<ak-loading></ak-loading>
<ak-loading></ak-loading>
</ak-interface-user>
{% endblock %}

View File

@ -5,78 +5,82 @@
{% block head_before %}
<link rel="prefetch" href="{{ request.brand.branding_default_flow_background_url }}" />
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}" />
<link
rel="stylesheet"
type="text/css"
href="{% static 'dist/theme-dark.css' %}"
media="(prefers-color-scheme: dark)"
/>
{% include "base/header_js.html" %}
{% endblock %}
{% block head %}
<style>
:root {
<style data-test-id="base-full-root-styles">
:root {
--ak-flow-background: url("{{ request.brand.branding_default_flow_background_url }}");
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background);
}
/* Form with user */
.form-control-static {
}
/* Form with user */
.form-control-static {
margin-top: var(--pf-global--spacer--sm);
display: flex;
align-items: center;
justify-content: space-between;
}
.form-control-static .avatar {
}
.form-control-static .avatar {
display: flex;
align-items: center;
}
.form-control-static img {
}
.form-control-static img {
margin-right: var(--pf-global--spacer--xs);
}
.form-control-static a {
}
.form-control-static a {
padding-top: var(--pf-global--spacer--xs);
padding-bottom: var(--pf-global--spacer--xs);
line-height: var(--pf-global--spacer--xl);
}
}
</style>
{% endblock %}
{% block body %}
<div class="pf-c-background-image">
</div>
<div class="pf-c-background-image"></div>
<ak-message-container></ak-message-container>
<div class="pf-c-login stacked">
<div class="ak-login-container">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="{{ brand.branding_logo_url }}" alt="authentik Logo" />
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{% block card_title %}
{% endblock %}
</h1>
</header>
<div class="pf-c-login__main-body">
{% block card %}
{% endblock %}
</div>
</main>
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
{% for link in footer_links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
</li>
{% endfor %}
<li>
<span>
{% trans 'Powered by authentik' %}
</span>
</li>
</ul>
</footer>
</div>
<div class="ak-login-container">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="{{ brand.branding_logo_url }}" alt="authentik Logo" />
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{% block card_title %}
{% endblock %}
</h1>
</header>
<div class="pf-c-login__main-body">
{% block card %}
{% endblock %}
</div>
</main>
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
{% for link in footer_links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
</li>
{% endfor %}
<li>
<span>
{% trans 'Powered by authentik' %}
</span>
</li>
</ul>
</footer>
</div>
</div>
{% endblock %}

View File

@ -2,53 +2,52 @@
{% load i18n %}
{% load authentik_core %}
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
<meta name="sentry-trace" content="{{ sentry_trace }}" />
{% include "base/header_js.html" %}
<style>
html,
body {
height: 100%;
}
body {
background-image: url("{{ flow.background_url }}");
background-repeat: no-repeat;
background-size: cover;
}
.card {
padding: 3rem;
}
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon_url }}" />
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}" />
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}" />
<meta name="sentry-trace" content="{{ sentry_trace }}" />
{% include "base/header_js.html" %}
<style>
html,
body {
height: 100%;
}
body {
background-image: url("{{ flow.background_url }}");
background-repeat: no-repeat;
background-size: cover;
}
.card {
padding: 3rem;
}
.form-signin {
max-width: 330px;
padding: 1rem;
}
.form-signin {
max-width: 330px;
padding: 1rem;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.brand-icon {
max-width: 100%;
}
</style>
</head>
<body class="d-flex align-items-center py-4 bg-body-tertiary">
<div class="card m-auto">
<main class="form-signin w-100 m-auto" id="flow-sfe-container">
</main>
<span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span>
</div>
<script src="{% static 'dist/sfe/index.js' %}"></script>
</body>
.form-signin .form-floating:focus-within {
z-index: 2;
}
.brand-icon {
max-width: 100%;
}
</style>
</head>
<body class="d-flex align-items-center py-4 bg-body-tertiary">
<div class="card m-auto">
<main class="form-signin w-100 m-auto" id="flow-sfe-container"></main>
<span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span>
</div>
<script src="{% static 'dist/sfe/index.js' %}"></script>
</body>
</html>

View File

@ -1,34 +1,40 @@
{% extends "base/skeleton.html" %}
{% load static %}
{% load authentik_core %}
{% block head_before %}
{{ block.super }}
<link rel="prefetch" href="{{ flow.background_url }}" />
{% if flow.compatibility_mode and not inspector %}
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}
{% include "base/header_js.html" %}
<script>
window.authentik.flow = {
"layout": "{{ flow.layout }}",
};
ShadyDOM = { force: !navigator.webdriver };
</script>
{% endif %}
{% include "base/header_js.html" %}
<script>
window.authentik.flow = {
layout: "{{ flow.layout }}",
};
</script>
{% endblock %}
{% block head %}
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
<style>
:root {
<style data-test-id="flow-root-styles">
:root {
--ak-flow-background: url("{{ flow.background_url }}");
}
}
</style>
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-flow-executor flowSlug="{{ flow.slug }}">
<ak-loading></ak-loading>
<ak-loading></ak-loading>
</ak-flow-executor>
{% endblock %}

View File

@ -48,7 +48,6 @@ class TestFlowInspector(APITestCase):
"allow_show_password": False,
"captcha_stage": None,
"component": "ak-stage-identification",
"enable_remember_me": False,
"flow_info": {
"background": "/static/dist/assets/images/flow_background.jpg",
"cancel_url": reverse("authentik_flows:cancel"),

View File

@ -108,9 +108,6 @@ class PolicyEngine:
self.__cached_policies.append(cached_policy)
return True
def _should_fork(self, binding: PolicyBinding) -> bool:
return binding.policy is not None
def build(self) -> "PolicyEngine":
"""Build wrapper which monitors performance"""
with (
@ -137,7 +134,7 @@ class PolicyEngine:
task = PolicyProcess(binding, self.request, task_end)
task.daemon = False
self.logger.debug("P_ENG: Starting Process", binding=binding, request=self.request)
if not CURRENT_PROCESS._config.get("daemon") or not self._should_fork(binding):
if not CURRENT_PROCESS._config.get("daemon"):
task.run()
else:
task.start()

View File

@ -4,10 +4,13 @@
{% block head %}
<script src="{% versioned_script 'dist/rac/index-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<link rel="icon" href="{{ tenant.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}">
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<link rel="icon" href="{{ tenant.branding_favicon_url }}" />
<link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}" />
{% include "base/header_js.html" %}
{% endblock %}

View File

@ -36,7 +36,6 @@ class IdentificationStageSerializer(StageSerializer):
"sources",
"show_source_labels",
"pretend_user_exists",
"enable_remember_me",
]

View File

@ -1,21 +0,0 @@
# Generated by Django 5.1.8 on 2025-04-16 17:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_identification", "0015_identificationstage_captcha_stage"),
]
operations = [
migrations.AddField(
model_name="identificationstage",
name="enable_remember_me",
field=models.BooleanField(
default=False,
help_text="Show the user the 'Remember me on this device' toggle, allowing repeat users to skip straight to entering their password.",
),
),
]

View File

@ -76,13 +76,7 @@ class IdentificationStage(Stage):
"is entered."
),
)
enable_remember_me = models.BooleanField(
default=False,
help_text=_(
"Show the user the 'Remember me on this device' toggle, allowing repeat "
"users to skip straight to entering their password."
),
)
enrollment_flow = models.ForeignKey(
Flow,
on_delete=models.SET_DEFAULT,

View File

@ -85,7 +85,6 @@ class IdentificationChallenge(Challenge):
primary_action = CharField()
sources = LoginSourceSerializer(many=True, required=False)
show_source_labels = BooleanField()
enable_remember_me = BooleanField(required=False, default=True)
component = CharField(default="ak-stage-identification")
@ -236,7 +235,6 @@ class IdentificationStageView(ChallengeStageView):
and current_stage.password_stage.allow_show_password,
"show_source_labels": current_stage.show_source_labels,
"flow_designation": self.executor.flow.designation,
"enable_remember_me": current_stage.enable_remember_me,
}
)
# If the user has been redirected to us whilst trying to access an

View File

@ -11893,11 +11893,6 @@
"type": "boolean",
"title": "Pretend user exists",
"description": "When enabled, the stage will succeed and continue even when incorrect user info is entered."
},
"enable_remember_me": {
"type": "boolean",
"title": "Enable remember me",
"description": "Show the user the 'Remember me on this device' toggle, allowing repeat users to skip straight to entering their password."
}
},
"required": []

10
eslint.config.mjs Normal file
View File

@ -0,0 +1,10 @@
import { createESLintPackageConfig } from "@goauthentik/eslint-config";
// @ts-check
/**
* ESLint configuration for authentik's monorepo.
*/
const ESLintConfig = createESLintPackageConfig();
export default ESLintConfig;

2
go.mod
View File

@ -27,7 +27,7 @@ require (
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025024.7
goauthentik.io/api/v3 v3.2025024.6
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.29.0
golang.org/x/sync v0.13.0

4
go.sum
View File

@ -300,8 +300,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025024.7 h1:OOBuyLzv+l5rtvrOYzoDs6Hy9cIfkE5sewRqR5ThSRc=
goauthentik.io/api/v3 v3.2025024.7/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2025024.6 h1:3mmZY7E0EM/RR8uMF17mxa7368ZgZEIq/FjlCLJ9+lA=
goauthentik.io/api/v3 v3.2025024.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@ -1,15 +1,34 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css">
<link rel="prefetch" href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg" />
<style>
<link
rel="shortcut icon"
type="image/png"
href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png"
/>
<link
rel="stylesheet"
type="text/css"
href="/outpost.goauthentik.io/static/dist/patternfly.min.css"
/>
<link
rel="stylesheet"
type="text/css"
href="/outpost.goauthentik.io/static/dist/authentik.css"
/>
<link
rel="prefetch"
href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg"
/>
<style data-test-id="outpost-error-root-styles">
.pf-c-background-image::before {
--ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg");
}
@ -24,13 +43,15 @@
</style>
</head>
<body>
<div class="pf-c-background-image">
</div>
<div class="pf-c-background-image"></div>
<div class="pf-c-login stacked">
<div class="ak-login-container">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik Logo" />
<img
src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg"
alt="authentik Logo"
/>
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
@ -47,9 +68,7 @@
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
<li>
<span>
Powered by authentik
</span>
<span> Powered by authentik </span>
</li>
</ul>
</footer>

View File

@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1010.0",
"aws-cdk": "^2.1007.0",
"cross-env": "^7.0.3"
},
"engines": {
@ -17,9 +17,9 @@
}
},
"node_modules/aws-cdk": {
"version": "2.1010.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1010.0.tgz",
"integrity": "sha512-kYNzBXVUZoRrTuYxRRA2Loz/Uvay0MqHobg8KPZaWylIbw/meUDgtoATRNt+stOdJ9PHODTjWmlDKI+2/KoF+w==",
"version": "2.1007.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1007.0.tgz",
"integrity": "sha512-/UOYOTGWUm+pP9qxg03tID5tL6euC+pb+xo0RBue+xhnUWwj/Bbsw6DbqbpOPMrNzTUxmM723/uMEQmM6S26dw==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@ -1,16 +1,16 @@
{
"name": "@goauthentik/lifecycle-aws",
"version": "0.0.0",
"private": true,
"license": "MIT",
"private": true,
"scripts": {
"aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml"
},
"engines": {
"node": ">=20"
},
"devDependencies": {
"aws-cdk": "^2.1010.0",
"aws-cdk": "^2.1007.0",
"cross-env": "^7.0.3"
},
"engines": {
"node": ">=20.11"
}
}

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-17 00:09+0000\n"
"POT-Creation-Date: 2025-04-15 00:11+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"
@ -2254,14 +2254,6 @@ msgstr ""
msgid "No token received."
msgstr ""
#: authentik/sources/oauth/models.py
msgid "HTTP Basic Authentication"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Include the client ID and secret as request parameters"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Request Token URL"
msgstr ""
@ -2299,11 +2291,6 @@ msgstr ""
msgid "Additional Scopes"
msgstr ""
#: authentik/sources/oauth/models.py
msgid ""
"How to perform authentication during an authorization_code token request flow"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "OAuth Source"
msgstr ""

44519
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,50 @@
{
"name": "@goauthentik/authentik",
"name": "@goauthentik/universe",
"version": "2025.2.4",
"private": true
"description": "Monorepo for authentik.",
"private": true,
"scripts": {
"lint": "run-s lint:prettier:check lint:eslint:check",
"lint:eslint:check": "eslint .",
"lint:eslint:fix": "eslint --fix .",
"lint:fix": "run-s lint:prettier:fix lint:eslint:fix",
"lint:prettier:check": "prettier --cache --check -u .",
"lint:prettier:fix": "prettier --cache --write -u .",
"typecheck": "NODE_OPTIONS=\"--max-old-space-size=3000\" tsc -b"
},
"dependencies": {
"@eslint/js": "^9.11.1",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"eslint": "^9.23.0",
"eslint-plugin-lit": "^2.0.0",
"eslint-plugin-wc": "^3.0.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.5.3",
"prettier-plugin-django-alpine": "^1.3.0",
"prettier-plugin-packagejson": "^2.5.10",
"typescript": "^5.8.2",
"typescript-eslint": "^8.29.0",
"zx": "^8.4.1"
},
"optionalDependencies": {
"@esbuild/darwin-arm64": "^0.24.0",
"@esbuild/linux-amd64": "^0.18.11",
"@esbuild/linux-arm64": "^0.24.0",
"@rollup/rollup-darwin-arm64": "4.23.0",
"@rollup/rollup-linux-arm64-gnu": "4.23.0",
"@rollup/rollup-linux-x64-gnu": "4.23.0"
},
"engines": {
"node": ">=20.11"
},
"workspaces": [
"gen-ts-api",
"web",
"web/packages/*",
"website",
"packages/*"
],
"prettier": "@goauthentik/prettier-config"
}

View File

@ -18,9 +18,7 @@
}
.badge--support-community {
--ifm-badge-background-color: var(
--ifm-color-secondary-contrast-foreground
);
--ifm-badge-background-color: var(--ifm-color-secondary-contrast-foreground);
--ifm-badge-border-color: var(--ifm-color-secondary-dark);
--ifm-badge-color: var(--ifm-color-secondary-contrast-background);
}

View File

@ -1,12 +1,12 @@
:root {
--ifm-font-family-base:
RedHatVF, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell,
Noto Sans, sans-serif, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
RedHatVF, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans,
sans-serif, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--ifm-font-family-monospace:
RedHatMonoVF, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
RedHatMonoVF, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace;
--ifm-heading-font-family: RedHatDisplayVF, var(--ifm-font-family-base);

View File

@ -7,11 +7,7 @@
}
.homepage_hero__subtitle p {
font-size: clamp(
1.125rem,
0.9946rem + 0.6522vi,
1.5rem
); /* Adjust font as page scales */
font-size: clamp(1.125rem, 0.9946rem + 0.6522vi, 1.5rem); /* Adjust font as page scales */
max-width: 28ch; /* Apply a maximum to keep everything in the box */
text-wrap: balance; /* Prevent widows, orphans, and runts. Doesn't work in Safari */
}

View File

@ -122,12 +122,8 @@
@media (min-width: 999px) {
border-inline-start: 1px solid var(--ifm-hover-overlay);
margin-inline-start: calc(
var(--ifm-navbar-item-padding-horizontal) / 2
);
padding-inline-start: calc(
var(--ifm-navbar-item-padding-horizontal) / 2
);
margin-inline-start: calc(var(--ifm-navbar-item-padding-horizontal) / 2);
padding-inline-start: calc(var(--ifm-navbar-item-padding-horizontal) / 2);
}
}
@ -151,19 +147,14 @@
hsl(236.84deg 34.55% 10.78%)
);
--docsearch-key-shadow:
inset 0 -2px 0 0 hsl(233.33deg 36% 24.51%),
inset 0 0 1px 1px hsl(232.11deg 34.86% 57.25%),
inset 0 -2px 0 0 hsl(233.33deg 36% 24.51%), inset 0 0 1px 1px hsl(232.11deg 34.86% 57.25%),
0 2px 2px 0 rgba(3, 4, 9, 0.3);
--docsearch-key-pressed-shadow:
inset 0 -2px 0 0 #282d55,
inset 0 0 1px 1px hsl(231.82deg 21.36% 40.39%),
inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px hsl(231.82deg 21.36% 40.39%),
0 1px 1px 0 hsl(230deg 50% 2.35% / 30.2%);
padding: var(--ifm-navbar-item-padding-vertical)
var(--ifm-navbar-item-padding-horizontal) !important;
padding-inline-end: calc(
var(--ifm-navbar-item-padding-horizontal) * 1.25
) !important;
padding: var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal) !important;
padding-inline-end: calc(var(--ifm-navbar-item-padding-horizontal) * 1.25) !important;
.DocSearch-Button-Placeholder {
font-family: var(--ifm-heading-font-family);

View File

@ -4,8 +4,8 @@
* @import { Config as DocusaurusConfig } from "@docusaurus/types"
* @import { UserThemeConfig } from "./theme.js"
*/
import { deepmerge } from "deepmerge-ts";
import { createThemeConfig } from "./theme.js";
//#region Types

View File

@ -4,7 +4,6 @@
* @import { UserThemeConfig as UserThemeConfigCommon } from "@docusaurus/theme-common";
* @import { UserThemeConfig as UserThemeConfigAlgolia } from "@docusaurus/theme-search-algolia";
*/
import { deepmerge } from "deepmerge-ts";
import { themes as prismThemes } from "prism-react-renderer";

View File

@ -1,13 +1,16 @@
/**
* @file
* Client-side observer for ESBuild events.
* @file Client-side observer for ESBuild events.
*
* @import { Message as ESBuildMessage } from "esbuild";
*/
import type { Message as ESBuildMessage } from "esbuild";
const logPrefix = "👷 [ESBuild]";
const log = console.debug.bind(console, logPrefix);
type BuildEventListener<Data = unknown> = (event: MessageEvent<Data>) => void;
/**
* @template {unknown} [Data=unknown]
* @typedef {(event: MessageEvent) => void} BuildEventListener
*/
/**
* A client-side watcher for ESBuild.
@ -17,13 +20,11 @@ type BuildEventListener<Data = unknown> = (event: MessageEvent<Data>) => void;
*
* ```ts
* if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
* const { ESBuildObserver } = await import("@goauthentik/common/client");
* const { ESBuildObserver } = await import("@goauthentik/esbuild-plugin-live-reload/client");
*
* new ESBuildObserver(process.env.WATCHER_URL);
* ESBuildObserver.initialize(process.env.WATCHER_URL);
* }
* ```
}
*/
export class ESBuildObserver extends EventSource {
/**
@ -58,15 +59,19 @@ export class ESBuildObserver extends EventSource {
/**
* The interval for the keep-alive check.
* @type {ReturnType<typeof setInterval> | undefined}
*/
#keepAliveInterval: ReturnType<typeof setInterval> | undefined;
#keepAliveInterval;
#trackActivity = () => {
this.lastUpdatedAt = Date.now();
this.alive = true;
};
#startListener: BuildEventListener = () => {
/**
* @type {BuildEventListener}
*/
#startListener = () => {
this.#trackActivity();
log("⏰ Build started...");
};
@ -82,13 +87,18 @@ export class ESBuildObserver extends EventSource {
}
};
#errorListener: BuildEventListener<string> = (event) => {
/**
* @type {BuildEventListener<string>}
*/
#errorListener = (event) => {
this.#trackActivity();
// eslint-disable-next-line no-console
console.group(logPrefix, "⛔️⛔️⛔️ Build error...");
const esbuildErrorMessages: ESBuildMessage[] = JSON.parse(event.data);
/**
* @type {ESBuildMessage[]}
*/
const esbuildErrorMessages = JSON.parse(event.data);
for (const error of esbuildErrorMessages) {
console.warn(error.text);
@ -101,11 +111,13 @@ export class ESBuildObserver extends EventSource {
}
}
// eslint-disable-next-line no-console
console.groupEnd();
};
#endListener: BuildEventListener = () => {
/**
* @type {BuildEventListener}
*/
#endListener = () => {
cancelAnimationFrame(this.#reloadFrameID);
this.#trackActivity();
@ -126,12 +138,32 @@ export class ESBuildObserver extends EventSource {
});
};
#keepAliveListener: BuildEventListener = () => {
/**
* @type {BuildEventListener}
*/
#keepAliveListener = () => {
this.#trackActivity();
log("🏓 Keep-alive");
};
constructor(url: string | URL) {
/**
* Initialize the ESBuild observer.
* This should be called once in your application.
*
* @param {string | URL} url
* @returns {ESBuildObserver}
*/
static initialize(url) {
const esbuildObserver = new ESBuildObserver(url);
return esbuildObserver;
}
/**
*
* @param {string | URL} url
*/
constructor(url) {
super(url);
this.addEventListener("esbuild:start", this.#startListener);

View File

@ -0,0 +1,2 @@
export * from "./client/index.js";
export * from "./plugin/index.js";

View File

@ -0,0 +1,46 @@
{
"name": "@goauthentik/esbuild-plugin-live-reload",
"version": "1.0.4",
"description": "ESBuild plugin to watch for file changes and trigger client-side reloads.",
"license": "MIT",
"private": true,
"main": "index.js",
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./index.js",
"types": "./out/index.d.ts"
},
"./client": {
"import": "./client/index.js",
"types": "./out/client/index.d.ts"
},
"./plugin": {
"import": "./plugin/index.js",
"types": "./out/plugin/index.d.ts"
}
},
"devDependencies": {
"@types/node": "^22.14.1",
"esbuild": "^0.25.0",
"typescript": "^5.6.2"
},
"peerDependencies": {
"esbuild": "^0.25.0"
},
"engines": {
"node": ">=20.11"
},
"types": "./out/index.d.ts",
"files": [
"./index.js",
"client/**/*",
"plugin/**/*",
"out/**/*"
],
"prettier": "@goauthentik/prettier-config",
"publishConfig": {
"access": "public"
}
}

View File

@ -1,5 +1,8 @@
import * as http from "http";
import path from "path";
/**
* @file Live reload plugin for ESBuild.
*/
import * as http from "node:http";
import * as path from "node:path";
/**
* Serializes a custom event to a text stream.
@ -22,7 +25,7 @@ export function serializeCustomEventToStream(event) {
* @typedef {Object} BuildObserverOptions
*
* @property {URL} serverURL
* @property {string} logPrefix
* @property {string} [logPrefix]
* @property {string} relativeRoot
*/
@ -32,8 +35,11 @@ export function serializeCustomEventToStream(event) {
* @param {BuildObserverOptions} options
* @returns {import('esbuild').Plugin}
*/
export function buildObserverPlugin({ serverURL, logPrefix, relativeRoot }) {
const timerLabel = `[${logPrefix}] Build`;
export function liveReloadPlugin({ serverURL, logPrefix = "Build Observer", relativeRoot }) {
const timerLabel = `[${logPrefix}] 🏁`;
// eslint-disable-next-line no-console
const log = console.log.bind(console, `[${logPrefix}]`);
const endpoint = serverURL.pathname;
const dispatcher = new EventTarget();
@ -43,18 +49,18 @@ export function buildObserverPlugin({ serverURL, logPrefix, relativeRoot }) {
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.url !== endpoint) {
console.log(`🚫 Invalid request to ${req.url}`);
log(`🚫 Invalid request to ${req.url}`);
res.writeHead(404);
res.end();
return;
}
console.log("🔌 Client connected");
log("🔌 Client connected");
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
Connection: "keep-alive",
});
/**
@ -71,7 +77,7 @@ export function buildObserverPlugin({ serverURL, logPrefix, relativeRoot }) {
dispatcher.addEventListener("esbuild:end", listener);
req.on("close", () => {
console.log("🔌 Client disconnected");
log("🔌 Client disconnected");
clearInterval(keepAliveInterval);

View File

@ -0,0 +1,10 @@
{
"extends": "@goauthentik/tsconfig",
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"resolveJsonModule": true,
"baseUrl": ".",
"checkJs": true,
"emitDeclarationOnly": true
}
}

View File

@ -23,8 +23,9 @@ export const DefaultIgnorePatterns = [
"**/out",
"**/dist",
"**/.wireit",
"**/.venv",
"website/build/**",
"website/.docusaurus/**",
"**/.docusaurus/**",
"**/node_modules",
"**/coverage",
"**/storybook-static",

View File

@ -1,6 +1,8 @@
// @ts-check
import tseslint from "typescript-eslint";
import NodeLintPlugin from "../plugins/node-lint.js";
const MAX_DEPTH = 4;
const MAX_NESTED_CALLBACKS = 4;
const MAX_PARAMS = 5;
@ -9,22 +11,29 @@ const MAX_PARAMS = 5;
* ESLint configuration for JavaScript authentik projects.
*/
export const javaScriptConfig = tseslint.config({
plugins: {
"node-lint": NodeLintPlugin,
},
files: ["**/*.{js,jsx,mjs,cjs}"],
rules: {
"node-lint/no-unprefixed-imports": "warn",
// TODO: Clean up before enabling.
"accessor-pairs": "off",
"array-callback-return": "error",
"block-scoped-var": "error",
"consistent-return": ["error", { treatUndefinedAsUnspecified: false }],
"consistent-return": "off",
// "consistent-return": ["error", { treatUndefinedAsUnspecified: false }],
"consistent-this": ["error", "that"],
"curly": "off",
"dot-notation": [
"error",
{
allowKeywords: true,
},
],
"eqeqeq": "error",
"func-names": ["error", "as-needed"],
curly: "off",
// "dot-notation": [
// "error",
// {
// allowKeywords: true,
// },
// ],
// "eqeqeq": "error",
eqeqeq: "off",
// "func-names": ["error", "as-needed"],
"guard-for-in": "error",
"max-depth": ["error", MAX_DEPTH],
"max-nested-callbacks": ["error", MAX_NESTED_CALLBACKS],
@ -33,13 +42,13 @@ export const javaScriptConfig = tseslint.config({
// "new-cap": "error",
"no-alert": "error",
"no-array-constructor": "error",
"no-bitwise": [
"error",
{
allow: ["~"],
int32Hint: true,
},
],
// "no-bitwise": [
// "error",
// {
// allow: ["~"],
// int32Hint: true,
// },
// ],
"no-caller": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
@ -53,10 +62,10 @@ export const javaScriptConfig = tseslint.config({
"no-dupe-args": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-else-return": "error",
// "no-else-return": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-function": ["error", { allow: ["constructors"] }],
// "no-empty-function": ["error", { allow: ["constructors"] }],
"no-labels": "error",
"no-eq-null": "error",
"no-eval": "error",
@ -68,7 +77,7 @@ export const javaScriptConfig = tseslint.config({
"no-fallthrough": "error",
"no-func-assign": "error",
"no-implied-eval": "error",
"no-implicit-coercion": "error",
"no-implicit-coercion": ["error", { boolean: true, allow: ["!!"] }],
"no-implicit-globals": "error",
"no-inner-declarations": ["error", "functions"],
"no-invalid-regexp": "error",
@ -76,18 +85,18 @@ export const javaScriptConfig = tseslint.config({
"no-iterator": "error",
"no-label-var": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
// "no-lonely-if": "error",
"no-loop-func": "error",
"no-multi-str": "error",
// TODO: Clean up before enabling.
"no-negated-condition": "off",
"no-new": "error",
"no-new": "off",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-obj-calls": "error",
"no-octal": "error",
"no-octal-escape": "error",
"no-param-reassign": ["error", { props: false }],
// "no-param-reassign": ["error", { props: false }],
"no-proto": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
@ -99,39 +108,58 @@ export const javaScriptConfig = tseslint.config({
// TODO: Clean up before enabling.
// "no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
// TODO: Clean up before enabling.
// "no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-throw-literal": "error",
// "no-throw-literal": "error",
"no-trailing-spaces": "off", // Handled by Prettier.
"no-undef": "off",
"no-undef-init": "off",
"no-unexpected-multiline": "error",
"no-useless-constructor": "error",
// "no-useless-constructor": "error",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unreachable": "error",
"no-unused-expressions": "error",
"no-unused-labels": "error",
"no-use-before-define": "error",
// "no-use-before-define": "error",
"no-useless-call": "error",
"no-dupe-class-members": "error",
"no-var": "error",
"no-void": "error",
"no-with": "error",
"prefer-arrow-callback": "error",
"prefer-const": "error",
// "prefer-arrow-callback": "error",
"prefer-const": "warn",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"radix": "error",
"prefer-template": "off",
radix: "error",
"require-yield": "error",
"strict": ["error", "global"],
strict: ["error", "global"],
"use-isnan": "error",
"valid-typeof": "error",
"vars-on-top": "error",
"yoda": ["error", "never"],
yoda: ["error", "never"],
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
"no-console": [
"warn",
{
allow: [
//---
"debug",
"warn",
"error",
"group",
"groupCollapsed",
"groupEnd",
"table",
"trace",
"time",
"timeEnd",
"timeStamp",
],
},
],
// SonarJS is not yet compatible with ESLint 9. Commenting these out
// until it is.
// "sonarjs/cognitive-complexity": ["off", MAX_COGNITIVE_COMPLEXITY],

View File

@ -13,7 +13,7 @@ export const reactConfig = tseslint.config({
},
plugins: {
"react": reactPlugin,
react: reactPlugin,
"react-hooks": hooksPlugin,
},

View File

@ -1,25 +1,42 @@
// @ts-check
import tseslint from "typescript-eslint";
import NodeLintPlugin from "../plugins/node-lint.js";
/**
* ESLint configuration for TypeScript authentik projects.
*/
export const typescriptConfig = tseslint.config({
plugins: {
"node-lint": NodeLintPlugin,
},
rules: {
"@typescript-eslint/ban-ts-comment": [
"error",
{
"ts-expect-error": "allow-with-description",
"ts-ignore": true,
"ts-nocheck": "allow-with-description",
"ts-check": false,
"minimumDescriptionLength": 5,
},
],
"@typescript-eslint/ban-ts-comment": "off",
// "@typescript-eslint/ban-ts-comment": [
// "error",
// {
// "ts-expect-error": "allow-with-description",
// "ts-ignore": true,
// "ts-nocheck": "allow-with-description",
// "ts-check": false,
// "minimumDescriptionLength": 5,
// },
// ],
"@typescript-eslint/no-explicit-any": "warn",
"no-unused-private-class-members": "warn",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "error",
// "@typescript-eslint/no-use-before-define": "error",
"no-invalid-this": "off",
"no-unused-vars": "off",
"@typescript-eslint/triple-slash-reference": [
"warn",
{
path: "never",
types: "always",
lib: "always",
},
],
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-unused-vars": [
"warn",

View File

@ -6,26 +6,9 @@
"scripts": {
"build": "tsc -p ."
},
"main": "./index.js",
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./index.js",
"types": "./out/index.d.ts"
},
"./react-config": {
"import": "./lib/react-config.js",
"types": "./out/lib/react-config.d.ts"
},
"./javascript-config": {
"import": "./lib/javascript-config.js",
"types": "./out/lib/javascript-config.d.ts"
},
"./typescript-config": {
"import": "./lib/typescript-config.js",
"types": "./out/lib/typescript-config.d.ts"
}
},
"exports": "./index.js",
"dependencies": {
"eslint": "^9.23.0",
"eslint-plugin-import": "^2.31.0",
@ -35,8 +18,8 @@
"eslint-plugin-wc": "^2.1.1"
},
"devDependencies": {
"@goauthentik/prettier-config": "^1.0.1",
"@goauthentik/tsconfig": "^1.0.1",
"@goauthentik/prettier-config": "^1.0.4",
"@goauthentik/tsconfig": "^1.0.4",
"@types/eslint": "^9.6.1",
"typescript": "^5.8.2",
"typescript-eslint": "^8.29.0"

View File

@ -0,0 +1,85 @@
/**
* @file ESLint rule to enforce the use of the `node:` prefix for Node.js built-in modules.
*
* @import { Rule, ESLint } from "eslint";
* @import { RuleTextEditor } from "@eslint/core";
*/
import { builtinModules } from "node:module";
const NODE_PROTOCOL_PREFIX = "node:";
/**
* @type {Map<string, string>}
*/
const NodeModulesIndex = new Map();
for (const moduleName of builtinModules) {
if (moduleName.startsWith("_")) continue;
if (moduleName.startsWith(NODE_PROTOCOL_PREFIX)) continue;
NodeModulesIndex.set(moduleName, `${NODE_PROTOCOL_PREFIX}${moduleName}`);
}
/**
* @type {Rule.RuleModule}
*/
const rule = {
meta: {
type: "problem",
fixable: "code",
hasSuggestions: true,
docs: {
description: "Enforce `node:` prefix for Node.js built-in modules.",
recommended: true,
},
},
create: (context) => {
/**
* @type {Rule.RuleListener}
*/
const ruleListener = {
ImportDeclaration({ source }) {
if (source.type !== "Literal" || typeof source.value !== "string") {
return;
}
const moduleName = source.value;
const prefixedModuleName = NodeModulesIndex.get(moduleName);
if (!prefixedModuleName) return;
/**
* @param {RuleTextEditor} editor
*/
const fix = (editor) =>
editor.replaceText(source, JSON.stringify(prefixedModuleName));
context.report({
node: source,
message: `Module "${moduleName}" must use the "node:" prefix.`,
fix,
suggest: [
{
fix,
desc: `Use "${prefixedModuleName}" instead.`,
},
],
});
},
};
return ruleListener;
},
};
/**
* @type {ESLint.Plugin}
*/
const NodeLintPlugin = {
rules: {
"no-unprefixed-imports": rule,
},
};
export default NodeLintPlugin;

View File

@ -0,0 +1,25 @@
/**
* @file Utility functions for building and copying files.
*/
/**
* Given an object of environment variables, returns a new object with the same keys and values, but
* with the values serialized as strings.
* @template {string} K
*
* @param {Record<K, string | number | boolean | object>} input
* @returns {Record<`process.env.${K}`, string>}
*/
export function serializeEnvironmentVars(input) {
/**
* @type {Record<string, string>}
*/
const env = {};
for (const [key, value] of Object.entries(input)) {
const namespaceKey = `process.env.${key}`;
env[namespaceKey] = JSON.stringify(value || "");
}
return /** @type {Record<string, string>} */ (env);
}

View File

@ -1,4 +1,5 @@
export * from "./paths.js";
export * from "./constants.js";
export * from "./build.js";
export * from "./version.js";
export * from "./scripting.js";

View File

@ -1,19 +1,14 @@
{
"name": "@goauthentik/monorepo",
"version": "1.0.0",
"version": "1.0.4",
"description": "Utilities for the authentik monorepo.",
"private": true,
"license": "MIT",
"private": true,
"main": "index.js",
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./index.js",
"types": "./out/index.d.ts"
}
},
"types": "./out/index.d.ts",
"exports": "./index.js",
"engines": {
"node": ">=20.11"
}
},
"types": "./out/index.d.ts"
}

View File

@ -2,7 +2,7 @@ import { createRequire } from "node:module";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const relativeDirname = dirname(fileURLToPath(import.meta.url));
/**
* @typedef {'~authentik'} MonoRepoRoot
@ -11,7 +11,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
/**
* The root of the authentik monorepo.
*/
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (resolve(__dirname, "..", ".."));
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (resolve(relativeDirname, "..", ".."));
const require = createRequire(import.meta.url);

View File

@ -23,7 +23,7 @@ export const AuthentikPrettierConfig = {
jsxSingleQuote: false,
printWidth: 100,
proseWrap: "preserve",
quoteProps: "consistent",
quoteProps: "as-needed",
requirePragma: false,
semi: true,
singleQuote: false,
@ -31,8 +31,23 @@ export const AuthentikPrettierConfig = {
trailingComma: "all",
useTabs: false,
vueIndentScriptAndStyle: false,
plugins: ["prettier-plugin-packagejson", "@trivago/prettier-plugin-sort-imports"],
importOrder: ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
plugins: [
// ---
"prettier-plugin-packagejson",
"@trivago/prettier-plugin-sort-imports",
"prettier-plugin-django-alpine",
],
importOrder: [
// ---
// Lit Imports
"^(@?)lit(.*)$",
// CSS Imports
"\\.css$",
// API Imports
"^@goauthentik/api$",
// Relative Imports
"^[./]",
],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
importOrderParserPlugins: ["typescript", "jsx", "classProperties", "decorators-legacy"],
@ -49,6 +64,13 @@ export const AuthentikPrettierConfig = {
trailingComma: "none",
},
},
{
files: "authentik/**/*.html",
options: {
tabWidth: 2,
parser: "html",
},
},
{
files: "package.json",
options: {

View File

@ -6,6 +6,7 @@
"scripts": {
"build": "tsc -p ."
},
"main": "./index.js",
"type": "module",
"exports": {
"./package.json": "./package.json",
@ -15,17 +16,17 @@
}
},
"devDependencies": {
"@goauthentik/tsconfig": "^1.0.1",
"@goauthentik/tsconfig": "^1.0.4",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-django-alpine": "^1.3.0",
"prettier-plugin-packagejson": "^2.5.10",
"typescript": "^5.8.2"
},
"peerDependencies": {
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-django-alpine": "^1.3.0",
"prettier-plugin-packagejson": "^2.5.10"
},
"engines": {

View File

@ -2,19 +2,16 @@
"name": "@goauthentik/tsconfig",
"version": "1.0.4",
"description": "authentik's base TypeScript configuration.",
"license": "MIT",
"main": "tsconfig.json",
"type": "module",
"engines": {
"node": ">=20.11"
},
"keywords": [
"tsconfig",
"typescript"
],
"license": "MIT",
"scripts": {
"build": ""
},
"type": "module",
"main": "tsconfig.json",
"engines": {
"node": ">=20.11"
},
"publishConfig": {
"access": "public"
}

View File

@ -6,15 +6,14 @@ FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS web-builder
ENV NODE_ENV=production
WORKDIR /static
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 \
npm ci --include=dev
COPY ./package.json ./package.json
COPY ./package-lock.json ./package-lock.json
COPY ./packages ./packages
COPY ./web ./web
RUN --mount=type=cache,target=/root/.npm npm ci --include=dev
RUN npm run build-proxy -w @goauthentik/web
COPY web .
RUN npm run build-proxy
# Stage 2: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS builder
@ -65,10 +64,10 @@ RUN apt-get update && \
rm -rf /tmp/* /var/lib/apt/lists/*
COPY --from=builder /go/proxy /
COPY --from=web-builder /static/robots.txt /web/robots.txt
COPY --from=web-builder /static/security.txt /web/security.txt
COPY --from=web-builder /static/dist/ /web/dist/
COPY --from=web-builder /static/authentik/ /web/authentik/
COPY --from=web-builder /static/web/robots.txt /web/robots.txt
COPY --from=web-builder /static/web/security.txt /web/security.txt
COPY --from=web-builder /static/web/dist/ /web/dist/
COPY --from=web-builder /static/web/authentik/ /web/authentik/
HEALTHCHECK --interval=5s --retries=20 --start-period=3s CMD [ "/proxy", "healthcheck" ]

View File

@ -46049,9 +46049,6 @@ components:
$ref: '#/components/schemas/LoginSource'
show_source_labels:
type: boolean
enable_remember_me:
type: boolean
default: true
required:
- flow_designation
- password_fields
@ -46164,10 +46161,6 @@ components:
type: boolean
description: When enabled, the stage will succeed and continue even when
incorrect user info is entered.
enable_remember_me:
type: boolean
description: Show the user the 'Remember me on this device' toggle, allowing
repeat users to skip straight to entering their password.
required:
- component
- meta_model_name
@ -46242,10 +46235,6 @@ components:
type: boolean
description: When enabled, the stage will succeed and continue even when
incorrect user info is entered.
enable_remember_me:
type: boolean
description: Show the user the 'Remember me on this device' toggle, allowing
repeat users to skip straight to entering their password.
required:
- name
ImpersonationRequest:
@ -52301,10 +52290,6 @@ components:
type: boolean
description: When enabled, the stage will succeed and continue even when
incorrect user info is entered.
enable_remember_me:
type: boolean
description: Show the user the 'Remember me on this device' toggle, allowing
repeat users to skip straight to entering their password.
PatchedInitialPermissionsRequest:
type: object
description: InitialPermissions serializer

27
tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
// TypeScript Project Configuration
{
"watchOptions": {
"excludeDirectories": [
"**/.git", // Git
"**/.yarn", // Yarn
"**/.vscode", // VS Code
"**/.vscode-test-web", // VS Code Web Test
"**/dist", // Distributed build files
"**/out", // Output build files
"**/.drafts", // Drafts
"**/.github", // GitHub
"**/node_modules" // Node modules
]
},
// The root project has no sources of its own. By setting `files` to an empty
// list, TS won't automatically include all sources below root (the default).
"files": [],
"references": [
// Note that references are in the order we want them to be built.
{ "path": "./packages/prettier-config" },
{ "path": "./packages/eslint-config" },
{ "path": "./packages/esbuild-plugin-live-reload" },
{ "path": "./web" }
]
}

View File

@ -1,16 +0,0 @@
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
# don't lint nyc coverage output
coverage
# Import order matters
poly.ts
src/locale-codes.ts
src/locales/
storybook-static/
# Prettier breaks the tsconfig file
tsconfig.json
.storybook/css-import-maps*
package.json
packages/**/package.json

View File

@ -1,23 +0,0 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "consistent",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 4,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"importOrderParserPlugins": ["typescript", "jsx", "classProperties", "decorators-legacy"]
}

View File

@ -1,14 +1,15 @@
import replace from "@rollup/plugin-replace";
import { NodeEnvironment, resolvePackage, serializeEnvironmentVars } from "@goauthentik/monorepo";
import { PackageRoot } from "@goauthentik/web/paths";
import type { StorybookConfig } from "@storybook/web-components-vite";
import { cwd } from "process";
import { deepmerge } from "deepmerge-ts";
import * as path from "node:path";
import modify from "rollup-plugin-modify";
import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths";
export const isProdBuild = process.env.NODE_ENV === "production";
export const apiBasePath = process.env.AK_API_BASE_PATH || "";
const AK_API_BASE_PATH = process.env.AK_API_BASE_PATH || "";
const importInlinePatterns = [
const inlineImportPatterns = [
'import AKGlobal from "(\\.\\./)*common/styles/authentik\\.css',
'import AKGlobal from "@goauthentik/common/styles/authentik\\.css',
'import PF.+ from "@patternfly/patternfly/\\S+\\.css',
@ -17,7 +18,9 @@ const importInlinePatterns = [
'import styles from "\\./LibraryPageImpl\\.css',
];
const importInlineRegexp = new RegExp(importInlinePatterns.map((a) => `(${a})`).join("|"));
const inlineImportPattern = new RegExp(inlineImportPatterns.map((a) => `(${a})`).join("|"));
const patternflyPath = resolvePackage("@patternfly/patternfly");
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
@ -29,19 +32,19 @@ const config: StorybookConfig = {
],
staticDirs: [
{
from: "../node_modules/@patternfly/patternfly/patternfly-base.css",
from: path.resolve(patternflyPath, "patternfly-base.css"),
to: "@patternfly/patternfly/patternfly-base.css",
},
{
from: "../src/common/styles/authentik.css",
from: path.resolve(PackageRoot, "src", "common", "styles", "authentik.css"),
to: "@goauthentik/common/styles/authentik.css",
},
{
from: "../src/common/styles/theme-dark.css",
from: path.resolve(PackageRoot, "src", "common", "styles", "theme-dark.css"),
to: "@goauthentik/common/styles/theme-dark.css",
},
{
from: "../src/common/styles/one-dark.css",
from: path.resolve(PackageRoot, "src", "common", "styles", "one-dark.css"),
to: "@goauthentik/common/styles/one-dark.css",
},
],
@ -53,28 +56,25 @@ const config: StorybookConfig = {
autodocs: "tag",
},
async viteFinal(config) {
return {
...config,
return deepmerge(config, {
define: serializeEnvironmentVars({
NODE_ENV: NodeEnvironment,
CWD: process.cwd(),
AK_API_BASE_PATH: AK_API_BASE_PATH,
WATCHER_URL: "",
}),
plugins: [
modify({
find: importInlineRegexp,
find: inlineImportPattern,
replace: (match: RegExpMatchArray) => {
return `${match}?inline`;
},
}),
replace({
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development",
),
"process.env.CWD": JSON.stringify(cwd()),
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
"preventAssignment": true,
}),
...config.plugins,
postcssLit(),
tsconfigPaths(),
],
};
});
},
};

View File

@ -1,5 +1,6 @@
<link rel="stylesheet" href="@patternfly/patternfly/patternfly-base.css" />
<link rel="stylesheet" href="@goauthentik/common/styles/authentik.css" />
<style>
body {
overflow-y: scroll;

View File

@ -16,16 +16,16 @@ three contexts in which to run.
The three contexts corresponds to objects in the API's `model` section, so let's use those names.
- The root `Config`. The root configuration object of the server, containing mostly caching and
error reporting information. This is misleading, however; the `Config` object contains some user
information, specifically a list of permissions the current user (or "no user") has.
- The root `CurrentTenant`. This describes the `Brand` information UIs should use, such as themes,
logos, favicon, and specific default flows for logging in, logging out, and recovering a user
password.
- The current `SessionUser`, the person logged in: username, display name, and various states.
(Note: the authentik server permits administrators to "impersonate" any other user in order to
debug their authentikation experience. If impersonation is active, the `user` field reflects that
user, but it also includes a field, `original`, with the administrator's information.)
- The root `Config`. The root configuration object of the server, containing mostly caching and
error reporting information. This is misleading, however; the `Config` object contains some user
information, specifically a list of permissions the current user (or "no user") has.
- The root `CurrentTenant`. This describes the `Brand` information UIs should use, such as themes,
logos, favicon, and specific default flows for logging in, logging out, and recovering a user
password.
- The current `SessionUser`, the person logged in: username, display name, and various states.
(Note: the authentik server permits administrators to "impersonate" any other user in order to
debug their authentikation experience. If impersonation is active, the `user` field reflects that
user, but it also includes a field, `original`, with the administrator's information.)
(There is a fourth context object, Version, but its use is limited to displaying version information
and checking for upgrades. Just be aware that you will see it, but you will probably never interact
@ -36,55 +36,55 @@ insides are provided by third-party libraries (Patternfly and Rapidoc, respectiv
three are actual applications. The descriptions below are wholly from the view of the user's
experience:
- `Flow`: From a given URL, displays a form that requests information from the user to accomplish a
task. Some tasks require the user to be logged in, but many (such as logging in itself!)
obviously do not.
- `User`: Provides the user with access to the applications they can access, plus a few user
settings.
- `Admin`: Provides someone with super-user permissions access to the administrative functions of
the authentik server.
- `Flow`: From a given URL, displays a form that requests information from the user to accomplish a
task. Some tasks require the user to be logged in, but many (such as logging in itself!)
obviously do not.
- `User`: Provides the user with access to the applications they can access, plus a few user
settings.
- `Admin`: Provides someone with super-user permissions access to the administrative functions of
the authentik server.
**Mental Model**
- Upon initialization, _every_ authentik UI application fetches `Config` and `CurrentTenant`. `User`
and `Admin` will also attempt to load the `SessionUser`; if there is none, the user is kicked out
to the `Flow` for logging into authentik itself.
- `Config`, `CurrentTenant`, and `SessionUser`, are provided by the `@goauthentik/api` application,
not by the codebase under `./web`. (Where you are now).
- `Flow`, `User`, and `Admin` are all called `Interfaces` and are found in
`./web/src/flow/FlowInterface`, `./web/src/user/UserInterface`, `./web/src/admin/AdminInterface`,
respectively.
- Upon initialization, _every_ authentik UI application fetches `Config` and `CurrentTenant`. `User`
and `Admin` will also attempt to load the `SessionUser`; if there is none, the user is kicked out
to the `Flow` for logging into authentik itself.
- `Config`, `CurrentTenant`, and `SessionUser`, are provided by the `@goauthentik/api` application,
not by the codebase under `./web`. (Where you are now).
- `Flow`, `User`, and `Admin` are all called `Interfaces` and are found in
`./web/src/flow/FlowInterface`, `./web/src/user/UserInterface`, `./web/src/admin/AdminInterface`,
respectively.
Inside each of these you will find, in a hierarchal order:
- The context layer described above
- A theme managing layer
- The orchestration layer:
- web socket handler for server-generated events
- The router
- Individual routes for each vertical slice and its relationship to other objects:
- The context layer described above
- A theme managing layer
- The orchestration layer:
- web socket handler for server-generated events
- The router
- Individual routes for each vertical slice and its relationship to other objects:
Each slice corresponds to an object table on the server, and each slice _usually_ consists of the
following:
- A paginated collection display, usually using the `Table` foundation (found in
`./web/src/elements/Table`)
- The ability to view an individual object from the collection, which you may be able to:
- Edit
- Delete
- A form for creating a new object
- Tabs showing that object's relationship to other objects
- Interactive elements for changing or deleting those relationships, or creating new ones.
- The ability to create new objects with which to have that relationship, if they're not part of
the core objects (such as User->MFA authenticator apps, since the latter is not a "core" object
and has no tab of its own).
- A paginated collection display, usually using the `Table` foundation (found in
`./web/src/elements/Table`)
- The ability to view an individual object from the collection, which you may be able to:
- Edit
- Delete
- A form for creating a new object
- Tabs showing that object's relationship to other objects
- Interactive elements for changing or deleting those relationships, or creating new ones.
- The ability to create new objects with which to have that relationship, if they're not part of
the core objects (such as User->MFA authenticator apps, since the latter is not a "core" object
and has no tab of its own).
We are still a bit "all over the place" with respect to sub-units and common units; there are
folders `common`, `elements`, and `components`, and ideally they would be:
- `common`: non-UI related libraries all of our applications need
- `elements`: UI elements shared among multiple applications that do not need context
- `components`: UI elements shared among multiple that use one or more context
- `common`: non-UI related libraries all of our applications need
- `elements`: UI elements shared among multiple applications that do not need context
- `components`: UI elements shared among multiple that use one or more context
... but at the moment there are some context-sensitive elements, and some UI-related stuff in
`common`.
@ -95,18 +95,18 @@ folders `common`, `elements`, and `components`, and ideally they would be:
reliably documented any other way. For the most part, they contain comments related to custom
settings in JSON files, which do not support comments.
- `tsconfig.json`:
- `compilerOptions.useDefineForClassFields: false` is required to make TSC use the "classic" form
of field definition when compiling class definitions. Storybook does not handle the ESNext
proposed definition mechanism (yet).
- `compilerOptions.plugins.ts-lit-plugin.rules.no-unknown-tag-name: "off"`: required to support
rapidoc, which exports its tag late.
- `compilerOptions.plugins.ts-lit-plugin.rules.no-missing-import: "off"`: lit-analyzer currently
does not support path aliases very well, and cannot find the definition files associated with
imports using them.
- `compilerOptions.plugins.ts-lit-plugin.rules.no-incompatible-type-binding: "warn"`: lit-analyzer
does not support generics well when parsing a subtype of `HTMLElement`. As a result, this threw
too many errors to be supportable.
- `tsconfig.json`:
- `compilerOptions.useDefineForClassFields: false` is required to make TSC use the "classic" form
of field definition when compiling class definitions. Storybook does not handle the ESNext
proposed definition mechanism (yet).
- `compilerOptions.plugins.ts-lit-plugin.rules.no-unknown-tag-name: "off"`: required to support
rapidoc, which exports its tag late.
- `compilerOptions.plugins.ts-lit-plugin.rules.no-missing-import: "off"`: lit-analyzer currently
does not support path aliases very well, and cannot find the definition files associated with
imports using them.
- `compilerOptions.plugins.ts-lit-plugin.rules.no-incompatible-type-binding: "warn"`: lit-analyzer
does not support generics well when parsing a subtype of `HTMLElement`. As a result, this threw
too many errors to be supportable.
### License

View File

@ -1,90 +0,0 @@
import eslint from "@eslint/js";
import tsparser from "@typescript-eslint/parser";
import litconf from "eslint-plugin-lit";
import wcconf from "eslint-plugin-wc";
import globals from "globals";
import tseslint from "typescript-eslint";
export default [
// You would not believe how much this change has frustrated users: ["if an ignores key is used
// without any other keys in the configuration object, then the patterns act as global
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
{
ignores: [
"dist/",
// don't lint the cache
".wireit/",
// let packages have their own configurations
"packages/",
// don't ever lint node_modules
"node_modules/",
".storybook/*",
// don't lint build output (make sure it's set to your correct build folder name)
// don't lint nyc coverage output
"coverage/",
"src/locale-codes.ts",
"storybook-static/",
"src/locales/",
],
},
eslint.configs.recommended,
wcconf.configs["flat/recommended"],
litconf.configs["flat/recommended"],
...tseslint.configs.recommended,
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
},
files: ["src/**"],
rules: {
"lit/attribute-names": "off",
// "lit/attribute-names": "error",
"lit/no-private-properties": "error",
// "lit/prefer-nothing": "warn",
"lit/no-template-bind": "error",
"no-unused-vars": "off",
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
...globals.node,
},
},
files: ["scripts/**/*.mjs", "*.ts", "*.mjs"],
rules: {
"no-unused-vars": "off",
// We WANT our scripts to output to the console!
"no-console": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
];

24683
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,40 @@
{
"name": "@goauthentik/web",
"version": "0.0.0",
"license": "MIT",
"private": true,
"scripts": {
"build": "wireit",
"build-locales": "wireit",
"build-locales:build": "wireit",
"build-proxy": "wireit",
"build:sfe": "wireit",
"esbuild:watch": "node scripts/build-web.mjs --watch",
"extract-locales": "wireit",
"format": "wireit",
"lint": "wireit",
"lint:imports": "wireit",
"lint:lockfile": "wireit",
"lit-analyse": "wireit",
"postinstall": "bash scripts/patch-spotlight.sh",
"precommit": "wireit",
"prettier": "wireit",
"prettier-check": "wireit",
"pseudolocalize": "wireit",
"storybook": "storybook dev -p 6006",
"storybook:build": "wireit",
"test": "wireit",
"test:e2e": "wireit",
"test:e2e:watch": "wireit",
"test:watch": "wireit",
"watch": "run-s build-locales esbuild:watch"
},
"type": "module",
"exports": {
"./package.json": "./package.json",
"./paths": "./paths.js",
"./scripts/*": "./scripts/*.mjs"
},
"dependencies": {
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.9",
@ -36,6 +70,7 @@
"date-fns": "^4.1.0",
"deepmerge-ts": "^7.1.5",
"dompurify": "^3.2.4",
"esbuild-plugin-copy": "^2.1.1",
"fuse.js": "^7.0.0",
"guacamole-common-js": "^1.5.0",
"hastscript": "^9.0.1",
@ -54,6 +89,7 @@
"remark-gfm": "^4.0.1",
"remark-mdx-frontmatter": "^5.0.0",
"style-mod": "^4.1.2",
"trusted-types": "^2.0.0",
"ts-pattern": "^5.4.0",
"unist-util-visit": "^5.0.0",
"webcomponent-qr-code": "^1.2.0",
@ -61,12 +97,15 @@
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@goauthentik/esbuild-plugin-live-reload": "^1.0.4",
"@goauthentik/monorepo": "^1.0.4",
"@goauthentik/prettier-config": "^1.0.4",
"@goauthentik/tsconfig": "^1.0.4",
"@hcaptcha/types": "^1.0.4",
"@lit/localize-tools": "^0.8.0",
"@rollup/plugin-replace": "^6.0.1",
"@storybook/addon-essentials": "^8.3.4",
"@storybook/addon-links": "^8.3.4",
"@storybook/api": "^7.6.17",
"@storybook/blocks": "^8.3.4",
"@storybook/builder-vite": "^8.3.4",
"@storybook/manager-api": "^8.3.4",
@ -76,13 +115,13 @@
"@types/chart.js": "^2.9.41",
"@types/codemirror": "^5.60.15",
"@types/dompurify": "^3.0.5",
"@types/eslint__js": "^8.42.3",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.2",
"@types/mocha": "^10.0.8",
"@types/node": "^22.7.4",
"@types/node": "^22.14.1",
"@types/react": "^18.3.13",
"@types/react-dom": "^18.3.0",
"@types/trusted-types": "^2.0.7",
"@typescript-eslint/eslint-plugin": "^8.8.0",
"@typescript-eslint/parser": "^8.8.0",
"@wdio/browser-runner": "9.4",
@ -97,18 +136,15 @@
"eslint-plugin-wc": "^2.1.1",
"find-free-ports": "^3.1.1",
"github-slugger": "^2.0.0",
"glob": "^11.0.0",
"globals": "^15.10.0",
"knip": "^5.30.6",
"lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"prettier": "^3.5.3",
"pseudolocale": "^2.1.0",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^8.3.4",
"storybook-addon-mock": "^5.0.0",
"syncpack": "^13.0.0",
"turnstile-types": "^1.2.3",
"typescript": "^5.6.2",
"typescript-eslint": "^8.8.0",
@ -116,10 +152,6 @@
"vite-tsconfig-paths": "^5.0.1",
"wireit": "^0.14.9"
},
"engines": {
"node": ">=20"
},
"license": "MIT",
"optionalDependencies": {
"@esbuild/darwin-arm64": "^0.24.0",
"@esbuild/linux-amd64": "^0.18.11",
@ -128,47 +160,6 @@
"@rollup/rollup-linux-arm64-gnu": "4.23.0",
"@rollup/rollup-linux-x64-gnu": "4.23.0"
},
"overrides": {
"rapidoc": {
"@apitools/openapi-parser@": "0.0.37"
},
"chromedriver": {
"axios": "^1.8.4"
}
},
"private": true,
"scripts": {
"build": "wireit",
"build-locales": "wireit",
"build-locales:build": "wireit",
"build-proxy": "wireit",
"build:sfe": "wireit",
"esbuild:watch": "node scripts/build-web.mjs --watch",
"extract-locales": "wireit",
"format": "wireit",
"lint": "wireit",
"lint:imports": "wireit",
"lint:lockfile": "wireit",
"lint:nightmare": "wireit",
"lint:package": "wireit",
"lint:precommit": "wireit",
"lint:types": "wireit",
"lit-analyse": "wireit",
"postinstall": "bash scripts/patch-spotlight.sh",
"precommit": "wireit",
"prettier": "wireit",
"prettier-check": "wireit",
"pseudolocalize": "wireit",
"storybook": "storybook dev -p 6006",
"storybook:build": "wireit",
"test": "wireit",
"test:e2e": "wireit",
"test:e2e:watch": "wireit",
"test:watch": "wireit",
"tsc": "wireit",
"watch": "run-s build-locales esbuild:watch"
},
"type": "module",
"wireit": {
"build": {
"#comment": [
@ -245,10 +236,7 @@
"command": "lit-localize extract"
},
"format": {
"command": "prettier --write .",
"dependencies": [
"lint:package"
]
"command": "prettier --write ."
},
"format:packages": {
"dependencies": [
@ -267,16 +255,6 @@
"lint:imports": {
"command": "knip --config scripts/knip.config.ts"
},
"lint:types:tests": {
"command": "tsc --noEmit -p ./tests"
},
"lint:types": {
"command": "tsc --noEmit -p .",
"dependencies": [
"build-locales",
"lint:types:tests"
]
},
"lint:lockfile": {
"__comment": "The lockfile-lint package does not have an option to ensure resolved hashes are set everywhere",
"shell": true,
@ -287,27 +265,6 @@
"./packages/sfe:lint:lockfile"
]
},
"lint:package": {
"command": "syncpack format -i ' '"
},
"lint:nightmare": {
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --nightmare",
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
},
"lint:precommit": {
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --precommit",
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
},
"lint:spelling": {
"command": "node scripts/check-spelling.mjs"
},
@ -317,13 +274,10 @@
"precommit": {
"command": "prettier --write .",
"dependencies": [
"lint:types",
"lint:components",
"lint:spelling",
"lint:package",
"lint:lockfile",
"lint:lockfiles",
"lint:precommit",
"format:packages"
]
},
@ -332,9 +286,6 @@
"format"
]
},
"prettier-check": {
"command": "prettier --check ."
},
"pseudolocalize": {
"command": "node scripts/pseudolocalize.mjs"
},
@ -378,15 +329,18 @@
"env": {
"TS_NODE_PROJECT": "tsconfig.test.json"
}
},
"tsc": {
"dependencies": [
"lint:types"
]
}
},
"workspaces": [
".",
"./packages/*"
]
"engines": {
"node": ">=22.14.0"
},
"prettier": "@goauthentik/prettier-config",
"overrides": {
"rapidoc": {
"@apitools/openapi-parser@": "0.0.37"
},
"chromedriver": {
"axios": "^1.8.4"
}
}
}

View File

@ -1,23 +0,0 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "consistent",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 4,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"importOrderParserPlugins": ["typescript", "classProperties", "decorators-legacy"]
}

View File

@ -9,11 +9,11 @@ It exists primarily to support late versions of Microsoft Office365 and Microsof
software that still uses the MSEdge-18 and IE-11 _Trident_ web engine for web-based log-ins. It has
limited support for the full language, supporting only the following stages:
- identification
- password
- redirect
- autosubmit
- authenticator validation (both code and WebAuthn)
- identification
- password
- redirect
- autosubmit
- authenticator validation (both code and WebAuthn)
### License

View File

@ -1,6 +1,14 @@
{
"name": "@goauthentik/web-sfe",
"version": "0.0.0",
"license": "MIT",
"private": true,
"scripts": {
"build": "wireit",
"lint:lockfile": "wireit",
"prettier": "prettier --write ./src ./tsconfig.json ./rollup.config.js ./package.json",
"watch": "rollup -w -c rollup.config.js --bundleConfigAsCjs"
},
"dependencies": {
"@goauthentik/api": "^2024.6.0-1719577139",
"base64-js": "^1.5.1",
@ -10,6 +18,7 @@
"weakmap-polyfill": "^2.0.4"
},
"devDependencies": {
"@goauthentik/prettier-config": "^1.0.4",
"@rollup/plugin-commonjs": "^28.0.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-swc": "^0.4.0",
@ -18,12 +27,11 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/jquery": "^3.5.31",
"lockfile-lint": "^4.14.0",
"prettier": "^3.3.2",
"prettier": "^3.5.3",
"rollup": "^4.23.0",
"rollup-plugin-copy": "^3.5.0",
"wireit": "^0.14.9"
},
"license": "MIT",
"optionalDependencies": {
"@swc/core": "^1.7.28",
"@swc/core-darwin-arm64": "^1.6.13",
@ -37,13 +45,6 @@
"@swc/core-win32-ia32-msvc": "^1.6.13",
"@swc/core-win32-x64-msvc": "^1.6.13"
},
"private": true,
"scripts": {
"build": "wireit",
"lint:lockfile": "wireit",
"prettier": "prettier --write ./src ./tsconfig.json ./rollup.config.js ./package.json",
"watch": "rollup -w -c rollup.config.js --bundleConfigAsCjs"
},
"wireit": {
"build:sfe": {
"command": "rollup -c rollup.config.js --bundleConfigAsCjs",

View File

@ -1,8 +1,11 @@
import { fromByteArray } from "base64-js";
// sort-imports-ignore
import "formdata-polyfill";
import $ from "jquery";
import "weakmap-polyfill";
import { fromByteArray } from "base64-js";
import $ from "jquery";
import {
type AuthenticatorValidationChallenge,
type AutosubmitChallenge,
@ -273,7 +276,7 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
deviceChallenge?: DeviceChallenge;
b64enc(buf: Uint8Array): string {
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]/g, "");
}
b64RawEnc(buf: Uint8Array): string {

78
web/paths.js Normal file
View File

@ -0,0 +1,78 @@
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const relativeDirname = dirname(fileURLToPath(import.meta.url));
//#region Base paths
/**
* @typedef {'@goauthentik/web'} WebPackageIdentifier
*/
/**
* The root of the web package.
*/
export const PackageRoot = /** @type {WebPackageIdentifier} */ (resolve(relativeDirname));
/**
* The name of the distribution directory.
*/
export const DistDirectoryName = "dist";
/**
* Path to the web package's distribution directory.
*
* This is where the built files are located after running the build process.
*/
export const DistDirectory = /** @type {`${WebPackageIdentifier}/${DistDirectoryName}`} */ (
resolve(relativeDirname, DistDirectoryName)
);
//#endregion
//#region Entry points
/**
* @typedef {{ in: string, out: string }} EntryPointTarget
*
* ESBuild entrypoint target.
* Matches the type defined in the ESBuild context.
*/
/**
* Entry points available for building.
*
* @satisfies {Record<string, EntryPointTarget>}
*/
export const EntryPoint = /** @type {const} */ ({
Admin: {
in: resolve(PackageRoot, "src", "admin", "AdminInterface", "index.entrypoint.ts"),
out: resolve(DistDirectory, "admin", "AdminInterface"),
},
User: {
in: resolve(PackageRoot, "src", "user", "index.entrypoint.ts"),
out: resolve(DistDirectory, "user", "UserInterface"),
},
Flow: {
in: resolve(PackageRoot, "src", "flow", "index.entrypoint.ts"),
out: resolve(DistDirectory, "flow", "FlowInterface"),
},
Standalone: {
in: resolve(PackageRoot, "src", "standalone", "api-browser/index.entrypoint.ts"),
out: resolve(DistDirectory, "standalone", "api-browser", "index"),
},
StandaloneLoading: {
in: resolve(PackageRoot, "src", "standalone", "loading/index.entrypoint.ts"),
out: resolve(DistDirectory, "standalone", "loading", "index"),
},
RAC: {
in: resolve(PackageRoot, "src", "rac", "index.entrypoint.ts"),
out: resolve(DistDirectory, "rac", "index"),
},
Polyfill: {
in: resolve(PackageRoot, "src", "polyfill", "index.entrypoint.ts"),
out: resolve(DistDirectory, "poly"),
},
});
//#endregion

View File

@ -1,7 +1,7 @@
import { spawnSync } from "child_process";
import fs from "fs";
import path from "path";
import process from "process";
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
/**
* Determines if all the Xliff translation source files are present and if the Typescript source
@ -12,6 +12,9 @@ import process from "process";
const localizeRules = JSON.parse(fs.readFileSync("./lit-localize.json", "utf-8"));
/**
* @param {string} loc
*/
function generatedFileIsUpToDateWithXliffSource(loc) {
const xliff = path.join("./xliff", `${loc}.xlf`);
const gened = path.join("./src/locales", `${loc}.ts`);
@ -21,16 +24,26 @@ function generatedFileIsUpToDateWithXliffSource(loc) {
// than the generated file. The missing XLF file is important enough it
// generates a unique error message and halts the build.
/**
* @type {fs.Stats}
*/
let xlfStat;
try {
var xlfStat = fs.statSync(xliff);
xlfStat = fs.statSync(xliff);
} catch (_error) {
console.error(`lit-localize expected '${loc}.xlf', but XLF file is not present`);
process.exit(1);
}
/**
* @type {fs.Stats}
*/
let genedStat;
// If the generated file doesn't exist, of course it's not up to date.
try {
var genedStat = fs.statSync(gened);
genedStat = fs.statSync(gened);
} catch (_error) {
return false;
}

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