Compare commits
41 Commits
safari-fol
...
build-scri
Author | SHA1 | Date | |
---|---|---|---|
3d06924f42 | |||
c6aa792076 | |||
ee4792734e | |||
445f11ca6b | |||
8e4810fb20 | |||
96a122c5d1 | |||
3c6b8b10e5 | |||
15999caa5d | |||
57d8375de1 | |||
07ec787076 | |||
bc96bef097 | |||
28869858b5 | |||
cbc5a1c39d | |||
5f6b69c998 | |||
cf065db3d5 | |||
86c65325ce | |||
2b8e10e979 | |||
9298807275 | |||
ed56d6ac50 | |||
8c07b385ad | |||
880db7a86c | |||
99c1250ba5 | |||
5ce126ac83 | |||
dfa21d0725 | |||
e7e4af3894 | |||
931d6ec579 | |||
ff45acb25c | |||
c96557ff2d | |||
734feac4ae | |||
b17a9ed145 | |||
2bef7695db | |||
df472dd842 | |||
98d201d34c | |||
47e89602ab | |||
ceb0851452 | |||
cac2593658 | |||
1c9705bfaa | |||
9e2566cec4 | |||
5bdef1c4f6 | |||
ae41ccd862 | |||
337956672f |
@ -1,6 +1,5 @@
|
||||
---
|
||||
name: "Prepare docker environment variables"
|
||||
description: "Prepare docker environment variables"
|
||||
|
||||
inputs:
|
||||
image-name:
|
||||
|
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: "Setup authentik testing environment"
|
||||
name: Setup authentik testing environment
|
||||
description: "Setup authentik testing environment"
|
||||
|
||||
inputs:
|
||||
|
@ -42,7 +42,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-qemu-action@v3.6.0
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
- name: Prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
@ -64,12 +64,12 @@ jobs:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: make empty clients
|
||||
- name: Make empty clients
|
||||
if: ${{ inputs.release }}
|
||||
run: |
|
||||
mkdir -p ./gen-ts-api
|
||||
mkdir -p ./gen-go-api
|
||||
- name: generate ts client
|
||||
- name: Generate TypeScript API Client
|
||||
if: ${{ !inputs.release }}
|
||||
run: make gen-client-ts
|
||||
- name: Build Docker Image
|
||||
|
@ -49,7 +49,7 @@ jobs:
|
||||
shouldPush: ${{ steps.ev.outputs.shouldPush }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: prepare variables
|
||||
- name: Prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
@ -69,7 +69,7 @@ jobs:
|
||||
tag: ${{ fromJson(needs.get-tags.outputs.tags) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: prepare variables
|
||||
- name: Prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
|
4
.github/workflows/api-py-publish.yml
vendored
4
.github/workflows/api-py-publish.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Publish
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@ -14,6 +15,7 @@ jobs:
|
||||
steps:
|
||||
- id: generate_token
|
||||
uses: tibdex/github-app-token@v2
|
||||
name: Generate token
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
@ -30,7 +32,7 @@ jobs:
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version-file: "pyproject.toml"
|
||||
- name: Generate API Client
|
||||
- name: Generate Python API Client
|
||||
run: make gen-client-py
|
||||
- name: Publish package
|
||||
working-directory: gen-py-api/
|
||||
|
3
.github/workflows/api-ts-publish.yml
vendored
3
.github/workflows/api-ts-publish.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Publish
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -22,7 +23,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: web/package.json
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- name: Generate API Client
|
||||
- name: Generate TypeScript API Client
|
||||
run: make gen-client-ts
|
||||
- name: Publish package
|
||||
working-directory: gen-ts-api/
|
||||
|
2
.github/workflows/ci-aws-cfn.yml
vendored
2
.github/workflows/ci-aws-cfn.yml
vendored
@ -18,6 +18,7 @@ env:
|
||||
|
||||
jobs:
|
||||
check-changes-applied:
|
||||
name: Check changes applied
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -36,6 +37,7 @@ jobs:
|
||||
uv run make aws-cfn
|
||||
git diff --exit-code
|
||||
ci-aws-cfn-mark:
|
||||
name: CI AWS CloudFormation Mark
|
||||
if: always()
|
||||
needs:
|
||||
- check-changes-applied
|
||||
|
2
.github/workflows/ci-main-daily.yml
vendored
2
.github/workflows/ci-main-daily.yml
vendored
@ -9,6 +9,7 @@ on:
|
||||
|
||||
jobs:
|
||||
test-container:
|
||||
name: Test Container ${{ matrix.version }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -19,6 +20,7 @@ jobs:
|
||||
- version-2024-12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: ${{ matrix.version }} Setup
|
||||
- run: |
|
||||
current="$(pwd)"
|
||||
dir="/tmp/authentik/${{ matrix.version }}"
|
||||
|
53
.github/workflows/ci-main.yml
vendored
53
.github/workflows/ci-main.yml
vendored
@ -19,6 +19,7 @@ env:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -33,9 +34,10 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: run job
|
||||
- name: Run job ${{ matrix.job }}
|
||||
run: uv run make ci-${{ matrix.job }}
|
||||
test-migrations:
|
||||
name: Test Migrations
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -44,6 +46,7 @@ jobs:
|
||||
- name: run migrations
|
||||
run: uv run python -m lifecycle.migrate
|
||||
test-make-seed:
|
||||
name: Test Make Seed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: seed
|
||||
@ -52,7 +55,7 @@ jobs:
|
||||
outputs:
|
||||
seed: ${{ steps.seed.outputs.seed }}
|
||||
test-migrations-from-stable:
|
||||
name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
|
||||
name: Test Migrations From Stable - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
needs: test-make-seed
|
||||
@ -67,7 +70,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: checkout stable
|
||||
- name: Checkout Stable
|
||||
run: |
|
||||
# Copy current, latest config to local
|
||||
# Temporarly comment the .github backup while migrating to uv
|
||||
@ -84,9 +87,9 @@ jobs:
|
||||
with:
|
||||
postgresql_version: ${{ matrix.psql }}
|
||||
continue-on-error: true
|
||||
- name: run migrations to stable
|
||||
- name: Run migrations to stable
|
||||
run: poetry run python -m lifecycle.migrate
|
||||
- name: checkout current code
|
||||
- name: Checkout current code
|
||||
run: |
|
||||
set -x
|
||||
git fetch
|
||||
@ -97,10 +100,10 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
postgresql_version: ${{ matrix.psql }}
|
||||
- name: migrate to latest
|
||||
- name: Migrate to latest
|
||||
run: |
|
||||
uv run python -m lifecycle.migrate
|
||||
- name: run tests
|
||||
- name: Run tests
|
||||
env:
|
||||
# Test in the main database that we just migrated from the previous stable version
|
||||
AUTHENTIK_POSTGRESQL__TEST__NAME: authentik
|
||||
@ -110,7 +113,7 @@ jobs:
|
||||
run: |
|
||||
uv run make ci-test
|
||||
test-unittest:
|
||||
name: test-unittest - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
|
||||
name: Unit tests - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
needs: test-make-seed
|
||||
@ -123,7 +126,7 @@ jobs:
|
||||
run_id: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
- name: Setup authentik env (${{ matrix.psql }})
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
postgresql_version: ${{ matrix.psql }}
|
||||
@ -146,6 +149,7 @@ jobs:
|
||||
file: unittest.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
test-integration:
|
||||
name: Integration tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
@ -154,7 +158,7 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@v1.12.0
|
||||
- name: run integration
|
||||
- name: Run integration
|
||||
run: |
|
||||
uv run coverage run manage.py test tests/integration
|
||||
uv run coverage xml
|
||||
@ -170,34 +174,34 @@ jobs:
|
||||
file: unittest.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
test-e2e:
|
||||
name: test-e2e (${{ matrix.job.name }})
|
||||
name: Test E2E (${{ matrix.job.name }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- name: proxy
|
||||
- name: Proxy Provider
|
||||
glob: tests/e2e/test_provider_proxy*
|
||||
- name: oauth
|
||||
- name: OAuth2 Provider
|
||||
glob: tests/e2e/test_provider_oauth2* tests/e2e/test_source_oauth*
|
||||
- name: oauth-oidc
|
||||
- name: OIDC Provider
|
||||
glob: tests/e2e/test_provider_oidc*
|
||||
- name: saml
|
||||
- name: SAML Provider
|
||||
glob: tests/e2e/test_provider_saml* tests/e2e/test_source_saml*
|
||||
- name: ldap
|
||||
- name: LDAP Provider
|
||||
glob: tests/e2e/test_provider_ldap* tests/e2e/test_source_ldap*
|
||||
- name: radius
|
||||
- name: RADIUS Provider
|
||||
glob: tests/e2e/test_provider_radius*
|
||||
- name: scim
|
||||
- name: SCIM Source
|
||||
glob: tests/e2e/test_source_scim*
|
||||
- name: flows
|
||||
- name: Flows
|
||||
glob: tests/e2e/test_flows*
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Setup e2e env (chrome, etc)
|
||||
- name: Setup E2E env (chrome, etc)
|
||||
run: |
|
||||
docker compose -f tests/e2e/docker-compose.yml up -d --quiet-pull
|
||||
- id: cache-web
|
||||
@ -205,14 +209,14 @@ jobs:
|
||||
with:
|
||||
path: web/dist
|
||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
|
||||
- name: prepare web ui
|
||||
- name: Prepare Web UI
|
||||
if: steps.cache-web.outputs.cache-hit != 'true'
|
||||
working-directory: web
|
||||
run: |
|
||||
npm ci
|
||||
make -C .. gen-client-ts
|
||||
npm run build
|
||||
- name: run e2e
|
||||
- name: Run E2E tests
|
||||
run: |
|
||||
uv run coverage run manage.py test ${{ matrix.job.glob }}
|
||||
uv run coverage xml
|
||||
@ -228,6 +232,7 @@ jobs:
|
||||
file: unittest.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
ci-core-mark:
|
||||
name: CI Core Mark
|
||||
if: always()
|
||||
needs:
|
||||
- lint
|
||||
@ -242,6 +247,7 @@ jobs:
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
build:
|
||||
name: Build
|
||||
permissions:
|
||||
# Needed to upload container images to ghcr.io
|
||||
packages: write
|
||||
@ -255,6 +261,7 @@ jobs:
|
||||
image_name: ghcr.io/goauthentik/dev-server
|
||||
release: false
|
||||
pr-comment:
|
||||
name: PR Comment
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
@ -267,7 +274,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: prepare variables
|
||||
- name: Prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
|
16
.github/workflows/ci-outpost.yml
vendored
16
.github/workflows/ci-outpost.yml
vendored
@ -14,6 +14,7 @@ on:
|
||||
|
||||
jobs:
|
||||
lint-golint:
|
||||
name: Lint Go
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -26,7 +27,7 @@ jobs:
|
||||
mkdir -p web/dist
|
||||
mkdir -p website/help
|
||||
touch web/dist/test website/help/test
|
||||
- name: Generate API
|
||||
- name: Generate Go API Client
|
||||
run: make gen-client-go
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v7
|
||||
@ -35,6 +36,7 @@ jobs:
|
||||
args: --timeout 5000s --verbose
|
||||
skip-cache: true
|
||||
test-unittest:
|
||||
name: Unit Test Go
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -43,12 +45,13 @@ jobs:
|
||||
go-version-file: "go.mod"
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Generate API
|
||||
- name: Generate Go API Client
|
||||
run: make gen-client-go
|
||||
- name: Go unittests
|
||||
run: |
|
||||
go test -timeout 0 -v -race -coverprofile=coverage.out -covermode=atomic -cover ./...
|
||||
ci-outpost-mark:
|
||||
name: CI Outpost Mark
|
||||
if: always()
|
||||
needs:
|
||||
- lint-golint
|
||||
@ -59,6 +62,7 @@ jobs:
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
build-container:
|
||||
name: Build Container
|
||||
timeout-minutes: 120
|
||||
needs:
|
||||
- ci-outpost-mark
|
||||
@ -85,7 +89,7 @@ jobs:
|
||||
uses: docker/setup-qemu-action@v3.6.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
- name: Prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
@ -99,7 +103,7 @@ jobs:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Generate API
|
||||
- name: Generate Go API Client
|
||||
run: make gen-client-go
|
||||
- name: Build Docker Image
|
||||
id: push
|
||||
@ -122,6 +126,7 @@ jobs:
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
build-binary:
|
||||
name: Build Binary
|
||||
timeout-minutes: 120
|
||||
needs:
|
||||
- ci-outpost-mark
|
||||
@ -140,7 +145,6 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- uses: actions/setup-node@v4
|
||||
@ -148,7 +152,7 @@ jobs:
|
||||
node-version-file: web/package.json
|
||||
cache: "npm"
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: Generate API
|
||||
- name: Generate Go API Client
|
||||
run: make gen-client-go
|
||||
- name: Build web
|
||||
working-directory: web/
|
||||
|
5
.github/workflows/ci-web.yml
vendored
5
.github/workflows/ci-web.yml
vendored
@ -13,6 +13,7 @@ on:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -39,12 +40,13 @@ jobs:
|
||||
- working-directory: ${{ matrix.project }}/
|
||||
run: |
|
||||
npm ci
|
||||
- name: Generate API
|
||||
- name: Generate TypeScript API
|
||||
run: make gen-client-ts
|
||||
- name: Lint
|
||||
working-directory: ${{ matrix.project }}/
|
||||
run: npm run ${{ matrix.command }}
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -61,6 +63,7 @@ jobs:
|
||||
working-directory: web/
|
||||
run: npm run build
|
||||
ci-web-mark:
|
||||
name: CI Web Mark
|
||||
if: always()
|
||||
needs:
|
||||
- build
|
||||
|
19
.github/workflows/ci-website.yml
vendored
19
.github/workflows/ci-website.yml
vendored
@ -13,6 +13,7 @@ on:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -24,10 +25,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- working-directory: website/
|
||||
run: npm ci
|
||||
- name: Lint
|
||||
- name: Lint ${{ matrix.command }}
|
||||
working-directory: website/
|
||||
run: npm run ${{ matrix.command }}
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -37,18 +39,14 @@ jobs:
|
||||
cache: "npm"
|
||||
cache-dependency-path: website/package-lock.json
|
||||
- working-directory: website/
|
||||
name: Install dependencies
|
||||
run: npm ci
|
||||
- name: test
|
||||
- name: Documentation test
|
||||
working-directory: website/
|
||||
run: npm test
|
||||
build:
|
||||
name: Build Docs
|
||||
runs-on: ubuntu-latest
|
||||
name: ${{ matrix.job }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
@ -58,10 +56,11 @@ jobs:
|
||||
cache-dependency-path: website/package-lock.json
|
||||
- working-directory: website/
|
||||
run: npm ci
|
||||
- name: build
|
||||
- name: Build Docusaurus
|
||||
working-directory: website/
|
||||
run: npm run ${{ matrix.job }}
|
||||
run: npm run build
|
||||
ci-website-mark:
|
||||
name: Mark CI Website
|
||||
if: always()
|
||||
needs:
|
||||
- lint
|
||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: "CodeQL"
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -11,6 +11,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Update WebAuthn MDS
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
1
.github/workflows/gha-cache-cleanup.yml
vendored
1
.github/workflows/gha-cache-cleanup.yml
vendored
@ -12,6 +12,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Cleanup Cache
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
|
2
.github/workflows/image-compress.yml
vendored
2
.github/workflows/image-compress.yml
vendored
@ -20,7 +20,7 @@ on:
|
||||
|
||||
jobs:
|
||||
compress:
|
||||
name: compress
|
||||
name: Compress Docker images
|
||||
runs-on: ubuntu-latest
|
||||
# Don't run on forks. Token will not be available. Will run on main and open a PR anyway
|
||||
if: |
|
||||
|
13
.github/workflows/packages-npm-publish.yml
vendored
13
.github/workflows/packages-npm-publish.yml
vendored
@ -3,10 +3,10 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- packages/docusaurus-config
|
||||
- packages/eslint-config
|
||||
- packages/prettier-config
|
||||
- packages/tsconfig
|
||||
- packages/docusaurus-config/**
|
||||
- packages/eslint-config/**
|
||||
- packages/prettier-config/**
|
||||
- packages/tsconfig/**
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
publish:
|
||||
@ -25,16 +25,17 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-node@v4
|
||||
name: Setup Node.js
|
||||
with:
|
||||
node-version-file: packages/${{ matrix.package }}/package.json
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- name: Get changed files
|
||||
- name: Changed files (${{ matrix.package }})
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c
|
||||
with:
|
||||
files: |
|
||||
packages/${{ matrix.package }}/package.json
|
||||
- name: Publish package
|
||||
- name: Publish package (${{ matrix.package }})
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
working-directory: packages/${{ matrix.package}}
|
||||
run: |
|
||||
|
5
.github/workflows/publish-source-docs.yml
vendored
5
.github/workflows/publish-source-docs.yml
vendored
@ -12,6 +12,7 @@ env:
|
||||
|
||||
jobs:
|
||||
publish-source-docs:
|
||||
name: Publish
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
@ -19,11 +20,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: generate docs
|
||||
- name: Generate docs
|
||||
run: |
|
||||
uv run make migrate
|
||||
uv run ak build_source_docs
|
||||
- name: Publish
|
||||
- name: Deploy to Netlify
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=source_docs --prod
|
||||
|
1
.github/workflows/release-next-branch.yml
vendored
1
.github/workflows/release-next-branch.yml
vendored
@ -11,6 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
update-next:
|
||||
name: Update Next Branch
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: internal-production
|
||||
|
12
.github/workflows/release-publish.yml
vendored
12
.github/workflows/release-publish.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build-server:
|
||||
name: Build server
|
||||
uses: ./.github/workflows/_reusable-docker-build.yaml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
@ -21,6 +22,7 @@ jobs:
|
||||
registry_dockerhub: true
|
||||
registry_ghcr: true
|
||||
build-outpost:
|
||||
name: Build outpost
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload container images to ghcr.io
|
||||
@ -45,14 +47,14 @@ jobs:
|
||||
uses: docker/setup-qemu-action@v3.6.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
- name: Prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }}
|
||||
- name: make empty clients
|
||||
- name: Make empty clients
|
||||
run: |
|
||||
mkdir -p ./gen-ts-api
|
||||
mkdir -p ./gen-go-api
|
||||
@ -85,6 +87,7 @@ jobs:
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
build-outpost-binary:
|
||||
name: Build outpost binary
|
||||
timeout-minutes: 120
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@ -129,6 +132,7 @@ jobs:
|
||||
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||
tag: ${{ github.ref }}
|
||||
upload-aws-cfn-template:
|
||||
name: Upload AWS CloudFormation template
|
||||
permissions:
|
||||
# Needed for AWS login
|
||||
id-token: write
|
||||
@ -150,6 +154,7 @@ jobs:
|
||||
aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml
|
||||
aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml
|
||||
test-release:
|
||||
name: Test release
|
||||
needs:
|
||||
- build-server
|
||||
- build-outpost
|
||||
@ -166,6 +171,7 @@ jobs:
|
||||
docker compose start postgresql redis
|
||||
docker compose run -u root server test-all
|
||||
sentry-release:
|
||||
name: Sentry release
|
||||
needs:
|
||||
- build-server
|
||||
- build-outpost
|
||||
@ -173,7 +179,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: prepare variables
|
||||
- name: Prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
|
2
.github/workflows/release-tag.yml
vendored
2
.github/workflows/release-tag.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- name: prepare variables
|
||||
- name: Prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
|
1
.github/workflows/repo-mirror.yml
vendored
1
.github/workflows/repo-mirror.yml
vendored
@ -4,6 +4,7 @@ on: [push, delete]
|
||||
|
||||
jobs:
|
||||
to_internal:
|
||||
name: Mirror to internal repository
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
1
.github/workflows/repo-stale.yml
vendored
1
.github/workflows/repo-stale.yml
vendored
@ -11,6 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
name: Stale Issues
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
1
.github/workflows/translation-advice.yml
vendored
1
.github/workflows/translation-advice.yml
vendored
@ -16,6 +16,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
post-comment:
|
||||
name: Post Comment
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Find Comment
|
||||
|
@ -16,6 +16,7 @@ env:
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
name: Compile Translations
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: generate_token
|
||||
@ -32,12 +33,12 @@ jobs:
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Generate API
|
||||
- name: Generate TypeScript API
|
||||
run: make gen-client-ts
|
||||
- name: run extract
|
||||
- name: Extract Translations
|
||||
run: |
|
||||
uv run make i18n-extract
|
||||
- name: run compile
|
||||
- name: Compile Messages
|
||||
run: |
|
||||
uv run ak compilemessages
|
||||
make web-check-compile
|
||||
|
1
.github/workflows/translation-rename.yml
vendored
1
.github/workflows/translation-rename.yml
vendored
@ -12,6 +12,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
rename_pr:
|
||||
name: Rename PR
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}}
|
||||
steps:
|
||||
|
@ -94,7 +94,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 5: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.6.16 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.6.17 AS uv
|
||||
# Stage 6: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base
|
||||
|
||||
|
@ -99,6 +99,7 @@ class RBACPermissionViewSet(ReadOnlyModelViewSet):
|
||||
filterset_class = PermissionFilter
|
||||
permission_classes = [IsAuthenticated]
|
||||
search_fields = [
|
||||
"name",
|
||||
"codename",
|
||||
"content_type__model",
|
||||
"content_type__app_label",
|
||||
|
File diff suppressed because one or more lines are too long
@ -62,7 +62,8 @@ function prepare_debug {
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends krb5-kdc krb5-user krb5-admin-server libkrb5-dev gcc
|
||||
VIRTUAL_ENV=/ak-root/.venv uv sync --frozen
|
||||
source "${VENV_PATH}/bin/activate"
|
||||
uv sync --active --frozen
|
||||
touch /unittest.xml
|
||||
chown authentik:authentik /unittest.xml
|
||||
}
|
||||
|
Binary file not shown.
@ -8,7 +8,6 @@
|
||||
# Jens L. <jens@goauthentik.io>, 2022
|
||||
# Lars Lehmann <lars@lars-lehmann.net>, 2023
|
||||
# Johannes —/—, 2023
|
||||
# Dominic Wagner <mail@dominic-wagner.de>, 2023
|
||||
# fde4f289d99ed356ff5cfdb762dc44aa_a8a971d, 2023
|
||||
# Christian Foellmann <foellmann@foe-services.de>, 2023
|
||||
# kidhab, 2023
|
||||
@ -30,17 +29,18 @@
|
||||
# Alexander Möbius, 2025
|
||||
# Jonas, 2025
|
||||
# Niklas Kroese, 2025
|
||||
# 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025
|
||||
# datenschmutz, 2025
|
||||
# 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025
|
||||
# Dominic Wagner <mail@dominic-wagner.de>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: datenschmutz, 2025\n"
|
||||
"Last-Translator: Dominic Wagner <mail@dominic-wagner.de>, 2025\n"
|
||||
"Language-Team: German (https://app.transifex.com/authentik/teams/119923/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -214,6 +214,7 @@ msgid "User's display name."
|
||||
msgstr "Anzeigename"
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "Benutzer"
|
||||
|
||||
@ -402,6 +403,18 @@ msgstr "Eigenschaft"
|
||||
msgid "Property Mappings"
|
||||
msgstr "Eigenschaften"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr "Sitzung"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr "Sitzungen"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "Authentifizierte Sitzung"
|
||||
@ -511,6 +524,38 @@ msgstr "Lizenzverwendung"
|
||||
msgid "License Usage Records"
|
||||
msgstr "Lizenzverwendung Aufzeichnungen"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Zu prüfender Feldschlüssel, die in den Aufforderungsstufen definierten "
|
||||
"Feldschlüssel sind verfügbar."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Passwort nicht im Kontext festgelegt"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "Enterprise ist erforderlich, um auf diese Funktion zuzugreifen."
|
||||
@ -1303,12 +1348,6 @@ msgstr "Richtlinien Cache Metriken anzeigen"
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr "Richtlinien Cache Metriken löschen"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Zu prüfender Feldschlüssel, die in den Aufforderungsstufen definierten "
|
||||
"Feldschlüssel sind verfügbar."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr "Wie häufig der Passwort-Hash auf haveibeenpwned vertreten sein darf"
|
||||
@ -1320,10 +1359,6 @@ msgstr ""
|
||||
"Die Richtlinie wird verweigert, wenn die zxcvbn-Bewertung gleich oder "
|
||||
"kleiner diesem Wert ist."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Passwort nicht im Kontext festgelegt"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr "Ungültiges Passwort."
|
||||
@ -1365,20 +1400,6 @@ msgstr "Reputationswert"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "Reputationswert"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "Erlaubnis verweigert"
|
||||
@ -2208,6 +2229,10 @@ msgstr "Rolle"
|
||||
msgid "Roles"
|
||||
msgstr "Rollen"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "Systemberechtigung"
|
||||
@ -2478,6 +2503,22 @@ msgstr "LDAP Quelle Eigenschafts-Zuordnung"
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr "LDAP Quelle Eigenschafts-Zuordnungen"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr ""
|
||||
@ -2487,6 +2528,14 @@ msgstr ""
|
||||
msgid "No token received."
|
||||
msgstr "Kein Token empfangen."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "Token-URL anfordern"
|
||||
@ -2528,6 +2577,12 @@ msgstr ""
|
||||
msgid "Additional Scopes"
|
||||
msgstr "zusätzliche Scopes"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "Outh Quelle"
|
||||
@ -3434,6 +3489,12 @@ msgstr ""
|
||||
"Wenn aktiviert, wird die Phase auch dann erfolgreich abgeschlossen und "
|
||||
"fortgesetzt, wenn falsche Benutzerdaten eingegeben wurden."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr "Optionaler Registrierungs-Flow, der unten auf der Seite verlinkt ist."
|
||||
@ -3826,6 +3887,14 @@ msgstr ""
|
||||
"Die Ereignisse werden nach dieser Dauer gelöscht (Format: "
|
||||
"Wochen=3;Tage=2;Stunden=3,Sekunden=2)."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr ""
|
||||
|
Binary file not shown.
@ -15,7 +15,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n"
|
||||
@ -190,6 +190,7 @@ msgid "User's display name."
|
||||
msgstr "Nombre para mostrar del usuario."
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "Usuario"
|
||||
|
||||
@ -378,6 +379,18 @@ msgstr "Asignación de Propiedades"
|
||||
msgid "Property Mappings"
|
||||
msgstr "Asignaciones de Propiedades"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr "Sesión"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr "Sesiones"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "Sesión autenticada"
|
||||
@ -485,6 +498,38 @@ msgstr "Uso de Licencias"
|
||||
msgid "License Usage Records"
|
||||
msgstr "Registro de Uso de Licencias"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Clave de campo a verificar, las claves de campo definidas en las etapas de "
|
||||
"Solicitud están disponibles."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "La contraseña no se ha establecido en contexto"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "Se requiere de Enterprise para acceder esta característica."
|
||||
@ -1268,12 +1313,6 @@ msgstr "Ver las métricas de caché de la Política"
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr "Borrar las métricas de caché de la Política"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Clave de campo a verificar, las claves de campo definidas en las etapas de "
|
||||
"Solicitud están disponibles."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr ""
|
||||
@ -1287,10 +1326,6 @@ msgstr ""
|
||||
"Si la puntuación zxcvbn es igual o menor que este valor, la política "
|
||||
"fallará."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "La contraseña no se ha establecido en contexto"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr "Contraseña inválida."
|
||||
@ -1332,20 +1367,6 @@ msgstr "Puntuación de Reputacion"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "Puntuaciones de Reputacion"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "Permiso denegado"
|
||||
@ -2175,6 +2196,10 @@ msgstr "Rol"
|
||||
msgid "Roles"
|
||||
msgstr "Roles"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "Permiso de sistema"
|
||||
@ -2443,6 +2468,22 @@ msgstr "Asignación de Propiedades de Fuente de LDAP"
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr "Asignaciones de Propiedades de Fuente de LDAP"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "La contraseña no coincide con la complejidad de Active Directory."
|
||||
@ -2451,6 +2492,14 @@ msgstr "La contraseña no coincide con la complejidad de Active Directory."
|
||||
msgid "No token received."
|
||||
msgstr "No se recibió ningún token."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "Solicitar URL de token"
|
||||
@ -2491,6 +2540,12 @@ msgstr "URL utilizada por authentik para obtener información del usuario."
|
||||
msgid "Additional Scopes"
|
||||
msgstr "Alcances Adicionales"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "Fuente de OAuth"
|
||||
@ -3407,6 +3462,12 @@ msgstr ""
|
||||
"Cuando está habilitado, la etapa tendrá éxito y continuará incluso cuando se"
|
||||
" ingrese información de usuario incorrecta."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr ""
|
||||
@ -3794,6 +3855,14 @@ msgstr ""
|
||||
"Los Eventos serán eliminados después de este periodo. (Formato: "
|
||||
"weeks=3;days=2;hours=3,seconds=2)."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr ""
|
||||
|
Binary file not shown.
@ -15,7 +15,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Ville Ranki, 2025\n"
|
||||
"Language-Team: Finnish (https://app.transifex.com/authentik/teams/119923/fi/)\n"
|
||||
@ -186,6 +186,7 @@ msgid "User's display name."
|
||||
msgstr "Käyttäjän näytettävä nimi"
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "Käyttäjä"
|
||||
|
||||
@ -371,6 +372,18 @@ msgstr "Ominaisuuskytkentä"
|
||||
msgid "Property Mappings"
|
||||
msgstr "Ominaisuuskytkennät"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr "Istunto"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "Autentikoitu istunto"
|
||||
@ -478,6 +491,38 @@ msgstr "Lisenssin käyttö"
|
||||
msgid "License Usage Records"
|
||||
msgstr "Lisenssin käyttötiedot"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Kentän avain, joka tarkistetaan. Kysymysvaiheissa määritellyt kenttien "
|
||||
"avaimet ovat käytettävissä."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Salasanaa ei ole asetettu kontekstissa"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "Tämän ominaisuuden käyttöön tarvitaan Enterprise-versiota."
|
||||
@ -1251,12 +1296,6 @@ msgstr "Näytä käytäntövälimuistitilastot"
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr "Tyhjennä käytäntövälimuistitilastot"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Kentän avain, joka tarkistetaan. Kysymysvaiheissa määritellyt kenttien "
|
||||
"avaimet ovat käytettävissä."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr ""
|
||||
@ -1269,10 +1308,6 @@ msgstr ""
|
||||
"Jos zxcvbn-pistemäärä on tämä arvo tai pienempi, käytännön suorittaminen "
|
||||
"epäonnistuu."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Salasanaa ei ole asetettu kontekstissa"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr "Virheellinen salasana."
|
||||
@ -1314,20 +1349,6 @@ msgstr "Mainepistemäärä"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "Mainepistemäärät"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "Käyttö evätty"
|
||||
@ -2155,6 +2176,10 @@ msgstr "Rooli"
|
||||
msgid "Roles"
|
||||
msgstr "Roolit"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "Järjestelmän käyttöoikeus"
|
||||
@ -2420,6 +2445,22 @@ msgstr "LDAP-lähteen ominaisuuskytkentä"
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr "LDAP-lähteen ominaisuuskytkennät"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "Salasana ei vastaa Active Directoryn monimutkaisuusmääritystä."
|
||||
@ -2428,6 +2469,14 @@ msgstr "Salasana ei vastaa Active Directoryn monimutkaisuusmääritystä."
|
||||
msgid "No token received."
|
||||
msgstr "Tunnistetta ei saatu."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "Pyyntötunnisteen URL"
|
||||
@ -2468,6 +2517,12 @@ msgstr "URL, jota authentik käyttää käyttäjätiedon hakemiseksi."
|
||||
msgid "Additional Scopes"
|
||||
msgstr "Lisäkäyttöalueet"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "OAuth-lähde"
|
||||
@ -3377,6 +3432,12 @@ msgstr ""
|
||||
"Kun tämä on käytössä, vaihe onnistuu ja suoritus jatkuu, vaikka olisi "
|
||||
"syötetty virheelliset käyttäjätiedot."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr ""
|
||||
@ -3754,6 +3815,14 @@ msgstr ""
|
||||
"Tapahtumat poistetaan tämän ajan jälkeen. (Muoto: "
|
||||
"weeks=3;days=2;hours=3;seconds=2)."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr ""
|
||||
|
Binary file not shown.
@ -12,17 +12,17 @@
|
||||
# tmassimi, 2024
|
||||
# Marc Schmitt, 2024
|
||||
# albanobattistella <albanobattistella@gmail.com>, 2024
|
||||
# Matteo Piccina <altermatte@gmail.com>, 2025
|
||||
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
|
||||
# Matteo Piccina <altermatte@gmail.com>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
|
||||
"Last-Translator: Matteo Piccina <altermatte@gmail.com>, 2025\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -194,6 +194,7 @@ msgid "User's display name."
|
||||
msgstr "Nome visualizzato dell'utente."
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "Utente"
|
||||
|
||||
@ -380,6 +381,18 @@ msgstr "Mappatura della proprietà"
|
||||
msgid "Property Mappings"
|
||||
msgstr "Mappatura delle proprietà"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr "Sessione"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr "Sessioni"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "Sessione Autenticata"
|
||||
@ -487,6 +500,38 @@ msgstr "Utilizzo della licenza"
|
||||
msgid "License Usage Records"
|
||||
msgstr "Registri sull'utilizzo della licenza"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Chiave di campo da verificare, sono disponibili le chiavi di campo definite "
|
||||
"nelle fasi Richiesta."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Password non impostata nel contesto"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "Versione Enterprise richiesta per accedere a questa funzione"
|
||||
@ -1274,12 +1319,6 @@ msgstr "Visualizza le metriche della cache della Policy"
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr "Cancellare le metriche della cache della Policy"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Chiave di campo da verificare, sono disponibili le chiavi di campo definite "
|
||||
"nelle fasi Richiesta."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr ""
|
||||
@ -1292,10 +1331,6 @@ msgstr ""
|
||||
"Se il punteggio zxcvbn è inferiore o uguale a questo valore, il criterio non"
|
||||
" verrà soddisfatto."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Password non impostata nel contesto"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr "Password invalida."
|
||||
@ -1337,22 +1372,6 @@ msgstr "Punteggio di reputazione"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "Punteggi di reputazione"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr "In attesa di autenticazione..."
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
"Ti stai già autenticando in un'altra scheda. Questa pagina si aggiornerà una"
|
||||
" volta completata l'autenticazione."
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr "Autenticati in questa scheda"
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "Permesso negato"
|
||||
@ -2182,6 +2201,10 @@ msgstr "Ruolo"
|
||||
msgid "Roles"
|
||||
msgstr "Ruoli"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "Autorizzazione di sistema"
|
||||
@ -2452,6 +2475,22 @@ msgstr "Mappatura delle proprietà sorgente LDAP"
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr "Mappature delle proprietà della sorgente LDAP"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "La password non soddisfa la complessità Active Directory."
|
||||
@ -2460,6 +2499,14 @@ msgstr "La password non soddisfa la complessità Active Directory."
|
||||
msgid "No token received."
|
||||
msgstr "Nessun token ricevuto."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "URL di Richiesta Token"
|
||||
@ -2500,6 +2547,12 @@ msgstr "URL utilizzato da authentik per ottenere le informazioni dell'utente."
|
||||
msgid "Additional Scopes"
|
||||
msgstr "Ambiti aggiuntivi"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "Sorgente OAuth"
|
||||
@ -3426,6 +3479,12 @@ msgstr ""
|
||||
"Quando abilitato, la fase avrà successo e continuerà anche quando vengono "
|
||||
"inserite informazioni utente errate."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr "Flusso di iscrizione opzionale, che è collegato in fondo alla pagina."
|
||||
@ -3812,6 +3871,14 @@ msgstr ""
|
||||
"Gli eventi saranno cancellati dopo questa durata. (Formato: "
|
||||
"weeks=3;days=2;hours=3,seconds=2)."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr ""
|
||||
|
@ -12,7 +12,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-31 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: NavyStack, 2023\n"
|
||||
"Language-Team: Korean (https://app.transifex.com/authentik/teams/119923/ko/)\n"
|
||||
@ -176,6 +176,7 @@ msgid "User's display name."
|
||||
msgstr "사용자의 표시 이름"
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "사용자"
|
||||
|
||||
@ -344,6 +345,18 @@ msgstr "속성 매핑"
|
||||
msgid "Property Mappings"
|
||||
msgstr "속성 매핑"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "인증된 세션"
|
||||
@ -447,6 +460,36 @@ msgstr "라이선스 사용"
|
||||
msgid "License Usage Records"
|
||||
msgstr "라이선스 사용 기록"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr "확인하려는 필드 키, 프롬프트 스테이지에서 정의된 필드 키를 사용할 수 있습니다."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "비밀번호가 컨텍스트에 설정되지 않음"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr ""
|
||||
@ -1182,10 +1225,6 @@ msgstr "정책의 캐시 메트릭 보기"
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr "정책의 캐시 메트릭 삭제"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr "확인하려는 필드 키, 프롬프트 스테이지에서 정의된 필드 키를 사용할 수 있습니다."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr "비밀번호 해시가 허용되는 해시 횟수"
|
||||
@ -1195,10 +1234,6 @@ msgid ""
|
||||
"If the zxcvbn score is equal or less than this value, the policy will fail."
|
||||
msgstr "만약 zxcvbn 점수가 이 값과 같거나 이 값보다 작다면, 정책이 실패합니다."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "비밀번호가 컨텍스트에 설정되지 않음"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr ""
|
||||
@ -1240,20 +1275,6 @@ msgstr "평판 점수"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "평판 점수"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "권한 거부됨"
|
||||
@ -2013,6 +2034,10 @@ msgstr "역할"
|
||||
msgid "Roles"
|
||||
msgstr "역할"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "시스템 권한"
|
||||
@ -2231,6 +2256,13 @@ msgid ""
|
||||
"enabled on a single LDAP source."
|
||||
msgstr "사용자가 비밀번호를 변경하면 LDAP로 다시 동기화합니다. 이 기능은 단일의 LDAP 소스에서만 활성화할 수 있습니다."
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid ""
|
||||
"Lookup group membership based on a user attribute instead of a group "
|
||||
"attribute. This allows nested group resolution on systems like FreeIPA and "
|
||||
"Active Directory"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "LDAP Source"
|
||||
msgstr "LDAP 소스"
|
||||
@ -2247,6 +2279,22 @@ msgstr ""
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "비밀번호가 Active Directory 복잡도와 일치하지 않습니다."
|
||||
@ -2255,6 +2303,14 @@ msgstr "비밀번호가 Active Directory 복잡도와 일치하지 않습니다.
|
||||
msgid "No token received."
|
||||
msgstr "수신된 토큰이 없습니다."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "토큰 요청 URL"
|
||||
@ -2293,6 +2349,12 @@ msgstr "사용자 정보를 가져오기 위해 authentik에서 사용하는 URL
|
||||
msgid "Additional Scopes"
|
||||
msgstr "추가 스코프"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "OAuth 소스"
|
||||
@ -3149,6 +3211,12 @@ msgid ""
|
||||
"info is entered."
|
||||
msgstr "활성화되면 잘못된 사용자 정보가 입력되더라도 단계가 성공하고 계속됩니다."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr "페이지 하단에 링크된, 선택적 등록 플로우를 참조하세요."
|
||||
@ -3500,6 +3568,14 @@ msgid ""
|
||||
"weeks=3;days=2;hours=3,seconds=2)."
|
||||
msgstr "이 기간이 지나면 이벤트가 삭제됩니다. (서식: hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr ""
|
||||
|
@ -7,18 +7,18 @@
|
||||
# Bartosz Karpiński, 2023
|
||||
# Michał Jastrzębski, 2024
|
||||
# Tomci 12 <drizztes@gmail.com>, 2024
|
||||
# Darek “NeroPcStation” NeroPcStation <dareknowacki2001@gmail.com>, 2024
|
||||
# Marc Schmitt, 2025
|
||||
# Jens L. <jens@goauthentik.io>, 2025
|
||||
# Darek “NeroPcStation” NeroPcStation <dareknowacki2001@gmail.com>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Darek “NeroPcStation” NeroPcStation <dareknowacki2001@gmail.com>, 2025\n"
|
||||
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
|
||||
"Language-Team: Polish (https://app.transifex.com/authentik/teams/119923/pl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -189,6 +189,7 @@ msgid "User's display name."
|
||||
msgstr "Wyświetlana nazwa użytkownika."
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "Użytkownik"
|
||||
|
||||
@ -371,6 +372,18 @@ msgstr "Mapowanie właściwości"
|
||||
msgid "Property Mappings"
|
||||
msgstr "Mapowanie właściwości"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr "Sesja"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr "Sesje"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "Sesja uwierzytelniona"
|
||||
@ -479,6 +492,38 @@ msgstr "Wykorzystanie licencji"
|
||||
msgid "License Usage Records"
|
||||
msgstr "Rejestr wykorzystania licencji"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Klucz pola do sprawdzenia, dostępne są klucze pola zdefiniowane w etapach "
|
||||
"monitu."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Hasło nie jest ustawione w kontekście"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "Wymagane jest konto Enterprise, aby uzyskać dostęp do tej funkcji."
|
||||
@ -1257,12 +1302,6 @@ msgstr "Wyświetl metryki pamięci podręcznej Zasady"
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr "Wyczyść metryki pamięci podręcznej Zasady"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Klucz pola do sprawdzenia, dostępne są klucze pola zdefiniowane w etapach "
|
||||
"monitu."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr "Ile razy skrót hasła może być na haveibeenpwned"
|
||||
@ -1274,10 +1313,6 @@ msgstr ""
|
||||
"Jeśli wynik zxcvbn jest równy lub mniejszy od tej wartości, zasada zakończy "
|
||||
"się niepowodzeniem."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Hasło nie jest ustawione w kontekście"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr ""
|
||||
@ -1319,20 +1354,6 @@ msgstr "Punkty reputacji"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "Punkty reputacji"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr "Oczekiwanie na uwierzytelnienie..."
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "Odmowa uprawnień"
|
||||
@ -2141,6 +2162,10 @@ msgstr "Rola"
|
||||
msgid "Roles"
|
||||
msgstr "Role"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "Uprawnienie systemowe"
|
||||
@ -2390,6 +2415,22 @@ msgstr ""
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "Hasło nie pasuje do złożoności usługi Active Directory."
|
||||
@ -2398,6 +2439,14 @@ msgstr "Hasło nie pasuje do złożoności usługi Active Directory."
|
||||
msgid "No token received."
|
||||
msgstr "Nie otrzymano tokena."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "URL żądania tokena"
|
||||
@ -2440,6 +2489,12 @@ msgstr "URL używany przez authentik do uzyskania informacji o użytkowniku."
|
||||
msgid "Additional Scopes"
|
||||
msgstr "Dodatkowe zakresy"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "Źródło OAuth"
|
||||
@ -3344,6 +3399,12 @@ msgstr ""
|
||||
"Po włączeniu tej opcji etap zakończy się powodzeniem i będzie kontynuowany "
|
||||
"nawet po wprowadzeniu nieprawidłowych danych użytkownika."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr ""
|
||||
@ -3727,6 +3788,14 @@ msgstr ""
|
||||
"Zdarzenia zostaną usunięte po upływie tego czasu. (Format: "
|
||||
"weeks=3;days=2;hours=3,seconds=2)."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr "Opcja ta konfiguruje łącza stopki na stronach wykonawców przepływu."
|
||||
|
@ -18,7 +18,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Gil Poiares-Oliveira, 2025\n"
|
||||
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/authentik/teams/119923/pt_BR/)\n"
|
||||
@ -192,6 +192,7 @@ msgid "User's display name."
|
||||
msgstr "Nome de exibição do usuário."
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "Usuário"
|
||||
|
||||
@ -376,6 +377,18 @@ msgstr "Mapeamento de propriedades"
|
||||
msgid "Property Mappings"
|
||||
msgstr "Mapeamentos de propriedades"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "Sessão Autenticada"
|
||||
@ -483,6 +496,38 @@ msgstr "Uso de licença"
|
||||
msgid "License Usage Records"
|
||||
msgstr "Registros de uso de licença"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Chave de campo para verificar, as chaves de campo definidas nos estágios de "
|
||||
"prompt estão disponíveis."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Senha não definida no contexto"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "Entrerprise é necessário para acessar essa funcionalidade"
|
||||
@ -1252,12 +1297,6 @@ msgstr ""
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Chave de campo para verificar, as chaves de campo definidas nos estágios de "
|
||||
"prompt estão disponíveis."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr "Quantas vezes o hash da senha pode estar em haveibeenpwned"
|
||||
@ -1268,10 +1307,6 @@ msgid ""
|
||||
msgstr ""
|
||||
"Se a pontuação zxcvbn for igual ou menor que esse valor, a política falhará."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Senha não definida no contexto"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr ""
|
||||
@ -1313,20 +1348,6 @@ msgstr "Pontuação de reputação"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "Pontuações de reputação"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "Permissão negada"
|
||||
@ -2141,6 +2162,10 @@ msgstr ""
|
||||
msgid "Roles"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "Permissão do sistema"
|
||||
@ -2387,6 +2412,22 @@ msgstr ""
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "A senha não corresponde à complexidade do Active Directory."
|
||||
@ -2395,6 +2436,14 @@ msgstr "A senha não corresponde à complexidade do Active Directory."
|
||||
msgid "No token received."
|
||||
msgstr "Nenhum token recebido."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "URL do token de solicitação"
|
||||
@ -2435,6 +2484,12 @@ msgstr "URL usado pelo authentik para obter informações do usuário."
|
||||
msgid "Additional Scopes"
|
||||
msgstr "Escopos Adicionais"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "Fonte OAuth"
|
||||
@ -3318,6 +3373,12 @@ msgid ""
|
||||
"info is entered."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
@ -3678,6 +3739,14 @@ msgstr ""
|
||||
"Os eventos serão excluídos após esta duração.(Formato: "
|
||||
"semanas=3;dias=2;horas=3,segundos=2)."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr ""
|
||||
|
@ -18,7 +18,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Marc Schmitt, 2025\n"
|
||||
"Language-Team: Russian (https://app.transifex.com/authentik/teams/119923/ru/)\n"
|
||||
@ -191,6 +191,7 @@ msgid "User's display name."
|
||||
msgstr "Отображаемое имя пользователя."
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "Пользователь"
|
||||
|
||||
@ -379,6 +380,18 @@ msgstr "Сопоставление свойств"
|
||||
msgid "Property Mappings"
|
||||
msgstr "Сопоставление свойств"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "Аутентифицированная Сессия"
|
||||
@ -487,6 +500,37 @@ msgstr "Использование лицензии"
|
||||
msgid "License Usage Records"
|
||||
msgstr "Записи использования лицензии"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Ключ поля для проверки, доступны ключи поля, определенные в этапах запроса."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Пароль не задан в контексте"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "Для доступа к этой функции требуется Enterprise."
|
||||
@ -1267,11 +1311,6 @@ msgstr "Просмотр показателей кэша политики"
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr "Очистка показателей кэша политики"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Ключ поля для проверки, доступны ключи поля, определенные в этапах запроса."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr "Как часто хэш пароля может быть представлен на haveibeenpwned"
|
||||
@ -1283,10 +1322,6 @@ msgstr ""
|
||||
"Если показатель zxcvbn равен или меньше этого значения, политика будет "
|
||||
"провалена."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Пароль не задан в контексте"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr "Неправильный пароль"
|
||||
@ -1328,20 +1363,6 @@ msgstr "Оценка репутации"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "Оценка репутации"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "Доступ запрещен"
|
||||
@ -2164,6 +2185,10 @@ msgstr "Роль"
|
||||
msgid "Roles"
|
||||
msgstr "Роли"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "Системное разрешение"
|
||||
@ -2421,6 +2446,22 @@ msgstr "Сопоставление свойства LDAP источника"
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr "Сопоставление свойств LDAP источника"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "Пароль не соответствует сложности Active Directory."
|
||||
@ -2429,6 +2470,14 @@ msgstr "Пароль не соответствует сложности Active D
|
||||
msgid "No token received."
|
||||
msgstr "Токен не был получен."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "URL-адрес запроса токена"
|
||||
@ -2471,6 +2520,12 @@ msgstr ""
|
||||
msgid "Additional Scopes"
|
||||
msgstr "Дополнительные области"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "Источник OAuth"
|
||||
@ -3376,6 +3431,12 @@ msgstr ""
|
||||
"При включении этап будет завершаться успешно и продолжаться даже в случае "
|
||||
"ввода неправильной информации о пользователе."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr ""
|
||||
@ -3767,6 +3828,14 @@ msgstr ""
|
||||
"По истечении этого времени события будут удалены. (Формат: недели=3; дни=2; "
|
||||
"часы=3, секунды=2)."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr ""
|
||||
|
Binary file not shown.
@ -13,7 +13,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-31 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
|
||||
"Language-Team: Turkish (https://app.transifex.com/authentik/teams/119923/tr/)\n"
|
||||
@ -187,6 +187,7 @@ msgid "User's display name."
|
||||
msgstr "Kullanıcının görünen adı."
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "Kullanıcı"
|
||||
|
||||
@ -372,6 +373,18 @@ msgstr "Özellik Eşleme"
|
||||
msgid "Property Mappings"
|
||||
msgstr "Özellik Eşlemeleri"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr "Oturum"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr "Oturumlar"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "Kimliği Doğrulanmış Oturum"
|
||||
@ -479,6 +492,38 @@ msgstr "Lisans Kullanımı"
|
||||
msgid "License Usage Records"
|
||||
msgstr "Lisans Kullanım Kayıtları"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Alan tuşu kontrol etmek için, İstem aşamalarında tanımlanan alan tuşları "
|
||||
"mevcuttur."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Parola bağlam içinde ayarlanmamış"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "Bu özelliğe erişmek için Kurumsal Paket gereklidir."
|
||||
@ -1253,12 +1298,6 @@ msgstr "İlke'nin önbellek ölçümlerini görüntüleme"
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr "İlke'nin önbellek ölçümlerini temizleyin"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr ""
|
||||
"Alan tuşu kontrol etmek için, İstem aşamalarında tanımlanan alan tuşları "
|
||||
"mevcuttur."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr ""
|
||||
@ -1271,10 +1310,6 @@ msgstr ""
|
||||
"Eğer zxcvbn puanı bu değere eşit veya daha az ise, politika başarısız "
|
||||
"olacaktır."
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "Parola bağlam içinde ayarlanmamış"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr ""
|
||||
@ -1316,20 +1351,6 @@ msgstr "İtibar Puanı"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "İtibar Puanları"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "İzin reddedildi"
|
||||
@ -2155,6 +2176,10 @@ msgstr "Rol"
|
||||
msgid "Roles"
|
||||
msgstr "Roller"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "Sistem yetkisi"
|
||||
@ -2398,6 +2423,13 @@ msgstr ""
|
||||
"Bir kullanıcı parolasını değiştirdiğinde, parolayı LDAP ile geri eşitleyin. "
|
||||
"Bu yalnızca tek bir LDAP kaynağında etkinleştirilebilir."
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid ""
|
||||
"Lookup group membership based on a user attribute instead of a group "
|
||||
"attribute. This allows nested group resolution on systems like FreeIPA and "
|
||||
"Active Directory"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "LDAP Source"
|
||||
msgstr "LDAP Kaynağı"
|
||||
@ -2414,6 +2446,22 @@ msgstr "LDAP Kaynak Özellik Eşlemesi"
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr "LDAP Kaynak Özellik Eşlemeleri"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "Parola Active Directory Karmaşıklığıyla eşleşmiyor."
|
||||
@ -2422,6 +2470,14 @@ msgstr "Parola Active Directory Karmaşıklığıyla eşleşmiyor."
|
||||
msgid "No token received."
|
||||
msgstr "Jeton alınmadı."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "Jeton URL'si İste"
|
||||
@ -2462,6 +2518,12 @@ msgstr "Kullanıcı bilgilerini almak için authentik tarafından kullanılan UR
|
||||
msgid "Additional Scopes"
|
||||
msgstr "Ek Kapsamlar"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "OAuth Kaynağı"
|
||||
@ -3360,6 +3422,12 @@ msgstr ""
|
||||
"Etkinleştirildiğinde, yanlış kullanıcı bilgisi girilse bile aşama başarılı "
|
||||
"olur ve devam eder."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr "Sayfanın alt kısmında bağlanan isteğe bağlı kayıt akışı."
|
||||
@ -3734,6 +3802,14 @@ msgstr ""
|
||||
"Olaylar bu süreden sonra silinecektir (Format: "
|
||||
"weeks=3;days=2;hours=3,seconds=2)."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr ""
|
||||
|
Binary file not shown.
@ -14,7 +14,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: 刘松, 2025\n"
|
||||
"Language-Team: Chinese (Taiwan) (https://app.transifex.com/authentik/teams/119923/zh_TW/)\n"
|
||||
@ -178,6 +178,7 @@ msgid "User's display name."
|
||||
msgstr "使用者的顯示名稱。"
|
||||
|
||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||
#: authentik/rbac/models.py
|
||||
msgid "User"
|
||||
msgstr "使用者"
|
||||
|
||||
@ -344,6 +345,18 @@ msgstr "屬性對應"
|
||||
msgid "Property Mappings"
|
||||
msgstr "屬性對應"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
msgstr "会话"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Sessions"
|
||||
msgstr "会话"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Authenticated Session"
|
||||
msgstr "已認證會談"
|
||||
@ -447,6 +460,36 @@ msgstr "授權使用情況"
|
||||
msgid "License Usage Records"
|
||||
msgstr "授權使用紀錄"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr "要檢查的欄位鍵,在提示階段中有可用的已定義欄位鍵。"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "未在上下文中設定密碼"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "企業版才能存取此功能。"
|
||||
@ -1176,10 +1219,6 @@ msgstr "檢視原則的快取指標"
|
||||
msgid "Clear Policy's cache metrics"
|
||||
msgstr "清除原則的快取指標"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||
msgstr "要檢查的欄位鍵,在提示階段中有可用的已定義欄位鍵。"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||
msgstr "密碼雜湊在 haveibeenpwned 上允許出現的次數"
|
||||
@ -1189,10 +1228,6 @@ msgid ""
|
||||
"If the zxcvbn score is equal or less than this value, the policy will fail."
|
||||
msgstr "如果 zxcvbn 分數等於或小於此值,則該政策將失敗。"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Password not set in context"
|
||||
msgstr "未在上下文中設定密碼"
|
||||
|
||||
#: authentik/policies/password/models.py
|
||||
msgid "Invalid password."
|
||||
msgstr ""
|
||||
@ -1234,20 +1269,6 @@ msgstr "信譽分數"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "信譽分數"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "權限不足。"
|
||||
@ -1999,6 +2020,10 @@ msgstr "角色"
|
||||
msgid "Roles"
|
||||
msgstr "角色"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
msgstr "系統權限"
|
||||
@ -2240,6 +2265,22 @@ msgstr ""
|
||||
msgid "LDAP Source Property Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "密碼不符合 Active Directory 的複雜性要求。"
|
||||
@ -2248,6 +2289,14 @@ msgstr "密碼不符合 Active Directory 的複雜性要求。"
|
||||
msgid "No token received."
|
||||
msgstr "未收到權杖。"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
msgstr "請求權杖的網址"
|
||||
@ -2286,6 +2335,12 @@ msgstr "authentik 用來擷取使用者資訊的網址。"
|
||||
msgid "Additional Scopes"
|
||||
msgstr "附加範圍"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
msgstr "OAuth 來源"
|
||||
@ -3137,6 +3192,12 @@ msgid ""
|
||||
"info is entered."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr "可選的註冊流程,連結在頁面的底部。"
|
||||
@ -3481,6 +3542,14 @@ msgid ""
|
||||
"weeks=3;days=2;hours=3,seconds=2)."
|
||||
msgstr "事件將在此期間後刪除。(格式:weeks=3;days=2;hours=3,seconds=2)"
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
msgstr ""
|
||||
|
12184
web/package-lock.json
generated
12184
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,8 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.5.7",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@goauthentik/api": "^2025.2.4-1745325566",
|
||||
"@goauthentik/api": "^2025.2.4-1745519715",
|
||||
"@lit-labs/ssr": "3.2.2",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
@ -53,7 +54,6 @@
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-mdx-frontmatter": "^5.0.0",
|
||||
"style-mod": "^4.1.2",
|
||||
"trusted-types": "^2.0.0",
|
||||
"ts-pattern": "^5.4.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"webcomponent-qr-code": "^1.2.0",
|
||||
|
@ -4,17 +4,13 @@ import { ROUTES } from "@goauthentik/admin/Routes";
|
||||
import {
|
||||
EVENT_API_DRAWER_TOGGLE,
|
||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||
EVENT_SIDEBAR_TOGGLE,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
|
||||
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";
|
||||
import "@goauthentik/elements/messages/MessageContainer";
|
||||
import "@goauthentik/elements/messages/MessageContainer";
|
||||
@ -25,32 +21,25 @@ import "@goauthentik/elements/router/RouterOutlet";
|
||||
import "@goauthentik/elements/sidebar/Sidebar";
|
||||
import "@goauthentik/elements/sidebar/SidebarItem";
|
||||
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
||||
import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { LicenseSummaryStatusEnum, SessionUser, UiThemeEnum } from "@goauthentik/api";
|
||||
import { SessionUser, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
import {
|
||||
AdminSidebarEnterpriseEntries,
|
||||
AdminSidebarEntries,
|
||||
renderSidebarItems,
|
||||
} from "./AdminSidebar.js";
|
||||
import "./AdminSidebar";
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
await import("@goauthentik/esbuild-plugin-live-reload/client");
|
||||
}
|
||||
|
||||
@customElement("ak-interface-admin")
|
||||
export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
//#region Properties
|
||||
|
||||
export class AdminInterface extends AuthenticatedInterface {
|
||||
@property({ type: Boolean })
|
||||
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
||||
|
||||
@ -65,29 +54,12 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
@query("ak-about-modal")
|
||||
aboutModal?: AboutModal;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public sidebarOpen: boolean;
|
||||
|
||||
#toggleSidebar = () => {
|
||||
this.sidebarOpen = !this.sidebarOpen;
|
||||
};
|
||||
|
||||
#sidebarMatcher: MediaQueryList;
|
||||
#sidebarListener = (event: MediaQueryListEvent) => {
|
||||
this.sidebarOpen = event.matches;
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Styles
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFPage,
|
||||
PFButton,
|
||||
PFDrawer,
|
||||
PFNav,
|
||||
css`
|
||||
.pf-c-page__main,
|
||||
.pf-c-drawer__content,
|
||||
@ -95,30 +67,23 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
z-index: auto !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pf-c-page {
|
||||
background-color: var(--pf-c-page--BackgroundColor) !important;
|
||||
}
|
||||
|
||||
:host([theme="dark"]) {
|
||||
/* Global page background colour */
|
||||
.pf-c-page {
|
||||
--pf-c-page--BackgroundColor: var(--ak-dark-background);
|
||||
}
|
||||
/* Global page background colour */
|
||||
:host([theme="dark"]) .pf-c-page {
|
||||
--pf-c-page--BackgroundColor: var(--ak-dark-background);
|
||||
}
|
||||
|
||||
ak-page-navbar {
|
||||
ak-enterprise-status,
|
||||
ak-version-banner {
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
.ak-sidebar {
|
||||
ak-admin-sidebar {
|
||||
grid-area: nav;
|
||||
}
|
||||
|
||||
.pf-c-drawer__panel {
|
||||
z-index: var(--pf-global--ZIndex--xl);
|
||||
}
|
||||
@ -126,23 +91,10 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
|
||||
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
|
||||
this.sidebarOpen = this.#sidebarMatcher.matches;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
|
||||
|
||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
||||
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
||||
updateURLParams({
|
||||
@ -156,14 +108,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
apiDrawerOpen: this.apiDrawerOpen,
|
||||
});
|
||||
});
|
||||
|
||||
this.#sidebarMatcher.addEventListener("change", this.#sidebarListener);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
|
||||
this.#sidebarMatcher.removeEventListener("change", this.#sidebarListener);
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
@ -174,7 +118,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
this.user.user.isSuperuser ||
|
||||
// TODO: somehow add `access_admin_interface` to the API schema
|
||||
this.user.user.systemPermissions.includes("access_admin_interface");
|
||||
|
||||
if (!canAccessAdmin && this.user.user.pk > 0) {
|
||||
window.location.assign("/if/user/");
|
||||
}
|
||||
@ -182,14 +125,10 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
|
||||
render(): TemplateResult {
|
||||
const sidebarClasses = {
|
||||
"pf-c-page__sidebar": true,
|
||||
"pf-m-light": this.activeTheme === UiThemeEnum.Light,
|
||||
"pf-m-expanded": this.sidebarOpen,
|
||||
"pf-m-collapsed": !this.sidebarOpen,
|
||||
};
|
||||
|
||||
const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen;
|
||||
|
||||
const drawerClasses = {
|
||||
"pf-m-expanded": drawerOpen,
|
||||
"pf-m-collapsed": !drawerOpen,
|
||||
@ -197,18 +136,11 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
|
||||
return html` <ak-locale-context>
|
||||
<div class="pf-c-page">
|
||||
<ak-page-navbar>
|
||||
<ak-version-banner></ak-version-banner>
|
||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||
</ak-page-navbar>
|
||||
|
||||
<ak-sidebar class="${classMap(sidebarClasses)}">
|
||||
${renderSidebarItems(AdminSidebarEntries)}
|
||||
${this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed
|
||||
? renderSidebarItems(AdminSidebarEnterpriseEntries)
|
||||
: nothing}
|
||||
</ak-sidebar>
|
||||
|
||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||
<ak-version-banner></ak-version-banner>
|
||||
<ak-admin-sidebar
|
||||
class="pf-c-page__sidebar ${classMap(sidebarClasses)}"
|
||||
></ak-admin-sidebar>
|
||||
<div class="pf-c-page__drawer">
|
||||
<div class="pf-c-drawer ${classMap(drawerClasses)}">
|
||||
<div class="pf-c-drawer__main">
|
||||
|
@ -1,97 +1,186 @@
|
||||
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import {
|
||||
CapabilitiesEnum,
|
||||
WithCapabilitiesConfig,
|
||||
} from "@goauthentik/elements/Interface/capabilitiesProvider";
|
||||
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
|
||||
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
|
||||
import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { map } from "lit/directives/map.js";
|
||||
|
||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
||||
// commonplace and singular enough to merit its own handler.
|
||||
type SidebarEntry = [
|
||||
path: string | null,
|
||||
label: string,
|
||||
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
|
||||
children?: SidebarEntry[],
|
||||
];
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
import type { SessionUser, UserSelf } from "@goauthentik/api";
|
||||
|
||||
/**
|
||||
* Recursively renders a sidebar entry.
|
||||
*/
|
||||
export function renderSidebarItem([
|
||||
path,
|
||||
label,
|
||||
attributes,
|
||||
children,
|
||||
]: SidebarEntry): TemplateResult {
|
||||
const properties = Array.isArray(attributes)
|
||||
? { ".activeWhen": attributes }
|
||||
: (attributes ?? {});
|
||||
@customElement("ak-admin-sidebar")
|
||||
export class AkAdminSidebar extends WithCapabilitiesConfig(WithVersion(AKElement)) {
|
||||
@property({ type: Boolean, reflect: true })
|
||||
open = true;
|
||||
|
||||
if (path) {
|
||||
properties.path = path;
|
||||
@state()
|
||||
impersonation: UserSelf["username"] | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
me().then((user: SessionUser) => {
|
||||
this.impersonation = user.original ? user.user.username : null;
|
||||
});
|
||||
this.toggleOpen = this.toggleOpen.bind(this);
|
||||
this.checkWidth = this.checkWidth.bind(this);
|
||||
}
|
||||
|
||||
return html`<ak-sidebar-item ${spread(properties)}>
|
||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
||||
${children ? renderSidebarItems(children) : nothing}
|
||||
</ak-sidebar-item>`;
|
||||
// This has to be a bound method so the event listener can be removed on disconnection as
|
||||
// needed.
|
||||
toggleOpen() {
|
||||
this.open = !this.open;
|
||||
}
|
||||
|
||||
checkWidth() {
|
||||
// This works just fine, but it assumes that the `--ak-sidebar--minimum-auto-width` is in
|
||||
// REMs. If that changes, this code will have to be adjusted as well.
|
||||
const minWidth =
|
||||
parseFloat(getRootStyle("--ak-sidebar--minimum-auto-width")) *
|
||||
parseFloat(getRootStyle("font-size"));
|
||||
this.open = window.innerWidth >= minWidth;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
|
||||
window.addEventListener("resize", this.checkWidth);
|
||||
// After connecting to the DOM, we can now perform this check to see if the sidebar should
|
||||
// be open by default.
|
||||
this.checkWidth();
|
||||
}
|
||||
|
||||
// The symmetry (☟, ☝) here is critical in that you want to start adding these handlers after
|
||||
// connection, and removing them before disconnection.
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
|
||||
window.removeEventListener("resize", this.checkWidth);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ak-sidebar
|
||||
class="pf-c-page__sidebar ${this.open ? "pf-m-expanded" : "pf-m-collapsed"} ${this
|
||||
.activeTheme === UiThemeEnum.Light
|
||||
? "pf-m-light"
|
||||
: ""}"
|
||||
>
|
||||
${this.renderSidebarItems()}
|
||||
</ak-sidebar>
|
||||
`;
|
||||
}
|
||||
|
||||
updated() {
|
||||
// This is permissible as`:host.classList` is not one of the properties Lit uses as a
|
||||
// scheduling trigger. This sort of shenanigans can trigger an loop, in that it will trigger
|
||||
// a browser reflow, which may trigger some other styling the application is monitoring,
|
||||
// triggering a re-render which triggers a browser reflow, ad infinitum. But we've been
|
||||
// living with that since jQuery, and it's both well-known and fortunately rare.
|
||||
|
||||
// eslint-disable-next-line wc/no-self-class
|
||||
this.classList.remove("pf-m-expanded", "pf-m-collapsed");
|
||||
// eslint-disable-next-line wc/no-self-class
|
||||
this.classList.add(this.open ? "pf-m-expanded" : "pf-m-collapsed");
|
||||
}
|
||||
|
||||
renderSidebarItems(): TemplateResult {
|
||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
||||
// commonplace and singular enough to merit its own handler.
|
||||
type SidebarEntry = [
|
||||
path: string | null,
|
||||
label: string,
|
||||
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
|
||||
children?: SidebarEntry[],
|
||||
];
|
||||
|
||||
// prettier-ignore
|
||||
const sidebarContent: SidebarEntry[] = [
|
||||
[null, msg("Dashboards"), { "?expanded": true }, [
|
||||
["/administration/overview", msg("Overview")],
|
||||
["/administration/dashboard/users", msg("User Statistics")],
|
||||
["/administration/system-tasks", msg("System Tasks")]]],
|
||||
[null, msg("Applications"), null, [
|
||||
["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
|
||||
["/outpost/outposts", msg("Outposts")]]],
|
||||
[null, msg("Events"), null, [
|
||||
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
|
||||
["/events/rules", msg("Notification Rules")],
|
||||
["/events/transports", msg("Notification Transports")]]],
|
||||
[null, msg("Customization"), null, [
|
||||
["/policy/policies", msg("Policies")],
|
||||
["/core/property-mappings", msg("Property Mappings")],
|
||||
["/blueprints/instances", msg("Blueprints")],
|
||||
["/policy/reputation", msg("Reputation scores")]]],
|
||||
[null, msg("Flows and Stages"), null, [
|
||||
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/flow/stages", msg("Stages")],
|
||||
["/flow/stages/prompts", msg("Prompts")]]],
|
||||
[null, msg("Directory"), null, [
|
||||
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
||||
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
|
||||
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/tokens", msg("Tokens and App passwords")],
|
||||
["/flow/stages/invitations", msg("Invitations")]]],
|
||||
[null, msg("System"), null, [
|
||||
["/core/brands", msg("Brands")],
|
||||
["/crypto/certificates", msg("Certificates")],
|
||||
["/outpost/integrations", msg("Outpost Integrations")],
|
||||
["/admin/settings", msg("Settings")]]],
|
||||
];
|
||||
|
||||
// Typescript requires the type here to correctly type the recursive path
|
||||
type SidebarRenderer = (_: SidebarEntry) => TemplateResult;
|
||||
|
||||
const renderOneSidebarItem: SidebarRenderer = ([path, label, attributes, children]) => {
|
||||
const properties = Array.isArray(attributes)
|
||||
? { ".activeWhen": attributes }
|
||||
: (attributes ?? {});
|
||||
if (path) {
|
||||
properties.path = path;
|
||||
}
|
||||
return html`<ak-sidebar-item ${spread(properties)}>
|
||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
||||
${map(children, renderOneSidebarItem)}
|
||||
</ak-sidebar-item>`;
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
return html`
|
||||
${map(sidebarContent, renderOneSidebarItem)}
|
||||
${this.renderEnterpriseMenu()}
|
||||
`;
|
||||
}
|
||||
|
||||
renderEnterpriseMenu() {
|
||||
return this.can(CapabilitiesEnum.IsEnterprise)
|
||||
? html`
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${msg("Enterprise")}</span>
|
||||
<ak-sidebar-item path="/enterprise/licenses">
|
||||
<span slot="label">${msg("Licenses")}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively renders a collection of sidebar entries.
|
||||
*/
|
||||
export function renderSidebarItems(entries: readonly SidebarEntry[]) {
|
||||
return repeat(entries, ([path, label]) => path || label, renderSidebarItem);
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-admin-sidebar": AkAdminSidebar;
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export const AdminSidebarEntries: readonly SidebarEntry[] = [
|
||||
[null, msg("Dashboards"), { "?expanded": true }, [
|
||||
["/administration/overview", msg("Overview")],
|
||||
["/administration/dashboard/users", msg("User Statistics")],
|
||||
["/administration/system-tasks", msg("System Tasks")]]
|
||||
],
|
||||
[null, msg("Applications"), null, [
|
||||
["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
|
||||
["/outpost/outposts", msg("Outposts")]]
|
||||
],
|
||||
[null, msg("Events"), null, [
|
||||
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
|
||||
["/events/rules", msg("Notification Rules")],
|
||||
["/events/transports", msg("Notification Transports")]]
|
||||
],
|
||||
[null, msg("Customization"), null, [
|
||||
["/policy/policies", msg("Policies")],
|
||||
["/core/property-mappings", msg("Property Mappings")],
|
||||
["/blueprints/instances", msg("Blueprints")],
|
||||
["/policy/reputation", msg("Reputation scores")]]
|
||||
],
|
||||
[null, msg("Flows and Stages"), null, [
|
||||
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/flow/stages", msg("Stages")],
|
||||
["/flow/stages/prompts", msg("Prompts")]]
|
||||
],
|
||||
[null, msg("Directory"), null, [
|
||||
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
||||
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
|
||||
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/tokens", msg("Tokens and App passwords")],
|
||||
["/flow/stages/invitations", msg("Invitations")]]
|
||||
],
|
||||
[null, msg("System"), null, [
|
||||
["/core/brands", msg("Brands")],
|
||||
["/crypto/certificates", msg("Certificates")],
|
||||
["/outpost/integrations", msg("Outpost Integrations")],
|
||||
["/admin/settings", msg("Settings")]]
|
||||
],
|
||||
];
|
||||
|
||||
// prettier-ignore
|
||||
export const AdminSidebarEnterpriseEntries: readonly SidebarEntry[] = [
|
||||
[null, msg("Enterprise"), null, [
|
||||
["/enterprise/licenses", msg("Licenses"), null]
|
||||
],
|
||||
]]
|
||||
|
@ -94,13 +94,10 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
const username = this.user?.user.name || this.user?.user.username;
|
||||
const name = this.user?.user.name ?? this.user?.user.username;
|
||||
|
||||
return html` <ak-page-header
|
||||
header=${msg(str`Welcome, ${username || ""}.`)}
|
||||
description=${msg("General system status")}
|
||||
?hasIcon=${false}
|
||||
>
|
||||
return html`<ak-page-header description=${msg("General system status")} ?hasIcon=${false}>
|
||||
<span slot="header"> ${msg(str`Welcome, ${name || ""}.`)} </span>
|
||||
</ak-page-header>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
|
@ -83,10 +83,13 @@ export class AdminSettingsPage extends AKElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.settings) return nothing;
|
||||
|
||||
if (!this.settings) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ak-page-header icon="fa fa-cog" header="${msg("System settings")}"> </ak-page-header>
|
||||
<ak-page-header icon="fa fa-cog" header="" description="">
|
||||
<span slot="header"> ${msg("System settings")} </span>
|
||||
</ak-page-header>
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
|
@ -1,110 +1,26 @@
|
||||
import type { Config as DOMPurifyConfig } from "dompurify";
|
||||
import DOMPurify from "dompurify";
|
||||
import { trustedTypes } from "trusted-types";
|
||||
|
||||
import { render } from "lit";
|
||||
import { render } from "@lit-labs/ssr";
|
||||
import { collectResult } from "@lit-labs/ssr/lib/render-result.js";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
/**
|
||||
* Trusted types policy that escapes HTML content in place.
|
||||
*
|
||||
* @see {@linkcode SanitizedTrustPolicy} to strip HTML content.
|
||||
*
|
||||
* @returns {TrustedHTML} All HTML content, escaped.
|
||||
*/
|
||||
export const EscapeTrustPolicy = trustedTypes.createPolicy("authentik-escape", {
|
||||
createHTML: (untrustedHTML: string) => {
|
||||
return DOMPurify.sanitize(untrustedHTML, {
|
||||
RETURN_TRUSTED_TYPE: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Trusted types policy, stripping all HTML content.
|
||||
*
|
||||
* @returns {TrustedHTML} Text content only, all HTML tags stripped.
|
||||
*/
|
||||
export const SanitizedTrustPolicy = trustedTypes.createPolicy("authentik-sanitize", {
|
||||
createHTML: (untrustedHTML: string) => {
|
||||
return DOMPurify.sanitize(untrustedHTML, {
|
||||
RETURN_TRUSTED_TYPE: false,
|
||||
ALLOWED_TAGS: ["#text"],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Trusted types policy, allowing a minimal set of _safe_ HTML tags supplied by
|
||||
* a trusted source, such as the brand API.
|
||||
*/
|
||||
export const BrandedHTMLPolicy = trustedTypes.createPolicy("authentik-restrict", {
|
||||
createHTML: (untrustedHTML: string) => {
|
||||
return DOMPurify.sanitize(untrustedHTML, {
|
||||
RETURN_TRUSTED_TYPE: false,
|
||||
FORBID_TAGS: [
|
||||
"script",
|
||||
"style",
|
||||
"iframe",
|
||||
"link",
|
||||
"object",
|
||||
"embed",
|
||||
"applet",
|
||||
"meta",
|
||||
"base",
|
||||
"form",
|
||||
"input",
|
||||
"textarea",
|
||||
"select",
|
||||
"button",
|
||||
],
|
||||
FORBID_ATTR: [
|
||||
"onerror",
|
||||
"onclick",
|
||||
"onload",
|
||||
"onmouseover",
|
||||
"onmouseout",
|
||||
"onmouseup",
|
||||
"onmousedown",
|
||||
"onfocus",
|
||||
"onblur",
|
||||
"onsubmit",
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export type AuthentikTrustPolicy =
|
||||
| typeof EscapeTrustPolicy
|
||||
| typeof SanitizedTrustPolicy
|
||||
| typeof BrandedHTMLPolicy;
|
||||
|
||||
/**
|
||||
* Sanitize an untrusted HTML string using a trusted types policy.
|
||||
*/
|
||||
export function sanitizeHTML(trustPolicy: AuthentikTrustPolicy, untrustedHTML: string) {
|
||||
return unsafeHTML(trustPolicy.createHTML(untrustedHTML).toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* DOMPurify configuration for strict sanitization.
|
||||
*
|
||||
* This configuration only allows text nodes and disallows all HTML tags.
|
||||
*/
|
||||
export const DOM_PURIFY_STRICT = {
|
||||
ALLOWED_TAGS: ["#text"],
|
||||
} as const satisfies DOMPurifyConfig;
|
||||
|
||||
/**
|
||||
* Render untrusted HTML to a string without escaping it.
|
||||
*
|
||||
* @returns {string} The rendered HTML string.
|
||||
*/
|
||||
export function renderStaticHTMLUnsafe(untrustedHTML: unknown): string {
|
||||
const container = document.createElement("html");
|
||||
render(untrustedHTML, container);
|
||||
|
||||
const result = container.innerHTML;
|
||||
|
||||
return result;
|
||||
export async function renderStatic(input: TemplateResult): Promise<string> {
|
||||
return await collectResult(render(input));
|
||||
}
|
||||
|
||||
export function purify(input: TemplateResult): TemplateResult {
|
||||
return html`${until(
|
||||
(async () => {
|
||||
const rendered = await renderStatic(input);
|
||||
const purified = DOMPurify.sanitize(rendered);
|
||||
return html`${unsafeHTML(purified)}`;
|
||||
})(),
|
||||
)}`;
|
||||
}
|
||||
|
@ -17,13 +17,6 @@
|
||||
|
||||
/* Minimum width after which the sidebar becomes automatic */
|
||||
--ak-sidebar--minimum-auto-width: 80rem;
|
||||
|
||||
/**
|
||||
* The height of the navbar and branded sidebar.
|
||||
* @todo This shouldn't be necessary. The sidebar can instead use a grid layout
|
||||
* ensuring they share the same height.
|
||||
*/
|
||||
--ak-navbar--height: 7rem;
|
||||
}
|
||||
|
||||
@supports selector(::-webkit-scrollbar) {
|
||||
|
@ -1,220 +0,0 @@
|
||||
/**
|
||||
* @file Stylesheet utilities.
|
||||
*/
|
||||
import { CSSResult, CSSResultOrNative, ReactiveElement, css } from "lit";
|
||||
|
||||
/**
|
||||
* Elements containing adoptable stylesheets.
|
||||
*/
|
||||
export type StyleSheetParent = Pick<DocumentOrShadowRoot, "adoptedStyleSheets">;
|
||||
|
||||
/**
|
||||
* Type-predicate to determine if a given object has adoptable stylesheets.
|
||||
*/
|
||||
export function isAdoptableStyleSheetParent(input: unknown): input is StyleSheetParent {
|
||||
// Sanity check - Does the input have the right shape?
|
||||
|
||||
if (!input || typeof input !== "object") return false;
|
||||
|
||||
if (!("adoptedStyleSheets" in input) || !input.adoptedStyleSheets) return false;
|
||||
|
||||
if (typeof input.adoptedStyleSheets !== "object") return false;
|
||||
|
||||
// We avoid `Array.isArray` because the adopted stylesheets property
|
||||
// is defined as a proxied array.
|
||||
// All we care about is that it's shaped like an array.
|
||||
if (!("length" in input.adoptedStyleSheets)) return false;
|
||||
|
||||
if (typeof input.adoptedStyleSheets.length !== "number") return false;
|
||||
|
||||
// Finally is the array mutable?
|
||||
return "push" in input.adoptedStyleSheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given input can adopt stylesheets.
|
||||
*/
|
||||
export function assertAdoptableStyleSheetParent<T>(
|
||||
input: T,
|
||||
): asserts input is T & StyleSheetParent {
|
||||
if (isAdoptableStyleSheetParent(input)) return;
|
||||
|
||||
console.debug("Given input missing `adoptedStyleSheets`", input);
|
||||
|
||||
throw new TypeError("Assertion failed: `adoptedStyleSheets` missing in given input");
|
||||
}
|
||||
|
||||
export function resolveStyleSheetParent<T extends HTMLElement | DocumentFragment | Document>(
|
||||
renderRoot: T,
|
||||
) {
|
||||
const styleRoot = "ShadyDOM" in window ? document : renderRoot;
|
||||
|
||||
assertAdoptableStyleSheetParent(styleRoot);
|
||||
|
||||
return styleRoot;
|
||||
}
|
||||
|
||||
export type StyleSheetInit = string | CSSResult | CSSStyleSheet;
|
||||
|
||||
/**
|
||||
* Given a source of CSS, create a `CSSStyleSheet`.
|
||||
*
|
||||
* @throw {@linkcode TypeError} if the input cannot be converted to a `CSSStyleSheet`
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Storybook's `build` does not currently have a coherent way of importing
|
||||
* CSS-as-text into CSSStyleSheet.
|
||||
*
|
||||
* It works well when Storybook is running in `dev`, but in `build` it fails.
|
||||
* Storied components will have to map their textual CSS imports.
|
||||
*/
|
||||
export function createStyleSheet(input: string): CSSResult {
|
||||
const inputTemplate = [input] as unknown as TemplateStringsArray;
|
||||
|
||||
const result = css(inputTemplate, []);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a source of CSS, create a `CSSStyleSheet`.
|
||||
*
|
||||
* @see {@linkcode createStyleSheet}
|
||||
*/
|
||||
export function normalizeCSSSource(css: string): CSSStyleSheet;
|
||||
export function normalizeCSSSource(styleSheet: CSSStyleSheet): CSSStyleSheet;
|
||||
export function normalizeCSSSource(cssResult: CSSResult): CSSResult;
|
||||
export function normalizeCSSSource(input: StyleSheetInit): CSSResultOrNative;
|
||||
export function normalizeCSSSource(input: StyleSheetInit): CSSResultOrNative {
|
||||
if (typeof input === "string") return createStyleSheet(input);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
export function createStyleSheetUnsafe(input: StyleSheetInit): CSSStyleSheet {
|
||||
const result = normalizeCSSSource(input);
|
||||
if (result instanceof CSSStyleSheet) return result;
|
||||
|
||||
if (!result.styleSheet) {
|
||||
console.debug(
|
||||
"authentik/common/stylesheets: CSSResult missing styleSheet, returning empty",
|
||||
{ result, input },
|
||||
);
|
||||
|
||||
throw new TypeError("Expected a CSSStyleSheet");
|
||||
}
|
||||
|
||||
return result.styleSheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append stylesheet(s) to the given roots.
|
||||
*/
|
||||
export function appendStyleSheet(
|
||||
insertions: CSSStyleSheet | Iterable<CSSStyleSheet>,
|
||||
...styleParents: StyleSheetParent[]
|
||||
): void {
|
||||
insertions = Array.isArray(insertions) ? insertions : [insertions];
|
||||
|
||||
for (const nextStyleSheet of insertions) {
|
||||
for (const styleParent of styleParents) {
|
||||
if (styleParent.adoptedStyleSheets.includes(nextStyleSheet)) return;
|
||||
|
||||
styleParent.adoptedStyleSheets = [...styleParent.adoptedStyleSheets, nextStyleSheet];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a stylesheet from the given roots, matching by referential equality.
|
||||
*/
|
||||
export function removeStyleSheet(
|
||||
currentStyleSheet: CSSStyleSheet,
|
||||
...styleParents: StyleSheetParent[]
|
||||
): void {
|
||||
for (const styleParent of styleParents) {
|
||||
const nextAdoptedStyleSheets = styleParent.adoptedStyleSheets.filter(
|
||||
(styleSheet) => styleSheet !== currentStyleSheet,
|
||||
);
|
||||
|
||||
if (nextAdoptedStyleSheets.length === styleParent.adoptedStyleSheets.length) return;
|
||||
|
||||
styleParent.adoptedStyleSheets = nextAdoptedStyleSheets;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a stylesheet to a string.
|
||||
*
|
||||
* This is useful for debugging or inspecting the contents of a stylesheet.
|
||||
*/
|
||||
export function serializeStyleSheet(stylesheet: CSSStyleSheet): string {
|
||||
return Array.from(stylesheet.cssRules || [], (rule) => rule.cssText || "").join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect the adopted stylesheets of a given style parent, serializing them to strings.
|
||||
*/
|
||||
export function inspectStyleSheets(styleParent: StyleSheetParent): string[] {
|
||||
return styleParent.adoptedStyleSheets.map((styleSheet) => serializeStyleSheet(styleSheet));
|
||||
}
|
||||
|
||||
interface InspectedStyleSheetEntry {
|
||||
tagName: string;
|
||||
element: ReactiveElement;
|
||||
styles: string[];
|
||||
children?: InspectedStyleSheetEntry[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively inspect the adopted stylesheets of a given style parent, serializing them to strings.
|
||||
*/
|
||||
export function inspectStyleSheetTree(element: ReactiveElement): InspectedStyleSheetEntry {
|
||||
const styleParent = resolveStyleSheetParent(element.renderRoot);
|
||||
const styles = inspectStyleSheets(styleParent);
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
|
||||
const treewalker = document.createTreeWalker(element.renderRoot, NodeFilter.SHOW_ELEMENT, {
|
||||
acceptNode(node) {
|
||||
if (node instanceof ReactiveElement) {
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
}
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
},
|
||||
});
|
||||
const children: InspectedStyleSheetEntry[] = [];
|
||||
let currentNode: Node | null = treewalker.nextNode();
|
||||
while (currentNode) {
|
||||
const childElement = currentNode as ReactiveElement;
|
||||
|
||||
if (!isAdoptableStyleSheetParent(childElement.renderRoot)) {
|
||||
currentNode = treewalker.nextNode();
|
||||
continue;
|
||||
}
|
||||
|
||||
const childStyles = inspectStyleSheets(childElement.renderRoot);
|
||||
|
||||
children.push({
|
||||
tagName: childElement.tagName.toLowerCase(),
|
||||
element: childElement,
|
||||
styles: childStyles,
|
||||
});
|
||||
currentNode = treewalker.nextNode();
|
||||
}
|
||||
|
||||
return {
|
||||
tagName,
|
||||
element,
|
||||
styles,
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
Object.assign(window, {
|
||||
inspectStyleSheetTree,
|
||||
serializeStyleSheet,
|
||||
inspectStyleSheets,
|
||||
});
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
/**
|
||||
* @file Theme utilities.
|
||||
*/
|
||||
import { UIConfig } from "@goauthentik/common/ui/config";
|
||||
|
||||
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
//#region Scheme Types
|
||||
|
||||
/**
|
||||
* Valid CSS color scheme values.
|
||||
*
|
||||
* @link {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme | MDN}
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export type CSSColorSchemeValue = "dark" | "light" | "auto";
|
||||
|
||||
/**
|
||||
* A CSS color scheme value that can be preferred by the user, i.e. not `"auto"`.
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export type ResolvedCSSColorSchemeValue = Exclude<CSSColorSchemeValue, "auto">;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region UI Theme Types
|
||||
|
||||
/**
|
||||
* A UI color scheme value that can be preferred by the user.
|
||||
*
|
||||
* i.e. not an lack of preference or unknown value.
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export type ResolvedUITheme = typeof UiThemeEnum.Light | typeof UiThemeEnum.Dark;
|
||||
|
||||
/**
|
||||
* A mapping of theme values to their respective inversion.
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export const UIThemeInversion = {
|
||||
dark: "light",
|
||||
light: "dark",
|
||||
} as const satisfies Record<ResolvedUITheme, ResolvedUITheme>;
|
||||
|
||||
/**
|
||||
* Either a valid CSS color scheme value, or a theme preference.
|
||||
*/
|
||||
export type UIThemeHint = CSSColorSchemeValue | UiThemeEnum;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Scheme Functions
|
||||
|
||||
/**
|
||||
* Creates an event target for the given color scheme.
|
||||
*
|
||||
* @param colorScheme The color scheme to target.
|
||||
* @returns A {@linkcode MediaQueryList} that can be used to listen for changes to the color scheme.
|
||||
*
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList | MDN}
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export function createColorSchemeTarget(colorScheme: ResolvedCSSColorSchemeValue): MediaQueryList {
|
||||
return window.matchMedia(`(prefers-color-scheme: ${colorScheme})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given input into a valid CSS color scheme value.
|
||||
*
|
||||
* If the input is not provided, it defaults to "auto".
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export function formatColorScheme(theme: ResolvedUITheme): ResolvedCSSColorSchemeValue;
|
||||
export function formatColorScheme(
|
||||
colorScheme: ResolvedCSSColorSchemeValue,
|
||||
): ResolvedCSSColorSchemeValue;
|
||||
export function formatColorScheme(hint?: UIThemeHint): CSSColorSchemeValue;
|
||||
export function formatColorScheme(hint?: UIThemeHint): CSSColorSchemeValue {
|
||||
if (!hint) return "auto";
|
||||
|
||||
switch (hint) {
|
||||
case "dark":
|
||||
case UiThemeEnum.Dark:
|
||||
return "dark";
|
||||
case "light":
|
||||
case UiThemeEnum.Light:
|
||||
return "light";
|
||||
case "auto":
|
||||
case UiThemeEnum.Automatic:
|
||||
return "auto";
|
||||
default:
|
||||
console.warn(`Unknown color scheme hint: ${hint}. Defaulting to "auto".`);
|
||||
return "auto";
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Theme Functions
|
||||
|
||||
/**
|
||||
* Resolve the current UI theme based on the user's preference or the provided color scheme.
|
||||
*
|
||||
* @param hint The color scheme hint to use.
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export function resolveUITheme(
|
||||
hint?: UIThemeHint,
|
||||
defaultUITheme: ResolvedUITheme = UiThemeEnum.Light,
|
||||
): ResolvedUITheme {
|
||||
const colorScheme = formatColorScheme(hint);
|
||||
|
||||
if (colorScheme !== "auto") return colorScheme;
|
||||
|
||||
// Given that we don't know the user's preference,
|
||||
// we can determine the theme based on whether the default theme is
|
||||
// currently being overridden.
|
||||
|
||||
const colorSchemeInversion = formatColorScheme(UIThemeInversion[defaultUITheme]);
|
||||
|
||||
const mediaQueryList = createColorSchemeTarget(colorSchemeInversion);
|
||||
|
||||
return mediaQueryList.matches ? colorSchemeInversion : defaultUITheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effect listener invoked when the color scheme changes.
|
||||
*/
|
||||
export type UIThemeListener = (currentUITheme: ResolvedUITheme) => void;
|
||||
/**
|
||||
* Create an effect that runs
|
||||
*
|
||||
* @returns A cleanup function that removes the effect.
|
||||
*/
|
||||
export function createUIThemeEffect(
|
||||
effect: UIThemeListener,
|
||||
listenerOptions?: AddEventListenerOptions,
|
||||
): () => void {
|
||||
const colorSchemeTarget = resolveUITheme();
|
||||
const invertedColorSchemeTarget = UIThemeInversion[colorSchemeTarget];
|
||||
|
||||
let previousUITheme: ResolvedUITheme | undefined;
|
||||
|
||||
// First, wrap the effect to ensure we can abort it.
|
||||
const changeListener = (event: MediaQueryListEvent) => {
|
||||
if (listenerOptions?.signal?.aborted) return;
|
||||
|
||||
const currentUITheme = event.matches ? colorSchemeTarget : invertedColorSchemeTarget;
|
||||
|
||||
if (previousUITheme === currentUITheme) return;
|
||||
|
||||
previousUITheme = currentUITheme;
|
||||
|
||||
effect(currentUITheme);
|
||||
};
|
||||
|
||||
const mediaQueryList = createColorSchemeTarget(colorSchemeTarget);
|
||||
|
||||
// Trigger the effect immediately.
|
||||
effect(colorSchemeTarget);
|
||||
|
||||
// Listen for changes to the color scheme...
|
||||
mediaQueryList.addEventListener("change", changeListener, listenerOptions);
|
||||
|
||||
// Finally, allow the caller to remove the effect.
|
||||
const cleanup = () => {
|
||||
mediaQueryList.removeEventListener("change", changeListener);
|
||||
};
|
||||
|
||||
return cleanup;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Theme Element
|
||||
|
||||
/**
|
||||
* An element that can be themed.
|
||||
*/
|
||||
export interface ThemedElement extends HTMLElement {
|
||||
brand?: CurrentBrand;
|
||||
uiConfig?: UIConfig;
|
||||
config?: Config;
|
||||
activeTheme: ResolvedUITheme;
|
||||
}
|
||||
|
||||
export function rootInterface<T extends ThemedElement = ThemedElement>(): T | null {
|
||||
const element = document.body.querySelector<T>("[data-ak-interface-root]");
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
//#endregion
|
@ -95,7 +95,7 @@ export class NavigationButtons extends AKElement {
|
||||
);
|
||||
};
|
||||
|
||||
return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-xl">
|
||||
return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg">
|
||||
<button class="pf-c-button pf-m-plain" type="button" @click=${onClick}>
|
||||
<pf-tooltip position="top" content=${msg("Open API drawer")}>
|
||||
<i class="fas fa-code" aria-hidden="true"></i>
|
||||
@ -116,7 +116,7 @@ export class NavigationButtons extends AKElement {
|
||||
);
|
||||
};
|
||||
|
||||
return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-xl">
|
||||
return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@ -156,7 +156,9 @@ export class NavigationButtons extends AKElement {
|
||||
}
|
||||
|
||||
renderImpersonation() {
|
||||
if (!this.me?.original) return nothing;
|
||||
if (!this.me?.original) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const onClick = async () => {
|
||||
await new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve();
|
||||
@ -173,14 +175,6 @@ export class NavigationButtons extends AKElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderAvatar() {
|
||||
return html`<img
|
||||
class="pf-c-page__header-tools-item pf-c-avatar pf-m-hidden pf-m-visible-on-xl"
|
||||
src=${ifDefined(this.me?.user.avatar)}
|
||||
alt="${msg("Avatar image")}"
|
||||
/>`;
|
||||
}
|
||||
|
||||
get userDisplayName() {
|
||||
return match<UserDisplay | undefined, string | undefined>(this.uiConfig?.navbar.userDisplay)
|
||||
.with(UserDisplay.username, () => this.me?.user.username)
|
||||
@ -212,13 +206,17 @@ export class NavigationButtons extends AKElement {
|
||||
</div>
|
||||
${this.renderImpersonation()}
|
||||
${this.userDisplayName != ""
|
||||
? html`<div class="pf-c-page__header-tools-group pf-m-hidden">
|
||||
<div class="pf-c-page__header-tools-item pf-m-visible-on-2xl">
|
||||
? html`<div class="pf-c-page__header-tools-group">
|
||||
<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md">
|
||||
${this.userDisplayName}
|
||||
</div>
|
||||
</div>`
|
||||
: nothing}
|
||||
${this.renderAvatar()}
|
||||
<img
|
||||
class="pf-c-avatar"
|
||||
src=${ifDefined(this.me?.user.avatar)}
|
||||
alt="${msg("Avatar image")}"
|
||||
/>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
@ -1,125 +1,165 @@
|
||||
import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import {
|
||||
StyleSheetInit,
|
||||
StyleSheetParent,
|
||||
appendStyleSheet,
|
||||
createStyleSheetUnsafe,
|
||||
removeStyleSheet,
|
||||
resolveStyleSheetParent,
|
||||
} from "@goauthentik/common/stylesheets";
|
||||
import { ResolvedUITheme, createUIThemeEffect, resolveUITheme } from "@goauthentik/common/theme";
|
||||
import { type ThemedElement } from "@goauthentik/common/theme";
|
||||
import { UIConfig } from "@goauthentik/common/ui/config";
|
||||
import { adaptCSS } from "@goauthentik/common/utils";
|
||||
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
|
||||
|
||||
import { localized } from "@lit/localize";
|
||||
import { CSSResultGroup, CSSResultOrNative, LitElement } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { LitElement, ReactiveElement } from "lit";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import OneDark from "@goauthentik/common/styles/one-dark.css";
|
||||
import ThemeDark from "@goauthentik/common/styles/theme-dark.css";
|
||||
|
||||
import { CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
// Re-export the theme helpers
|
||||
export { rootInterface } from "@goauthentik/common/theme";
|
||||
type AkInterface = HTMLElement & {
|
||||
getTheme: () => Promise<UiThemeEnum>;
|
||||
brand?: CurrentBrand;
|
||||
uiConfig?: UIConfig;
|
||||
config?: Config;
|
||||
get activeTheme(): UiThemeEnum | undefined;
|
||||
};
|
||||
|
||||
export interface AKElementInit {
|
||||
brand?: Partial<CurrentBrand>;
|
||||
styleParents?: StyleSheetParent[];
|
||||
}
|
||||
export const rootInterface = <T extends AkInterface>(): T | undefined =>
|
||||
(document.body.querySelector("[data-ak-interface-root]") as T) ?? undefined;
|
||||
|
||||
export const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)";
|
||||
|
||||
// Ensure themes are converted to a static instance of CSS Stylesheet, otherwise the
|
||||
// when changing themes we might not remove the correct css stylesheet instance.
|
||||
const _darkTheme = ensureCSSStyleSheet(ThemeDark);
|
||||
|
||||
@localized()
|
||||
export class AKElement extends LitElement implements ThemedElement {
|
||||
/**
|
||||
* The resolved theme of the current element.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Unlike the browser's current color scheme, this is a value that can be
|
||||
* resolved to a specific theme, i.e. dark or light.
|
||||
*/
|
||||
@property({
|
||||
attribute: "theme",
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
public activeTheme: ResolvedUITheme;
|
||||
export class AKElement extends LitElement {
|
||||
_mediaMatcher?: MediaQueryList;
|
||||
_mediaMatcherHandler?: (ev?: MediaQueryListEvent) => void;
|
||||
_activeTheme?: UiThemeEnum;
|
||||
|
||||
protected static readonly DarkColorSchemeStyleSheet = createStyleSheetUnsafe(ThemeDark);
|
||||
|
||||
protected static finalizeStyles(styles?: CSSResultGroup): CSSResultOrNative[] {
|
||||
// Ensure all style sheets being passed are really style sheets.
|
||||
const baseStyles: StyleSheetInit[] = [AKGlobal, OneDark];
|
||||
|
||||
if (!styles) return baseStyles.map(createStyleSheetUnsafe);
|
||||
|
||||
if (Array.isArray(styles)) {
|
||||
return [
|
||||
//---
|
||||
...(styles as unknown as CSSResultOrNative[]),
|
||||
...baseStyles,
|
||||
].flatMap(createStyleSheetUnsafe);
|
||||
}
|
||||
return [styles, ...baseStyles].map(createStyleSheetUnsafe);
|
||||
get activeTheme(): UiThemeEnum | undefined {
|
||||
return this._activeTheme;
|
||||
}
|
||||
|
||||
constructor(init?: AKElementInit) {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const config = globalAK();
|
||||
const { brand = config.brand, styleParents = [] } = init || {};
|
||||
|
||||
this.activeTheme = resolveUITheme(brand?.uiTheme);
|
||||
this.#styleParents = styleParents;
|
||||
|
||||
this.#customCSSStyleSheet = brand?.brandingCustomCss
|
||||
? createStyleSheetUnsafe(brand.brandingCustomCss)
|
||||
: null;
|
||||
}
|
||||
|
||||
#styleParents: StyleSheetParent[] = [];
|
||||
#customCSSStyleSheet: CSSStyleSheet | null;
|
||||
|
||||
#themeAbortController: AbortController | null = null;
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.#themeAbortController?.abort();
|
||||
setInitialStyles(root: DocumentOrShadowRoot) {
|
||||
const styleRoot: DocumentOrShadowRoot = (
|
||||
"ShadyDOM" in window ? document : root
|
||||
) as DocumentOrShadowRoot;
|
||||
styleRoot.adoptedStyleSheets = adaptCSS([
|
||||
...styleRoot.adoptedStyleSheets,
|
||||
ensureCSSStyleSheet(AKGlobal),
|
||||
ensureCSSStyleSheet(OneDark),
|
||||
]);
|
||||
this._initTheme(styleRoot);
|
||||
this._initCustomCSS(styleRoot);
|
||||
}
|
||||
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
const renderRoot = super.createRenderRoot();
|
||||
protected createRenderRoot() {
|
||||
this.fixElementStyles();
|
||||
const root = super.createRenderRoot();
|
||||
this.setInitialStyles(root as unknown as DocumentOrShadowRoot);
|
||||
return root;
|
||||
}
|
||||
|
||||
const styleRoot = resolveStyleSheetParent(renderRoot);
|
||||
const styleParents = Array.from(
|
||||
new Set<StyleSheetParent>([styleRoot, ...this.#styleParents]),
|
||||
);
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
return rootInterface()?.getTheme() || UiThemeEnum.Automatic;
|
||||
}
|
||||
|
||||
if (this.#customCSSStyleSheet) {
|
||||
console.debug(`authentik/element[${this.tagName.toLowerCase()}]: Adding custom CSS`);
|
||||
fixElementStyles() {
|
||||
// Ensure all style sheets being passed are really style sheets.
|
||||
(this.constructor as typeof ReactiveElement).elementStyles = (
|
||||
this.constructor as typeof ReactiveElement
|
||||
).elementStyles.map(ensureCSSStyleSheet);
|
||||
}
|
||||
|
||||
styleRoot.adoptedStyleSheets = [
|
||||
...styleRoot.adoptedStyleSheets,
|
||||
this.#customCSSStyleSheet,
|
||||
];
|
||||
async _initTheme(root: DocumentOrShadowRoot): Promise<void> {
|
||||
// Early activate theme based on media query to prevent light flash
|
||||
// when dark is preferred
|
||||
this._applyTheme(root, globalAK().brand.uiTheme);
|
||||
this._applyTheme(root, await this.getTheme());
|
||||
}
|
||||
|
||||
async _initCustomCSS(root: DocumentOrShadowRoot): Promise<void> {
|
||||
const brand = globalAK().brand;
|
||||
if (!brand) {
|
||||
return;
|
||||
}
|
||||
const sheet = await new CSSStyleSheet().replace(brand.brandingCustomCss);
|
||||
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
|
||||
}
|
||||
|
||||
this.#themeAbortController = new AbortController();
|
||||
_applyTheme(root: DocumentOrShadowRoot, theme?: UiThemeEnum): void {
|
||||
if (!theme) {
|
||||
theme = UiThemeEnum.Automatic;
|
||||
}
|
||||
if (theme === UiThemeEnum.Automatic) {
|
||||
// Create a media matcher to automatically switch the theme depending on
|
||||
// prefers-color-scheme
|
||||
if (!this._mediaMatcher) {
|
||||
this._mediaMatcher = window.matchMedia(QUERY_MEDIA_COLOR_LIGHT);
|
||||
this._mediaMatcherHandler = (ev?: MediaQueryListEvent) => {
|
||||
const theme =
|
||||
ev?.matches || this._mediaMatcher?.matches
|
||||
? UiThemeEnum.Light
|
||||
: UiThemeEnum.Dark;
|
||||
this._activateTheme(theme, root);
|
||||
};
|
||||
this._mediaMatcherHandler(undefined);
|
||||
this._mediaMatcher.addEventListener("change", this._mediaMatcherHandler);
|
||||
}
|
||||
return;
|
||||
} else if (this._mediaMatcher && this._mediaMatcherHandler) {
|
||||
// Theme isn't automatic and we have a matcher configured, remove the matcher
|
||||
// to prevent changes
|
||||
this._mediaMatcher.removeEventListener("change", this._mediaMatcherHandler);
|
||||
this._mediaMatcher = undefined;
|
||||
}
|
||||
this._activateTheme(theme, root);
|
||||
}
|
||||
|
||||
createUIThemeEffect(
|
||||
(currentUITheme) => {
|
||||
if (currentUITheme === UiThemeEnum.Dark) {
|
||||
appendStyleSheet(AKElement.DarkColorSchemeStyleSheet, ...styleParents);
|
||||
} else {
|
||||
removeStyleSheet(AKElement.DarkColorSchemeStyleSheet, ...styleParents);
|
||||
}
|
||||
this.activeTheme = currentUITheme;
|
||||
},
|
||||
{
|
||||
signal: this.#themeAbortController.signal,
|
||||
},
|
||||
static themeToStylesheet(theme?: UiThemeEnum): CSSStyleSheet | undefined {
|
||||
if (theme === UiThemeEnum.Dark) {
|
||||
return _darkTheme;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly activate a given theme, accepts multiple document/ShadowDOMs to apply the stylesheet
|
||||
* to. The stylesheets are applied to each DOM in order. Does nothing if the given theme is already active.
|
||||
*/
|
||||
_activateTheme(theme: UiThemeEnum, ...roots: DocumentOrShadowRoot[]) {
|
||||
if (theme === this._activeTheme) {
|
||||
return;
|
||||
}
|
||||
// Make sure we only get to this callback once we've picked a concise theme choice
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_THEME_CHANGE, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: theme,
|
||||
}),
|
||||
);
|
||||
|
||||
return renderRoot;
|
||||
this.setAttribute("theme", theme);
|
||||
const stylesheet = AKElement.themeToStylesheet(theme);
|
||||
const oldStylesheet = AKElement.themeToStylesheet(this._activeTheme);
|
||||
roots.forEach((root) => {
|
||||
if (stylesheet) {
|
||||
root.adoptedStyleSheets = [
|
||||
...root.adoptedStyleSheets,
|
||||
ensureCSSStyleSheet(stylesheet),
|
||||
];
|
||||
}
|
||||
if (oldStylesheet) {
|
||||
root.adoptedStyleSheets = root.adoptedStyleSheets.filter(
|
||||
(v) => v !== oldStylesheet,
|
||||
);
|
||||
}
|
||||
});
|
||||
this._activeTheme = theme;
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { ThemedElement } from "@goauthentik/common/theme";
|
||||
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
||||
@ -10,12 +9,14 @@ import type { ReactiveController } from "lit";
|
||||
import type { CurrentBrand } from "@goauthentik/api";
|
||||
import { CoreApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkInterface } from "./Interface";
|
||||
|
||||
export class BrandContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost<ThemedElement>;
|
||||
host!: ReactiveElementHost<AkInterface>;
|
||||
|
||||
context!: ContextProvider<{ __context__: CurrentBrand | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost<ThemedElement>) {
|
||||
constructor(host: ReactiveElementHost<AkInterface>) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikBrandContext,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ThemedElement } from "@goauthentik/common/theme";
|
||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
||||
@ -11,12 +10,14 @@ import type { ReactiveController } from "lit";
|
||||
import type { Config } from "@goauthentik/api";
|
||||
import { RootApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkInterface } from "./Interface";
|
||||
|
||||
export class ConfigContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost<ThemedElement>;
|
||||
host!: ReactiveElementHost<AkInterface>;
|
||||
|
||||
context!: ContextProvider<{ __context__: Config | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost<ThemedElement>) {
|
||||
constructor(host: ReactiveElementHost<AkInterface>) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikConfigContext,
|
||||
|
@ -1,85 +1,107 @@
|
||||
import {
|
||||
appendStyleSheet,
|
||||
createStyleSheetUnsafe,
|
||||
resolveStyleSheetParent,
|
||||
} from "@goauthentik/common/stylesheets";
|
||||
import { ThemedElement } from "@goauthentik/common/theme";
|
||||
import { UIConfig } from "@goauthentik/common/ui/config";
|
||||
import { AKElement, AKElementInit } from "@goauthentik/elements/Base";
|
||||
import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
|
||||
import { VersionContextController } from "@goauthentik/elements/Interface/VersionContextController";
|
||||
import { ModalOrchestrationController } from "@goauthentik/elements/controllers/ModalOrchestrationController.js";
|
||||
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
|
||||
|
||||
import { state } from "lit/decorators.js";
|
||||
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import type { Config, CurrentBrand, LicenseSummary, Version } from "@goauthentik/api";
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
import { AKElement, rootInterface } from "../Base";
|
||||
import { BrandContextController } from "./BrandContextController";
|
||||
import { ConfigContextController } from "./ConfigContextController";
|
||||
import { EnterpriseContextController } from "./EnterpriseContextController";
|
||||
|
||||
export type AkInterface = HTMLElement & {
|
||||
getTheme: () => Promise<UiThemeEnum>;
|
||||
brand?: CurrentBrand;
|
||||
uiConfig?: UIConfig;
|
||||
config?: Config;
|
||||
};
|
||||
|
||||
const brandContext = Symbol("brandContext");
|
||||
const configContext = Symbol("configContext");
|
||||
const modalController = Symbol("modalController");
|
||||
const versionContext = Symbol("versionContext");
|
||||
|
||||
export abstract class Interface extends AKElement implements ThemedElement {
|
||||
protected static readonly PFBaseStyleSheet = createStyleSheetUnsafe(PFBase);
|
||||
export class Interface extends AKElement implements AkInterface {
|
||||
[brandContext]!: BrandContextController;
|
||||
|
||||
[brandContext]: BrandContextController;
|
||||
[configContext]!: ConfigContextController;
|
||||
|
||||
[configContext]: ConfigContextController;
|
||||
|
||||
[modalController]: ModalOrchestrationController;
|
||||
[modalController]!: ModalOrchestrationController;
|
||||
|
||||
@state()
|
||||
public config?: Config;
|
||||
uiConfig?: UIConfig;
|
||||
|
||||
@state()
|
||||
public brand?: CurrentBrand;
|
||||
config?: Config;
|
||||
|
||||
constructor({ styleParents = [], ...init }: AKElementInit = {}) {
|
||||
const styleParent = resolveStyleSheetParent(document);
|
||||
@state()
|
||||
brand?: CurrentBrand;
|
||||
|
||||
super({
|
||||
...init,
|
||||
styleParents: [styleParent, ...styleParents],
|
||||
});
|
||||
|
||||
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
||||
|
||||
appendStyleSheet(Interface.PFBaseStyleSheet, styleParent);
|
||||
constructor() {
|
||||
super();
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)];
|
||||
this._initContexts();
|
||||
this.dataset.akInterfaceRoot = "true";
|
||||
}
|
||||
|
||||
_initContexts() {
|
||||
this[brandContext] = new BrandContextController(this);
|
||||
this[configContext] = new ConfigContextController(this);
|
||||
this[modalController] = new ModalOrchestrationController(this);
|
||||
}
|
||||
|
||||
_activateTheme(theme: UiThemeEnum, ...roots: DocumentOrShadowRoot[]): void {
|
||||
if (theme === this._activeTheme) {
|
||||
return;
|
||||
}
|
||||
console.debug(
|
||||
`authentik/interface[${rootInterface()?.tagName.toLowerCase()}]: Enabling theme ${theme}`,
|
||||
);
|
||||
// Special case for root interfaces, as they need to modify the global document CSS too
|
||||
// Instead of calling ._activateTheme() twice, we insert the root document in the call
|
||||
// since multiple calls to ._activateTheme() would not do anything after the first call
|
||||
// as the theme is already enabled.
|
||||
roots.unshift(document as unknown as DocumentOrShadowRoot);
|
||||
super._activateTheme(theme, ...roots);
|
||||
}
|
||||
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
if (!this.uiConfig) {
|
||||
this.uiConfig = await uiConfig();
|
||||
}
|
||||
return this.uiConfig.theme?.base || UiThemeEnum.Automatic;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AkAuthenticatedInterface extends ThemedElement {
|
||||
export type AkAuthenticatedInterface = AkInterface & {
|
||||
licenseSummary?: LicenseSummary;
|
||||
version?: Version;
|
||||
}
|
||||
};
|
||||
|
||||
const enterpriseContext = Symbol("enterpriseContext");
|
||||
|
||||
export class AuthenticatedInterface extends Interface implements AkAuthenticatedInterface {
|
||||
export class AuthenticatedInterface extends Interface {
|
||||
[enterpriseContext]!: EnterpriseContextController;
|
||||
[versionContext]!: VersionContextController;
|
||||
|
||||
@state()
|
||||
public uiConfig?: UIConfig;
|
||||
licenseSummary?: LicenseSummary;
|
||||
|
||||
@state()
|
||||
public licenseSummary?: LicenseSummary;
|
||||
version?: Version;
|
||||
|
||||
@state()
|
||||
public version?: Version;
|
||||
|
||||
constructor(init?: AKElementInit) {
|
||||
super(init);
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
_initContexts(): void {
|
||||
super._initContexts();
|
||||
this[enterpriseContext] = new EnterpriseContextController(this);
|
||||
this[versionContext] = new VersionContextController(this);
|
||||
}
|
||||
|
@ -5,23 +5,20 @@ import {
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { currentInterface } from "@goauthentik/common/sentry";
|
||||
import { UIConfig, UserDisplay, getConfigForUser } from "@goauthentik/common/ui/config";
|
||||
import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import "@goauthentik/components/ak-nav-buttons";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
|
||||
import { themeImage } from "@goauthentik/elements/utils/images";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, LitElement, TemplateResult, css, html, nothing } from "lit";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
||||
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
|
||||
import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
@ -29,52 +26,34 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { SessionUser } from "@goauthentik/api";
|
||||
|
||||
//#region Page Navbar
|
||||
|
||||
export interface PageNavbarDetails {
|
||||
header?: string;
|
||||
description?: string;
|
||||
@customElement("ak-page-header")
|
||||
export class PageHeader extends WithBrandConfig(AKElement) {
|
||||
@property()
|
||||
icon?: string;
|
||||
iconImage?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A global navbar component at the top of the page.
|
||||
*
|
||||
* Internally, this component listens for the `ak-page-header` event, which is
|
||||
* dispatched by the `ak-page-header` component.
|
||||
*/
|
||||
@customElement("ak-page-navbar")
|
||||
export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavbarDetails {
|
||||
//#region Static Properties
|
||||
@property({ type: Boolean })
|
||||
iconImage = false;
|
||||
|
||||
private static elementRef: AKPageNavbar | null = null;
|
||||
@property()
|
||||
header = "";
|
||||
|
||||
static readonly setNavbarDetails = (detail: Partial<PageNavbarDetails>): void => {
|
||||
const { elementRef } = AKPageNavbar;
|
||||
if (!elementRef) {
|
||||
console.debug(
|
||||
`ak-page-header: Could not find ak-page-navbar, skipping event dispatch.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@property()
|
||||
description?: string;
|
||||
|
||||
const { header, description, icon, iconImage } = detail;
|
||||
@property({ type: Boolean })
|
||||
hasIcon = true;
|
||||
|
||||
elementRef.header = header;
|
||||
elementRef.description = description;
|
||||
elementRef.icon = icon;
|
||||
elementRef.iconImage = iconImage || false;
|
||||
elementRef.hasIcon = !!icon;
|
||||
};
|
||||
@state()
|
||||
me?: SessionUser;
|
||||
|
||||
@state()
|
||||
uiConfig!: UIConfig;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFButton,
|
||||
PFPage,
|
||||
PFDrawer,
|
||||
|
||||
PFNotificationBadge,
|
||||
PFContent,
|
||||
PFAvatar,
|
||||
@ -84,392 +63,143 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--pf-global--ZIndex--lg);
|
||||
--pf-c-page__header-tools--MarginRight: 0;
|
||||
--ak-brand-logo-height: var(--pf-global--FontSize--4xl, 2.25rem);
|
||||
--ak-brand-background-color: var(
|
||||
--pf-c-page__sidebar--m-light--BackgroundColor
|
||||
);
|
||||
}
|
||||
|
||||
:host([theme="dark"]) {
|
||||
--ak-brand-background-color: var(--pf-c-page__sidebar--BackgroundColor);
|
||||
--pf-c-page__sidebar--BackgroundColor: var(--ak-dark-background-light);
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
|
||||
navbar {
|
||||
.bar {
|
||||
border-bottom: var(--pf-global--BorderWidth--sm);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--pf-global--BorderColor--100);
|
||||
background-color: var(--pf-c-page--BackgroundColor);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 6rem;
|
||||
|
||||
display: grid;
|
||||
row-gap: var(--pf-global--spacer--sm);
|
||||
column-gap: var(--pf-global--spacer--sm);
|
||||
grid-template-columns: [brand] auto [toggle] auto [primary] 1fr [secondary] auto;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"brand toggle primary secondary"
|
||||
"brand toggle description secondary";
|
||||
|
||||
@media (max-width: 768px) {
|
||||
row-gap: var(--pf-global--spacer--xs);
|
||||
|
||||
align-items: center;
|
||||
grid-template-areas:
|
||||
"toggle primary secondary"
|
||||
"toggle description description";
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
min-height: 114px;
|
||||
max-height: 114px;
|
||||
background-color: var(--pf-c-page--BackgroundColor);
|
||||
}
|
||||
|
||||
.items {
|
||||
display: block;
|
||||
|
||||
&.primary {
|
||||
grid-column: primary;
|
||||
grid-row: primary / description;
|
||||
|
||||
align-content: center;
|
||||
padding-block: var(--pf-global--spacer--md);
|
||||
|
||||
@media (min-width: 426px) {
|
||||
&.block-sibling {
|
||||
padding-block-end: 0;
|
||||
grid-row: primary;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding-block: var(--pf-global--spacer--sm);
|
||||
}
|
||||
|
||||
.accent-icon {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.page-description {
|
||||
grid-area: description;
|
||||
padding-block-end: var(--pf-global--spacer--md);
|
||||
|
||||
@media (max-width: 425px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
grid-area: secondary;
|
||||
flex: 0 0 auto;
|
||||
justify-self: end;
|
||||
padding-block: var(--pf-global--spacer--sm);
|
||||
padding-inline-end: var(--pf-global--spacer--sm);
|
||||
|
||||
@media (min-width: 769px) {
|
||||
align-content: center;
|
||||
padding-block: var(--pf-global--spacer--md);
|
||||
padding-inline-end: var(--pf-global--spacer--xl);
|
||||
}
|
||||
}
|
||||
.pf-c-page__main-section.pf-m-light {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.brand {
|
||||
grid-area: brand;
|
||||
background-color: var(--ak-brand-background-color);
|
||||
height: 100%;
|
||||
width: var(--pf-c-page__sidebar--Width);
|
||||
align-items: center;
|
||||
padding-inline: var(--pf-global--spacer--sm);
|
||||
|
||||
.pf-c-page__main-section {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
&.pf-m-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-trigger {
|
||||
grid-area: toggle;
|
||||
height: 100%;
|
||||
img.pf-icon {
|
||||
max-height: 24px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
flex: 0 0 auto;
|
||||
height: var(--ak-brand-logo-height);
|
||||
|
||||
& img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-trigger,
|
||||
.notification-trigger {
|
||||
font-size: 1.5rem;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.notification-trigger.has-notifications {
|
||||
color: var(--pf-global--active-color--100);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: flex;
|
||||
gap: var(--pf-global--spacer--xs);
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center !important;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Properties
|
||||
|
||||
@property({ type: String })
|
||||
icon?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
iconImage = false;
|
||||
|
||||
@property({ type: String })
|
||||
header?: string;
|
||||
|
||||
@property({ type: String })
|
||||
description?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
hasIcon = true;
|
||||
|
||||
@property({ type: Boolean })
|
||||
open = true;
|
||||
|
||||
@state()
|
||||
session?: SessionUser;
|
||||
|
||||
@state()
|
||||
uiConfig!: UIConfig;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Private Methods
|
||||
|
||||
#setTitle(header?: string) {
|
||||
const currentIf = currentInterface();
|
||||
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
|
||||
|
||||
if (currentIf === "admin") {
|
||||
title = `${msg("Admin")} - ${title}`;
|
||||
}
|
||||
// Prepend the header to the title
|
||||
if (header) {
|
||||
title = `${header} - ${title}`;
|
||||
}
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
#toggleSidebar() {
|
||||
this.open = !this.open;
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
AKPageNavbar.elementRef = this;
|
||||
|
||||
window.addEventListener(EVENT_WS_MESSAGE, () => {
|
||||
this.firstUpdated();
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
AKPageNavbar.elementRef = null;
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
this.session = await me();
|
||||
this.uiConfig = getConfigForUser(this.session.user);
|
||||
this.uiConfig.navbar.userDisplay = UserDisplay.none;
|
||||
}
|
||||
|
||||
willUpdate() {
|
||||
// Always update title, even if there's no header value set,
|
||||
// as in that case we still need to return to the generic title
|
||||
this.#setTitle(this.header);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Render
|
||||
|
||||
renderIcon() {
|
||||
if (this.icon) {
|
||||
if (this.iconImage && !this.icon.startsWith("fa://")) {
|
||||
return html`<img class="accent-icon pf-icon" src="${this.icon}" alt="page icon" />`;
|
||||
}
|
||||
|
||||
const icon = this.icon.replaceAll("fa://", "fa ");
|
||||
|
||||
return html`<i class="accent-icon ${icon}"></i>`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<navbar aria-label="Main" class="navbar">
|
||||
<aside class="brand ${this.open ? "" : "pf-m-collapsed"}">
|
||||
<a href="#/">
|
||||
<div class="logo">
|
||||
<img
|
||||
src=${themeImage(
|
||||
this.brand?.brandingLogo ?? DefaultBrand.brandingLogo,
|
||||
)}
|
||||
alt="${msg("authentik Logo")}"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</aside>
|
||||
<button
|
||||
class="sidebar-trigger pf-c-button pf-m-plain"
|
||||
@click=${this.#toggleSidebar}
|
||||
aria-label=${msg("Toggle sidebar")}
|
||||
aria-expanded=${this.open ? "true" : "false"}
|
||||
>
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<section
|
||||
class="items primary pf-c-content ${this.description ? "block-sibling" : ""}"
|
||||
>
|
||||
<h1 class="page-title">
|
||||
${this.hasIcon
|
||||
? html`<slot name="icon">${this.renderIcon()}</slot>`
|
||||
: nothing}
|
||||
${this.header}
|
||||
</h1>
|
||||
</section>
|
||||
${this.description
|
||||
? html`<section class="items page-description pf-c-content">
|
||||
<p>${this.description}</p>
|
||||
</section>`
|
||||
: nothing}
|
||||
|
||||
<section class="items secondary">
|
||||
<div class="pf-c-page__header-tools-group">
|
||||
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.session}>
|
||||
<a
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||
href="${globalAK().api.base}if/user/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("User interface")}
|
||||
</a>
|
||||
</ak-nav-buttons>
|
||||
</div>
|
||||
</section>
|
||||
</navbar>
|
||||
<slot></slot>`;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Page Header
|
||||
|
||||
/**
|
||||
* A page header component, used to display the page title and description.
|
||||
*
|
||||
* Internally, this component dispatches the `ak-page-header` event, which is
|
||||
* listened to by the `ak-page-navbar` component.
|
||||
*
|
||||
* @singleton
|
||||
*/
|
||||
@customElement("ak-page-header")
|
||||
export class AKPageHeader extends LitElement implements PageNavbarDetails {
|
||||
@property({ type: String })
|
||||
header?: string;
|
||||
|
||||
@property({ type: String })
|
||||
description?: string;
|
||||
|
||||
@property({ type: String })
|
||||
icon?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
iconImage = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
:host {
|
||||
display: none;
|
||||
.pf-c-page__header-tools {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.pf-c-page__header-tools-group {
|
||||
height: 100%;
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-page__header-tools {
|
||||
color: var(--ak-dark-foreground) !important;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
AKPageNavbar.setNavbarDetails({
|
||||
header: this.header,
|
||||
description: this.description,
|
||||
icon: this.icon,
|
||||
iconImage: this.iconImage,
|
||||
constructor() {
|
||||
super();
|
||||
window.addEventListener(EVENT_WS_MESSAGE, () => {
|
||||
this.firstUpdated();
|
||||
});
|
||||
}
|
||||
|
||||
updated(): void {
|
||||
AKPageNavbar.setNavbarDetails({
|
||||
header: this.header,
|
||||
description: this.description,
|
||||
icon: this.icon,
|
||||
iconImage: this.iconImage,
|
||||
});
|
||||
async firstUpdated() {
|
||||
this.me = await me();
|
||||
this.uiConfig = await uiConfig();
|
||||
this.uiConfig.navbar.userDisplay = UserDisplay.none;
|
||||
}
|
||||
|
||||
setTitle(header?: string) {
|
||||
const currentIf = currentInterface();
|
||||
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
|
||||
if (currentIf === "admin") {
|
||||
title = `${msg("Admin")} - ${title}`;
|
||||
}
|
||||
// Prepend the header to the title
|
||||
if (header !== undefined && header !== "") {
|
||||
title = `${header} - ${title}`;
|
||||
}
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
willUpdate() {
|
||||
// Always update title, even if there's no header value set,
|
||||
// as in that case we still need to return to the generic title
|
||||
this.setTitle(this.header);
|
||||
}
|
||||
|
||||
renderIcon() {
|
||||
if (this.icon) {
|
||||
if (this.iconImage && !this.icon.startsWith("fa://")) {
|
||||
return html`<img class="pf-icon" src="${this.icon}" alt="page icon" />`;
|
||||
}
|
||||
const icon = this.icon.replaceAll("fa://", "fa ");
|
||||
return html`<i class=${icon}></i>`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="bar">
|
||||
<button
|
||||
class="sidebar-trigger pf-c-button pf-m-plain"
|
||||
@click=${() => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1>
|
||||
${this.hasIcon
|
||||
? html`<slot name="icon">${this.renderIcon()}</slot> `
|
||||
: nothing}
|
||||
<slot name="header">${this.header}</slot>
|
||||
</h1>
|
||||
${this.description ? html`<p>${this.description}</p>` : html``}
|
||||
</div>
|
||||
</section>
|
||||
<div class="pf-c-page__header-tools">
|
||||
<div class="pf-c-page__header-tools-group">
|
||||
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.me}>
|
||||
<a
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||
href="${globalAK().api.base}if/user/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("User interface")}
|
||||
</a>
|
||||
</ak-nav-buttons>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-page-header": AKPageHeader;
|
||||
"ak-page-navbar": AKPageNavbar;
|
||||
"ak-page-header": PageHeader;
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,6 @@ export abstract class AKChart<T> extends AKElement {
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -22,7 +22,6 @@ export class Sidebar extends AKElement {
|
||||
css`
|
||||
:host {
|
||||
z-index: 100;
|
||||
--pf-c-page__sidebar--Transition: 0 !important;
|
||||
}
|
||||
.pf-c-nav__link.pf-m-current::after,
|
||||
.pf-c-nav__link.pf-m-current:hover::after,
|
||||
@ -36,7 +35,10 @@ export class Sidebar extends AKElement {
|
||||
.pf-c-nav__section + .pf-c-nav__section {
|
||||
--pf-c-nav__section--section--MarginTop: var(--pf-global--spacer--sm);
|
||||
}
|
||||
|
||||
.pf-c-nav__list .sidebar-brand {
|
||||
max-height: 82px;
|
||||
margin-bottom: -0.5rem;
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -68,6 +70,7 @@ export class Sidebar extends AKElement {
|
||||
class="pf-c-nav ${this.activeTheme === UiThemeEnum.Light ? "pf-m-light" : ""}"
|
||||
aria-label=${msg("Global")}
|
||||
>
|
||||
<ak-sidebar-brand></ak-sidebar-brand>
|
||||
<ul class="pf-c-nav__list">
|
||||
<slot></slot>
|
||||
</ul>
|
||||
|
@ -1,3 +1,18 @@
|
||||
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import { themeImage } from "@goauthentik/elements/utils/images";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGlobal from "@patternfly/patternfly/patternfly-base.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
// If the viewport is wider than MIN_WIDTH, the sidebar
|
||||
@ -14,3 +29,78 @@ export const DefaultBrand: CurrentBrand = {
|
||||
matchedDomain: "",
|
||||
defaultLocale: "",
|
||||
};
|
||||
|
||||
@customElement("ak-sidebar-brand")
|
||||
export class SidebarBrand extends WithBrandConfig(AKElement) {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFGlobal,
|
||||
PFPage,
|
||||
PFButton,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 114px;
|
||||
min-height: 114px;
|
||||
border-bottom: var(--pf-global--BorderWidth--sm);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--pf-global--BorderColor--100);
|
||||
}
|
||||
.pf-c-brand img {
|
||||
padding: 0 0.5rem;
|
||||
height: 42px;
|
||||
}
|
||||
button.pf-c-button.sidebar-trigger {
|
||||
background-color: transparent;
|
||||
border-radius: 0px;
|
||||
height: 100%;
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
window.addEventListener("resize", () => {
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
const logoUrl =
|
||||
globalAK().brand.brandingLogo || this.brand?.brandingLogo || DefaultBrand.brandingLogo;
|
||||
|
||||
return html`${window.innerWidth <= MIN_WIDTH
|
||||
? html`
|
||||
<button
|
||||
class="sidebar-trigger pf-c-button"
|
||||
@click=${() => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
`
|
||||
: nothing}
|
||||
<a href="#/" class="pf-c-page__header-brand-link">
|
||||
<div class="pf-c-brand ak-brand">
|
||||
<img src=${themeImage(logoUrl)} alt="${msg("authentik Logo")}" loading="lazy" />
|
||||
</div>
|
||||
</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-sidebar-brand": SidebarBrand;
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,19 @@
|
||||
import {
|
||||
appendStyleSheet,
|
||||
assertAdoptableStyleSheetParent,
|
||||
} from "@goauthentik/common/stylesheets.js";
|
||||
|
||||
import { TemplateResult, render as litRender } from "lit";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { ensureCSSStyleSheet } from "../utils/ensureCSSStyleSheet.js";
|
||||
|
||||
// A special version of render that ensures our style sheets will always be available
|
||||
// to all elements under test. Ensures they look right during testing, and that any
|
||||
// CSS-based checks for visibility will return correct values.
|
||||
|
||||
export const render = (body: TemplateResult) => {
|
||||
assertAdoptableStyleSheetParent(document);
|
||||
|
||||
appendStyleSheet([PFBase, AKGlobal], document);
|
||||
document.adoptedStyleSheets = [
|
||||
...document.adoptedStyleSheets,
|
||||
ensureCSSStyleSheet(PFBase),
|
||||
ensureCSSStyleSheet(AKGlobal),
|
||||
];
|
||||
return litRender(body, document.body);
|
||||
};
|
||||
|
@ -1,14 +1,9 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
|
||||
import { type LitElement, type ReactiveControllerHost, type TemplateResult, nothing } from "lit";
|
||||
import "lit";
|
||||
|
||||
/**
|
||||
* A custom element which may be used as a host for a ReactiveController.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This type is derived from an internal type in Lit.
|
||||
*/
|
||||
export type ReactiveElementHost<T> = Partial<ReactiveControllerHost & T> & HTMLElement;
|
||||
export type ReactiveElementHost<T = AKElement> = Partial<ReactiveControllerHost> & T;
|
||||
|
||||
export type AbstractLitElementConstructor = abstract new (...args: never[]) => LitElement;
|
||||
|
||||
|
35
web/src/elements/utils/ensureCSSStyleSheet.ts
Normal file
35
web/src/elements/utils/ensureCSSStyleSheet.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { CSSResult, unsafeCSS } from "lit";
|
||||
|
||||
const supportsAdoptingStyleSheets: boolean =
|
||||
window.ShadowRoot &&
|
||||
(window.ShadyCSS === undefined || window.ShadyCSS.nativeShadow) &&
|
||||
"adoptedStyleSheets" in Document.prototype &&
|
||||
"replace" in CSSStyleSheet.prototype;
|
||||
|
||||
function stringToStylesheet(css: string) {
|
||||
if (supportsAdoptingStyleSheets) {
|
||||
const sheet = unsafeCSS(css).styleSheet;
|
||||
if (sheet === undefined) {
|
||||
throw new Error(
|
||||
`CSS processing error: undefined stylesheet from string. Source: ${css}`,
|
||||
);
|
||||
}
|
||||
return sheet;
|
||||
}
|
||||
|
||||
const sheet = new CSSStyleSheet();
|
||||
sheet.replaceSync(css);
|
||||
return sheet;
|
||||
}
|
||||
|
||||
function cssResultToStylesheet(css: CSSResult) {
|
||||
const sheet = css.styleSheet;
|
||||
return sheet ? sheet : stringToStylesheet(css.toString());
|
||||
}
|
||||
|
||||
export const ensureCSSStyleSheet = (css: string | CSSStyleSheet | CSSResult): CSSStyleSheet =>
|
||||
css instanceof CSSResult
|
||||
? cssResultToStylesheet(css)
|
||||
: typeof css === "string"
|
||||
? stringToStylesheet(css)
|
||||
: css;
|
@ -1,55 +0,0 @@
|
||||
/**
|
||||
* @file IFrame Utilities
|
||||
*/
|
||||
|
||||
interface IFrameLoadResult {
|
||||
contentWindow: Window;
|
||||
contentDocument: Document;
|
||||
}
|
||||
|
||||
export function pluckIFrameContent(iframe: HTMLIFrameElement) {
|
||||
const contentWindow = iframe.contentWindow;
|
||||
const contentDocument = iframe.contentDocument;
|
||||
|
||||
if (!contentWindow) {
|
||||
throw new Error("Iframe contentWindow is not accessible");
|
||||
}
|
||||
|
||||
if (!contentDocument) {
|
||||
throw new Error("Iframe contentDocument is not accessible");
|
||||
}
|
||||
|
||||
return {
|
||||
contentWindow,
|
||||
contentDocument,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveIFrameContent(iframe: HTMLIFrameElement): Promise<IFrameLoadResult> {
|
||||
if (iframe.contentDocument?.readyState === "complete") {
|
||||
return Promise.resolve(pluckIFrameContent(iframe));
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
iframe.addEventListener("load", () => resolve(pluckIFrameContent(iframe)), { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a minimal HTML wrapper for an iframe.
|
||||
*
|
||||
* @deprecated Use the `contentDocument.body` directly instead.
|
||||
*/
|
||||
export function createIFrameHTMLWrapper(bodyContent: string): string {
|
||||
const html = String.raw;
|
||||
|
||||
return html`<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body style="display:flex;flex-direction:row;justify-content:center;">
|
||||
${bodyContent}
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
import { resolveUITheme } from "@goauthentik/common/theme";
|
||||
import { rootInterface } from "@goauthentik/elements/Base";
|
||||
import { QUERY_MEDIA_COLOR_LIGHT, rootInterface } from "@goauthentik/elements/Base";
|
||||
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
export function themeImage(rawPath: string) {
|
||||
const enabledTheme = rootInterface()?.activeTheme || resolveUITheme();
|
||||
|
||||
let enabledTheme = rootInterface()?.activeTheme;
|
||||
if (!enabledTheme || enabledTheme === UiThemeEnum.Automatic) {
|
||||
enabledTheme = window.matchMedia(QUERY_MEDIA_COLOR_LIGHT).matches
|
||||
? UiThemeEnum.Light
|
||||
: UiThemeEnum.Dark;
|
||||
}
|
||||
return rawPath.replaceAll("%(theme)s", enabledTheme);
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ import {
|
||||
FlowsApi,
|
||||
ResponseError,
|
||||
ShellChallenge,
|
||||
UiThemeEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-flow-executor")
|
||||
@ -199,6 +200,10 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
});
|
||||
}
|
||||
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
|
||||
}
|
||||
|
||||
async submit(
|
||||
payload?: FlowChallengeResponseRequest,
|
||||
options?: SubmitOptions,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BrandedHTMLPolicy, sanitizeHTML } from "@goauthentik/common/purify";
|
||||
import { purify } from "@goauthentik/common/purify";
|
||||
import { AKElement } from "@goauthentik/elements/Base.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@ -21,6 +21,8 @@ const styles = css`
|
||||
}
|
||||
`;
|
||||
|
||||
const poweredBy: FooterLink = { name: msg("Powered by authentik"), href: null };
|
||||
|
||||
@customElement("ak-brand-links")
|
||||
export class BrandLinks extends AKElement {
|
||||
static get styles() {
|
||||
@ -31,21 +33,13 @@ export class BrandLinks extends AKElement {
|
||||
links: FooterLink[] = [];
|
||||
|
||||
render() {
|
||||
const links = [...(this.links ?? [])];
|
||||
|
||||
const links = [...(this.links ?? []), poweredBy];
|
||||
return html` <ul class="pf-c-list pf-m-inline">
|
||||
${map(links, (link) => {
|
||||
const children = sanitizeHTML(BrandedHTMLPolicy, link.name);
|
||||
|
||||
if (link.href) {
|
||||
return html`<li><a href="${link.href}">${children}</a></li>`;
|
||||
}
|
||||
|
||||
return html`<li>
|
||||
<span> ${children} </span>
|
||||
</li>`;
|
||||
})}
|
||||
<li><span>${msg("Powered by authentik")}</span></li>
|
||||
${map(links, (link) =>
|
||||
link.href
|
||||
? purify(html`<li><a href="${link.href}">${link.name}</a></li>`)
|
||||
: html`<li><span>${link.name}</span></li>`,
|
||||
)}
|
||||
</ul>`;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
/// <reference types="@hcaptcha/types"/>
|
||||
/// <reference types="turnstile-types"/>
|
||||
import { renderStaticHTMLUnsafe } from "@goauthentik/common/purify";
|
||||
///<reference types="@hcaptcha/types"/>
|
||||
import { renderStatic } from "@goauthentik/common/purify";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import { akEmptyState } from "@goauthentik/elements/EmptyState";
|
||||
import { bound } from "@goauthentik/elements/decorators/bound";
|
||||
import "@goauthentik/elements/forms/FormElement";
|
||||
import { createIFrameHTMLWrapper } from "@goauthentik/elements/utils/iframe";
|
||||
import { ListenerController } from "@goauthentik/elements/utils/listenerController.js";
|
||||
import { randomId } from "@goauthentik/elements/utils/randomId";
|
||||
import "@goauthentik/flow/FormStatic";
|
||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||
import { P, match } from "ts-pattern";
|
||||
import type * as _ from "turnstile-types";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
|
||||
@ -57,36 +56,40 @@ type CaptchaHandler = {
|
||||
// a resize. Because the Captcha is itself in an iframe, the reported height is often off by some
|
||||
// margin, so adding 2rem of height to our container adds padding and prevents scroll bars or hidden
|
||||
// rendering.
|
||||
function iframeTemplate(children: TemplateResult, challengeURL: string): TemplateResult {
|
||||
return html` ${children}
|
||||
<script>
|
||||
new ResizeObserver((entries) => {
|
||||
const height =
|
||||
document.body.offsetHeight +
|
||||
parseFloat(getComputedStyle(document.body).fontSize) * 2;
|
||||
|
||||
window.parent.postMessage({
|
||||
message: "resize",
|
||||
source: "goauthentik.io",
|
||||
context: "flow-executor",
|
||||
size: { height },
|
||||
});
|
||||
}).observe(document.querySelector(".ak-captcha-container"));
|
||||
</script>
|
||||
|
||||
<script src=${challengeURL}></script>
|
||||
|
||||
<script>
|
||||
function callback(token) {
|
||||
window.parent.postMessage({
|
||||
message: "captcha",
|
||||
source: "goauthentik.io",
|
||||
context: "flow-executor",
|
||||
token,
|
||||
});
|
||||
}
|
||||
</script>`;
|
||||
}
|
||||
const iframeTemplate = (captchaElement: TemplateResult, challengeUrl: string) =>
|
||||
html`<!doctype html>
|
||||
<head>
|
||||
<html>
|
||||
<body style="display:flex;flex-direction:row;justify-content:center;">
|
||||
${captchaElement}
|
||||
<script>
|
||||
new ResizeObserver((entries) => {
|
||||
const height =
|
||||
document.body.offsetHeight +
|
||||
parseFloat(getComputedStyle(document.body).fontSize) * 2;
|
||||
window.parent.postMessage({
|
||||
message: "resize",
|
||||
source: "goauthentik.io",
|
||||
context: "flow-executor",
|
||||
size: { height },
|
||||
});
|
||||
}).observe(document.querySelector(".ak-captcha-container"));
|
||||
</script>
|
||||
<script src=${challengeUrl}></script>
|
||||
<script>
|
||||
function callback(token) {
|
||||
window.parent.postMessage({
|
||||
message: "captcha",
|
||||
source: "goauthentik.io",
|
||||
context: "flow-executor",
|
||||
token: token,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</head>`;
|
||||
|
||||
@customElement("ak-stage-captcha")
|
||||
export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeResponseRequest> {
|
||||
@ -302,25 +305,11 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
}
|
||||
|
||||
async renderFrame(captchaElement: TemplateResult) {
|
||||
const { contentDocument } = this.captchaFrame || {};
|
||||
|
||||
if (!contentDocument) {
|
||||
console.debug(
|
||||
"authentik/stages/captcha: unable to render captcha frame, no contentDocument",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
contentDocument.open();
|
||||
|
||||
contentDocument.write(
|
||||
createIFrameHTMLWrapper(
|
||||
renderStaticHTMLUnsafe(iframeTemplate(captchaElement, this.challenge.jsUrl)),
|
||||
),
|
||||
this.captchaFrame.contentWindow?.document.open();
|
||||
this.captchaFrame.contentWindow?.document.write(
|
||||
await renderStatic(iframeTemplate(captchaElement, this.challenge.jsUrl)),
|
||||
);
|
||||
|
||||
contentDocument.close();
|
||||
this.captchaFrame.contentWindow?.document.close();
|
||||
}
|
||||
|
||||
renderBody() {
|
||||
|
@ -3,6 +3,7 @@ import "rapidoc";
|
||||
|
||||
import { CSRFHeaderName } from "@goauthentik/common/api/config";
|
||||
import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { first, getCookie } from "@goauthentik/common/utils";
|
||||
import { Interface } from "@goauthentik/elements/Interface";
|
||||
import "@goauthentik/elements/ak-locale-context";
|
||||
@ -61,6 +62,10 @@ export class APIBrowser extends Interface {
|
||||
);
|
||||
}
|
||||
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<ak-locale-context>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { Interface } from "@goauthentik/elements/Interface";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@ -9,6 +10,8 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-loading")
|
||||
export class Loading extends Interface {
|
||||
static get styles(): CSSResult[] {
|
||||
@ -25,7 +28,7 @@ export class Loading extends Interface {
|
||||
];
|
||||
}
|
||||
|
||||
registerContexts(): void {
|
||||
_initContexts(): void {
|
||||
// Stub function to avoid making API requests for things we don't need. The `Interface` base class loads
|
||||
// a bunch of data that is used globally by various things, however this is an interface that is shown
|
||||
// very briefly and we don't need any of that data.
|
||||
@ -35,6 +38,10 @@ export class Loading extends Interface {
|
||||
// Stub function to avoid fetching custom CSS.
|
||||
}
|
||||
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html` <section
|
||||
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
|
||||
|
@ -1,9 +1,18 @@
|
||||
import { FlowExecutor } from "@goauthentik/flow/FlowExecutor";
|
||||
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-storybook-interface-flow")
|
||||
export class StoryFlowInterface extends FlowExecutor {}
|
||||
export class StoryFlowInterface extends FlowExecutor {
|
||||
@property()
|
||||
storyTheme: UiThemeEnum = UiThemeEnum.Dark;
|
||||
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
return this.storyTheme;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
@ -1,9 +1,18 @@
|
||||
import { Interface } from "@goauthentik/elements/Interface";
|
||||
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-storybook-interface")
|
||||
export class StoryInterface extends Interface {}
|
||||
export class StoryInterface extends Interface {
|
||||
@property()
|
||||
storyTheme: UiThemeEnum = UiThemeEnum.Dark;
|
||||
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
return this.storyTheme;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { UIConfig, getConfigForUser } from "@goauthentik/common/ui/config";
|
||||
import { UIConfig } from "@goauthentik/common/ui/config";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||
import "@goauthentik/components/ak-nav-buttons";
|
||||
@ -292,7 +292,6 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
|
||||
window.addEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
|
||||
window.addEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
|
||||
@ -302,7 +301,6 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
window.removeEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
|
||||
window.removeEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
|
||||
window.removeEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
|
||||
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
@ -321,10 +319,8 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
}
|
||||
|
||||
fetchConfigurationDetails() {
|
||||
me().then((session: SessionUser) => {
|
||||
this.me = session;
|
||||
this.uiConfig = getConfigForUser(session.user);
|
||||
|
||||
me().then((me: SessionUser) => {
|
||||
this.me = me;
|
||||
new EventsApi(DEFAULT_CONFIG)
|
||||
.eventsNotificationsList({
|
||||
seen: false,
|
||||
@ -338,16 +334,12 @@ export class UserInterface extends AuthenticatedInterface {
|
||||
});
|
||||
}
|
||||
|
||||
get isFullyConfigured() {
|
||||
return Boolean(this.uiConfig && this.me);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.me) {
|
||||
console.debug(`authentik/user/UserInterface: waiting for user session to be available`);
|
||||
|
||||
return nothing;
|
||||
}
|
||||
|
||||
if (!this.uiConfig) {
|
||||
console.debug(`authentik/user/UserInterface: waiting for UI config to be available`);
|
||||
|
||||
if (!this.isFullyConfigured) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
|
@ -1091,7 +1091,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="sd62cfc27ad4aa33b">
|
||||
<source>Based on the User's Email</source>
|
||||
<target>Basierend auf der E-Mail Adresse des Benutzers</target>
|
||||
<target>Basierend auf der E-Mail-Adresse des Benutzers</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s55eb75bedf96be0f">
|
||||
@ -2361,6 +2361,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="s9307f3dbb07a73b5">
|
||||
<source>Only fail the policy, don't invalidate user's password</source>
|
||||
<target>Nur die Richtlinie fehlschlagen lassen, das Passwort des Benutzers nicht ungültig machen.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="scea1f16238093e35">
|
||||
@ -2809,6 +2810,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
</trans-unit>
|
||||
<trans-unit id="sfbc59ff17a73503d">
|
||||
<source>User path</source>
|
||||
<target>Nutzerpfad</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sd18170637295bace">
|
||||
@ -2878,6 +2880,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
</trans-unit>
|
||||
<trans-unit id="s995535e7af30d754">
|
||||
<source>Use the user's email address, but deny enrollment when the email address already exists</source>
|
||||
<target>Verwende die E-Mail-Adresse des Benutzers, aber verweigere die Registrierung, wenn die E-Mail-Adresse bereits existiert.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s542ecb4130f6cea5">
|
||||
@ -2886,6 +2889,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
</trans-unit>
|
||||
<trans-unit id="s2a1debf34e5aeba4">
|
||||
<source>Use the user's username, but deny enrollment when the username already exists</source>
|
||||
<target>Verwende den Anmeldenamen des Benutzers, aber verweigere die Registrierung von der Anmeldename bereits existiert.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s81ce0d54727f42d2">
|
||||
@ -3740,6 +3744,9 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
</trans-unit>
|
||||
<trans-unit id="s14401ff4a0cba208">
|
||||
<source>Failed to update <x id="0" equiv-text="${this.objectLabel}"/>: <x id="1" equiv-text="${pluckErrorDetail(parsedError)}"/></source>
|
||||
<target>Aktualisieren von
|
||||
<x id="0" equiv-text="${this.objectLabel}"/>fehlgeschlagen:
|
||||
<x id="1" equiv-text="${e.toString()}"/></target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa95a538bfbb86111">
|
||||
@ -4951,7 +4958,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
</trans-unit>
|
||||
<trans-unit id="se50a08ab71bb96ed">
|
||||
<source>When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown.</source>
|
||||
<target>Sofern eine gültige E-Mailadresse oder Benutzername angegeben wurde und diese Option aktiviert ist, wird das Profilbild und der Benutzername des Benutzers angezeigt. Ansonsten wird der vom Benutzer eingegebene Text angezeigt.</target>
|
||||
<target>Sofern eine gültige E-Mail-Adresse oder Benutzername angegeben wurde und diese Option aktiviert ist, wird das Profilbild und der Benutzername des Benutzers angezeigt. Ansonsten wird der vom Benutzer eingegebene Text angezeigt.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s0295ce5d6f635d75">
|
||||
@ -6245,7 +6252,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s670ad066cc0e50a3">
|
||||
<source>Login to continue to <x id="0" equiv-text="${this.challenge.applicationPre}"/>.</source>
|
||||
<target>Anmelden um mit <x id="0" equiv-text="${this.challenge.applicationPre}"/> fortzufahren.</target>
|
||||
<target>Anmelden, um mit <x id="0" equiv-text="${this.challenge.applicationPre}"/> fortzufahren.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="scf5ce91bfba10a61">
|
||||
@ -7451,6 +7458,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s28b99b59541f54ca">
|
||||
<source>Connection failed after <x id="0" equiv-text="${this.connectionAttempt}"/> attempts.</source>
|
||||
<target>Verbindung nach <x id="0" equiv-text="${this.connectionAttempt}"/> Versuch(en) fehlgeschlagen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7c7d956418e1c8c8">
|
||||
<source>Re-connecting in <x id="0" equiv-text="${Math.max(1, delay / 1000)}"/> second(s).</source>
|
||||
@ -7572,11 +7580,11 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s456d88f3679190fd">
|
||||
<source>Allow users to change email</source>
|
||||
<target>Benutzer können E-Mail ändern</target>
|
||||
<target>Benutzer können E-Mail-Adresse ändern</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5fc6c14d106f40d3">
|
||||
<source>Enable the ability for users to change their email.</source>
|
||||
<target>Benutzer haben die Möglichkeit, ihre E-Mail zu ändern.</target>
|
||||
<target>Benutzer haben die Möglichkeit, ihre E-Mail-Adresse zu ändern.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s628e414bb2367057">
|
||||
<source>Allow users to change username</source>
|
||||
@ -7801,7 +7809,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8cc0075913c67566">
|
||||
<source>Enter the email associated with your account, and we'll send you a link to reset your password.</source>
|
||||
<target>Gib die Email deines Accounts ein und du erhältst einen Link zum zurücksetzen des Passworts.</target>
|
||||
<target>Gib die E-Mail-Adresse deines Accounts ein und du erhältst einen Link zum zurücksetzen des Passworts.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s06bfe45ffef2cf60">
|
||||
<source>Stage name: <x id="0" equiv-text="${this.challenge.name}"/></source>
|
||||
@ -7885,6 +7893,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc1673c93148583ba">
|
||||
<source>Request failed. Please try again later.</source>
|
||||
<target>Anfrage fehlgeschlagen. Bitte versuchen Sie es später erneut.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s85be1f5e7a0fa3b1">
|
||||
<source>Available Roles</source>
|
||||
@ -8277,7 +8286,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s354405ae02cb262d">
|
||||
<source>Last seen: <x id="0" equiv-text="${formatElapsedTime(lastSeen)}"/> (<x id="1" equiv-text="${lastSeen.toLocaleTimeString()}"/>)</source>
|
||||
<target>Zuletzt gesehen: <x id="0" equiv-text="${getRelativeTime(lastSeen)}"/> (<x id="1" equiv-text="${lastSeen.toLocaleTimeString()}"/>)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5aebe06e3ddf6ca9">
|
||||
<source>Sign assertions</source>
|
||||
@ -8849,9 +8857,11 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s61ffea061fae0af4">
|
||||
<source>No notifications found.</source>
|
||||
<target>Keine Benachrichtigungen gefunden.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="scf8d5cdc8b434982">
|
||||
<source>You don't have any notifications currently.</source>
|
||||
<target>Sie haben zur Zeit keine Benachrichtigungen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s358e08de4fbebf51">
|
||||
<source>Version <x id="0" equiv-text="${this.version?.versionCurrent || ""}"/></source>
|
||||
|
@ -9602,6 +9602,81 @@ Liitokset käyttäjiin/ryhmiin tarkistetaan tapahtuman käyttäjästä.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s17359123e1f24504">
|
||||
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s891cd64acabf23bf">
|
||||
<source>Initial Permissions</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sedb57bf4b42a8e40">
|
||||
<source>Unknown Initial Permissions mode</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6ea6a64acb45dfdf">
|
||||
<source>Successfully updated initial permissions.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sfddf7896ab5938b6">
|
||||
<source>Successfully created initial permissions.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5c5f240cbb6d0bae">
|
||||
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sbf27294eef56ac81">
|
||||
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s93f04537efda1b24">
|
||||
<source>Available Permissions</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s297bc57f9e494470">
|
||||
<source>Selected Permissions</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0ea71d53764d781c">
|
||||
<source>Permissions to grant when a new object is created.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s06fc21a40f5d7de1">
|
||||
<source>Set initial permissions for newly created objects.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc7104a4d0fc35c7c">
|
||||
<source>Update Initial Permissions</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s83fa005829a65be9">
|
||||
<source>Create Initial Permissions</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="se37ac6cf9c72f21a">
|
||||
<source>Reputation: lower limit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa634ffa797037aac">
|
||||
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s862986ce8e70edd7">
|
||||
<source>Reputation: upper limit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sdd04913b3b46cf30">
|
||||
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4d5cb134999b50df">
|
||||
<source>HTTP Basic Auth</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6927635d1c339cfc">
|
||||
<source>Include the client ID and secret as request parameters</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4fca384c634e1a92">
|
||||
<source>Authorization code authentication method</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sdc02c276ed429008">
|
||||
<source>How to perform authentication during an authorization_code token request flow</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s844baf19a6c4a9b4">
|
||||
<source>Enable "Remember me on this device"</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sfa72bca733f40692">
|
||||
<source>When enabled, the user can save their username in a cookie, allowing them to skip directly to entering their password.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1c336c2d6cef77b3">
|
||||
<source>Remember me on this device</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86cf007b861152ca">
|
||||
<source>Ensure that the user's new password is different from their previous passwords. The number of past passwords to check is configurable.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s79b3fcd40dd63921">
|
||||
<source>Number of previous passwords to check</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -9700,6 +9700,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1d47b4f61ca53e8e">
|
||||
<source>Lookup using user attribute</source>
|
||||
<target>Ricerca tramite attributo utente</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s17359123e1f24504">
|
||||
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
|
||||
|
@ -8620,7 +8620,6 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
|
||||
</trans-unit>
|
||||
<trans-unit id="s354405ae02cb262d">
|
||||
<source>Last seen: <x id="0" equiv-text="${formatElapsedTime(lastSeen)}"/> (<x id="1" equiv-text="${lastSeen.toLocaleTimeString()}"/>)</source>
|
||||
<target>Son görülme: <x id="0" equiv-text="${getRelativeTime(lastSeen)}"/> (<x id="1" equiv-text="${lastSeen.toLocaleTimeString()}"/>)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5aebe06e3ddf6ca9">
|
||||
<source>Sign assertions</source>
|
||||
|
@ -49,3 +49,17 @@ device_code=device_code_from_above
|
||||
If the user has not opened the link above yet, or has not finished the authentication and authorization yet, the response will contain an `error` element set to `authorization_pending`. The device should re-send the request in the interval set above.
|
||||
|
||||
If the user _has_ finished the authentication and authorization, the response will be similar to any other generic OAuth2 Token request, containing `access_token` and `id_token`.
|
||||
|
||||
### Creating and applying a device code flow
|
||||
|
||||
1. Log in to authentik as an admin, and open the authentik Admin interface.
|
||||
2. Navigate to **Flows and Stages** > **Flows** and click **Create**.
|
||||
3. Set the following required configurations:
|
||||
- **Name**: provide a name (e.g. `default-device-code-flow`)
|
||||
- **Title**: provide a title (e.g. `Device code flow`)
|
||||
- **Slug**: provide a slug (e.g `default-device-code-flow`)
|
||||
- **Designation**: `Stage Configuration`
|
||||
- **Authentication**: `Require authentication`
|
||||
4. Click **Create**.
|
||||
5. Navigate to **System** > **Brands** and click the **Edit** icon on the default brand.
|
||||
6. Set **Default code flow** to the newly created device code flow and click **Update**.
|
||||
|
@ -6,12 +6,53 @@ slug: "/releases/2025.4"
|
||||
:::::note
|
||||
2025.4 has not been released yet! We're publishing these release notes as a preview of what's to come, and for our awesome beta testers trying out release candidates.
|
||||
|
||||
To try out the release candidate, replace your Docker image tag with the latest release candidate number, such as 2025.4.0-rc1. You can find the latest one in [the latest releases on GitHub](https://github.com/goauthentik/authentik/releases). If you don't find any, it means we haven't released one yet.
|
||||
To try out the release candidate, replace your Docker image tag with the latest release candidate number, such as 2025.4.0-rc2. You can find the latest one in [the latest releases on GitHub](https://github.com/goauthentik/authentik/releases). If you don't find any, it means we haven't released one yet.
|
||||
:::::
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Improve membership resolution for the LDAP Source** Allow lookups of LDAP group memberships from user attributes as an alternative to lookups from group attributes. This also allows for nested group lookups in Active Directory.
|
||||
|
||||
- **Support for PostgreSQL Connection Pools** PostgreSQL Connection Pools provides a set of open connections in order to reduce latency.
|
||||
|
||||
- **RBAC: Initial Permissions** :ak-preview Provides more flexible access control by assigning permissions to the user/role creating a new object in authentik. Use **Initial Permissions** as a pragmatic way to implement the principle of least privilege.
|
||||
|
||||
- **Password History Policy** :ak-enterprise A new policy can prevent users from reusing previous passwords; admins are able to configure how many previous password hashes the system will store and evaluate. This new policy makes it easier to enforce password reuse requirements, such as for fedRAMP compliance.
|
||||
|
||||
- **Source Sync Dry Run** :ak-preview Add the option for dry-run syncs for SCIM, Google Workspace, and Entra to preview the results of a sync without affecting live accounts.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
### Manual action may be required
|
||||
- **Reputation score limit**: The default value for the new limits on Reputation score is between `-5` and `5`. This might break some current setups which count on the possibility of scores decreasing or increasing beyond these limits. You can set your custom limits under **System > Settings**.
|
||||
|
||||
- **Deprecated and frozen `:latest` container image tag after 2025.2**
|
||||
|
||||
Using the `:latest` tag with container images is not recommended as it can lead to unintentional updates and potentially broken setups.
|
||||
|
||||
The tag will not be removed, however it will also not be updated past 2025.2.
|
||||
|
||||
We strongly recommended the use of a specific version tag for authentik instances' container images like `:2025.4`.
|
||||
|
||||
- **Helm chart dependencies update**: Following [Bitnami's changes to only publish latest version of containers](https://github.com/bitnami/containers/issues/75671), the Helm chart dependencies (PostgreSQL and Redis) will now be updated with each release.
|
||||
|
||||
For this release:
|
||||
|
||||
- The Redis chart will be upgraded to the latest version. As the image is not pinned, it will also get upgraded.
|
||||
- The PostgreSQL chart will be upgraded to the latest version, but the image will remain pinned to `15.8.0-debian-12-r18`.
|
||||
|
||||
For the next release:
|
||||
|
||||
- The Redis chart will be upgraded to the latest version again.
|
||||
- The PostgreSQL chart will be upgraded to the latest version again, and the image will no longer be pinned, which will bring it to PostgreSQL major version `17`. This will require following PostgreSQL major upgrade steps, for which we provide [documentation](../../troubleshooting/postgres/upgrade_kubernetes.md).
|
||||
|
||||
For subsequent releases:
|
||||
|
||||
- The Redis chart will be upgraded to the latest version.
|
||||
- The PostgreSQL chart will be upgraded to the latest version, with major upgrades being called out in authentik release notes.
|
||||
|
||||
We encourage users to pin their PostgreSQL image version.
|
||||
|
||||
### Manual action might be required
|
||||
|
||||
#### Sessions are now stored in the database
|
||||
|
||||
@ -19,7 +60,38 @@ Previously, sessions were stored by default in the cache. Now, they are stored i
|
||||
|
||||
## New features
|
||||
|
||||
### Postgres pool
|
||||
- **Remember me**: Users can now choose to skip entering their usernames after their first login on the same device. This feature is optional and admins can enable it in the [Identification stage](https://TODO).
|
||||
|
||||
- **RBAC: Initial Permissions**
|
||||
|
||||
In hardened authentik installations, it is possible for a user to have permission to create an object type, but not to view/change/delete(/etc) objects of that type. Previously, this could result in a user creating an object and then not having any access to that object. `InitialPermissions` is a mechanism to automatically add permissions to any object on creation.
|
||||
|
||||
- **Reputation score limit**
|
||||
|
||||
Reputation scores now have a configurable numerical limit in addition to the [already existing temporal limit](https://docs.goauthentik.io/docs/install-config/configuration/#authentik_reputation__expiry).
|
||||
|
||||
- **Support for PostgreSQL Connection Pools**: See [description](#highlights) under Highlights. Refer to our [documentation](https://TODO).
|
||||
|
||||
- **Password History Policy**: See [description](#highlights) under Highlights. Refer to our [documentation](https://TODO).
|
||||
|
||||
## New integration guides
|
||||
|
||||
An integration is a how authentik connects to third-party applications, directories, and other identity providers. The following integration guides were recently added.
|
||||
|
||||
- [Apple Business Manager](../../../integrations/services/apple/)
|
||||
- [FleetDM](../../../integrations/services/fleet/)
|
||||
- [Gravity](../../../integrations/services/gravity/)
|
||||
- [Homarr](../../../integrations/services/homarr/)
|
||||
- [KnocKnoc](../../../integrations/services/knocknoc)
|
||||
- [Mautic](../../../integrations/services/mautic/)
|
||||
- [Mailcow](../../../integrations/services/mailcow/)
|
||||
- [Mealie](../../../integrations/services/mealie/)
|
||||
- [OpenProject](../../../integrations/services/openproject)
|
||||
- [Sidero Omni](../../../integrations/services/omni)
|
||||
- [Tandoor](../../../integrations/services/tandoor/)
|
||||
- [Wazuh](../../../integrations/services/wazuh)
|
||||
- [XCreds](../../../integrations/services/xcreds)
|
||||
- [Zipline](../../../integrations/services/zipline/)
|
||||
|
||||
## Upgrading
|
||||
|
||||
@ -51,8 +123,8 @@ helm upgrade authentik authentik/authentik -f values.yaml --version ^2025.4
|
||||
|
||||
## Minor changes/fixes
|
||||
|
||||
<!-- _Insert the output of `make gen-changelog` here_ -->
|
||||
|
||||
## API Changes
|
||||
|
||||
<!-- _Insert output of `make gen-diff` here_ -->
|
||||
#### What's New
|
||||
|
||||
---
|
||||
|
@ -48,7 +48,7 @@ Add the following environment variables to your Homarr configuration. Make sure
|
||||
AUTH_PROVIDERS="oidc,credentials"
|
||||
AUTH_OIDC_CLIENT_ID=<Client ID from authentik>
|
||||
AUTH_OIDC_CLIENT_SECRET=<Client secret from authentik>
|
||||
AUTH_OIDC_ISSUER=https://authentik.company/application/o/<slug from authentik>
|
||||
AUTH_OIDC_ISSUER=https://authentik.company/application/o/<slug from authentik>/
|
||||
AUTH_OIDC_URI=https://authentik.company/application/o/authorize
|
||||
AUTH_OIDC_CLIENT_NAME=authentik
|
||||
OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING=true
|
||||
|
Reference in New Issue
Block a user