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
232 changed files with 48213 additions and 54725 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
name: authentik-gen-update-webauthn-mds name: "authentik CI Update WebAuthn MDS"
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
@ -11,6 +11,7 @@ env:
jobs: jobs:
build: build:
name: "Update WebAuthn MDS"
if: ${{ github.repository != 'goauthentik/authentik-internal' }} if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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 # 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: on:
pull_request: pull_request:
types: types:
@ -12,6 +12,7 @@ permissions:
jobs: jobs:
cleanup: cleanup:
name: "Cleanup Cache"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code - name: Check out code

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

23
.gitignore vendored
View File

@ -217,3 +217,26 @@ source_docs/
### Docker ### ### Docker ###
docker-compose.override.yml 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 **/LICENSE
authentik/stages/**/* authentik/stages/**/*
authentik/sources/**/*
schemas/**/*
blueprints/**/*
## Build asset directories ## Build asset directories
coverage coverage
dist dist
out out
.docusaurus .docusaurus
.wireit
website/docs/developer-docs/api/**/* website/docs/developer-docs/api/**/*
## Environment ## Environment
@ -32,14 +36,15 @@ coverage
# Templates # Templates
# TODO: Rename affected files to *.template.* or similar. # TODO: Rename affected files to *.template.* or similar.
authentik/**/*.html
*.html *.html
*.mdx *.mdx
*.md *.md
## Import order matters ## Import order matters
poly.ts web/src/poly.ts
src/locale-codes.ts web/src/locale-codes.ts
src/locales/ web/src/locales/
# Storybook # Storybook
storybook-static/ storybook-static/

View File

@ -17,6 +17,6 @@
"ms-python.vscode-pylance", "ms-python.vscode-pylance",
"redhat.vscode-yaml", "redhat.vscode-yaml",
"Tobermory.es6-string-html", "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.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index", "typescript.preferences.importModuleSpecifierEnding": "index",
"typescript.tsdk": "./web/node_modules/typescript/lib", "typescript.tsdk": "./node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true, "typescript.enablePromptUseWorkspaceTsdk": true,
"yaml.schemas": { "yaml.schemas": {
"./blueprints/schema.json": "blueprints/**/*.yaml" "./blueprints/schema.json": "blueprints/**/*.yaml"
@ -30,7 +30,71 @@
} }
], ],
"go.testFlags": ["-count=1"], "go.testFlags": ["-count=1"],
"github-actions.workflows.pinned.workflows": [ "github-actions.workflows.pinned.workflows": [".github/workflows/ci-main.yml"],
".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", "label": "authentik/core: make",
"command": "uv", "command": "uv",
"args": [ "args": ["run", "make", "lint-fix", "lint"],
"run",
"make",
"lint-fix",
"lint"
],
"presentation": { "presentation": {
"panel": "new" "panel": "new"
}, },
@ -18,11 +13,7 @@
{ {
"label": "authentik/core: run", "label": "authentik/core: run",
"command": "uv", "command": "uv",
"args": [ "args": ["run", "ak", "server"],
"run",
"ak",
"server"
],
"group": "build", "group": "build",
"presentation": { "presentation": {
"panel": "dedicated", "panel": "dedicated",
@ -32,17 +23,13 @@
{ {
"label": "authentik/web: make", "label": "authentik/web: make",
"command": "make", "command": "make",
"args": [ "args": ["web"],
"web"
],
"group": "build" "group": "build"
}, },
{ {
"label": "authentik/web: watch", "label": "authentik/web: watch",
"command": "make", "command": "make",
"args": [ "args": ["web-watch"],
"web-watch"
],
"group": "build", "group": "build",
"presentation": { "presentation": {
"panel": "dedicated", "panel": "dedicated",
@ -52,26 +39,19 @@
{ {
"label": "authentik: install", "label": "authentik: install",
"command": "make", "command": "make",
"args": [ "args": ["install", "-j4"],
"install",
"-j4"
],
"group": "build" "group": "build"
}, },
{ {
"label": "authentik/website: make", "label": "authentik/website: make",
"command": "make", "command": "make",
"args": [ "args": ["website"],
"website"
],
"group": "build" "group": "build"
}, },
{ {
"label": "authentik/website: watch", "label": "authentik/website: watch",
"command": "make", "command": "make",
"args": [ "args": ["website-watch"],
"website-watch"
],
"group": "build", "group": "build",
"presentation": { "presentation": {
"panel": "dedicated", "panel": "dedicated",
@ -81,11 +61,7 @@
{ {
"label": "authentik/api: generate", "label": "authentik/api: generate",
"command": "uv", "command": "uv",
"args": [ "args": ["run", "make", "gen"],
"run",
"make",
"gen"
],
"group": "build" "group": "build"
} }
] ]

View File

@ -1,48 +1,31 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
# Stage 1: Build website # Stage 1 Web UI and Documentation build
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS website-builder
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 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 ENV NODE_ENV=production
WORKDIR /work/web WORKDIR /work
RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \ COPY ./package.json ./package.json
--mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \ COPY ./package-lock.json ./package-lock.json
--mount=type=bind,target=/work/web/packages/sfe/package.json,src=./web/packages/sfe/package.json \ COPY ./packages ./packages
--mount=type=bind,target=/work/web/scripts,src=./web/scripts \ COPY ./web ./web
--mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \ COPY ./website ./website
npm ci --include=dev
COPY ./package.json /work COPY ./gen-ts-api ./gen-ts-api
COPY ./web /work/web/ COPY ./blueprints ./blueprints
COPY ./website /work/website/ COPY ./schema.yml ./schema.yml
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api 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 FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
ARG TARGETOS ARG TARGETOS
@ -79,7 +62,8 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \ CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \
go build -o /go/authentik ./cmd/server 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 FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN" 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 && \ mkdir -p /usr/share/GeoIP && \
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0" /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 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 FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base
ENV VENV_PATH="/ak-root/.venv" \ ENV VENV_PATH="/ak-root/.venv" \
@ -109,7 +94,7 @@ WORKDIR /ak-root/
COPY --from=uv /uv /uvx /bin/ COPY --from=uv /uv /uvx /bin/
# Stage 7: Python dependencies # Stage 6: Python dependencies
FROM python-base AS python-deps FROM python-base AS python-deps
ARG TARGETARCH ARG TARGETARCH
@ -144,7 +129,7 @@ RUN --mount=type=bind,target=pyproject.toml,src=pyproject.toml \
--mount=type=cache,target=/root/.cache/uv \ --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-install-project --no-dev uv sync --frozen --no-install-project --no-dev
# Stage 8: Run # Stage 7: Run
FROM python-base AS final-image FROM python-base AS final-image
ARG VERSION 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=python-deps /ak-root/.venv /ak-root/.venv
COPY --from=web-builder /work/web/dist/ /web/dist/ COPY --from=web-builder /work/web/dist/ /web/dist/
COPY --from=web-builder /work/web/authentik/ /web/authentik/ 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 COPY --from=geoip /usr/share/GeoIP /geoip
USER 1000 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 html
uv run coverage report 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. lint-fix: lint-codespell ## Lint and automatically fix errors in the python source code. Reports spelling errors.
uv run black $(PY_SOURCES) uv run black $(PY_SOURCES)
uv run ruff check --fix $(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) uv run bandit -c pyproject.toml -r $(PY_SOURCES)
golangci-lint run -v golangci-lint run -v
core-install:
uv sync --frozen
migrate: ## Run the Authentik Django server's migrations migrate: ## Run the Authentik Django server's migrations
uv run python -m lifecycle.migrate uv run python -m lifecycle.migrate
@ -72,7 +76,9 @@ core-i18n-extract:
--ignore website \ --ignore website \
-l en -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: dev-drop-db:
dropdb -U ${pg_user} -h ${pg_host} ${pg_name} 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_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
uv run ak make_blueprint_schema > blueprints/schema.json uv run ak make_blueprint_schema > blueprints/schema.json
AUTHENTIK_DEBUG=true \ AUTHENTIK_DEBUG=true \
AUTHENTIK_TENANTS__ENABLED=true \ AUTHENTIK_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=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 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 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 npx prettier --write changelog.md
gen-diff: ## (Release) generate the changelog diff between the current schema and the last tag 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 git show $(shell git describe --tags $(shell git rev-list --tags --max-count=1)):schema.yml > old_schema.yml
docker run \ docker run \
--rm -v ${PWD}:/local \ --rm -v ${PWD}:/local \
--user ${UID}:${GID} \ --user ${UID}:${GID} \
docker.io/openapitools/openapi-diff:2.1.0-beta.8 \ docker.io/openapitools/openapi-diff:2.1.0-beta.8 \
--markdown /local/diff.md \ --markdown /local/diff.md \
/local/old_schema.yml /local/schema.yml /local/old_schema.yml /local/schema.yml
rm old_schema.yml rm old_schema.yml
sed -i 's/{/{/g' diff.md sed -i 's/{/{/g' diff.md
sed -i 's/}/}/g' diff.md sed -i 's/}/}/g' diff.md
npx prettier --write diff.md npx prettier --write diff.md
gen-clean-ts: ## Remove generated API client for Typescript 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 \ --rm -v ${PWD}:/local \
--user ${UID}:${GID} \ --user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \ docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
-i /local/schema.yml \ --input-spec /local/schema.yml \
-g typescript-fetch \ --generator-name typescript-fetch \
-o /local/${GEN_API_TS} \ --output /local/${GEN_API_TS} \
-c /local/scripts/api-ts-config.yaml \ --config /local/scripts/api-ts-config.yaml \
--additional-properties=npmVersion=${NPM_VERSION} \ --additional-properties=npmVersion=${NPM_VERSION} \
--git-repo-id authentik \ --git-repo-id authentik \
--git-user-id goauthentik --git-user-id goauthentik
mkdir -p web/node_modules/@goauthentik/api
cd ./${GEN_API_TS} && npm i npm install
\cp -rf ./${GEN_API_TS}/* web/node_modules/@goauthentik/api
gen-client-py: gen-clean-py ## Build and install the authentik API for Python gen-client-py: gen-clean-py ## Build and install the authentik API for Python
docker run \ docker run \
--rm -v ${PWD}:/local \ --rm -v ${PWD}:/local \
--user ${UID}:${GID} \ --user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \ docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
-i /local/schema.yml \ --input-spec /local/schema.yml \
-g python \ --generator-name python \
-o /local/${GEN_API_PY} \ --output /local/${GEN_API_PY} \
-c /local/scripts/api-py-config.yaml \ --config /local/scripts/api-py-config.yaml \
--additional-properties=packageVersion=${NPM_VERSION} \ --additional-properties=packageVersion=${NPM_VERSION} \
--git-repo-id authentik \ --git-repo-id authentik \
--git-user-id goauthentik --git-user-id goauthentik
pip install ./${GEN_API_PY} pip install ./${GEN_API_PY}
gen-client-go: gen-clean-go ## Build and install the authentik API for Golang gen-client-go: gen-clean-go ## Build and install the authentik API for Golang
mkdir -p ./${GEN_API_GO} ./${GEN_API_GO}/templates 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/config.yaml \
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache -O ./${GEN_API_GO}/templates/go.mod.mustache -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}/ cp schema.yml ./${GEN_API_GO}/
docker run \ docker run \
--rm -v ${PWD}/${GEN_API_GO}:/local \ --rm -v ${PWD}/${GEN_API_GO}:/local \
--user ${UID}:${GID} \ --user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \ docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \
-i /local/schema.yml \ --input-spec /local/schema.yml \
-g go \ --generator-name go \
-o /local/ \ --output /local/ \
-c /local/config.yaml --config /local/config.yaml
go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO} go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO}
rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/ rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/
gen-dev-config: ## Generate a local development config file gen-dev-config: ## Generate a local development config file
@ -184,56 +207,38 @@ gen: gen-build gen-client-ts
## Web ## Web
######################### #########################
web-build: web-install ## Build the Authentik UI 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
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-test: ## Run tests for the Authentik UI 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 web-watch: ## Build and watch the Authentik UI for changes, updating automatically
rm -rf web/dist/ npm run watch -w @goauthentik/web
mkdir web/dist/
touch web/dist/.gitkeep
cd web && npm run watch
web-storybook-watch: ## Build and run the storybook documentation server web-storybook-watch: ## Build and run the storybook documentation server
cd web && npm run storybook npm run storybook -w @goauthentik/web
web-lint-fix: web-lint-fix:
cd web && npm run prettier npm run prettier -w @goauthentik/web
web-lint: web-lint:
cd web && npm run lint npm run lint -w @goauthentik/web
cd web && npm run lit-analyse npm run lit-analyse -w @goauthentik/web
web-check-compile:
cd web && npm run tsc
web-i18n-extract: web-i18n-extract:
cd web && npm run extract-locales npm run extract-locales -w @goauthentik/web
######################### #########################
## Website ## 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: node-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-build: website-build:
cd website && npm run build npm run build -w @goauthentik/docs
website-watch: ## Build and watch the documentation website, updating automatically website-watch: ## Build and watch the documentation website, updating automatically
cd website && npm run watch npm run watch -w @goauthentik/docs
######################### #########################
## Docker ## Docker

View File

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

View File

@ -2,31 +2,79 @@
{% load i18n %} {% load i18n %}
{% load authentik_core %} {% load authentik_core %}
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <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"> {% comment %}
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title> Darkreader breaks the site regardless of theme as its not compatible with webcomponents, and we
<link rel="icon" href="{{ brand.branding_favicon_url }}"> default to a dark theme based on preferred colour-scheme
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}"> {% endcomment %}
{% block head_before %}
{% endblock %} <meta name="darkreader-lock" />
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<style>{{ brand.branding_custom_css }}</style> <title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script> <link rel="icon" href="{{ brand.branding_favicon_url }}" />
{% block head %} <link rel="shortcut icon" href="{{ brand.branding_favicon_url }}" />
{% endblock %}
<meta name="sentry-trace" content="{{ sentry_trace }}" /> {% block head_before %}
</head> {% endblock %}
<body>
{% block body %} <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}" />
{% endblock %}
{% block scripts %} <style data-test-id="color-scheme">
{% endblock %} @media (prefers-color-scheme: dark) {
</body> :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> </html>

View File

@ -4,14 +4,16 @@
{% block head %} {% block head %}
<script src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}" type="module"></script> <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" %} {% include "base/header_js.html" %}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-interface-admin> <ak-interface-admin>
<ak-loading></ak-loading> <ak-loading></ak-loading>
</ak-interface-admin> </ak-interface-admin>
{% endblock %} {% endblock %}

View File

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

View File

@ -4,14 +4,17 @@
{% block head %} {% block head %}
<script src="{% versioned_script 'dist/user/UserInterface-%v.js' %}" type="module"></script> <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" %} {% include "base/header_js.html" %}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-interface-user> <ak-interface-user>
<ak-loading></ak-loading> <ak-loading></ak-loading>
</ak-interface-user> </ak-interface-user>
{% endblock %} {% endblock %}

View File

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

View File

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

View File

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

View File

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

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;

View File

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

View File

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

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", "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 { .badge--support-community {
--ifm-badge-background-color: var( --ifm-badge-background-color: var(--ifm-color-secondary-contrast-foreground);
--ifm-color-secondary-contrast-foreground
);
--ifm-badge-border-color: var(--ifm-color-secondary-dark); --ifm-badge-border-color: var(--ifm-color-secondary-dark);
--ifm-badge-color: var(--ifm-color-secondary-contrast-background); --ifm-badge-color: var(--ifm-color-secondary-contrast-background);
} }

View File

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

View File

@ -7,11 +7,7 @@
} }
.homepage_hero__subtitle p { .homepage_hero__subtitle p {
font-size: clamp( font-size: clamp(1.125rem, 0.9946rem + 0.6522vi, 1.5rem); /* Adjust font as page scales */
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 */ 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 */ text-wrap: balance; /* Prevent widows, orphans, and runts. Doesn't work in Safari */
} }

View File

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

View File

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

View File

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

View File

@ -1,13 +1,16 @@
/** /**
* @file * @file Client-side observer for ESBuild events.
* Client-side observer for ESBuild events. *
* @import { Message as ESBuildMessage } from "esbuild";
*/ */
import type { Message as ESBuildMessage } from "esbuild";
const logPrefix = "👷 [ESBuild]"; const logPrefix = "👷 [ESBuild]";
const log = console.debug.bind(console, logPrefix); 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. * A client-side watcher for ESBuild.
@ -17,13 +20,11 @@ type BuildEventListener<Data = unknown> = (event: MessageEvent<Data>) => void;
* *
* ```ts * ```ts
* if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) { * 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 { export class ESBuildObserver extends EventSource {
/** /**
@ -58,15 +59,19 @@ export class ESBuildObserver extends EventSource {
/** /**
* The interval for the keep-alive check. * The interval for the keep-alive check.
* @type {ReturnType<typeof setInterval> | undefined}
*/ */
#keepAliveInterval: ReturnType<typeof setInterval> | undefined; #keepAliveInterval;
#trackActivity = () => { #trackActivity = () => {
this.lastUpdatedAt = Date.now(); this.lastUpdatedAt = Date.now();
this.alive = true; this.alive = true;
}; };
#startListener: BuildEventListener = () => { /**
* @type {BuildEventListener}
*/
#startListener = () => {
this.#trackActivity(); this.#trackActivity();
log("⏰ Build started..."); log("⏰ Build started...");
}; };
@ -82,13 +87,18 @@ export class ESBuildObserver extends EventSource {
} }
}; };
#errorListener: BuildEventListener<string> = (event) => { /**
* @type {BuildEventListener<string>}
*/
#errorListener = (event) => {
this.#trackActivity(); this.#trackActivity();
// eslint-disable-next-line no-console
console.group(logPrefix, "⛔️⛔️⛔️ Build error..."); 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) { for (const error of esbuildErrorMessages) {
console.warn(error.text); console.warn(error.text);
@ -101,11 +111,13 @@ export class ESBuildObserver extends EventSource {
} }
} }
// eslint-disable-next-line no-console
console.groupEnd(); console.groupEnd();
}; };
#endListener: BuildEventListener = () => { /**
* @type {BuildEventListener}
*/
#endListener = () => {
cancelAnimationFrame(this.#reloadFrameID); cancelAnimationFrame(this.#reloadFrameID);
this.#trackActivity(); this.#trackActivity();
@ -126,12 +138,32 @@ export class ESBuildObserver extends EventSource {
}); });
}; };
#keepAliveListener: BuildEventListener = () => { /**
* @type {BuildEventListener}
*/
#keepAliveListener = () => {
this.#trackActivity(); this.#trackActivity();
log("🏓 Keep-alive"); 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); super(url);
this.addEventListener("esbuild:start", this.#startListener); 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. * Serializes a custom event to a text stream.
@ -22,7 +25,7 @@ export function serializeCustomEventToStream(event) {
* @typedef {Object} BuildObserverOptions * @typedef {Object} BuildObserverOptions
* *
* @property {URL} serverURL * @property {URL} serverURL
* @property {string} logPrefix * @property {string} [logPrefix]
* @property {string} relativeRoot * @property {string} relativeRoot
*/ */
@ -32,8 +35,11 @@ export function serializeCustomEventToStream(event) {
* @param {BuildObserverOptions} options * @param {BuildObserverOptions} options
* @returns {import('esbuild').Plugin} * @returns {import('esbuild').Plugin}
*/ */
export function buildObserverPlugin({ serverURL, logPrefix, relativeRoot }) { export function liveReloadPlugin({ serverURL, logPrefix = "Build Observer", relativeRoot }) {
const timerLabel = `[${logPrefix}] Build`; const timerLabel = `[${logPrefix}] 🏁`;
// eslint-disable-next-line no-console
const log = console.log.bind(console, `[${logPrefix}]`);
const endpoint = serverURL.pathname; const endpoint = serverURL.pathname;
const dispatcher = new EventTarget(); const dispatcher = new EventTarget();
@ -43,18 +49,18 @@ export function buildObserverPlugin({ serverURL, logPrefix, relativeRoot }) {
res.setHeader("Access-Control-Allow-Headers", "Content-Type"); res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.url !== endpoint) { if (req.url !== endpoint) {
console.log(`🚫 Invalid request to ${req.url}`); log(`🚫 Invalid request to ${req.url}`);
res.writeHead(404); res.writeHead(404);
res.end(); res.end();
return; return;
} }
console.log("🔌 Client connected"); log("🔌 Client connected");
res.writeHead(200, { res.writeHead(200, {
"Content-Type": "text/event-stream", "Content-Type": "text/event-stream",
"Cache-Control": "no-cache", "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); dispatcher.addEventListener("esbuild:end", listener);
req.on("close", () => { req.on("close", () => {
console.log("🔌 Client disconnected"); log("🔌 Client disconnected");
clearInterval(keepAliveInterval); 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", "**/out",
"**/dist", "**/dist",
"**/.wireit", "**/.wireit",
"**/.venv",
"website/build/**", "website/build/**",
"website/.docusaurus/**", "**/.docusaurus/**",
"**/node_modules", "**/node_modules",
"**/coverage", "**/coverage",
"**/storybook-static", "**/storybook-static",

View File

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

View File

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

View File

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

View File

@ -6,26 +6,9 @@
"scripts": { "scripts": {
"build": "tsc -p ." "build": "tsc -p ."
}, },
"main": "./index.js",
"type": "module", "type": "module",
"exports": { "exports": "./index.js",
"./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"
}
},
"dependencies": { "dependencies": {
"eslint": "^9.23.0", "eslint": "^9.23.0",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",
@ -35,8 +18,8 @@
"eslint-plugin-wc": "^2.1.1" "eslint-plugin-wc": "^2.1.1"
}, },
"devDependencies": { "devDependencies": {
"@goauthentik/prettier-config": "^1.0.1", "@goauthentik/prettier-config": "^1.0.4",
"@goauthentik/tsconfig": "^1.0.1", "@goauthentik/tsconfig": "^1.0.4",
"@types/eslint": "^9.6.1", "@types/eslint": "^9.6.1",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"typescript-eslint": "^8.29.0" "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 "./paths.js";
export * from "./constants.js"; export * from "./constants.js";
export * from "./build.js";
export * from "./version.js"; export * from "./version.js";
export * from "./scripting.js"; export * from "./scripting.js";

View File

@ -1,19 +1,14 @@
{ {
"name": "@goauthentik/monorepo", "name": "@goauthentik/monorepo",
"version": "1.0.0", "version": "1.0.4",
"description": "Utilities for the authentik monorepo.", "description": "Utilities for the authentik monorepo.",
"private": true,
"license": "MIT", "license": "MIT",
"private": true,
"main": "index.js",
"type": "module", "type": "module",
"exports": { "exports": "./index.js",
"./package.json": "./package.json",
".": {
"import": "./index.js",
"types": "./out/index.d.ts"
}
},
"types": "./out/index.d.ts",
"engines": { "engines": {
"node": ">=20.11" "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 { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url)); const relativeDirname = dirname(fileURLToPath(import.meta.url));
/** /**
* @typedef {'~authentik'} MonoRepoRoot * @typedef {'~authentik'} MonoRepoRoot
@ -11,7 +11,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
/** /**
* The root of the authentik monorepo. * 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); const require = createRequire(import.meta.url);

View File

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

View File

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

View File

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

View File

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

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 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 modify from "rollup-plugin-modify";
import postcssLit from "rollup-plugin-postcss-lit"; import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from "vite-tsconfig-paths";
export const isProdBuild = process.env.NODE_ENV === "production"; const AK_API_BASE_PATH = process.env.AK_API_BASE_PATH || "";
export const apiBasePath = process.env.AK_API_BASE_PATH || "";
const importInlinePatterns = [ const inlineImportPatterns = [
'import AKGlobal from "(\\.\\./)*common/styles/authentik\\.css', 'import AKGlobal from "(\\.\\./)*common/styles/authentik\\.css',
'import AKGlobal from "@goauthentik/common/styles/authentik\\.css', 'import AKGlobal from "@goauthentik/common/styles/authentik\\.css',
'import PF.+ from "@patternfly/patternfly/\\S+\\.css', 'import PF.+ from "@patternfly/patternfly/\\S+\\.css',
@ -17,7 +18,9 @@ const importInlinePatterns = [
'import styles from "\\./LibraryPageImpl\\.css', '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 = { const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
@ -29,19 +32,19 @@ const config: StorybookConfig = {
], ],
staticDirs: [ staticDirs: [
{ {
from: "../node_modules/@patternfly/patternfly/patternfly-base.css", from: path.resolve(patternflyPath, "patternfly-base.css"),
to: "@patternfly/patternfly/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", 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", 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", to: "@goauthentik/common/styles/one-dark.css",
}, },
], ],
@ -53,28 +56,25 @@ const config: StorybookConfig = {
autodocs: "tag", autodocs: "tag",
}, },
async viteFinal(config) { async viteFinal(config) {
return { return deepmerge(config, {
...config, define: serializeEnvironmentVars({
NODE_ENV: NodeEnvironment,
CWD: process.cwd(),
AK_API_BASE_PATH: AK_API_BASE_PATH,
WATCHER_URL: "",
}),
plugins: [ plugins: [
modify({ modify({
find: importInlineRegexp, find: inlineImportPattern,
replace: (match: RegExpMatchArray) => { replace: (match: RegExpMatchArray) => {
return `${match}?inline`; 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(), postcssLit(),
tsconfigPaths(), tsconfigPaths(),
], ],
}; });
}, },
}; };

View File

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

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: "^_",
},
],
},
},
];

26423
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

@ -1,6 +1,14 @@
{ {
"name": "@goauthentik/web-sfe", "name": "@goauthentik/web-sfe",
"version": "0.0.0", "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": { "dependencies": {
"@goauthentik/api": "^2024.6.0-1719577139", "@goauthentik/api": "^2024.6.0-1719577139",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
@ -10,6 +18,7 @@
"weakmap-polyfill": "^2.0.4" "weakmap-polyfill": "^2.0.4"
}, },
"devDependencies": { "devDependencies": {
"@goauthentik/prettier-config": "^1.0.4",
"@rollup/plugin-commonjs": "^28.0.0", "@rollup/plugin-commonjs": "^28.0.0",
"@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-swc": "^0.4.0", "@rollup/plugin-swc": "^0.4.0",
@ -18,12 +27,11 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/jquery": "^3.5.31", "@types/jquery": "^3.5.31",
"lockfile-lint": "^4.14.0", "lockfile-lint": "^4.14.0",
"prettier": "^3.3.2", "prettier": "^3.5.3",
"rollup": "^4.23.0", "rollup": "^4.23.0",
"rollup-plugin-copy": "^3.5.0", "rollup-plugin-copy": "^3.5.0",
"wireit": "^0.14.9" "wireit": "^0.14.9"
}, },
"license": "MIT",
"optionalDependencies": { "optionalDependencies": {
"@swc/core": "^1.7.28", "@swc/core": "^1.7.28",
"@swc/core-darwin-arm64": "^1.6.13", "@swc/core-darwin-arm64": "^1.6.13",
@ -37,13 +45,6 @@
"@swc/core-win32-ia32-msvc": "^1.6.13", "@swc/core-win32-ia32-msvc": "^1.6.13",
"@swc/core-win32-x64-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": { "wireit": {
"build:sfe": { "build:sfe": {
"command": "rollup -c rollup.config.js --bundleConfigAsCjs", "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 "formdata-polyfill";
import $ from "jquery";
import "weakmap-polyfill"; import "weakmap-polyfill";
import { fromByteArray } from "base64-js";
import $ from "jquery";
import { import {
type AuthenticatorValidationChallenge, type AuthenticatorValidationChallenge,
type AutosubmitChallenge, type AutosubmitChallenge,
@ -273,7 +276,7 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
deviceChallenge?: DeviceChallenge; deviceChallenge?: DeviceChallenge;
b64enc(buf: Uint8Array): string { 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 { 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 { spawnSync } from "node:child_process";
import fs from "fs"; import fs from "node:fs";
import path from "path"; import path from "node:path";
import process from "process"; import process from "node:process";
/** /**
* Determines if all the Xliff translation source files are present and if the Typescript source * 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")); const localizeRules = JSON.parse(fs.readFileSync("./lit-localize.json", "utf-8"));
/**
* @param {string} loc
*/
function generatedFileIsUpToDateWithXliffSource(loc) { function generatedFileIsUpToDateWithXliffSource(loc) {
const xliff = path.join("./xliff", `${loc}.xlf`); const xliff = path.join("./xliff", `${loc}.xlf`);
const gened = path.join("./src/locales", `${loc}.ts`); 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 // than the generated file. The missing XLF file is important enough it
// generates a unique error message and halts the build. // generates a unique error message and halts the build.
/**
* @type {fs.Stats}
*/
let xlfStat;
try { try {
var xlfStat = fs.statSync(xliff); xlfStat = fs.statSync(xliff);
} catch (_error) { } catch (_error) {
console.error(`lit-localize expected '${loc}.xlf', but XLF file is not present`); console.error(`lit-localize expected '${loc}.xlf', but XLF file is not present`);
process.exit(1); process.exit(1);
} }
/**
* @type {fs.Stats}
*/
let genedStat;
// If the generated file doesn't exist, of course it's not up to date. // If the generated file doesn't exist, of course it's not up to date.
try { try {
var genedStat = fs.statSync(gened); genedStat = fs.statSync(gened);
} catch (_error) { } catch (_error) {
return false; return false;
} }

View File

@ -1,137 +1,92 @@
import { execFileSync } from "child_process"; /**
* @file ESBuild script for building the authentik web UI.
*
* @import { BuildOptions } from "esbuild";
*/
import { liveReloadPlugin } from "@goauthentik/esbuild-plugin-live-reload/plugin";
import {
MonoRepoRoot,
NodeEnvironment,
readBuildIdentifier,
resolvePackage,
serializeEnvironmentVars,
} from "@goauthentik/monorepo";
import { DistDirectory, DistDirectoryName, EntryPoint, PackageRoot } from "@goauthentik/web/paths";
import { deepmerge } from "deepmerge-ts"; import { deepmerge } from "deepmerge-ts";
import esbuild from "esbuild"; import esbuild from "esbuild";
import copy from "esbuild-plugin-copy";
import { polyfillNode } from "esbuild-plugin-polyfill-node"; import { polyfillNode } from "esbuild-plugin-polyfill-node";
import findFreePorts from "find-free-ports"; import findFreePorts from "find-free-ports";
import { copyFileSync, mkdirSync, readFileSync, statSync } from "fs"; import * as fs from "node:fs/promises";
import { globSync } from "glob"; import * as path from "node:path";
import * as path from "path"; import process from "node:process";
import { cwd } from "process";
import process from "process";
import { fileURLToPath } from "url";
import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs"; import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs";
import { buildObserverPlugin } from "./esbuild/build-observer-plugin.mjs";
const __dirname = fileURLToPath(new URL(".", import.meta.url)); const logPrefix = "[Build]";
let authentikProjectRoot = path.join(__dirname, "..", "..");
try { const definitions = serializeEnvironmentVars({
// Use the package.json file in the root folder, as it has the current version information. NODE_ENV: NodeEnvironment,
authentikProjectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], { CWD: process.cwd(),
encoding: "utf8", AK_API_BASE_PATH: process.env.AK_API_BASE_PATH,
}).replace("\n", ""); WATCHER_URL: process.env.WATCHER_URL,
} catch (_error) { });
// We probably don't have a .git folder, which could happen in container builds.
}
const packageJSONPath = path.join(authentikProjectRoot, "./package.json"); const patternflyPath = resolvePackage("@patternfly/patternfly");
const rootPackage = JSON.parse(readFileSync(packageJSONPath, "utf8"));
const NODE_ENV = process.env.NODE_ENV || "development";
const AK_API_BASE_PATH = process.env.AK_API_BASE_PATH || "";
const environmentVars = new Map([
["NODE_ENV", NODE_ENV],
["CWD", cwd()],
["AK_API_BASE_PATH", AK_API_BASE_PATH],
]);
const definitions = Object.fromEntries(
Array.from(environmentVars).map(([key, value]) => {
return [`process.env.${key}`, JSON.stringify(value)];
}),
);
/** /**
* All is magic is just to make sure the assets are copied into the right places. This is a very * @type {Readonly<BuildOptions>}
* stripped down version of what the rollup-copy-plugin does, without any of the features we don't
* use, and using globSync instead of globby since we already had globSync lying around thanks to
* Typescript. If there's a third argument in an array entry, it's used to replace the internal path
* before concatenating it all together as the destination target.
* @type {Array<[string, string, string?]>}
*/
const assetsFileMappings = [
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
["src/common/styles/**", "."],
["src/assets/images/**", "./assets/images"],
["./icons/*", "./assets/icons"],
];
/**
* @param {string} filePath
*/
const isFile = (filePath) => statSync(filePath).isFile();
/**
* @param {string} src Source file
* @param {string} dest Destination folder
* @param {string} [strip] Path to strip from the source file
*/
function nameCopyTarget(src, dest, strip) {
const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
return [src, target];
}
for (const [source, rawdest, strip] of assetsFileMappings) {
const matchedPaths = globSync(source);
const dest = path.join("dist", rawdest);
const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
for (const [src, dest] of copyTargets) {
if (isFile(src)) {
mkdirSync(path.dirname(dest), { recursive: true });
copyFileSync(src, dest);
}
}
}
/**
* @typedef {[source: string, destination: string]} EntryPoint
*/
/**
* This starts the definitions used for esbuild: Our targets, our arguments, the function for
* running a build, and three options for building: watching, building, and building the proxy.
* Ordered by largest to smallest interface to build even faster
*
* @type {EntryPoint[]}
*/
const entryPoints = [
["admin/AdminInterface/AdminInterface.ts", "admin"],
["user/UserInterface.ts", "user"],
["flow/FlowInterface.ts", "flow"],
["standalone/api-browser/index.ts", "standalone/api-browser"],
["rac/index.ts", "rac"],
["standalone/loading/index.ts", "standalone/loading"],
["polyfill/poly.ts", "."],
];
/**
* @type {import("esbuild").BuildOptions}
*/ */
const BASE_ESBUILD_OPTIONS = { const BASE_ESBUILD_OPTIONS = {
entryNames: `[dir]/[name]-${readBuildIdentifier()}`,
chunkNames: "[dir]/chunks/[name]-[hash]",
assetNames: "assets/[dir]/[name]-[hash]",
publicPath: path.join("/static", DistDirectoryName),
outdir: DistDirectory,
bundle: true, bundle: true,
write: true, write: true,
sourcemap: true, sourcemap: true,
minify: NODE_ENV === "production", minify: NodeEnvironment === "production",
legalComments: "external",
splitting: true, splitting: true,
treeShaking: true, treeShaking: true,
external: ["*.woff", "*.woff2"], external: ["*.woff", "*.woff2"],
tsconfig: "./tsconfig.json", tsconfig: path.resolve(PackageRoot, "tsconfig.build.json"),
loader: { loader: {
".css": "text", ".css": "text",
}, },
plugins: [ plugins: [
copy({
assets: [
{
from: path.join(patternflyPath, "patternfly.min.css"),
to: ".",
},
{
from: path.join(patternflyPath, "assets", "**"),
to: "./assets",
},
{
from: path.resolve(PackageRoot, "src", "common", "styles", "**"),
to: ".",
},
{
from: path.resolve(PackageRoot, "src", "assets", "images", "**"),
to: "./assets/images",
},
{
from: path.resolve(PackageRoot, "icons", "*"),
to: "./assets/icons",
},
],
}),
polyfillNode({ polyfillNode({
polyfills: { polyfills: {
path: true, path: true,
}, },
}), }),
mdxPlugin({ mdxPlugin({
root: authentikProjectRoot, root: MonoRepoRoot,
}), }),
], ],
define: definitions, define: definitions,
@ -147,69 +102,42 @@ const BASE_ESBUILD_OPTIONS = {
}, },
}; };
/** async function cleanDistDirectory() {
* Creates a version ID for the build. const timerLabel = `${logPrefix} ♻️ Cleaning previous builds...`;
* @returns {string}
*/
function composeVersionID() {
const { version } = rootPackage;
const buildHash = process.env.GIT_BUILD_HASH;
if (buildHash) { console.time(timerLabel);
return `${version}+${buildHash}`;
}
return version; await fs.rm(DistDirectory, {
recursive: true,
force: true,
});
await fs.mkdir(DistDirectory, {
recursive: true,
});
console.timeEnd(timerLabel);
} }
/** /**
* Build a single entry point. * Creates an ESBuild options, extending the base options with the given overrides.
* *
* @param {EntryPoint} buildTarget * @param {BuildOptions} overrides
* @param {Partial<esbuild.BuildOptions>} [overrides] * @returns {BuildOptions}
* @throws {Error} on build failure
*/ */
function createEntryPointOptions([source, dest], overrides = {}) { export function createESBuildOptions(overrides) {
const outdir = path.join(__dirname, "..", "dist", dest);
/** /**
* @type {esbuild.BuildOptions} * @type {BuildOptions}
*/ */
const mergedOptions = deepmerge(BASE_ESBUILD_OPTIONS, overrides);
const entryPointConfig = { return mergedOptions;
entryPoints: [`./src/${source}`],
entryNames: `[dir]/[name]-${composeVersionID()}`,
publicPath: path.join("/static", "dist", dest),
outdir,
};
/**
* @type {esbuild.BuildOptions}
*/
const mergedConfig = deepmerge(BASE_ESBUILD_OPTIONS, entryPointConfig, overrides);
return mergedConfig;
}
/**
* Build all entry points in parallel.
*
* @param {EntryPoint[]} entryPoints
* @returns {Promise<esbuild.BuildResult[]>}
*/
async function buildParallel(entryPoints) {
return Promise.all(
entryPoints.map((entryPoint) => {
return esbuild.build(createEntryPointOptions(entryPoint));
}),
);
} }
function doHelp() { function doHelp() {
console.log(`Build the authentik UI console.log(`Build the authentik UI
options: options:
-w, --watch: Build all ${entryPoints.length} interfaces -w, --watch: Build all interfaces
-p, --proxy: Build only the polyfills and the loading application -p, --proxy: Build only the polyfills and the loading application
-h, --help: This help message -h, --help: This help message
`); `);
@ -218,36 +146,37 @@ function doHelp() {
} }
async function doWatch() { async function doWatch() {
console.log("Watching all entry points..."); console.group(`${logPrefix} 🤖 Watching entry points`);
const wathcherPorts = await findFreePorts(entryPoints.length); const entryPoints = Object.entries(EntryPoint).map(([entrypointID, target]) => {
console.log(entrypointID);
const buildContexts = await Promise.all( return target;
entryPoints.map((entryPoint, i) => { });
const port = wathcherPorts[i];
const serverURL = new URL(`http://localhost:${port}/events`);
return esbuild.context( console.groupEnd();
createEntryPointOptions(entryPoint, {
plugins: [ const wathcherPorts = await findFreePorts(1);
buildObserverPlugin({ const port = wathcherPorts[0];
serverURL, const serverURL = new URL(`http://localhost:${port}/events`);
logPrefix: entryPoint[1],
relativeRoot: path.join(__dirname, ".."), const buildOptions = createESBuildOptions({
}), entryPoints,
], plugins: [
define: { liveReloadPlugin({
...definitions, serverURL,
"process.env.WATCHER_URL": JSON.stringify(serverURL.toString()), relativeRoot: PackageRoot,
}, }),
}), ],
); define: serializeEnvironmentVars({
WATCHER_URL: serverURL.toString(),
}), }),
); });
await Promise.all(buildContexts.map((context) => context.rebuild())); const buildContext = await esbuild.context(buildOptions);
await Promise.allSettled(buildContexts.map((context) => context.watch())); await buildContext.rebuild();
await buildContext.watch();
return /** @type {Promise<void>} */ ( return /** @type {Promise<void>} */ (
new Promise((resolve) => { new Promise((resolve) => {
@ -259,15 +188,34 @@ async function doWatch() {
} }
async function doBuild() { async function doBuild() {
console.log("Building all entry points"); console.group(`${logPrefix} 🚀 Building entry points:`);
return buildParallel(entryPoints); const entryPoints = Object.entries(EntryPoint).map(([entrypointID, target]) => {
console.log(entrypointID);
return target;
});
console.groupEnd();
const buildOptions = createESBuildOptions({
entryPoints,
});
await esbuild.build(buildOptions);
console.log("Build complete");
} }
async function doProxy() { async function doProxy() {
return buildParallel( const entryPoints = [EntryPoint.StandaloneLoading];
entryPoints.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)),
); const buildOptions = createESBuildOptions({
entryPoints,
});
await esbuild.build(buildOptions);
console.log("Proxy build complete");
} }
async function delegateCommand() { async function delegateCommand() {
@ -289,12 +237,16 @@ async function delegateCommand() {
} }
} }
await delegateCommand() await cleanDistDirectory()
.then(() => { // ---
console.log("Build complete"); .then(() =>
process.exit(0); delegateCommand()
}) .then(() => {
.catch((error) => { console.log("Build complete");
console.error(error); process.exit(0);
process.exit(1); })
}); .catch((error) => {
console.error(error);
process.exit(1);
}),
);

View File

@ -1,5 +1,5 @@
import { execSync } from "child_process"; import { execSync } from "node:child_process";
import path from "path"; import path from "node:path";
const projectRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).replace( const projectRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).replace(
"\n", "\n",

View File

@ -1,56 +0,0 @@
import { execFileSync } from "child_process";
import { ESLint } from "eslint";
import fs from "fs";
import path from "path";
import process from "process";
import { fileURLToPath } from "url";
function changedFiles() {
const gitStatus = execFileSync("git", ["diff", "--name-only", "HEAD"], { encoding: "utf8" });
const gitUntracked = execFileSync("git", ["ls-files", "--others", "--exclude-standard"], {
encoding: "utf8",
});
const changed = gitStatus
.split("\n")
.filter((line) => line.trim().substring(0, 4) === "web/")
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
.map((line) => line.substring(4))
.filter((line) => fs.existsSync(line));
const untracked = gitUntracked
.split("\n")
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
.filter((line) => fs.existsSync(line));
const sourceFiles = [...changed, ...untracked].filter((line) => /^src\//.test(line));
const scriptFiles = [...changed, ...untracked].filter(
(line) => /^scripts\//.test(line) || !/^src\//.test(line),
);
return [...sourceFiles, ...scriptFiles];
}
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const projectRoot = path.join(__dirname, "..");
process.chdir(projectRoot);
const hasFlag = (flags) => process.argv.length > 1 && flags.includes(process.argv[2]);
const [configFile, files] = hasFlag(["-n", "--nightmare"])
? [path.join(__dirname, "eslint.nightmare.mjs"), changedFiles()]
: hasFlag(["-p", "--precommit"])
? [path.join(__dirname, "eslint.precommit.mjs"), changedFiles()]
: [path.join(projectRoot, "eslint.config.mjs"), ["."]];
const eslint = new ESLint({
overrideConfigFile: configFile,
warnIgnored: false,
});
const results = await eslint.lintFiles(files);
const formatter = await eslint.loadFormatter("stylish");
const resultText = formatter.format(results);
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
console.log(resultText);
process.exit(errors > 1 ? 1 : 0);

View File

@ -1,217 +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";
const MAX_DEPTH = 4;
const MAX_NESTED_CALLBACKS = 4;
const MAX_PARAMS = 5;
// Waiting for SonarJS to be compatible
// const MAX_COGNITIVE_COMPLEXITY = 9;
const rules = {
"accessor-pairs": "error",
"array-callback-return": "error",
"block-scoped-var": "error",
"consistent-return": "error",
"consistent-this": ["error", "that"],
"curly": ["error", "all"],
"dot-notation": [
"error",
{
allowKeywords: true,
},
],
"eqeqeq": "error",
"func-names": "error",
"guard-for-in": "error",
"max-depth": ["error", MAX_DEPTH],
"max-nested-callbacks": ["error", MAX_NESTED_CALLBACKS],
"max-params": ["error", MAX_PARAMS],
"new-cap": "error",
"no-alert": "error",
"no-array-constructor": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-div-regex": "error",
"no-dupe-args": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-else-return": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-function": "error",
"no-labels": "error",
"no-eq-null": "error",
"no-eval": "error",
"no-ex-assign": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-boolean-cast": "error",
"no-extra-label": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-implied-eval": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-inner-declarations": ["error", "functions"],
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-iterator": "error",
"no-invalid-this": "error",
"no-label-var": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": ["error", { ignore: [0, 1, -1] }],
"no-multi-str": "error",
"no-negated-condition": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-obj-calls": "error",
"no-octal": "error",
"no-octal-escape": "error",
"no-param-reassign": "error",
"no-proto": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-restricted-syntax": ["error", "WithStatement"],
"no-script-url": "error",
"no-self-assign": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef": "error",
"no-undef-init": "error",
"no-unexpected-multiline": "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-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-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"radix": "error",
"require-yield": "error",
"strict": ["error", "global"],
"use-isnan": "error",
"valid-typeof": "error",
"vars-on-top": "error",
"yoda": ["error", "never"],
"no-unused-vars": "off",
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
// SonarJS is not yet compatible with ESLint 9. Commenting these out
// until it is.
// "sonarjs/cognitive-complexity": ["off", MAX_COGNITIVE_COMPLEXITY],
// "sonarjs/no-duplicate-string": "off",
// "sonarjs/no-nested-template-literals": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
};
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/",
".wireit/",
"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/",
"src/**/*.test.ts",
],
},
eslint.configs.recommended,
wcconf.configs["flat/recommended"],
litconf.configs["flat/recommended"],
...tseslint.configs.recommended,
// sonar.configs.recommended,
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.browser,
},
},
files: ["src/**"],
rules,
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
},
},
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
rules,
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
...globals.jest,
},
},
files: ["src/**/*.test.ts"],
rules,
},
];

View File

@ -1,87 +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/",
".wireit/",
"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,
// sonar.configs.recommended,
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
},
files: ["src/**"],
rules: {
"no-unused-vars": "off",
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
// SonarJS is not yet compatible with ESLint 9. Commenting these out
// until it is.
// "sonarjs/cognitive-complexity": ["off", 9],
// "sonarjs/no-duplicate-string": "off",
// "sonarjs/no-nested-template-literals": "off",
"@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,
},
},
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
rules: {
"no-unused-vars": "off",
"no-console": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
];

View File

@ -1,7 +1,7 @@
import { type KnipConfig } from "knip"; import { type KnipConfig } from "knip";
const config: KnipConfig = { const config: KnipConfig = {
"entry": [ entry: [
"./src/admin/AdminInterface/AdminInterface.ts", "./src/admin/AdminInterface/AdminInterface.ts",
"./src/user/UserInterface.ts", "./src/user/UserInterface.ts",
"./src/flow/FlowInterface.ts", "./src/flow/FlowInterface.ts",
@ -10,18 +10,18 @@ const config: KnipConfig = {
"./src/standalone/loading/index.ts", "./src/standalone/loading/index.ts",
"./src/polyfill/poly.ts", "./src/polyfill/poly.ts",
], ],
"project": ["src/**/*.ts", "src/**/*.js", "./scripts/*.mjs", ".storybook/*.ts"], project: ["src/**/*.ts", "src/**/*.js", "./scripts/*.mjs", ".storybook/*.ts"],
// "ignore": ["src/**/*.test.ts", "src/**/*.stories.ts"], // "ignore": ["src/**/*.test.ts", "src/**/*.stories.ts"],
// Prevent Knip from complaining about web components, which export their classes but also // Prevent Knip from complaining about web components, which export their classes but also
// export their registration, and we don't always use both. // export their registration, and we don't always use both.
"ignoreExportsUsedInFile": true, ignoreExportsUsedInFile: true,
"typescript": { typescript: {
config: ["tsconfig.json"], config: ["tsconfig.json"],
}, },
"wireit": { wireit: {
config: ["package.json"], config: ["package.json"],
}, },
"storybook": { storybook: {
config: [".storybook/{main,test-runner}.{js,ts}"], config: [".storybook/{main,test-runner}.{js,ts}"],
entry: [ entry: [
".storybook/{manager,preview}.{js,jsx,ts,tsx}", ".storybook/{manager,preview}.{js,jsx,ts,tsx}",
@ -29,7 +29,7 @@ const config: KnipConfig = {
], ],
project: [".storybook/**/*.{js,jsx,ts,tsx}"], project: [".storybook/**/*.{js,jsx,ts,tsx}"],
}, },
"eslint": { eslint: {
entry: [ entry: [
"eslint.config.mjs", "eslint.config.mjs",
"scripts/eslint.precommit.mjs", "scripts/eslint.precommit.mjs",

View File

@ -1,16 +1,18 @@
import { readFileSync } from "fs"; import { readFileSync } from "node:fs";
import path from "path"; import path from "node:path";
import { fileURLToPath } from "node:url";
import pseudolocale from "pseudolocale"; import pseudolocale from "pseudolocale";
import { fileURLToPath } from "url";
import { makeFormatter } from "@lit/localize-tools/lib/formatters/index.js"; import { makeFormatter } from "@lit/localize-tools/lib/formatters/index.js";
import { sortProgramMessages } from "@lit/localize-tools/lib/messages.js"; import { sortProgramMessages } from "@lit/localize-tools/lib/messages.js";
import { TransformLitLocalizer } from "@lit/localize-tools/lib/modes/transform.js"; import { TransformLitLocalizer } from "@lit/localize-tools/lib/modes/transform.js";
const __dirname = fileURLToPath(new URL(".", import.meta.url)); const relativeDirname = fileURLToPath(new URL(".", import.meta.url));
const pseudoLocale = "pseudo-LOCALE"; const pseudoLocale = "pseudo-LOCALE";
const targetLocales = [pseudoLocale]; const targetLocales = [pseudoLocale];
const baseConfig = JSON.parse(readFileSync(path.join(__dirname, "../lit-localize.json"), "utf-8")); const baseConfig = JSON.parse(
readFileSync(path.join(relativeDirname, "../lit-localize.json"), "utf-8"),
);
// Need to make some internal specifications to satisfy the transformer. It doesn't actually matter // Need to make some internal specifications to satisfy the transformer. It doesn't actually matter
// which Localizer we use (transformer or runtime), because all of the functionality we care about // which Localizer we use (transformer or runtime), because all of the functionality we care about
@ -19,7 +21,7 @@ const baseConfig = JSON.parse(readFileSync(path.join(__dirname, "../lit-localize
const config = { const config = {
...baseConfig, ...baseConfig,
baseDir: path.join(__dirname, ".."), baseDir: path.join(relativeDirname, ".."),
targetLocales, targetLocales,
output: { output: {
...baseConfig.output, ...baseConfig.output,
@ -41,4 +43,8 @@ const translations = messages.map(pseudoMessagify);
const sorted = sortProgramMessages([...messages]); const sorted = sortProgramMessages([...messages]);
const formatter = makeFormatter(config); const formatter = makeFormatter(config);
formatter.writeOutput(sorted, new Map([[pseudoLocale, translations]])); formatter.writeOutput(
sorted,
// @ts-expect-error Needs typing.
new Map([[pseudoLocale, translations]]),
);

View File

@ -2,9 +2,9 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { VERSION } from "@goauthentik/common/constants"; import { VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { globalAK } from "@goauthentik/common/global";
import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/EmptyState";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton"; import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
import { WithBrandConfig } from "@goauthentik/elements/mixins/brand";
import { WithLicenseSummary } from "@goauthentik/elements/mixins/license";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand"; import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -59,7 +59,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
renderModal() { renderModal() {
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle; let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle;
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) { if (this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed) {
product += ` ${msg("Enterprise")}`; product += ` ${msg("Enterprise")}`;
} }
return html`<div return html`<div

View File

@ -4,8 +4,8 @@ import { AKElement } from "@goauthentik/elements/Base";
import { import {
CapabilitiesEnum, CapabilitiesEnum,
WithCapabilitiesConfig, WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider"; } from "@goauthentik/elements/mixins/capabilities";
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider"; import { WithVersion } from "@goauthentik/elements/mixins/version";
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle"; import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
import { spread } from "@open-wc/lit-helpers"; import { spread } from "@open-wc/lit-helpers";
@ -13,6 +13,7 @@ import { spread } from "@open-wc/lit-helpers";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { TemplateResult, html, nothing } from "lit"; import { TemplateResult, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { map } from "lit/directives/map.js"; import { map } from "lit/directives/map.js";
import { UiThemeEnum } from "@goauthentik/api"; import { UiThemeEnum } from "@goauthentik/api";
@ -71,10 +72,12 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(WithVersion(AKElement
render() { render() {
return html` return html`
<ak-sidebar <ak-sidebar
class="pf-c-page__sidebar ${this.open ? "pf-m-expanded" : "pf-m-collapsed"} ${this class=${classMap({
.activeTheme === UiThemeEnum.Light "pf-c-page__sidebar": true,
? "pf-m-light" "pf-m-expanded": this.open,
: ""}" "pf-m-collapsed": !this.open,
"pf-m-light": this.colorScheme === UiThemeEnum.Light,
})}
> >
${this.renderSidebarItems()} ${this.renderSidebarItems()}
</ak-sidebar> </ak-sidebar>

View File

@ -8,7 +8,7 @@ import {
import { configureSentry } from "@goauthentik/common/sentry"; import { configureSentry } from "@goauthentik/common/sentry";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { WebsocketClient } from "@goauthentik/common/ws"; import { WebsocketClient } from "@goauthentik/common/ws";
import { AuthenticatedInterface } from "@goauthentik/elements/Interface"; import { AKAuthenticatedInterfaceElement } from "@goauthentik/elements/Interface";
import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/elements/ak-locale-context";
import "@goauthentik/elements/banner/EnterpriseStatusBanner"; import "@goauthentik/elements/banner/EnterpriseStatusBanner";
import "@goauthentik/elements/banner/VersionBanner"; import "@goauthentik/elements/banner/VersionBanner";
@ -30,12 +30,12 @@ import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { SessionUser, UiThemeEnum } from "@goauthentik/api"; import { SessionUser } from "@goauthentik/api";
import "./AdminSidebar"; import "./AdminSidebar";
@customElement("ak-interface-admin") @customElement("ak-interface-admin")
export class AdminInterface extends AuthenticatedInterface { export class AdminInterface extends AKAuthenticatedInterfaceElement {
@property({ type: Boolean }) @property({ type: Boolean })
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false); notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
@ -123,15 +123,17 @@ export class AdminInterface extends AuthenticatedInterface {
super.connectedCallback(); super.connectedCallback();
if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) { 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);
} }
} }
render(): TemplateResult { render(): TemplateResult {
const sidebarClasses = { const sidebarClasses = {
"pf-m-light": this.activeTheme === UiThemeEnum.Light, "pf-m-light": this.colorScheme === "light",
}; };
const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen; const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen;

View File

@ -1,5 +1,5 @@
import { AdminInterface } from "./AdminInterface"; import { AdminInterface } from "./index.entrypoint";
import "./AdminInterface"; import "./index.entrypoint";
export { AdminInterface }; export { AdminInterface };
export default AdminInterface; export default AdminInterface;

View File

@ -11,11 +11,11 @@ import "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
import { VERSION } from "@goauthentik/common/constants"; import { VERSION } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
import "@goauthentik/elements/PageHeader"; import "@goauthentik/elements/PageHeader";
import "@goauthentik/elements/cards/AggregatePromiseCard"; import "@goauthentik/elements/cards/AggregatePromiseCard";
import "@goauthentik/elements/cards/QuickActionsCard.js"; import "@goauthentik/elements/cards/QuickActionsCard.js";
import type { QuickAction } from "@goauthentik/elements/cards/QuickActionsCard.js"; import type { QuickAction } from "@goauthentik/elements/cards/QuickActionsCard.js";
import { WithLicenseSummary, isEnterpriseLicense } from "@goauthentik/elements/mixins/license.js";
import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
import { msg, str } from "@lit/localize"; import { msg, str } from "@lit/localize";
@ -164,15 +164,16 @@ export class AdminOverviewPage extends AdminOverviewBase {
} }
renderCards() { renderCards() {
const isEnterprise = this.hasEnterpriseLicense; const enterprise = isEnterpriseLicense(this.licenseSummary);
const classes = { const classes = {
"card-container": true, "card-container": true,
"pf-l-grid__item": true, "pf-l-grid__item": true,
"pf-m-6-col": true, "pf-m-6-col": true,
"pf-m-4-col-on-md": !isEnterprise, "pf-m-4-col-on-md": !enterprise,
"pf-m-4-col-on-xl": !isEnterprise, "pf-m-4-col-on-xl": !enterprise,
"pf-m-3-col-on-md": isEnterprise, "pf-m-3-col-on-md": enterprise,
"pf-m-3-col-on-xl": isEnterprise, "pf-m-3-col-on-xl": enterprise,
}; };
return html`<div class=${classMap(classes)}> return html`<div class=${classMap(classes)}>
@ -184,7 +185,7 @@ export class AdminOverviewPage extends AdminOverviewBase {
<div class=${classMap(classes)}> <div class=${classMap(classes)}>
<ak-admin-status-card-workers> </ak-admin-status-card-workers> <ak-admin-status-card-workers> </ak-admin-status-card-workers>
</div> </div>
${isEnterprise ${enterprise
? html` <div class=${classMap(classes)}> ? html` <div class=${classMap(classes)}>
<ak-admin-fips-status-system> </ak-admin-fips-status-system> <ak-admin-fips-status-system> </ak-admin-fips-status-system>
</div>` </div>`

View File

@ -207,8 +207,8 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
.row=${(f?: FooterLink) => .row=${(f?: FooterLink) =>
akFooterLinkInput({ akFooterLinkInput({
".footerLink": f, ".footerLink": f,
"style": "width: 100%", style: "width: 100%",
"name": "footer-link", name: "footer-link",
} as unknown as IFooterLinkInput)} } as unknown as IFooterLinkInput)}
> >
</ak-array-input> </ak-array-input>

View File

@ -8,10 +8,6 @@ import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input"; import "@goauthentik/components/ak-textarea-input";
import "@goauthentik/elements/Alert.js"; import "@goauthentik/elements/Alert.js";
import {
CapabilitiesEnum,
WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/ModalForm"; import "@goauthentik/elements/forms/ModalForm";
@ -19,6 +15,10 @@ import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/ProxyForm"; import "@goauthentik/elements/forms/ProxyForm";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect"; import "@goauthentik/elements/forms/SearchSelect";
import {
CapabilitiesEnum,
WithCapabilitiesConfig,
} from "@goauthentik/elements/mixins/capabilities.js";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";

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