Compare commits
	
		
			64 Commits
		
	
	
		
			monorepo-v
			...
			safari-cra
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2e473c16fa | |||
| cf160f800d | |||
| e9822cd937 | |||
| 5244f64be4 | |||
| 0df4824fd4 | |||
| ea22abc75d | |||
| b09bab7543 | |||
| 5aedc8a5f2 | |||
| 2f3ae0f607 | |||
| e3674426b7 | |||
| df915d3a5e | |||
| 4949c31860 | |||
| 4580dec06b | |||
| 56de969640 | |||
| 413902508d | |||
| 64af0ccba6 | |||
| 673db53777 | |||
| 8df7716d90 | |||
| 19bb2de13f | |||
| a218fd7628 | |||
| 78cfb50a90 | |||
| 2033d52dc2 | |||
| be00f47ddc | |||
| 2cc5f4b273 | |||
| 4e8f3407a4 | |||
| 7f861cc2a1 | |||
| 7bf58d0ba2 | |||
| fffcb00f39 | |||
| 77ee868573 | |||
| 6aaec08496 | |||
| cc15584650 | |||
| e55e446b89 | |||
| 76088e48b5 | |||
| 4165a0a6b2 | |||
| 647fefe5ce | |||
| 723dccdae3 | |||
| c82f747e5e | |||
| 43406e2464 | |||
| a0ff0bef85 | |||
| bedf548a5f | |||
| 976e81c1dd | |||
| ad733033d7 | |||
| ba686f6a93 | |||
| dc50be1e13 | |||
| 205686d252 | |||
| 6d589013e6 | |||
| 2d6433ca9a | |||
| b5f07acb26 | |||
| ea8702077c | |||
| 6593357115 | |||
| 6daed865c1 | |||
| c48a21707a | |||
| e857770c0a | |||
| add74c8799 | |||
| 12d854035d | |||
| 57dd4ae91d | |||
| 37fbc98177 | |||
| 14f216eb40 | |||
| 1209dd022e | |||
| c96f13ac66 | |||
| 5e6874cc1f | |||
| fb5053ec83 | |||
| 6f7dc2c543 | |||
| 542b69b224 | 
| @ -10,9 +10,6 @@ insert_final_newline = true | |||||||
| [*.html] | [*.html] | ||||||
| indent_size = 2 | indent_size = 2 | ||||||
|  |  | ||||||
| [schemas/*.json] |  | ||||||
| indent_size = 2 |  | ||||||
|  |  | ||||||
| [*.{yaml,yml}] | [*.{yaml,yml}] | ||||||
| indent_size = 2 | indent_size = 2 | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							| @ -28,9 +28,9 @@ runs: | |||||||
|     - name: Setup node |     - name: Setup node | ||||||
|       uses: actions/setup-node@v4 |       uses: actions/setup-node@v4 | ||||||
|       with: |       with: | ||||||
|         node-version-file: package.json |         node-version-file: web/package.json | ||||||
|         cache: "npm" |         cache: "npm" | ||||||
|         cache-dependency-path: package-lock.json |         cache-dependency-path: web/package-lock.json | ||||||
|     - name: Setup go |     - name: Setup go | ||||||
|       uses: actions/setup-go@v5 |       uses: actions/setup-go@v5 | ||||||
|       with: |       with: | ||||||
| @ -44,7 +44,7 @@ runs: | |||||||
|       run: | |       run: | | ||||||
|         export PSQL_TAG=${{ inputs.postgresql_version }} |         export PSQL_TAG=${{ inputs.postgresql_version }} | ||||||
|         docker compose -f .github/actions/setup/docker-compose.yml up -d |         docker compose -f .github/actions/setup/docker-compose.yml up -d | ||||||
|         npm ci |         cd web && npm ci | ||||||
|     - name: Generate config |     - name: Generate config | ||||||
|       shell: uv run python {0} |       shell: uv run python {0} | ||||||
|       run: | |       run: | | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| # Re-usable workflow for a single-architecture build | # Re-usable workflow for a single-architecture build | ||||||
| name: "Single-arch Container build" | name: Single-arch Container build | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   workflow_call: |   workflow_call: | ||||||
| @ -42,7 +42,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: docker/setup-qemu-action@v3.6.0 |       - uses: docker/setup-qemu-action@v3.6.0 | ||||||
|       - uses: docker/setup-buildx-action@v3 |       - uses: docker/setup-buildx-action@v3 | ||||||
|       - name: Prepare variables |       - name: prepare variables | ||||||
|         uses: ./.github/actions/docker-push-variables |         uses: ./.github/actions/docker-push-variables | ||||||
|         id: ev |         id: ev | ||||||
|         env: |         env: | ||||||
| @ -64,12 +64,12 @@ jobs: | |||||||
|           registry: ghcr.io |           registry: ghcr.io | ||||||
|           username: ${{ github.repository_owner }} |           username: ${{ github.repository_owner }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN }} |           password: ${{ secrets.GITHUB_TOKEN }} | ||||||
|       - name: Make empty clients |       - name: make empty clients | ||||||
|         if: ${{ inputs.release }} |         if: ${{ inputs.release }} | ||||||
|         run: | |         run: | | ||||||
|           mkdir -p ./gen-ts-api |           mkdir -p ./gen-ts-api | ||||||
|           mkdir -p ./gen-go-api |           mkdir -p ./gen-go-api | ||||||
|       - name: Generate TypeScript API Client |       - name: generate ts client | ||||||
|         if: ${{ !inputs.release }} |         if: ${{ !inputs.release }} | ||||||
|         run: make gen-client-ts |         run: make gen-client-ts | ||||||
|       - name: Build Docker Image |       - name: Build Docker Image | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| # Re-usable workflow for a multi-architecture build | # Re-usable workflow for a multi-architecture build | ||||||
| name: "Multi-arch container build" | name: Multi-arch container build | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   workflow_call: |   workflow_call: | ||||||
| @ -49,7 +49,7 @@ jobs: | |||||||
|       shouldPush: ${{ steps.ev.outputs.shouldPush }} |       shouldPush: ${{ steps.ev.outputs.shouldPush }} | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Prepare variables |       - name: prepare variables | ||||||
|         uses: ./.github/actions/docker-push-variables |         uses: ./.github/actions/docker-push-variables | ||||||
|         id: ev |         id: ev | ||||||
|         env: |         env: | ||||||
| @ -69,7 +69,7 @@ jobs: | |||||||
|         tag: ${{ fromJson(needs.get-tags.outputs.tags) }} |         tag: ${{ fromJson(needs.get-tags.outputs.tags) }} | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Prepare variables |       - name: prepare variables | ||||||
|         uses: ./.github/actions/docker-push-variables |         uses: ./.github/actions/docker-push-variables | ||||||
|         id: ev |         id: ev | ||||||
|         env: |         env: | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								.github/workflows/api-py-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/api-py-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,4 @@ | |||||||
| name: "Python API Publish" | name: authentik-api-py-publish | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [main] |     branches: [main] | ||||||
| @ -8,7 +7,6 @@ on: | |||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
| jobs: | jobs: | ||||||
|   build: |   build: | ||||||
|     name: "Build and Publish" |  | ||||||
|     if: ${{ github.repository != 'goauthentik/authentik-internal' }} |     if: ${{ github.repository != 'goauthentik/authentik-internal' }} | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
| @ -32,7 +30,7 @@ jobs: | |||||||
|         uses: actions/setup-python@v5 |         uses: actions/setup-python@v5 | ||||||
|         with: |         with: | ||||||
|           python-version-file: "pyproject.toml" |           python-version-file: "pyproject.toml" | ||||||
|       - name: Generate Python API Client |       - name: Generate API Client | ||||||
|         run: make gen-client-py |         run: make gen-client-py | ||||||
|       - name: Publish package |       - name: Publish package | ||||||
|         working-directory: gen-py-api/ |         working-directory: gen-py-api/ | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								.github/workflows/api-ts-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/api-ts-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: "TypeScript API Publish" | name: authentik-api-ts-publish | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [main] |     branches: [main] | ||||||
| @ -7,7 +7,6 @@ on: | |||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
| jobs: | jobs: | ||||||
|   build: |   build: | ||||||
|     name: "Build and Publish" |  | ||||||
|     if: ${{ github.repository != 'goauthentik/authentik-internal' }} |     if: ${{ github.repository != 'goauthentik/authentik-internal' }} | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
| @ -21,9 +20,9 @@ jobs: | |||||||
|           token: ${{ steps.generate_token.outputs.token }} |           token: ${{ steps.generate_token.outputs.token }} | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version-file: package.json |           node-version-file: web/package.json | ||||||
|           registry-url: "https://registry.npmjs.org" |           registry-url: "https://registry.npmjs.org" | ||||||
|       - name: Generate TypeScript API Client |       - name: Generate API Client | ||||||
|         run: make gen-client-ts |         run: make gen-client-ts | ||||||
|       - name: Publish package |       - name: Publish package | ||||||
|         working-directory: gen-ts-api/ |         working-directory: gen-ts-api/ | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.github/workflows/ci-aws-cfn.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci-aws-cfn.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: "authentik CI AWS CloudFormation" | name: authentik-ci-aws-cfn | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @ -18,7 +18,6 @@ env: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   check-changes-applied: |   check-changes-applied: | ||||||
|     name: "Check changes applied" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
| @ -37,7 +36,6 @@ jobs: | |||||||
|           uv run make aws-cfn |           uv run make aws-cfn | ||||||
|           git diff --exit-code |           git diff --exit-code | ||||||
|   ci-aws-cfn-mark: |   ci-aws-cfn-mark: | ||||||
|     name: "CI AWS CloudFormation Mark" |  | ||||||
|     if: always() |     if: always() | ||||||
|     needs: |     needs: | ||||||
|       - check-changes-applied |       - check-changes-applied | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/ci-main-daily.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/ci-main-daily.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,5 @@ | |||||||
| --- | --- | ||||||
| name: "authentik CI Main Daily" | name: authentik-ci-main-daily | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
| @ -9,7 +9,6 @@ on: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   test-container: |   test-container: | ||||||
|     name: "Test Container ${{ matrix.version }}" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|  | |||||||
							
								
								
									
										62
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										62
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,5 @@ | |||||||
| --- | --- | ||||||
| name: "authentik CI Main" | name: authentik-ci-main | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @ -19,7 +19,6 @@ env: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lint: |   lint: | ||||||
|     name: "Lint" |  | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
| @ -34,10 +33,9 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Setup authentik env |       - name: Setup authentik env | ||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|       - name: Run job ${{ matrix.job }} |       - name: run job | ||||||
|         run: uv run make ci-${{ matrix.job }} |         run: uv run make ci-${{ matrix.job }} | ||||||
|   test-migrations: |   test-migrations: | ||||||
|     name: "Test Migrations" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
| @ -46,7 +44,6 @@ jobs: | |||||||
|       - name: run migrations |       - name: run migrations | ||||||
|         run: uv run python -m lifecycle.migrate |         run: uv run python -m lifecycle.migrate | ||||||
|   test-make-seed: |   test-make-seed: | ||||||
|     name: "Test Make Seed" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - id: seed |       - id: seed | ||||||
| @ -55,7 +52,7 @@ jobs: | |||||||
|     outputs: |     outputs: | ||||||
|       seed: ${{ steps.seed.outputs.seed }} |       seed: ${{ steps.seed.outputs.seed }} | ||||||
|   test-migrations-from-stable: |   test-migrations-from-stable: | ||||||
|     name: "Test Migrations From Stable - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5" |     name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5 | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     timeout-minutes: 20 |     timeout-minutes: 20 | ||||||
|     needs: test-make-seed |     needs: test-make-seed | ||||||
| @ -70,7 +67,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|       - name: Checkout Stable |       - name: checkout stable | ||||||
|         run: | |         run: | | ||||||
|           # Copy current, latest config to local |           # Copy current, latest config to local | ||||||
|           # Temporarly comment the .github backup while migrating to uv |           # Temporarly comment the .github backup while migrating to uv | ||||||
| @ -87,9 +84,9 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           postgresql_version: ${{ matrix.psql }} |           postgresql_version: ${{ matrix.psql }} | ||||||
|         continue-on-error: true |         continue-on-error: true | ||||||
|       - name: Run migrations to stable |       - name: run migrations to stable | ||||||
|         run: poetry run python -m lifecycle.migrate |         run: poetry run python -m lifecycle.migrate | ||||||
|       - name: Checkout current code |       - name: checkout current code | ||||||
|         run: | |         run: | | ||||||
|           set -x |           set -x | ||||||
|           git fetch |           git fetch | ||||||
| @ -100,10 +97,10 @@ jobs: | |||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|         with: |         with: | ||||||
|           postgresql_version: ${{ matrix.psql }} |           postgresql_version: ${{ matrix.psql }} | ||||||
|       - name: Migrate to latest |       - name: migrate to latest | ||||||
|         run: | |         run: | | ||||||
|           uv run python -m lifecycle.migrate |           uv run python -m lifecycle.migrate | ||||||
|       - name: Run tests |       - name: run tests | ||||||
|         env: |         env: | ||||||
|           # Test in the main database that we just migrated from the previous stable version |           # Test in the main database that we just migrated from the previous stable version | ||||||
|           AUTHENTIK_POSTGRESQL__TEST__NAME: authentik |           AUTHENTIK_POSTGRESQL__TEST__NAME: authentik | ||||||
| @ -113,7 +110,7 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           uv run make ci-test |           uv run make ci-test | ||||||
|   test-unittest: |   test-unittest: | ||||||
|     name: "Unit tests - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5" |     name: test-unittest - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5 | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     timeout-minutes: 20 |     timeout-minutes: 20 | ||||||
|     needs: test-make-seed |     needs: test-make-seed | ||||||
| @ -149,7 +146,6 @@ jobs: | |||||||
|           file: unittest.xml |           file: unittest.xml | ||||||
|           token: ${{ secrets.CODECOV_TOKEN }} |           token: ${{ secrets.CODECOV_TOKEN }} | ||||||
|   test-integration: |   test-integration: | ||||||
|     name: "Integration tests" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     timeout-minutes: 30 |     timeout-minutes: 30 | ||||||
|     steps: |     steps: | ||||||
| @ -158,7 +154,7 @@ jobs: | |||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|       - name: Create k8s Kind Cluster |       - name: Create k8s Kind Cluster | ||||||
|         uses: helm/kind-action@v1.12.0 |         uses: helm/kind-action@v1.12.0 | ||||||
|       - name: Run integration |       - name: run integration | ||||||
|         run: | |         run: | | ||||||
|           uv run coverage run manage.py test tests/integration |           uv run coverage run manage.py test tests/integration | ||||||
|           uv run coverage xml |           uv run coverage xml | ||||||
| @ -174,50 +170,49 @@ jobs: | |||||||
|           file: unittest.xml |           file: unittest.xml | ||||||
|           token: ${{ secrets.CODECOV_TOKEN }} |           token: ${{ secrets.CODECOV_TOKEN }} | ||||||
|   test-e2e: |   test-e2e: | ||||||
|     name: "Test E2E (${{ matrix.job.name }})" |     name: test-e2e (${{ matrix.job.name }}) | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     timeout-minutes: 30 |     timeout-minutes: 30 | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         job: |         job: | ||||||
|           - name: Proxy Provider |           - name: proxy | ||||||
|             glob: tests/e2e/test_provider_proxy* |             glob: tests/e2e/test_provider_proxy* | ||||||
|           - name: OAuth2 Provider |           - name: oauth | ||||||
|             glob: tests/e2e/test_provider_oauth2* tests/e2e/test_source_oauth* |             glob: tests/e2e/test_provider_oauth2* tests/e2e/test_source_oauth* | ||||||
|           - name: OIDC Provider |           - name: oauth-oidc | ||||||
|             glob: tests/e2e/test_provider_oidc* |             glob: tests/e2e/test_provider_oidc* | ||||||
|           - name: SAML Provider |           - name: saml | ||||||
|             glob: tests/e2e/test_provider_saml* tests/e2e/test_source_saml* |             glob: tests/e2e/test_provider_saml* tests/e2e/test_source_saml* | ||||||
|           - name: LDAP Provider |           - name: ldap | ||||||
|             glob: tests/e2e/test_provider_ldap* tests/e2e/test_source_ldap* |             glob: tests/e2e/test_provider_ldap* tests/e2e/test_source_ldap* | ||||||
|           - name: RADIUS Provider |           - name: radius | ||||||
|             glob: tests/e2e/test_provider_radius* |             glob: tests/e2e/test_provider_radius* | ||||||
|           - name: SCIM Source |           - name: scim | ||||||
|             glob: tests/e2e/test_source_scim* |             glob: tests/e2e/test_source_scim* | ||||||
|           - name: Flows |           - name: flows | ||||||
|             glob: tests/e2e/test_flows* |             glob: tests/e2e/test_flows* | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Setup authentik env |       - name: Setup authentik env | ||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|       - name: Setup E2E env (chrome, etc) |       - name: Setup e2e env (chrome, etc) | ||||||
|         run: | |         run: | | ||||||
|           docker compose -f tests/e2e/docker-compose.yml up -d --quiet-pull |           docker compose -f tests/e2e/docker-compose.yml up -d --quiet-pull | ||||||
|       - id: cache-web |       - id: cache-web | ||||||
|         uses: actions/cache@v4 |         uses: actions/cache@v4 | ||||||
|         with: |         with: | ||||||
|           path: web/dist |           path: web/dist | ||||||
|           key: ${{ runner.os }}-web-${{ hashFiles('./package-lock.json', 'web/src/**') }} |           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' |         if: steps.cache-web.outputs.cache-hit != 'true' | ||||||
|  |         working-directory: web | ||||||
|         run: | |         run: | | ||||||
|           npm ci |           npm ci | ||||||
|           make gen-client-ts |           make -C .. gen-client-ts | ||||||
|           npm run build -w @goauthentik/web |           npm run build | ||||||
|  |       - name: run e2e | ||||||
|           npm run typecheck |  | ||||||
|       - name: Run E2E tests |  | ||||||
|         run: | |         run: | | ||||||
|           uv run coverage run manage.py test ${{ matrix.job.glob }} |           uv run coverage run manage.py test ${{ matrix.job.glob }} | ||||||
|           uv run coverage xml |           uv run coverage xml | ||||||
| @ -233,7 +228,6 @@ jobs: | |||||||
|           file: unittest.xml |           file: unittest.xml | ||||||
|           token: ${{ secrets.CODECOV_TOKEN }} |           token: ${{ secrets.CODECOV_TOKEN }} | ||||||
|   ci-core-mark: |   ci-core-mark: | ||||||
|     name: "CI Core Mark" |  | ||||||
|     if: always() |     if: always() | ||||||
|     needs: |     needs: | ||||||
|       - lint |       - lint | ||||||
| @ -248,7 +242,6 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           jobs: ${{ toJSON(needs) }} |           jobs: ${{ toJSON(needs) }} | ||||||
|   build: |   build: | ||||||
|     name: "Build" |  | ||||||
|     permissions: |     permissions: | ||||||
|       # Needed to upload container images to ghcr.io |       # Needed to upload container images to ghcr.io | ||||||
|       packages: write |       packages: write | ||||||
| @ -262,7 +255,6 @@ jobs: | |||||||
|       image_name: ghcr.io/goauthentik/dev-server |       image_name: ghcr.io/goauthentik/dev-server | ||||||
|       release: false |       release: false | ||||||
|   pr-comment: |   pr-comment: | ||||||
|     name: "PR Comment" |  | ||||||
|     needs: |     needs: | ||||||
|       - build |       - build | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| @ -275,7 +267,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|         with: |         with: | ||||||
|           ref: ${{ github.event.pull_request.head.sha }} |           ref: ${{ github.event.pull_request.head.sha }} | ||||||
|       - name: Prepare variables |       - name: prepare variables | ||||||
|         uses: ./.github/actions/docker-push-variables |         uses: ./.github/actions/docker-push-variables | ||||||
|         id: ev |         id: ev | ||||||
|         env: |         env: | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,5 @@ | |||||||
| --- | --- | ||||||
| name: "authentik CI Outpost" | name: authentik-ci-outpost | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @ -14,7 +14,6 @@ on: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lint-golint: |   lint-golint: | ||||||
|     name: "Lint Go" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
| @ -27,7 +26,7 @@ jobs: | |||||||
|           mkdir -p web/dist |           mkdir -p web/dist | ||||||
|           mkdir -p website/help |           mkdir -p website/help | ||||||
|           touch web/dist/test website/help/test |           touch web/dist/test website/help/test | ||||||
|       - name: Generate Go API Client |       - name: Generate API | ||||||
|         run: make gen-client-go |         run: make gen-client-go | ||||||
|       - name: golangci-lint |       - name: golangci-lint | ||||||
|         uses: golangci/golangci-lint-action@v7 |         uses: golangci/golangci-lint-action@v7 | ||||||
| @ -36,7 +35,6 @@ jobs: | |||||||
|           args: --timeout 5000s --verbose |           args: --timeout 5000s --verbose | ||||||
|           skip-cache: true |           skip-cache: true | ||||||
|   test-unittest: |   test-unittest: | ||||||
|     name: "Unit Test Go" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
| @ -45,13 +43,12 @@ jobs: | |||||||
|           go-version-file: "go.mod" |           go-version-file: "go.mod" | ||||||
|       - name: Setup authentik env |       - name: Setup authentik env | ||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|       - name: Generate Go API Client |       - name: Generate API | ||||||
|         run: make gen-client-go |         run: make gen-client-go | ||||||
|       - name: Go unittests |       - name: Go unittests | ||||||
|         run: | |         run: | | ||||||
|           go test -timeout 0 -v -race -coverprofile=coverage.out -covermode=atomic -cover ./... |           go test -timeout 0 -v -race -coverprofile=coverage.out -covermode=atomic -cover ./... | ||||||
|   ci-outpost-mark: |   ci-outpost-mark: | ||||||
|     name: "CI Outpost Mark" |  | ||||||
|     if: always() |     if: always() | ||||||
|     needs: |     needs: | ||||||
|       - lint-golint |       - lint-golint | ||||||
| @ -62,7 +59,6 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           jobs: ${{ toJSON(needs) }} |           jobs: ${{ toJSON(needs) }} | ||||||
|   build-container: |   build-container: | ||||||
|     name: "Build Container" |  | ||||||
|     timeout-minutes: 120 |     timeout-minutes: 120 | ||||||
|     needs: |     needs: | ||||||
|       - ci-outpost-mark |       - ci-outpost-mark | ||||||
| @ -89,7 +85,7 @@ jobs: | |||||||
|         uses: docker/setup-qemu-action@v3.6.0 |         uses: docker/setup-qemu-action@v3.6.0 | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3 |         uses: docker/setup-buildx-action@v3 | ||||||
|       - name: Prepare variables |       - name: prepare variables | ||||||
|         uses: ./.github/actions/docker-push-variables |         uses: ./.github/actions/docker-push-variables | ||||||
|         id: ev |         id: ev | ||||||
|         env: |         env: | ||||||
| @ -103,7 +99,7 @@ jobs: | |||||||
|           registry: ghcr.io |           registry: ghcr.io | ||||||
|           username: ${{ github.repository_owner }} |           username: ${{ github.repository_owner }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN }} |           password: ${{ secrets.GITHUB_TOKEN }} | ||||||
|       - name: Generate Go API Client |       - name: Generate API | ||||||
|         run: make gen-client-go |         run: make gen-client-go | ||||||
|       - name: Build Docker Image |       - name: Build Docker Image | ||||||
|         id: push |         id: push | ||||||
| @ -126,7 +122,6 @@ jobs: | |||||||
|           subject-digest: ${{ steps.push.outputs.digest }} |           subject-digest: ${{ steps.push.outputs.digest }} | ||||||
|           push-to-registry: true |           push-to-registry: true | ||||||
|   build-binary: |   build-binary: | ||||||
|     name: "Build Binary" |  | ||||||
|     timeout-minutes: 120 |     timeout-minutes: 120 | ||||||
|     needs: |     needs: | ||||||
|       - ci-outpost-mark |       - ci-outpost-mark | ||||||
| @ -145,22 +140,21 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|         with: |         with: | ||||||
|           ref: ${{ github.event.pull_request.head.sha }} |           ref: ${{ github.event.pull_request.head.sha }} | ||||||
|       - uses: actions/setup-node@v4 |  | ||||||
|         with: |  | ||||||
|           node-version-file: package.json |  | ||||||
|           cache: "npm" |  | ||||||
|           cache-dependency-path: package-lock.json |  | ||||||
|       - name: Install Node.js dependencies |  | ||||||
|         run: npm ci |  | ||||||
|       - uses: actions/setup-go@v5 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: "go.mod" |           go-version-file: "go.mod" | ||||||
|       - name: Generate Go API Client |       - uses: actions/setup-node@v4 | ||||||
|  |         with: | ||||||
|  |           node-version-file: web/package.json | ||||||
|  |           cache: "npm" | ||||||
|  |           cache-dependency-path: web/package-lock.json | ||||||
|  |       - name: Generate API | ||||||
|         run: make gen-client-go |         run: make gen-client-go | ||||||
|       - name: Build web |       - name: Build web | ||||||
|  |         working-directory: web/ | ||||||
|         run: | |         run: | | ||||||
|           npm ci |           npm ci | ||||||
|           npm run build-proxy -w @goauthentik/web |           npm run build-proxy | ||||||
|       - name: Build outpost |       - name: Build outpost | ||||||
|         run: | |         run: | | ||||||
|           set -x |           set -x | ||||||
|  | |||||||
							
								
								
									
										70
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										70
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: CI Web UI | name: authentik-ci-web | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @ -13,50 +13,54 @@ on: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lint: |   lint: | ||||||
|     name: Lint |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  |       matrix: | ||||||
|  |         command: | ||||||
|  |           - lint | ||||||
|  |           - lint:lockfile | ||||||
|  |           - tsc | ||||||
|  |           - prettier-check | ||||||
|  |         project: | ||||||
|  |           - web | ||||||
|  |         include: | ||||||
|  |           - command: tsc | ||||||
|  |             project: web | ||||||
|  |           - command: lit-analyse | ||||||
|  |             project: web | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version-file: package.json |           node-version-file: ${{ matrix.project }}/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: package-lock.json |           cache-dependency-path: ${{ matrix.project }}/package-lock.json | ||||||
|       - name: Install Node.js dependencies |       - working-directory: ${{ matrix.project }}/ | ||||||
|         run: npm ci |         run: | | ||||||
|       - name: Generate TypeScript API |           npm ci | ||||||
|  |       - name: Generate API | ||||||
|         run: make gen-client-ts |         run: make gen-client-ts | ||||||
|       - name: Build |  | ||||||
|         run: | |  | ||||||
|           npm run build -w @goauthentik/web |  | ||||||
|       - name: Type check |  | ||||||
|         run: | |  | ||||||
|           npm run typecheck |  | ||||||
|       - name: Lint |       - name: Lint | ||||||
|         run: | |         working-directory: ${{ matrix.project }}/ | ||||||
|           npm run lint -w @goauthentik/web |         run: npm run ${{ matrix.command }} | ||||||
|           npm run lint:lockfile -w @goauthentik/web |  | ||||||
|           npm run lit-analyse -w @goauthentik/web |  | ||||||
|   build: |   build: | ||||||
|     name: Build |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version-file: package.json |           node-version-file: web/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: package-lock.json |           cache-dependency-path: web/package-lock.json | ||||||
|       - name: Install Node.js dependencies |       - working-directory: web/ | ||||||
|         run: npm ci |         run: npm ci | ||||||
|       - name: Generate TypeScript API |       - name: Generate API | ||||||
|         run: make gen-client-ts |         run: make gen-client-ts | ||||||
|       - name: build |       - name: build | ||||||
|         run: | |         working-directory: web/ | ||||||
|           npm run build -w @goauthentik/web |         run: npm run build | ||||||
|           npm run typecheck |  | ||||||
|   ci-web-mark: |   ci-web-mark: | ||||||
|     name: CI Web Mark |  | ||||||
|     if: always() |     if: always() | ||||||
|     needs: |     needs: | ||||||
|       - build |       - build | ||||||
| @ -67,7 +71,6 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           jobs: ${{ toJSON(needs) }} |           jobs: ${{ toJSON(needs) }} | ||||||
|   test: |   test: | ||||||
|     name: Test |  | ||||||
|     needs: |     needs: | ||||||
|       - ci-web-mark |       - ci-web-mark | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| @ -75,12 +78,13 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version-file: package.json |           node-version-file: web/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: package-lock.json |           cache-dependency-path: web/package-lock.json | ||||||
|       - name: Install Node.js dependencies |       - working-directory: web/ | ||||||
|         run: npm ci |         run: npm ci | ||||||
|       - name: Generate TypeScript API |       - name: Generate API | ||||||
|         run: make gen-client-ts |         run: make gen-client-ts | ||||||
|       - name: Test Web UI |       - name: test | ||||||
|         run: npm run test -w @goauthentik/web || exit 0 |         working-directory: web/ | ||||||
|  |         run: npm run test || exit 0 | ||||||
|  | |||||||
							
								
								
									
										94
									
								
								.github/workflows/ci-website.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										94
									
								
								.github/workflows/ci-website.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: CI Docs Website | name: authentik-ci-website | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @ -13,59 +13,55 @@ on: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lint: |   lint: | ||||||
|     name: "Lint" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  |       matrix: | ||||||
|  |         command: | ||||||
|  |           - lint:lockfile | ||||||
|  |           - prettier-check | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - working-directory: website/ | ||||||
|         with: |  | ||||||
|           node-version-file: package.json |  | ||||||
|           cache: "npm" |  | ||||||
|           cache-dependency-path: package-lock.json |  | ||||||
|       - name: Install Node.js dependencies |  | ||||||
|         run: | |  | ||||||
|           npm ci |  | ||||||
|       - name: Generate TypeScript API |  | ||||||
|         run: make gen-client-ts |  | ||||||
|       - name: Lint Docs |  | ||||||
|         run: | |  | ||||||
|           npm run lint:prettier:check |  | ||||||
|           npm run lint:lockfile -w @goauthentik/docs |  | ||||||
|   test: |  | ||||||
|     name: "Test Docs" |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@v4 |  | ||||||
|       - uses: actions/setup-node@v4 |  | ||||||
|         with: |  | ||||||
|           node-version-file: package.json |  | ||||||
|           cache: "npm" |  | ||||||
|           cache-dependency-path: package-lock.json |  | ||||||
|       - name: Install Node.js dependencies |  | ||||||
|         run: | |  | ||||||
|           npm ci |  | ||||||
|       - name: Generate TypeScript API |  | ||||||
|         run: make gen-client-ts |  | ||||||
|       - name: Test Docs |  | ||||||
|         run: | |  | ||||||
|           npm run test -w @goauthentik/docs |  | ||||||
|   build: |  | ||||||
|     name: "Build Docs" |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@v4 |  | ||||||
|       - uses: actions/setup-node@v4 |  | ||||||
|         with: |  | ||||||
|           node-version-file: package.json |  | ||||||
|           cache: "npm" |  | ||||||
|           cache-dependency-path: package-lock.json |  | ||||||
|       - name: Install Node.js dependencies |  | ||||||
|         run: npm ci |         run: npm ci | ||||||
|       - name: Build |       - name: Lint | ||||||
|         run: | |         working-directory: website/ | ||||||
|           npm run build -w @goauthentik/docs |         run: npm run ${{ matrix.command }} | ||||||
|  |   test: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v4 | ||||||
|  |       - uses: actions/setup-node@v4 | ||||||
|  |         with: | ||||||
|  |           node-version-file: website/package.json | ||||||
|  |           cache: "npm" | ||||||
|  |           cache-dependency-path: website/package-lock.json | ||||||
|  |       - working-directory: website/ | ||||||
|  |         run: npm ci | ||||||
|  |       - name: test | ||||||
|  |         working-directory: website/ | ||||||
|  |         run: npm test | ||||||
|  |   build: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     name: ${{ matrix.job }} | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  |       matrix: | ||||||
|  |         job: | ||||||
|  |           - build | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v4 | ||||||
|  |       - uses: actions/setup-node@v4 | ||||||
|  |         with: | ||||||
|  |           node-version-file: website/package.json | ||||||
|  |           cache: "npm" | ||||||
|  |           cache-dependency-path: website/package-lock.json | ||||||
|  |       - working-directory: website/ | ||||||
|  |         run: npm ci | ||||||
|  |       - name: build | ||||||
|  |         working-directory: website/ | ||||||
|  |         run: npm run ${{ matrix.job }} | ||||||
|   ci-website-mark: |   ci-website-mark: | ||||||
|     name: "CI Website Mark" |  | ||||||
|     if: always() |     if: always() | ||||||
|     needs: |     needs: | ||||||
|       - lint |       - lint | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @ -10,7 +10,7 @@ on: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   analyze: |   analyze: | ||||||
|     name: "Analyze" |     name: Analyze | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
|       actions: read |       actions: read | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| name: "authentik CI Update WebAuthn MDS" | name: authentik-gen-update-webauthn-mds | ||||||
| on: | on: | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|   schedule: |   schedule: | ||||||
| @ -11,7 +11,6 @@ env: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   build: |   build: | ||||||
|     name: "Update WebAuthn MDS" |  | ||||||
|     if: ${{ github.repository != 'goauthentik/authentik-internal' }} |     if: ${{ github.repository != 'goauthentik/authentik-internal' }} | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/gha-cache-cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/gha-cache-cleanup.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| --- | --- | ||||||
| # See https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries | # See https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries | ||||||
| name: "Post-PR Closed Cache Cleanup" | name: Cleanup cache after PR is closed | ||||||
| on: | on: | ||||||
|   pull_request: |   pull_request: | ||||||
|     types: |     types: | ||||||
| @ -12,7 +12,6 @@ permissions: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   cleanup: |   cleanup: | ||||||
|     name: "Cleanup Cache" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out code |       - name: Check out code | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.github/workflows/ghcr-retention.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ghcr-retention.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: "authentik GHCR Retention Policy" | name: ghcr-retention | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   # schedule: |   # schedule: | ||||||
| @ -8,7 +8,7 @@ on: | |||||||
| jobs: | jobs: | ||||||
|   clean-ghcr: |   clean-ghcr: | ||||||
|     if: ${{ github.repository != 'goauthentik/authentik-internal' }} |     if: ${{ github.repository != 'goauthentik/authentik-internal' }} | ||||||
|     name: "Delete old unused container images" |     name: Delete old unused container images | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - id: generate_token |       - id: generate_token | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.github/workflows/image-compress.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/image-compress.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,5 @@ | |||||||
| --- | --- | ||||||
| name: "authentik CI Image Compression" | name: authentik-compress-images | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @ -20,7 +20,7 @@ on: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   compress: |   compress: | ||||||
|     name: "Compress Docker images" |     name: compress | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     # Don't run on forks. Token will not be available. Will run on main and open a PR anyway |     # Don't run on forks. Token will not be available. Will run on main and open a PR anyway | ||||||
|     if: | |     if: | | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								.github/workflows/publish-source-docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/publish-source-docs.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: "authentik Publish Source Docs" | name: authentik-publish-source-docs | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @ -12,7 +12,6 @@ env: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   publish-source-docs: |   publish-source-docs: | ||||||
|     name: "Publish" |  | ||||||
|     if: ${{ github.repository != 'goauthentik/authentik-internal' }} |     if: ${{ github.repository != 'goauthentik/authentik-internal' }} | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     timeout-minutes: 120 |     timeout-minutes: 120 | ||||||
| @ -20,11 +19,11 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Setup authentik env |       - name: Setup authentik env | ||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|       - name: Generate docs |       - name: generate docs | ||||||
|         run: | |         run: | | ||||||
|           uv run make migrate |           uv run make migrate | ||||||
|           uv run ak build_source_docs |           uv run ak build_source_docs | ||||||
|       - name: Deploy to Netlify |       - name: Publish | ||||||
|         uses: netlify/actions/cli@master |         uses: netlify/actions/cli@master | ||||||
|         with: |         with: | ||||||
|           args: deploy --dir=source_docs --prod |           args: deploy --dir=source_docs --prod | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/release-next-branch.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/release-next-branch.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: "authentik on Release Next Branch" | name: authentik-on-release-next-branch | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   schedule: |   schedule: | ||||||
| @ -11,7 +11,6 @@ permissions: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   update-next: |   update-next: | ||||||
|     name: "Update Next Branch" |  | ||||||
|     if: ${{ github.repository != 'goauthentik/authentik-internal' }} |     if: ${{ github.repository != 'goauthentik/authentik-internal' }} | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     environment: internal-production |     environment: internal-production | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,5 @@ | |||||||
| --- | --- | ||||||
| name: "Release publish" | name: authentik-on-release | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   release: |   release: | ||||||
| @ -7,7 +7,6 @@ on: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   build-server: |   build-server: | ||||||
|     name: "Build server" |  | ||||||
|     uses: ./.github/workflows/_reusable-docker-build.yaml |     uses: ./.github/workflows/_reusable-docker-build.yaml | ||||||
|     secrets: inherit |     secrets: inherit | ||||||
|     permissions: |     permissions: | ||||||
| @ -22,7 +21,6 @@ jobs: | |||||||
|       registry_dockerhub: true |       registry_dockerhub: true | ||||||
|       registry_ghcr: true |       registry_ghcr: true | ||||||
|   build-outpost: |   build-outpost: | ||||||
|     name: "Build outpost" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
|       # Needed to upload container images to ghcr.io |       # Needed to upload container images to ghcr.io | ||||||
| @ -47,14 +45,14 @@ jobs: | |||||||
|         uses: docker/setup-qemu-action@v3.6.0 |         uses: docker/setup-qemu-action@v3.6.0 | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3 |         uses: docker/setup-buildx-action@v3 | ||||||
|       - name: Prepare variables |       - name: prepare variables | ||||||
|         uses: ./.github/actions/docker-push-variables |         uses: ./.github/actions/docker-push-variables | ||||||
|         id: ev |         id: ev | ||||||
|         env: |         env: | ||||||
|           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} |           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} | ||||||
|         with: |         with: | ||||||
|           image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }} |           image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }} | ||||||
|       - name: Make empty clients |       - name: make empty clients | ||||||
|         run: | |         run: | | ||||||
|           mkdir -p ./gen-ts-api |           mkdir -p ./gen-ts-api | ||||||
|           mkdir -p ./gen-go-api |           mkdir -p ./gen-go-api | ||||||
| @ -87,7 +85,6 @@ jobs: | |||||||
|           subject-digest: ${{ steps.push.outputs.digest }} |           subject-digest: ${{ steps.push.outputs.digest }} | ||||||
|           push-to-registry: true |           push-to-registry: true | ||||||
|   build-outpost-binary: |   build-outpost-binary: | ||||||
|     name: "Build outpost binary" |  | ||||||
|     timeout-minutes: 120 |     timeout-minutes: 120 | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
| @ -109,13 +106,14 @@ jobs: | |||||||
|           go-version-file: "go.mod" |           go-version-file: "go.mod" | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version-file: package.json |           node-version-file: web/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: package-lock.json |           cache-dependency-path: web/package-lock.json | ||||||
|       - name: Build web |       - name: Build web | ||||||
|  |         working-directory: web/ | ||||||
|         run: | |         run: | | ||||||
|           npm ci |           npm ci | ||||||
|           npm run build-proxy -w @goauthentik/web |           npm run build-proxy | ||||||
|       - name: Build outpost |       - name: Build outpost | ||||||
|         run: | |         run: | | ||||||
|           set -x |           set -x | ||||||
| @ -131,7 +129,6 @@ jobs: | |||||||
|           asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} |           asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} | ||||||
|           tag: ${{ github.ref }} |           tag: ${{ github.ref }} | ||||||
|   upload-aws-cfn-template: |   upload-aws-cfn-template: | ||||||
|     name: "Upload AWS CloudFormation template" |  | ||||||
|     permissions: |     permissions: | ||||||
|       # Needed for AWS login |       # Needed for AWS login | ||||||
|       id-token: write |       id-token: write | ||||||
| @ -153,7 +150,6 @@ jobs: | |||||||
|           aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml |           aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml | ||||||
|           aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml |           aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml | ||||||
|   test-release: |   test-release: | ||||||
|     name: "Test release" |  | ||||||
|     needs: |     needs: | ||||||
|       - build-server |       - build-server | ||||||
|       - build-outpost |       - build-outpost | ||||||
| @ -170,7 +166,6 @@ jobs: | |||||||
|           docker compose start postgresql redis |           docker compose start postgresql redis | ||||||
|           docker compose run -u root server test-all |           docker compose run -u root server test-all | ||||||
|   sentry-release: |   sentry-release: | ||||||
|     name: "Sentry release" |  | ||||||
|     needs: |     needs: | ||||||
|       - build-server |       - build-server | ||||||
|       - build-outpost |       - build-outpost | ||||||
| @ -178,7 +173,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Prepare variables |       - name: prepare variables | ||||||
|         uses: ./.github/actions/docker-push-variables |         uses: ./.github/actions/docker-push-variables | ||||||
|         id: ev |         id: ev | ||||||
|         env: |         env: | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								.github/workflows/release-tag.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/release-tag.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,5 @@ | |||||||
| --- | --- | ||||||
| name: "authentik on Tag Release" | name: authentik-on-tag | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @ -8,7 +8,7 @@ on: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   build: |   build: | ||||||
|     name: "Create Release from Tag" |     name: Create Release from Tag | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
| @ -20,7 +20,7 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           app_id: ${{ secrets.GH_APP_ID }} |           app_id: ${{ secrets.GH_APP_ID }} | ||||||
|           private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} |           private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} | ||||||
|       - name: Prepare variables |       - name: prepare variables | ||||||
|         uses: ./.github/actions/docker-push-variables |         uses: ./.github/actions/docker-push-variables | ||||||
|         id: ev |         id: ev | ||||||
|         env: |         env: | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.github/workflows/repo-mirror.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/repo-mirror.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,15 +1,13 @@ | |||||||
| name: "authentik Repository Mirror" | name: "authentik-repo-mirror" | ||||||
|  |  | ||||||
| on: [push, delete] | on: [push, delete] | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   to_internal: |   to_internal: | ||||||
|     name: "Mirror to internal repository" |  | ||||||
|     if: ${{ github.repository != 'goauthentik/authentik-internal' }} |     if: ${{ github.repository != 'goauthentik/authentik-internal' }} | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|         name: "Checkout repository" |  | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|       - if: ${{ env.MIRROR_KEY != '' }} |       - if: ${{ env.MIRROR_KEY != '' }} | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/repo-stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/repo-stale.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: "authentik Repository Stale Issues" | name: "authentik-repo-stale" | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   schedule: |   schedule: | ||||||
| @ -11,7 +11,6 @@ permissions: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   stale: |   stale: | ||||||
|     name: "Stale Issues" |  | ||||||
|     if: ${{ github.repository != 'goauthentik/authentik-internal' }} |     if: ${{ github.repository != 'goauthentik/authentik-internal' }} | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.github/workflows/semgrep.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/semgrep.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: "authentik CI Semgrep" | name: authentik-semgrep | ||||||
| on: | on: | ||||||
|   workflow_dispatch: {} |   workflow_dispatch: {} | ||||||
|   pull_request: {} |   pull_request: {} | ||||||
| @ -13,7 +13,7 @@ on: | |||||||
|     - cron: '12 15 * * *' |     - cron: '12 15 * * *' | ||||||
| jobs: | jobs: | ||||||
|   semgrep: |   semgrep: | ||||||
|     name: "semgrep/ci" |     name: semgrep/ci | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
|       contents: read |       contents: read | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/translation-advice.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/translation-advice.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| name: "authentik Translations Advice" | name: authentik-translation-advice | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   pull_request: |   pull_request: | ||||||
| @ -16,7 +16,6 @@ permissions: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   post-comment: |   post-comment: | ||||||
|     name: "Post Comment" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Find Comment |       - name: Find Comment | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| --- | --- | ||||||
| name: "authentik Extract & Compile Translations" | name: authentik-translate-extract-compile | ||||||
| on: | on: | ||||||
|   schedule: |   schedule: | ||||||
|     - cron: "0 0 * * *" # every day at midnight |     - cron: "0 0 * * *" # every day at midnight | ||||||
| @ -16,7 +16,6 @@ env: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   compile: |   compile: | ||||||
|     name: "Compile Translations" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - id: generate_token |       - id: generate_token | ||||||
| @ -33,20 +32,15 @@ jobs: | |||||||
|         if: ${{ github.event_name == 'pull_request' }} |         if: ${{ github.event_name == 'pull_request' }} | ||||||
|       - name: Setup authentik env |       - name: Setup authentik env | ||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|       - name: Generate TypeScript API |       - name: Generate API | ||||||
|         run: make gen-client-ts |         run: make gen-client-ts | ||||||
|       - name: Extract Translations |       - name: run extract | ||||||
|         run: | |         run: | | ||||||
|           uv run make i18n-extract |           uv run make i18n-extract | ||||||
|       - name: Build Docs Site |       - name: run compile | ||||||
|         run: npm run build-bundled -w @goauthentik/docs |  | ||||||
|       - name: Build Web UI |  | ||||||
|         run: npm run build -w @goauthentik/web |  | ||||||
|       - name: Type check |  | ||||||
|         run: npm run typecheck |  | ||||||
|       - name: Compile Messages |  | ||||||
|         run: | |         run: | | ||||||
|           uv run ak compilemessages |           uv run ak compilemessages | ||||||
|  |           make web-check-compile | ||||||
|       - name: Create Pull Request |       - name: Create Pull Request | ||||||
|         if: ${{ github.event_name != 'pull_request' }} |         if: ${{ github.event_name != 'pull_request' }} | ||||||
|         uses: peter-evans/create-pull-request@v7 |         uses: peter-evans/create-pull-request@v7 | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/translation-rename.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/translation-rename.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| # Rename transifex pull requests to have a correct naming | # Rename transifex pull requests to have a correct naming | ||||||
| # Also enables auto squash-merge | # Also enables auto squash-merge | ||||||
| name: "authentik Translations Transifex PR Rename" | name: authentik-translation-transifex-rename | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   pull_request: |   pull_request: | ||||||
| @ -12,7 +12,6 @@ permissions: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   rename_pr: |   rename_pr: | ||||||
|     name: "Rename PR" |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}} |     if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}} | ||||||
|     steps: |     steps: | ||||||
|  | |||||||
							
								
								
									
										23
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -217,26 +217,3 @@ source_docs/ | |||||||
|  |  | ||||||
| ### Docker ### | ### Docker ### | ||||||
| docker-compose.override.yml | docker-compose.override.yml | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Node ### |  | ||||||
| # Logs |  | ||||||
| logs |  | ||||||
| *.log |  | ||||||
| npm-debug.log* |  | ||||||
| yarn-debug.log* |  | ||||||
| yarn-error.log* |  | ||||||
| lerna-debug.log* |  | ||||||
|  |  | ||||||
| node_modules/ |  | ||||||
|  |  | ||||||
| tsconfig.tsbuildinfo |  | ||||||
|  |  | ||||||
| # Wireit's cache |  | ||||||
| .wireit |  | ||||||
|  |  | ||||||
| custom-elements.json |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Development ### |  | ||||||
| .drafts |  | ||||||
|  | |||||||
| @ -4,16 +4,12 @@ | |||||||
| **/LICENSE | **/LICENSE | ||||||
|  |  | ||||||
| authentik/stages/**/* | authentik/stages/**/* | ||||||
| authentik/sources/**/* |  | ||||||
| schemas/**/* |  | ||||||
| blueprints/**/* |  | ||||||
|  |  | ||||||
| ## Build asset directories | ## Build asset directories | ||||||
| coverage | coverage | ||||||
| dist | dist | ||||||
| out | out | ||||||
| .docusaurus | .docusaurus | ||||||
| .wireit |  | ||||||
| website/docs/developer-docs/api/**/* | website/docs/developer-docs/api/**/* | ||||||
|  |  | ||||||
| ## Environment | ## Environment | ||||||
| @ -36,15 +32,14 @@ coverage | |||||||
|  |  | ||||||
| # Templates | # Templates | ||||||
| # TODO: Rename affected files to *.template.* or similar. | # TODO: Rename affected files to *.template.* or similar. | ||||||
| authentik/**/*.html |  | ||||||
| *.html | *.html | ||||||
| *.mdx | *.mdx | ||||||
| *.md | *.md | ||||||
|  |  | ||||||
| ## Import order matters | ## Import order matters | ||||||
| web/src/poly.ts | poly.ts | ||||||
| web/src/locale-codes.ts | src/locale-codes.ts | ||||||
| web/src/locales/ | src/locales/ | ||||||
|  |  | ||||||
| # Storybook | # Storybook | ||||||
| storybook-static/ | storybook-static/ | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							| @ -17,6 +17,6 @@ | |||||||
|         "ms-python.vscode-pylance", |         "ms-python.vscode-pylance", | ||||||
|         "redhat.vscode-yaml", |         "redhat.vscode-yaml", | ||||||
|         "Tobermory.es6-string-html", |         "Tobermory.es6-string-html", | ||||||
|         "unifiedjs.vscode-mdx" |         "unifiedjs.vscode-mdx", | ||||||
|     ] |     ] | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										72
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										72
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @ -16,7 +16,7 @@ | |||||||
|     ], |     ], | ||||||
|     "typescript.preferences.importModuleSpecifier": "non-relative", |     "typescript.preferences.importModuleSpecifier": "non-relative", | ||||||
|     "typescript.preferences.importModuleSpecifierEnding": "index", |     "typescript.preferences.importModuleSpecifierEnding": "index", | ||||||
|     "typescript.tsdk": "./node_modules/typescript/lib", |     "typescript.tsdk": "./web/node_modules/typescript/lib", | ||||||
|     "typescript.enablePromptUseWorkspaceTsdk": true, |     "typescript.enablePromptUseWorkspaceTsdk": true, | ||||||
|     "yaml.schemas": { |     "yaml.schemas": { | ||||||
|         "./blueprints/schema.json": "blueprints/**/*.yaml" |         "./blueprints/schema.json": "blueprints/**/*.yaml" | ||||||
| @ -30,71 +30,7 @@ | |||||||
|         } |         } | ||||||
|     ], |     ], | ||||||
|     "go.testFlags": ["-count=1"], |     "go.testFlags": ["-count=1"], | ||||||
|     "github-actions.workflows.pinned.workflows": [".github/workflows/ci-main.yml"], |     "github-actions.workflows.pinned.workflows": [ | ||||||
|  |         ".github/workflows/ci-main.yml" | ||||||
|     "eslint.useFlatConfig": true, |     ] | ||||||
|  |  | ||||||
|     "explorer.fileNesting.enabled": true, |  | ||||||
|     "explorer.fileNesting.patterns": { |  | ||||||
|         "*.mjs": "*.d.mts", |  | ||||||
|         "*.cjs": "*.d.cts", |  | ||||||
|         "package.json": "package-lock.json, yarn.lock, .yarnrc, .yarnrc.yml, .yarn, .nvmrc, .node-version", |  | ||||||
|         "tsconfig.json": "tsconfig.*.json, jsconfig.json", |  | ||||||
|         "Dockerfile": "*.Dockerfile" |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     "search.exclude": { |  | ||||||
|         "**/node_modules": true, |  | ||||||
|         "**/*.code-search": true, |  | ||||||
|         "**/dist": true, |  | ||||||
|         "**/out": true, |  | ||||||
|         "**/package-lock.json": true |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     "[css]": { |  | ||||||
|         "editor.defaultFormatter": "esbenp.prettier-vscode" |  | ||||||
|     }, |  | ||||||
|     "[javascript]": { |  | ||||||
|         "editor.defaultFormatter": "esbenp.prettier-vscode" |  | ||||||
|     }, |  | ||||||
|     "[javascriptreact]": { |  | ||||||
|         "editor.defaultFormatter": "esbenp.prettier-vscode" |  | ||||||
|     }, |  | ||||||
|     "[json]": { |  | ||||||
|         "editor.defaultFormatter": "esbenp.prettier-vscode" |  | ||||||
|     }, |  | ||||||
|     "[markdown]": { |  | ||||||
|         "editor.defaultFormatter": "esbenp.prettier-vscode" |  | ||||||
|     }, |  | ||||||
|     "[shellscript]": { |  | ||||||
|         "editor.defaultFormatter": "esbenp.prettier-vscode" |  | ||||||
|     }, |  | ||||||
|     "[typescript]": { |  | ||||||
|         "editor.defaultFormatter": "esbenp.prettier-vscode" |  | ||||||
|     }, |  | ||||||
|     "[typescriptreact]": { |  | ||||||
|         "editor.defaultFormatter": "esbenp.prettier-vscode" |  | ||||||
|     }, |  | ||||||
|     "[django-html]": { |  | ||||||
|         "editor.defaultFormatter": "esbenp.prettier-vscode" |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     "editor.codeActionsOnSave": { |  | ||||||
|         "source.removeUnusedImports": "explicit" |  | ||||||
|     }, |  | ||||||
|     // We use Prettier for formatting, but specifying these settings |  | ||||||
|     // will ensure that VS Code's IntelliSense doesn't autocomplete unformatted code. |  | ||||||
|     "javascript.format.semicolons": "insert", |  | ||||||
|     "typescript.format.semicolons": "insert", |  | ||||||
|     "javascript.preferences.quoteStyle": "double", |  | ||||||
|     "typescript.preferences.quoteStyle": "double", |  | ||||||
|     "github.copilot.enable": { |  | ||||||
|         "*": true, |  | ||||||
|         "plaintext": true, |  | ||||||
|         "markdown": true, |  | ||||||
|         "scminput": false, |  | ||||||
|         "csv": false, |  | ||||||
|         "json": true, |  | ||||||
|         "yaml": true |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							| @ -4,7 +4,12 @@ | |||||||
|         { |         { | ||||||
|             "label": "authentik/core: make", |             "label": "authentik/core: make", | ||||||
|             "command": "uv", |             "command": "uv", | ||||||
|             "args": ["run", "make", "lint-fix", "lint"], |             "args": [ | ||||||
|  |                 "run", | ||||||
|  |                 "make", | ||||||
|  |                 "lint-fix", | ||||||
|  |                 "lint" | ||||||
|  |             ], | ||||||
|             "presentation": { |             "presentation": { | ||||||
|                 "panel": "new" |                 "panel": "new" | ||||||
|             }, |             }, | ||||||
| @ -13,7 +18,11 @@ | |||||||
|         { |         { | ||||||
|             "label": "authentik/core: run", |             "label": "authentik/core: run", | ||||||
|             "command": "uv", |             "command": "uv", | ||||||
|             "args": ["run", "ak", "server"], |             "args": [ | ||||||
|  |                 "run", | ||||||
|  |                 "ak", | ||||||
|  |                 "server" | ||||||
|  |             ], | ||||||
|             "group": "build", |             "group": "build", | ||||||
|             "presentation": { |             "presentation": { | ||||||
|                 "panel": "dedicated", |                 "panel": "dedicated", | ||||||
| @ -23,13 +32,17 @@ | |||||||
|         { |         { | ||||||
|             "label": "authentik/web: make", |             "label": "authentik/web: make", | ||||||
|             "command": "make", |             "command": "make", | ||||||
|             "args": ["web"], |             "args": [ | ||||||
|  |                 "web" | ||||||
|  |             ], | ||||||
|             "group": "build" |             "group": "build" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "label": "authentik/web: watch", |             "label": "authentik/web: watch", | ||||||
|             "command": "make", |             "command": "make", | ||||||
|             "args": ["web-watch"], |             "args": [ | ||||||
|  |                 "web-watch" | ||||||
|  |             ], | ||||||
|             "group": "build", |             "group": "build", | ||||||
|             "presentation": { |             "presentation": { | ||||||
|                 "panel": "dedicated", |                 "panel": "dedicated", | ||||||
| @ -39,19 +52,26 @@ | |||||||
|         { |         { | ||||||
|             "label": "authentik: install", |             "label": "authentik: install", | ||||||
|             "command": "make", |             "command": "make", | ||||||
|             "args": ["install", "-j4"], |             "args": [ | ||||||
|  |                 "install", | ||||||
|  |                 "-j4" | ||||||
|  |             ], | ||||||
|             "group": "build" |             "group": "build" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "label": "authentik/website: make", |             "label": "authentik/website: make", | ||||||
|             "command": "make", |             "command": "make", | ||||||
|             "args": ["website"], |             "args": [ | ||||||
|  |                 "website" | ||||||
|  |             ], | ||||||
|             "group": "build" |             "group": "build" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "label": "authentik/website: watch", |             "label": "authentik/website: watch", | ||||||
|             "command": "make", |             "command": "make", | ||||||
|             "args": ["website-watch"], |             "args": [ | ||||||
|  |                 "website-watch" | ||||||
|  |             ], | ||||||
|             "group": "build", |             "group": "build", | ||||||
|             "presentation": { |             "presentation": { | ||||||
|                 "panel": "dedicated", |                 "panel": "dedicated", | ||||||
| @ -61,7 +81,11 @@ | |||||||
|         { |         { | ||||||
|             "label": "authentik/api: generate", |             "label": "authentik/api: generate", | ||||||
|             "command": "uv", |             "command": "uv", | ||||||
|             "args": ["run", "make", "gen"], |             "args": [ | ||||||
|  |                 "run", | ||||||
|  |                 "make", | ||||||
|  |                 "gen" | ||||||
|  |             ], | ||||||
|             "group": "build" |             "group": "build" | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|  | |||||||
							
								
								
									
										67
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								Dockerfile
									
									
									
									
									
								
							| @ -1,31 +1,48 @@ | |||||||
| # syntax=docker/dockerfile:1 | # syntax=docker/dockerfile:1 | ||||||
|  |  | ||||||
| # Stage 1 Web UI and Documentation build | # Stage 1: Build website | ||||||
|  | FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS website-builder | ||||||
| FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS web-builder |  | ||||||
|  |  | ||||||
| ENV NODE_ENV=production | ENV NODE_ENV=production | ||||||
|  |  | ||||||
| WORKDIR /work | WORKDIR /work/website | ||||||
|  |  | ||||||
| COPY ./package.json ./package.json | RUN --mount=type=bind,target=/work/website/package.json,src=./website/package.json \ | ||||||
| COPY ./package-lock.json ./package-lock.json |     --mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \ | ||||||
| COPY ./packages ./packages |     --mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \ | ||||||
| COPY ./web ./web |     npm ci --include=dev | ||||||
| COPY ./website ./website |  | ||||||
|  |  | ||||||
| COPY ./gen-ts-api ./gen-ts-api | COPY ./website /work/website/ | ||||||
| COPY ./blueprints ./blueprints | COPY ./blueprints /work/blueprints/ | ||||||
| COPY ./schema.yml ./schema.yml | COPY ./schema.yml /work/ | ||||||
| COPY ./SECURITY.md ./SECURITY.md | COPY ./SECURITY.md /work/ | ||||||
|  |  | ||||||
| RUN --mount=type=cache,target=/root/.npm npm ci --include=dev | RUN npm run build-bundled | ||||||
|  |  | ||||||
| RUN npm run build-bundled -w @goauthentik/docs | # Stage 2: Build webui | ||||||
| RUN npm run build -w @goauthentik/web | FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS web-builder | ||||||
|  |  | ||||||
| # Stage 2: Build go proxy | ARG GIT_BUILD_HASH | ||||||
|  | ENV GIT_BUILD_HASH=$GIT_BUILD_HASH | ||||||
|  | ENV NODE_ENV=production | ||||||
|  |  | ||||||
|  | WORKDIR /work/web | ||||||
|  |  | ||||||
|  | RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \ | ||||||
|  |     --mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \ | ||||||
|  |     --mount=type=bind,target=/work/web/packages/sfe/package.json,src=./web/packages/sfe/package.json \ | ||||||
|  |     --mount=type=bind,target=/work/web/scripts,src=./web/scripts \ | ||||||
|  |     --mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \ | ||||||
|  |     npm ci --include=dev | ||||||
|  |  | ||||||
|  | COPY ./package.json /work | ||||||
|  | COPY ./web /work/web/ | ||||||
|  | COPY ./website /work/website/ | ||||||
|  | COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api | ||||||
|  |  | ||||||
|  | RUN npm run build | ||||||
|  |  | ||||||
|  | # Stage 3: Build go proxy | ||||||
| FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder | FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder | ||||||
|  |  | ||||||
| ARG TARGETOS | ARG TARGETOS | ||||||
| @ -62,8 +79,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ | |||||||
|     CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \ |     CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \ | ||||||
|     go build -o /go/authentik ./cmd/server |     go build -o /go/authentik ./cmd/server | ||||||
|  |  | ||||||
| # Stage 3: MaxMind GeoIP | # Stage 4: MaxMind GeoIP | ||||||
|  |  | ||||||
| FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip | FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip | ||||||
|  |  | ||||||
| ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN" | ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN" | ||||||
| @ -77,10 +93,9 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \ | |||||||
|     mkdir -p /usr/share/GeoIP && \ |     mkdir -p /usr/share/GeoIP && \ | ||||||
|     /bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0" |     /bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0" | ||||||
|  |  | ||||||
| # Stage 4: Download uv | # Stage 5: Download uv | ||||||
| FROM ghcr.io/astral-sh/uv:0.6.14 AS uv | FROM ghcr.io/astral-sh/uv:0.6.16 AS uv | ||||||
|  | # Stage 6: Base python image | ||||||
| # Stage 5: Base python image |  | ||||||
| FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base | FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base | ||||||
|  |  | ||||||
| ENV VENV_PATH="/ak-root/.venv" \ | ENV VENV_PATH="/ak-root/.venv" \ | ||||||
| @ -94,7 +109,7 @@ WORKDIR /ak-root/ | |||||||
|  |  | ||||||
| COPY --from=uv /uv /uvx /bin/ | COPY --from=uv /uv /uvx /bin/ | ||||||
|  |  | ||||||
| # Stage 6: Python dependencies | # Stage 7: Python dependencies | ||||||
| FROM python-base AS python-deps | FROM python-base AS python-deps | ||||||
|  |  | ||||||
| ARG TARGETARCH | ARG TARGETARCH | ||||||
| @ -129,7 +144,7 @@ RUN --mount=type=bind,target=pyproject.toml,src=pyproject.toml \ | |||||||
|     --mount=type=cache,target=/root/.cache/uv \ |     --mount=type=cache,target=/root/.cache/uv \ | ||||||
|     uv sync --frozen --no-install-project --no-dev |     uv sync --frozen --no-install-project --no-dev | ||||||
|  |  | ||||||
| # Stage 7: Run | # Stage 8: Run | ||||||
| FROM python-base AS final-image | FROM python-base AS final-image | ||||||
|  |  | ||||||
| ARG VERSION | ARG VERSION | ||||||
| @ -174,7 +189,7 @@ COPY --from=go-builder /go/authentik /bin/authentik | |||||||
| COPY --from=python-deps /ak-root/.venv /ak-root/.venv | COPY --from=python-deps /ak-root/.venv /ak-root/.venv | ||||||
| COPY --from=web-builder /work/web/dist/ /web/dist/ | COPY --from=web-builder /work/web/dist/ /web/dist/ | ||||||
| COPY --from=web-builder /work/web/authentik/ /web/authentik/ | COPY --from=web-builder /work/web/authentik/ /web/authentik/ | ||||||
| COPY --from=web-builder /work/website/build/ /website/help/ | COPY --from=website-builder /work/website/build/ /website/help/ | ||||||
| COPY --from=geoip /usr/share/GeoIP /geoip | COPY --from=geoip /usr/share/GeoIP /geoip | ||||||
|  |  | ||||||
| USER 1000 | USER 1000 | ||||||
|  | |||||||
							
								
								
									
										107
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								Makefile
									
									
									
									
									
								
							| @ -36,13 +36,6 @@ test: ## Run the server tests and produce a coverage report (locally) | |||||||
| 	uv run coverage html | 	uv run coverage html | ||||||
| 	uv run coverage report | 	uv run coverage report | ||||||
|  |  | ||||||
| node-check-compile: ## Check and compile the TypeScript source code |  | ||||||
| 	npm run typecheck |  | ||||||
|  |  | ||||||
| node-lint-fix: ## Lint and automatically fix errors in the javascript source code |  | ||||||
| 	lint-codespell |  | ||||||
| 	npm run lint:fix |  | ||||||
|  |  | ||||||
| lint-fix: lint-codespell  ## Lint and automatically fix errors in the python source code. Reports spelling errors. | lint-fix: lint-codespell  ## Lint and automatically fix errors in the python source code. Reports spelling errors. | ||||||
| 	uv run black $(PY_SOURCES) | 	uv run black $(PY_SOURCES) | ||||||
| 	uv run ruff check --fix $(PY_SOURCES) | 	uv run ruff check --fix $(PY_SOURCES) | ||||||
| @ -54,6 +47,9 @@ lint: ## Lint the python and golang sources | |||||||
| 	uv run bandit -c pyproject.toml -r $(PY_SOURCES) | 	uv run bandit -c pyproject.toml -r $(PY_SOURCES) | ||||||
| 	golangci-lint run -v | 	golangci-lint run -v | ||||||
|  |  | ||||||
|  | core-install: | ||||||
|  | 	uv sync --frozen | ||||||
|  |  | ||||||
| migrate: ## Run the Authentik Django server's migrations | migrate: ## Run the Authentik Django server's migrations | ||||||
| 	uv run python -m lifecycle.migrate | 	uv run python -m lifecycle.migrate | ||||||
|  |  | ||||||
| @ -76,9 +72,7 @@ core-i18n-extract: | |||||||
| 		--ignore website \ | 		--ignore website \ | ||||||
| 		-l en | 		-l en | ||||||
|  |  | ||||||
| install:  ## Install all requires dependencies for `web`, `website` and `core` | install: web-install website-install core-install  ## Install all requires dependencies for `web`, `website` and `core` | ||||||
| 	npm ci |  | ||||||
| 	uv sync --frozen |  | ||||||
|  |  | ||||||
| dev-drop-db: | dev-drop-db: | ||||||
| 	dropdb -U ${pg_user} -h ${pg_host} ${pg_name} | 	dropdb -U ${pg_user} -h ${pg_host} ${pg_name} | ||||||
| @ -100,7 +94,6 @@ gen-build:  ## Extract the schema from the database | |||||||
| 		AUTHENTIK_TENANTS__ENABLED=true \ | 		AUTHENTIK_TENANTS__ENABLED=true \ | ||||||
| 		AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ | 		AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ | ||||||
| 		uv run ak make_blueprint_schema > blueprints/schema.json | 		uv run ak make_blueprint_schema > blueprints/schema.json | ||||||
|  |  | ||||||
| 	AUTHENTIK_DEBUG=true \ | 	AUTHENTIK_DEBUG=true \ | ||||||
| 		AUTHENTIK_TENANTS__ENABLED=true \ | 		AUTHENTIK_TENANTS__ENABLED=true \ | ||||||
| 		AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ | 		AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ | ||||||
| @ -108,24 +101,19 @@ gen-build:  ## Extract the schema from the database | |||||||
|  |  | ||||||
| gen-changelog:  ## (Release) generate the changelog based from the commits since the last tag | gen-changelog:  ## (Release) generate the changelog based from the commits since the last tag | ||||||
| 	git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md | 	git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md | ||||||
|  |  | ||||||
| 	npx prettier --write changelog.md | 	npx prettier --write changelog.md | ||||||
|  |  | ||||||
| gen-diff:  ## (Release) generate the changelog diff between the current schema and the last tag | gen-diff:  ## (Release) generate the changelog diff between the current schema and the last tag | ||||||
| 	git show $(shell git describe --tags $(shell git rev-list --tags --max-count=1)):schema.yml > old_schema.yml | 	git show $(shell git describe --tags $(shell git rev-list --tags --max-count=1)):schema.yml > old_schema.yml | ||||||
|  |  | ||||||
| 	docker run \ | 	docker run \ | ||||||
| 		--rm -v ${PWD}:/local \ | 		--rm -v ${PWD}:/local \ | ||||||
| 		--user ${UID}:${GID} \ | 		--user ${UID}:${GID} \ | ||||||
| 		docker.io/openapitools/openapi-diff:2.1.0-beta.8 \ | 		docker.io/openapitools/openapi-diff:2.1.0-beta.8 \ | ||||||
| 		--markdown /local/diff.md \ | 		--markdown /local/diff.md \ | ||||||
| 		/local/old_schema.yml /local/schema.yml | 		/local/old_schema.yml /local/schema.yml | ||||||
|  |  | ||||||
| 	rm old_schema.yml | 	rm old_schema.yml | ||||||
|  |  | ||||||
| 	sed -i 's/{/{/g' diff.md | 	sed -i 's/{/{/g' diff.md | ||||||
| 	sed -i 's/}/}/g' diff.md | 	sed -i 's/}/}/g' diff.md | ||||||
|  |  | ||||||
| 	npx prettier --write diff.md | 	npx prettier --write diff.md | ||||||
|  |  | ||||||
| gen-clean-ts:  ## Remove generated API client for Typescript | gen-clean-ts:  ## Remove generated API client for Typescript | ||||||
| @ -145,57 +133,46 @@ gen-client-ts: gen-clean-ts  ## Build and install the authentik API for Typescri | |||||||
| 		--rm -v ${PWD}:/local \ | 		--rm -v ${PWD}:/local \ | ||||||
| 		--user ${UID}:${GID} \ | 		--user ${UID}:${GID} \ | ||||||
| 		docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \ | 		docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \ | ||||||
| 		--input-spec /local/schema.yml \ | 		-i /local/schema.yml \ | ||||||
| 		--generator-name typescript-fetch \ | 		-g typescript-fetch \ | ||||||
| 		--output /local/${GEN_API_TS} \ | 		-o /local/${GEN_API_TS} \ | ||||||
| 		--config /local/scripts/api-ts-config.yaml \ | 		-c /local/scripts/api-ts-config.yaml \ | ||||||
| 		--additional-properties=npmVersion=${NPM_VERSION} \ | 		--additional-properties=npmVersion=${NPM_VERSION} \ | ||||||
| 		--git-repo-id authentik \ | 		--git-repo-id authentik \ | ||||||
| 		--git-user-id goauthentik | 		--git-user-id goauthentik | ||||||
|  | 	mkdir -p web/node_modules/@goauthentik/api | ||||||
| 	npm install | 	cd ./${GEN_API_TS} && npm i | ||||||
|  | 	\cp -rf ./${GEN_API_TS}/* web/node_modules/@goauthentik/api | ||||||
|  |  | ||||||
| gen-client-py: gen-clean-py ## Build and install the authentik API for Python | gen-client-py: gen-clean-py ## Build and install the authentik API for Python | ||||||
|  |  | ||||||
| 	docker run \ | 	docker run \ | ||||||
| 		--rm -v ${PWD}:/local \ | 		--rm -v ${PWD}:/local \ | ||||||
| 		--user ${UID}:${GID} \ | 		--user ${UID}:${GID} \ | ||||||
| 		docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \ | 		docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \ | ||||||
| 		--input-spec /local/schema.yml \ | 		-i /local/schema.yml \ | ||||||
| 		--generator-name python \ | 		-g python \ | ||||||
| 		--output /local/${GEN_API_PY} \ | 		-o /local/${GEN_API_PY} \ | ||||||
| 		--config /local/scripts/api-py-config.yaml \ | 		-c /local/scripts/api-py-config.yaml \ | ||||||
| 		--additional-properties=packageVersion=${NPM_VERSION} \ | 		--additional-properties=packageVersion=${NPM_VERSION} \ | ||||||
| 		--git-repo-id authentik \ | 		--git-repo-id authentik \ | ||||||
| 		--git-user-id goauthentik | 		--git-user-id goauthentik | ||||||
|  |  | ||||||
| 	pip install ./${GEN_API_PY} | 	pip install ./${GEN_API_PY} | ||||||
|  |  | ||||||
| gen-client-go: gen-clean-go  ## Build and install the authentik API for Golang | gen-client-go: gen-clean-go  ## Build and install the authentik API for Golang | ||||||
| 	mkdir -p ./${GEN_API_GO} ./${GEN_API_GO}/templates | 	mkdir -p ./${GEN_API_GO} ./${GEN_API_GO}/templates | ||||||
|  | 	wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml -O ./${GEN_API_GO}/config.yaml | ||||||
| 	wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml \ | 	wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache -O ./${GEN_API_GO}/templates/README.mustache | ||||||
| 		-O ./${GEN_API_GO}/config.yaml | 	wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache -O ./${GEN_API_GO}/templates/go.mod.mustache | ||||||
|  |  | ||||||
| 	wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache \ |  | ||||||
| 		-O ./${GEN_API_GO}/templates/README.mustache |  | ||||||
|  |  | ||||||
| 	wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache \ |  | ||||||
| 		-O ./${GEN_API_GO}/templates/go.mod.mustache |  | ||||||
|  |  | ||||||
| 	cp schema.yml ./${GEN_API_GO}/ | 	cp schema.yml ./${GEN_API_GO}/ | ||||||
|  |  | ||||||
| 	docker run \ | 	docker run \ | ||||||
| 		--rm -v ${PWD}/${GEN_API_GO}:/local \ | 		--rm -v ${PWD}/${GEN_API_GO}:/local \ | ||||||
| 		--user ${UID}:${GID} \ | 		--user ${UID}:${GID} \ | ||||||
| 		docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \ | 		docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \ | ||||||
| 		--input-spec /local/schema.yml \ | 		-i /local/schema.yml \ | ||||||
| 		--generator-name go \ | 		-g go \ | ||||||
| 		--output /local/ \ | 		-o /local/ \ | ||||||
| 		--config /local/config.yaml | 		-c /local/config.yaml | ||||||
|  |  | ||||||
| 	go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO} | 	go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO} | ||||||
|  |  | ||||||
| 	rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/ | 	rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/ | ||||||
|  |  | ||||||
| gen-dev-config:  ## Generate a local development config file | gen-dev-config:  ## Generate a local development config file | ||||||
| @ -207,38 +184,56 @@ gen: gen-build gen-client-ts | |||||||
| ## Web | ## Web | ||||||
| ######################### | ######################### | ||||||
|  |  | ||||||
| web: web-lint-fix web-lint node-check-compile  ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it | web-build: web-install  ## Build the Authentik UI | ||||||
|  | 	cd web && npm run build | ||||||
|  |  | ||||||
|  | web: web-lint-fix web-lint web-check-compile  ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it | ||||||
|  |  | ||||||
|  | web-install:  ## Install the necessary libraries to build the Authentik UI | ||||||
|  | 	cd web && npm ci | ||||||
|  |  | ||||||
| web-test: ## Run tests for the Authentik UI | web-test: ## Run tests for the Authentik UI | ||||||
| 	npm run test -w @goauthentik/web | 	cd web && npm run test | ||||||
|  |  | ||||||
| web-watch:  ## Build and watch the Authentik UI for changes, updating automatically | web-watch:  ## Build and watch the Authentik UI for changes, updating automatically | ||||||
| 	npm run watch -w @goauthentik/web | 	rm -rf web/dist/ | ||||||
|  | 	mkdir web/dist/ | ||||||
|  | 	touch web/dist/.gitkeep | ||||||
|  | 	cd web && npm run watch | ||||||
|  |  | ||||||
| web-storybook-watch:  ## Build and run the storybook documentation server | web-storybook-watch:  ## Build and run the storybook documentation server | ||||||
| 	npm run storybook -w @goauthentik/web | 	cd web && npm run storybook | ||||||
|  |  | ||||||
| web-lint-fix: | web-lint-fix: | ||||||
| 	npm run prettier -w @goauthentik/web | 	cd web && npm run prettier | ||||||
|  |  | ||||||
| web-lint: | web-lint: | ||||||
| 	npm run lint -w @goauthentik/web | 	cd web && npm run lint | ||||||
| 	npm run lit-analyse -w @goauthentik/web | 	cd web && npm run lit-analyse | ||||||
|  |  | ||||||
|  | web-check-compile: | ||||||
|  | 	cd web && npm run tsc | ||||||
|  |  | ||||||
| web-i18n-extract: | web-i18n-extract: | ||||||
| 	npm run extract-locales -w @goauthentik/web | 	cd web && npm run extract-locales | ||||||
|  |  | ||||||
| ######################### | ######################### | ||||||
| ## Website | ## Website | ||||||
| ######################### | ######################### | ||||||
|  |  | ||||||
| website: node-lint-fix website-build  ## Automatically fix formatting issues in the Authentik website/docs source code, lint the code, and compile it | website: website-lint-fix website-build  ## Automatically fix formatting issues in the Authentik website/docs source code, lint the code, and compile it | ||||||
|  |  | ||||||
|  | website-install: | ||||||
|  | 	cd website && npm ci | ||||||
|  |  | ||||||
|  | website-lint-fix: lint-codespell | ||||||
|  | 	cd website && npm run prettier | ||||||
|  |  | ||||||
| website-build: | website-build: | ||||||
| 	npm run build -w @goauthentik/docs | 	cd website && npm run build | ||||||
|  |  | ||||||
| website-watch:  ## Build and watch the documentation website, updating automatically | website-watch:  ## Build and watch the documentation website, updating automatically | ||||||
| 	npm run watch -w @goauthentik/docs | 	cd website && npm run watch | ||||||
|  |  | ||||||
| ######################### | ######################### | ||||||
| ## Docker | ## Docker | ||||||
|  | |||||||
| @ -2,22 +2,20 @@ | |||||||
| {% get_current_language as LANGUAGE_CODE %} | {% get_current_language as LANGUAGE_CODE %} | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|   window.authentik = { |     window.authentik = { | ||||||
|     locale: "{{ LANGUAGE_CODE }}", |         locale: "{{ LANGUAGE_CODE }}", | ||||||
|     config: JSON.parse("{{ config_json|escapejs }}" || "{}"), |         config: JSON.parse('{{ config_json|escapejs }}'), | ||||||
|     brand: JSON.parse("{{ brand_json|escapejs }}" || "{}"), |         brand: JSON.parse('{{ brand_json|escapejs }}'), | ||||||
|     versionFamily: "{{ version_family }}", |         versionFamily: "{{ version_family }}", | ||||||
|     versionSubdomain: "{{ version_subdomain }}", |         versionSubdomain: "{{ version_subdomain }}", | ||||||
|     build: "{{ build }}", |         build: "{{ build }}", | ||||||
|     api: { |         api: { | ||||||
|       base: "{{ base_url }}", |             base: "{{ base_url }}", | ||||||
|       relBase: "{{ base_url_rel }}", |             relBase: "{{ base_url_rel }}", | ||||||
|     }, |         }, | ||||||
|   }; |     }; | ||||||
|  |  | ||||||
|   {% if messages %} |  | ||||||
|     window.addEventListener("DOMContentLoaded", function () { |     window.addEventListener("DOMContentLoaded", function () { | ||||||
|       {% for message in messages %} |         {% for message in messages %} | ||||||
|         window.dispatchEvent( |         window.dispatchEvent( | ||||||
|             new CustomEvent("ak-message", { |             new CustomEvent("ak-message", { | ||||||
|                 bubbles: true, |                 bubbles: true, | ||||||
| @ -28,7 +26,6 @@ | |||||||
|                 }, |                 }, | ||||||
|             }), |             }), | ||||||
|         ); |         ); | ||||||
|       {% endfor %} |         {% endfor %} | ||||||
|     }); |     }); | ||||||
|   {% endif %} |  | ||||||
| </script> | </script> | ||||||
|  | |||||||
| @ -2,79 +2,31 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load authentik_core %} | {% load authentik_core %} | ||||||
|  |  | ||||||
| <!doctype html> | <!DOCTYPE html> | ||||||
|  |  | ||||||
| <html> | <html> | ||||||
|   <head> |     <head> | ||||||
|     <meta charset="UTF-8" /> |         <meta charset="UTF-8"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> |         <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> | ||||||
|  |         {# Darkreader breaks the site regardless of theme as its not compatible with webcomponents, and we default to a dark theme based on preferred colour-scheme #} | ||||||
|     {% comment %} |         <meta name="darkreader-lock"> | ||||||
|     Darkreader breaks the site regardless of theme as its not compatible with webcomponents, and we |         <title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title> | ||||||
|     default to a dark theme based on preferred colour-scheme |         <link rel="icon" href="{{ brand.branding_favicon_url }}"> | ||||||
|     {% endcomment %} |         <link rel="shortcut icon" href="{{ brand.branding_favicon_url }}"> | ||||||
|  |         {% block head_before %} | ||||||
|     <meta name="darkreader-lock" /> |         {% endblock %} | ||||||
|  |         <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}"> | ||||||
|     <title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title> |         <style>{{ brand.branding_custom_css }}</style> | ||||||
|  |         <script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script> | ||||||
|     <link rel="icon" href="{{ brand.branding_favicon_url }}" /> |         <script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script> | ||||||
|     <link rel="shortcut icon" href="{{ brand.branding_favicon_url }}" /> |         {% block head %} | ||||||
|  |         {% endblock %} | ||||||
|     {% block head_before %} |         <meta name="sentry-trace" content="{{ sentry_trace }}" /> | ||||||
|     {% endblock %} |     </head> | ||||||
|  |     <body> | ||||||
|     <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}" /> |         {% block body %} | ||||||
|  |         {% endblock %} | ||||||
|     <style data-test-id="color-scheme"> |         {% block scripts %} | ||||||
|       @media (prefers-color-scheme: dark) { |         {% endblock %} | ||||||
|         :root { |     </body> | ||||||
|           color-scheme: dark light; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       @media (prefers-color-scheme: light) { |  | ||||||
|         :root { |  | ||||||
|           color-scheme: light dark; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     </style> |  | ||||||
|  |  | ||||||
|     <style data-test-id="custom-branding-css"> |  | ||||||
|       {{ brand.branding_custom_css }} |  | ||||||
|     </style> |  | ||||||
|  |  | ||||||
|     <script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script> |  | ||||||
|     <script |  | ||||||
|       src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" |  | ||||||
|       type="module" |  | ||||||
|     ></script> |  | ||||||
|  |  | ||||||
|     {% block head %} |  | ||||||
|     {% endblock %} |  | ||||||
|  |  | ||||||
|     <meta name="sentry-trace" content="{{ sentry_trace }}" /> |  | ||||||
|   </head> |  | ||||||
|  |  | ||||||
|   <body> |  | ||||||
|     {% block body %}{% endblock %} |  | ||||||
|     {% block scripts %}{% endblock %} |  | ||||||
|  |  | ||||||
|     <noscript> |  | ||||||
|       <style> |  | ||||||
|         body { |  | ||||||
|           font-family: var(--ak-font-family-base), sans-serif; |  | ||||||
|         } |  | ||||||
|       </style> |  | ||||||
|  |  | ||||||
|       <h1> |  | ||||||
|         JavaScript is required to use |  | ||||||
|         {% trans title|default:brand.branding_title %} |  | ||||||
|       </h1> |  | ||||||
|       <p> |  | ||||||
|         Please enable JavaScript in your browser settings and reload the page. If you are using a |  | ||||||
|         browser extension that blocks JavaScript, please disable it for this site. |  | ||||||
|       </p> |  | ||||||
|     </noscript> |  | ||||||
|   </body> |  | ||||||
| </html> | </html> | ||||||
|  | |||||||
| @ -4,16 +4,14 @@ | |||||||
|  |  | ||||||
| {% block head %} | {% block head %} | ||||||
| <script src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}" type="module"></script> | <script src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}" type="module"></script> | ||||||
|  | <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)"> | ||||||
| <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)" /> | <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> | ||||||
| <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" /> |  | ||||||
| {% include "base/header_js.html" %} | {% include "base/header_js.html" %} | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block body %} | {% block body %} | ||||||
| <ak-message-container></ak-message-container> | <ak-message-container></ak-message-container> | ||||||
|  |  | ||||||
| <ak-interface-admin> | <ak-interface-admin> | ||||||
|   <ak-loading></ak-loading> |     <ak-loading></ak-loading> | ||||||
| </ak-interface-admin> | </ak-interface-admin> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | |||||||
| @ -13,14 +13,9 @@ | |||||||
|  |  | ||||||
| {% block card %} | {% block card %} | ||||||
| <form method="POST" class="pf-c-form"> | <form method="POST" class="pf-c-form"> | ||||||
|   <p>{% trans message %}</p> |     <p>{% trans message %}</p> | ||||||
|  |     <a id="ak-back-home" href="{% url 'authentik_core:root-redirect' %}" class="pf-c-button pf-m-primary"> | ||||||
|   <a |         {% trans 'Go home' %} | ||||||
|     id="ak-back-home" |     </a> | ||||||
|     href="{% url 'authentik_core:root-redirect' %}" |  | ||||||
|     class="pf-c-button pf-m-primary" |  | ||||||
|   > |  | ||||||
|     {% trans 'Go home' %} |  | ||||||
|   </a> |  | ||||||
| </form> | </form> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | |||||||
| @ -4,17 +4,14 @@ | |||||||
|  |  | ||||||
| {% block head %} | {% block head %} | ||||||
| <script src="{% versioned_script 'dist/user/UserInterface-%v.js' %}" type="module"></script> | <script src="{% versioned_script 'dist/user/UserInterface-%v.js' %}" type="module"></script> | ||||||
|  | <meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)"> | ||||||
| <meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)" /> | <meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)"> | ||||||
| <meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)" /> |  | ||||||
|  |  | ||||||
| {% include "base/header_js.html" %} | {% include "base/header_js.html" %} | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block body %} | {% block body %} | ||||||
| <ak-message-container></ak-message-container> | <ak-message-container></ak-message-container> | ||||||
|  |  | ||||||
| <ak-interface-user> | <ak-interface-user> | ||||||
|   <ak-loading></ak-loading> |     <ak-loading></ak-loading> | ||||||
| </ak-interface-user> | </ak-interface-user> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | |||||||
| @ -5,82 +5,78 @@ | |||||||
|  |  | ||||||
| {% block head_before %} | {% block head_before %} | ||||||
| <link rel="prefetch" href="{{ request.brand.branding_default_flow_background_url }}" /> | <link rel="prefetch" href="{{ request.brand.branding_default_flow_background_url }}" /> | ||||||
| <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}" /> | <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}"> | ||||||
| <link | <link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)"> | ||||||
|   rel="stylesheet" |  | ||||||
|   type="text/css" |  | ||||||
|   href="{% static 'dist/theme-dark.css' %}" |  | ||||||
|   media="(prefers-color-scheme: dark)" |  | ||||||
| /> |  | ||||||
| {% include "base/header_js.html" %} | {% include "base/header_js.html" %} | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block head %} | {% block head %} | ||||||
| <style data-test-id="base-full-root-styles"> | <style> | ||||||
|   :root { | :root { | ||||||
|     --ak-flow-background: url("{{ request.brand.branding_default_flow_background_url }}"); |     --ak-flow-background: url("{{ request.brand.branding_default_flow_background_url }}"); | ||||||
|     --pf-c-background-image--BackgroundImage: var(--ak-flow-background); |     --pf-c-background-image--BackgroundImage: var(--ak-flow-background); | ||||||
|     --pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background); |     --pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background); | ||||||
|     --pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background); |     --pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background); | ||||||
|     --pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background); |     --pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background); | ||||||
|     --pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background); |     --pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background); | ||||||
|   } | } | ||||||
|   /* Form with user */ | /* Form with user */ | ||||||
|   .form-control-static { | .form-control-static { | ||||||
|     margin-top: var(--pf-global--spacer--sm); |     margin-top: var(--pf-global--spacer--sm); | ||||||
|     display: flex; |     display: flex; | ||||||
|     align-items: center; |     align-items: center; | ||||||
|     justify-content: space-between; |     justify-content: space-between; | ||||||
|   } | } | ||||||
|   .form-control-static .avatar { | .form-control-static .avatar { | ||||||
|     display: flex; |     display: flex; | ||||||
|     align-items: center; |     align-items: center; | ||||||
|   } | } | ||||||
|   .form-control-static img { | .form-control-static img { | ||||||
|     margin-right: var(--pf-global--spacer--xs); |     margin-right: var(--pf-global--spacer--xs); | ||||||
|   } | } | ||||||
|   .form-control-static a { | .form-control-static a { | ||||||
|     padding-top: var(--pf-global--spacer--xs); |     padding-top: var(--pf-global--spacer--xs); | ||||||
|     padding-bottom: var(--pf-global--spacer--xs); |     padding-bottom: var(--pf-global--spacer--xs); | ||||||
|     line-height: var(--pf-global--spacer--xl); |     line-height: var(--pf-global--spacer--xl); | ||||||
|   } | } | ||||||
| </style> | </style> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block body %} | {% block body %} | ||||||
| <div class="pf-c-background-image"></div> | <div class="pf-c-background-image"> | ||||||
|  | </div> | ||||||
| <ak-message-container></ak-message-container> | <ak-message-container></ak-message-container> | ||||||
| <div class="pf-c-login stacked"> | <div class="pf-c-login stacked"> | ||||||
|   <div class="ak-login-container"> |     <div class="ak-login-container"> | ||||||
|     <main class="pf-c-login__main"> |         <main class="pf-c-login__main"> | ||||||
|       <div class="pf-c-login__main-header pf-c-brand ak-brand"> |             <div class="pf-c-login__main-header pf-c-brand ak-brand"> | ||||||
|         <img src="{{ brand.branding_logo_url }}" alt="authentik Logo" /> |                 <img src="{{ brand.branding_logo_url }}" alt="authentik Logo" /> | ||||||
|       </div> |             </div> | ||||||
|       <header class="pf-c-login__main-header"> |             <header class="pf-c-login__main-header"> | ||||||
|         <h1 class="pf-c-title pf-m-3xl"> |                 <h1 class="pf-c-title pf-m-3xl"> | ||||||
|           {% block card_title %} |                     {% block card_title %} | ||||||
|           {% endblock %} |                     {% endblock %} | ||||||
|         </h1> |                 </h1> | ||||||
|       </header> |             </header> | ||||||
|       <div class="pf-c-login__main-body"> |             <div class="pf-c-login__main-body"> | ||||||
|         {% block card %} |                 {% block card %} | ||||||
|         {% endblock %} |                 {% endblock %} | ||||||
|       </div> |             </div> | ||||||
|     </main> |         </main> | ||||||
|     <footer class="pf-c-login__footer"> |         <footer class="pf-c-login__footer"> | ||||||
|       <ul class="pf-c-list pf-m-inline"> |             <ul class="pf-c-list pf-m-inline"> | ||||||
|         {% for link in footer_links %} |                 {% for link in footer_links %} | ||||||
|         <li> |                 <li> | ||||||
|           <a href="{{ link.href }}">{{ link.name }}</a> |                     <a href="{{ link.href }}">{{ link.name }}</a> | ||||||
|         </li> |                 </li> | ||||||
|         {% endfor %} |                 {% endfor %} | ||||||
|         <li> |                 <li> | ||||||
|           <span> |                     <span> | ||||||
|             {% trans 'Powered by authentik' %} |                         {% trans 'Powered by authentik' %} | ||||||
|           </span> |                     </span> | ||||||
|         </li> |                 </li> | ||||||
|       </ul> |             </ul> | ||||||
|     </footer> |         </footer> | ||||||
|   </div> |     </div> | ||||||
| </div> | </div> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | |||||||
| @ -13,7 +13,10 @@ from authentik.core.models import ( | |||||||
|     TokenIntents, |     TokenIntents, | ||||||
|     User, |     User, | ||||||
| ) | ) | ||||||
| from authentik.core.tasks import clean_expired_models, clean_temporary_users | from authentik.core.tasks import ( | ||||||
|  |     clean_expired_models, | ||||||
|  |     clean_temporary_users, | ||||||
|  | ) | ||||||
| from authentik.core.tests.utils import create_test_admin_user | from authentik.core.tests.utils import create_test_admin_user | ||||||
| from authentik.lib.generators import generate_id | from authentik.lib.generators import generate_id | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										0
									
								
								authentik/enterprise/policies/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								authentik/enterprise/policies/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										27
									
								
								authentik/enterprise/policies/unique_password/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								authentik/enterprise/policies/unique_password/api.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | from rest_framework.viewsets import ModelViewSet | ||||||
|  |  | ||||||
|  | from authentik.core.api.used_by import UsedByMixin | ||||||
|  | from authentik.enterprise.api import EnterpriseRequiredMixin | ||||||
|  | from authentik.enterprise.policies.unique_password.models import UniquePasswordPolicy | ||||||
|  | from authentik.policies.api.policies import PolicySerializer | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UniquePasswordPolicySerializer(EnterpriseRequiredMixin, PolicySerializer): | ||||||
|  |     """Password Uniqueness Policy Serializer""" | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         model = UniquePasswordPolicy | ||||||
|  |         fields = PolicySerializer.Meta.fields + [ | ||||||
|  |             "password_field", | ||||||
|  |             "num_historical_passwords", | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UniquePasswordPolicyViewSet(UsedByMixin, ModelViewSet): | ||||||
|  |     """Password Uniqueness Policy Viewset""" | ||||||
|  |  | ||||||
|  |     queryset = UniquePasswordPolicy.objects.all() | ||||||
|  |     serializer_class = UniquePasswordPolicySerializer | ||||||
|  |     filterset_fields = "__all__" | ||||||
|  |     ordering = ["name"] | ||||||
|  |     search_fields = ["name"] | ||||||
							
								
								
									
										10
									
								
								authentik/enterprise/policies/unique_password/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								authentik/enterprise/policies/unique_password/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | """authentik Unique Password policy app config""" | ||||||
|  |  | ||||||
|  | from authentik.enterprise.apps import EnterpriseConfig | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AuthentikEnterprisePoliciesUniquePasswordConfig(EnterpriseConfig): | ||||||
|  |     name = "authentik.enterprise.policies.unique_password" | ||||||
|  |     label = "authentik_policies_unique_password" | ||||||
|  |     verbose_name = "authentik Enterprise.Policies.Unique Password" | ||||||
|  |     default = True | ||||||
| @ -0,0 +1,81 @@ | |||||||
|  | # Generated by Django 5.0.13 on 2025-03-26 23:02 | ||||||
|  |  | ||||||
|  | import django.db.models.deletion | ||||||
|  | from django.conf import settings | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     initial = True | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_policies", "0011_policybinding_failure_result_and_more"), | ||||||
|  |         migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name="UniquePasswordPolicy", | ||||||
|  |             fields=[ | ||||||
|  |                 ( | ||||||
|  |                     "policy_ptr", | ||||||
|  |                     models.OneToOneField( | ||||||
|  |                         auto_created=True, | ||||||
|  |                         on_delete=django.db.models.deletion.CASCADE, | ||||||
|  |                         parent_link=True, | ||||||
|  |                         primary_key=True, | ||||||
|  |                         serialize=False, | ||||||
|  |                         to="authentik_policies.policy", | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "password_field", | ||||||
|  |                     models.TextField( | ||||||
|  |                         default="password", | ||||||
|  |                         help_text="Field key to check, field keys defined in Prompt stages are available.", | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "num_historical_passwords", | ||||||
|  |                     models.PositiveIntegerField( | ||||||
|  |                         default=1, help_text="Number of passwords to check against." | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 "verbose_name": "Password Uniqueness Policy", | ||||||
|  |                 "verbose_name_plural": "Password Uniqueness Policies", | ||||||
|  |                 "indexes": [ | ||||||
|  |                     models.Index(fields=["policy_ptr_id"], name="authentik_p_policy__f559aa_idx") | ||||||
|  |                 ], | ||||||
|  |             }, | ||||||
|  |             bases=("authentik_policies.policy",), | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name="UserPasswordHistory", | ||||||
|  |             fields=[ | ||||||
|  |                 ( | ||||||
|  |                     "id", | ||||||
|  |                     models.AutoField( | ||||||
|  |                         auto_created=True, primary_key=True, serialize=False, verbose_name="ID" | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("old_password", models.CharField(max_length=128)), | ||||||
|  |                 ("created_at", models.DateTimeField(auto_now_add=True)), | ||||||
|  |                 ("hibp_prefix_sha1", models.CharField(max_length=5)), | ||||||
|  |                 ("hibp_pw_hash", models.TextField()), | ||||||
|  |                 ( | ||||||
|  |                     "user", | ||||||
|  |                     models.ForeignKey( | ||||||
|  |                         on_delete=django.db.models.deletion.CASCADE, | ||||||
|  |                         related_name="old_passwords", | ||||||
|  |                         to=settings.AUTH_USER_MODEL, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 "verbose_name": "User Password History", | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										151
									
								
								authentik/enterprise/policies/unique_password/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								authentik/enterprise/policies/unique_password/models.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | |||||||
|  | from hashlib import sha1 | ||||||
|  |  | ||||||
|  | from django.contrib.auth.hashers import identify_hasher, make_password | ||||||
|  | from django.db import models | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from rest_framework.serializers import BaseSerializer | ||||||
|  | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
|  | from authentik.core.models import User | ||||||
|  | from authentik.policies.models import Policy | ||||||
|  | from authentik.policies.types import PolicyRequest, PolicyResult | ||||||
|  | from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||||
|  |  | ||||||
|  | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UniquePasswordPolicy(Policy): | ||||||
|  |     """This policy prevents users from reusing old passwords.""" | ||||||
|  |  | ||||||
|  |     password_field = models.TextField( | ||||||
|  |         default="password", | ||||||
|  |         help_text=_("Field key to check, field keys defined in Prompt stages are available."), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # Limit on the number of previous passwords the policy evaluates | ||||||
|  |     # Also controls number of old passwords the system stores. | ||||||
|  |     num_historical_passwords = models.PositiveIntegerField( | ||||||
|  |         default=1, | ||||||
|  |         help_text=_("Number of passwords to check against."), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def serializer(self) -> type[BaseSerializer]: | ||||||
|  |         from authentik.enterprise.policies.unique_password.api import UniquePasswordPolicySerializer | ||||||
|  |  | ||||||
|  |         return UniquePasswordPolicySerializer | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def component(self) -> str: | ||||||
|  |         return "ak-policy-password-uniqueness-form" | ||||||
|  |  | ||||||
|  |     def passes(self, request: PolicyRequest) -> PolicyResult: | ||||||
|  |         from authentik.enterprise.policies.unique_password.models import UserPasswordHistory | ||||||
|  |  | ||||||
|  |         password = request.context.get(PLAN_CONTEXT_PROMPT, {}).get( | ||||||
|  |             self.password_field, request.context.get(self.password_field) | ||||||
|  |         ) | ||||||
|  |         if not password: | ||||||
|  |             LOGGER.warning( | ||||||
|  |                 "Password field not found in request when checking UniquePasswordPolicy", | ||||||
|  |                 field=self.password_field, | ||||||
|  |                 fields=request.context.keys(), | ||||||
|  |             ) | ||||||
|  |             return PolicyResult(False, _("Password not set in context")) | ||||||
|  |         password = str(password) | ||||||
|  |  | ||||||
|  |         if not self.num_historical_passwords: | ||||||
|  |             # Policy not configured to check against any passwords | ||||||
|  |             return PolicyResult(True) | ||||||
|  |  | ||||||
|  |         num_to_check = self.num_historical_passwords | ||||||
|  |         password_history = UserPasswordHistory.objects.filter(user=request.user).order_by( | ||||||
|  |             "-created_at" | ||||||
|  |         )[:num_to_check] | ||||||
|  |  | ||||||
|  |         if not password_history: | ||||||
|  |             return PolicyResult(True) | ||||||
|  |  | ||||||
|  |         for record in password_history: | ||||||
|  |             if not record.old_password: | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             if self._passwords_match(new_password=password, old_password=record.old_password): | ||||||
|  |                 # Return on first match. Authentik does not consider timing attacks | ||||||
|  |                 # on old passwords to be an attack surface. | ||||||
|  |                 return PolicyResult( | ||||||
|  |                     False, | ||||||
|  |                     _("This password has been used previously. Please choose a different one."), | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |         return PolicyResult(True) | ||||||
|  |  | ||||||
|  |     def _passwords_match(self, *, new_password: str, old_password: str) -> bool: | ||||||
|  |         try: | ||||||
|  |             hasher = identify_hasher(old_password) | ||||||
|  |         except ValueError: | ||||||
|  |             LOGGER.warning( | ||||||
|  |                 "Skipping password; could not load hash algorithm", | ||||||
|  |             ) | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         return hasher.verify(new_password, old_password) | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def is_in_use(cls): | ||||||
|  |         """Check if any UniquePasswordPolicy is in use, either through policy bindings | ||||||
|  |         or direct attachment to a PromptStage. | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             bool: True if any policy is in use, False otherwise | ||||||
|  |         """ | ||||||
|  |         from authentik.policies.models import PolicyBinding | ||||||
|  |  | ||||||
|  |         # Check if any policy is in use through bindings | ||||||
|  |         if PolicyBinding.in_use.for_policy(cls).exists(): | ||||||
|  |             return True | ||||||
|  |  | ||||||
|  |         # Check if any policy is attached to a PromptStage | ||||||
|  |         if cls.objects.filter(promptstage__isnull=False).exists(): | ||||||
|  |             return True | ||||||
|  |  | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  |     class Meta(Policy.PolicyMeta): | ||||||
|  |         verbose_name = _("Password Uniqueness Policy") | ||||||
|  |         verbose_name_plural = _("Password Uniqueness Policies") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserPasswordHistory(models.Model): | ||||||
|  |     user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="old_passwords") | ||||||
|  |     # Mimic's column type of AbstractBaseUser.password | ||||||
|  |     old_password = models.CharField(max_length=128) | ||||||
|  |     created_at = models.DateTimeField(auto_now_add=True) | ||||||
|  |  | ||||||
|  |     hibp_prefix_sha1 = models.CharField(max_length=5) | ||||||
|  |     hibp_pw_hash = models.TextField() | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         verbose_name = _("User Password History") | ||||||
|  |  | ||||||
|  |     def __str__(self) -> str: | ||||||
|  |         timestamp = f"{self.created_at:%Y/%m/%d %X}" if self.created_at else "N/A" | ||||||
|  |         return f"Previous Password (user: {self.user_id}, recorded: {timestamp})" | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def create_for_user(cls, user: User, password: str): | ||||||
|  |         # To check users' passwords against Have I been Pwned, we need the first 5 chars | ||||||
|  |         # of the password hashed with SHA1 without a salt... | ||||||
|  |         pw_hash_sha1 = sha1(password.encode("utf-8")).hexdigest()  # nosec | ||||||
|  |         # ...however that'll give us a list of hashes from HIBP, and to compare that we still | ||||||
|  |         # need a full unsalted SHA1 of the password. We don't want to save that directly in | ||||||
|  |         # the database, so we hash that SHA1 again with a modern hashing alg, | ||||||
|  |         # and then when we check users' passwords against HIBP we can use `check_password` | ||||||
|  |         # which will take care of this. | ||||||
|  |         hibp_hash_hash = make_password(pw_hash_sha1) | ||||||
|  |         return cls.objects.create( | ||||||
|  |             user=user, | ||||||
|  |             old_password=password, | ||||||
|  |             hibp_prefix_sha1=pw_hash_sha1[:5], | ||||||
|  |             hibp_pw_hash=hibp_hash_hash, | ||||||
|  |         ) | ||||||
							
								
								
									
										20
									
								
								authentik/enterprise/policies/unique_password/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								authentik/enterprise/policies/unique_password/settings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | """Unique Password Policy settings""" | ||||||
|  |  | ||||||
|  | from celery.schedules import crontab | ||||||
|  |  | ||||||
|  | from authentik.lib.utils.time import fqdn_rand | ||||||
|  |  | ||||||
|  | CELERY_BEAT_SCHEDULE = { | ||||||
|  |     "policies_unique_password_trim_history": { | ||||||
|  |         "task": "authentik.enterprise.policies.unique_password.tasks.trim_password_histories", | ||||||
|  |         "schedule": crontab(minute=fqdn_rand("policies_unique_password_trim"), hour="*/12"), | ||||||
|  |         "options": {"queue": "authentik_scheduled"}, | ||||||
|  |     }, | ||||||
|  |     "policies_unique_password_check_purge": { | ||||||
|  |         "task": ( | ||||||
|  |             "authentik.enterprise.policies.unique_password.tasks.check_and_purge_password_history" | ||||||
|  |         ), | ||||||
|  |         "schedule": crontab(minute=fqdn_rand("policies_unique_password_purge"), hour="*/24"), | ||||||
|  |         "options": {"queue": "authentik_scheduled"}, | ||||||
|  |     }, | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								authentik/enterprise/policies/unique_password/signals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								authentik/enterprise/policies/unique_password/signals.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | """authentik policy signals""" | ||||||
|  |  | ||||||
|  | from django.dispatch import receiver | ||||||
|  |  | ||||||
|  | from authentik.core.models import User | ||||||
|  | from authentik.core.signals import password_changed | ||||||
|  | from authentik.enterprise.policies.unique_password.models import ( | ||||||
|  |     UniquePasswordPolicy, | ||||||
|  |     UserPasswordHistory, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @receiver(password_changed) | ||||||
|  | def copy_password_to_password_history(sender, user: User, *args, **kwargs): | ||||||
|  |     """Preserve the user's old password if UniquePasswordPolicy is enabled anywhere""" | ||||||
|  |     # Check if any UniquePasswordPolicy is in use | ||||||
|  |     unique_pwd_policy_in_use = UniquePasswordPolicy.is_in_use() | ||||||
|  |  | ||||||
|  |     if unique_pwd_policy_in_use: | ||||||
|  |         """NOTE: Because we run this in a signal after saving the user, | ||||||
|  |         we are not atomically guaranteed to save password history. | ||||||
|  |         """ | ||||||
|  |         UserPasswordHistory.create_for_user(user, user.password) | ||||||
							
								
								
									
										66
									
								
								authentik/enterprise/policies/unique_password/tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								authentik/enterprise/policies/unique_password/tasks.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | from django.db.models.aggregates import Count | ||||||
|  | from structlog import get_logger | ||||||
|  |  | ||||||
|  | from authentik.enterprise.policies.unique_password.models import ( | ||||||
|  |     UniquePasswordPolicy, | ||||||
|  |     UserPasswordHistory, | ||||||
|  | ) | ||||||
|  | from authentik.events.system_tasks import SystemTask, TaskStatus, prefill_task | ||||||
|  | from authentik.root.celery import CELERY_APP | ||||||
|  |  | ||||||
|  | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @CELERY_APP.task(bind=True, base=SystemTask) | ||||||
|  | @prefill_task | ||||||
|  | def check_and_purge_password_history(self: SystemTask): | ||||||
|  |     """Check if any UniquePasswordPolicy exists, and if not, purge the password history table. | ||||||
|  |     This is run on a schedule instead of being triggered by policy binding deletion. | ||||||
|  |     """ | ||||||
|  |     if not UniquePasswordPolicy.objects.exists(): | ||||||
|  |         UserPasswordHistory.objects.all().delete() | ||||||
|  |         LOGGER.debug("Purged UserPasswordHistory table as no policies are in use") | ||||||
|  |         self.set_status(TaskStatus.SUCCESSFUL, "Successfully purged UserPasswordHistory") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     self.set_status( | ||||||
|  |         TaskStatus.SUCCESSFUL, "Not purging password histories, a unique password policy exists" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @CELERY_APP.task(bind=True, base=SystemTask) | ||||||
|  | def trim_password_histories(self: SystemTask): | ||||||
|  |     """Removes rows from UserPasswordHistory older than | ||||||
|  |     the `n` most recent entries. | ||||||
|  |  | ||||||
|  |     The `n` is defined by the largest configured value for all bound | ||||||
|  |     UniquePasswordPolicy policies. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     # No policy, we'll let the cleanup above do its thing | ||||||
|  |     if not UniquePasswordPolicy.objects.exists(): | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     num_rows_to_preserve = 0 | ||||||
|  |     for policy in UniquePasswordPolicy.objects.all(): | ||||||
|  |         num_rows_to_preserve = max(num_rows_to_preserve, policy.num_historical_passwords) | ||||||
|  |  | ||||||
|  |     all_pks_to_keep = [] | ||||||
|  |  | ||||||
|  |     # Get all users who have password history entries | ||||||
|  |     users_with_history = ( | ||||||
|  |         UserPasswordHistory.objects.values("user") | ||||||
|  |         .annotate(count=Count("user")) | ||||||
|  |         .filter(count__gt=0) | ||||||
|  |         .values_list("user", flat=True) | ||||||
|  |     ) | ||||||
|  |     for user_pk in users_with_history: | ||||||
|  |         entries = UserPasswordHistory.objects.filter(user__pk=user_pk) | ||||||
|  |         pks_to_keep = entries.order_by("-created_at")[:num_rows_to_preserve].values_list( | ||||||
|  |             "pk", flat=True | ||||||
|  |         ) | ||||||
|  |         all_pks_to_keep.extend(pks_to_keep) | ||||||
|  |  | ||||||
|  |     num_deleted, _ = UserPasswordHistory.objects.exclude(pk__in=all_pks_to_keep).delete() | ||||||
|  |     LOGGER.debug("Deleted stale password history records", count=num_deleted) | ||||||
|  |     self.set_status(TaskStatus.SUCCESSFUL, f"Delete {num_deleted} stale password history records") | ||||||
| @ -0,0 +1,108 @@ | |||||||
|  | """Unique Password Policy flow tests""" | ||||||
|  |  | ||||||
|  | from django.contrib.auth.hashers import make_password | ||||||
|  | from django.urls.base import reverse | ||||||
|  |  | ||||||
|  | from authentik.core.tests.utils import create_test_flow, create_test_user | ||||||
|  | from authentik.enterprise.policies.unique_password.models import ( | ||||||
|  |     UniquePasswordPolicy, | ||||||
|  |     UserPasswordHistory, | ||||||
|  | ) | ||||||
|  | from authentik.flows.models import FlowDesignation, FlowStageBinding | ||||||
|  | from authentik.flows.tests import FlowTestCase | ||||||
|  | from authentik.lib.generators import generate_id | ||||||
|  | from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestUniquePasswordPolicyFlow(FlowTestCase): | ||||||
|  |     """Test Unique Password Policy in a flow""" | ||||||
|  |  | ||||||
|  |     REUSED_PASSWORD = "hunter1"  # nosec B105 | ||||||
|  |  | ||||||
|  |     def setUp(self) -> None: | ||||||
|  |         self.user = create_test_user() | ||||||
|  |         self.flow = create_test_flow(FlowDesignation.AUTHENTICATION) | ||||||
|  |  | ||||||
|  |         password_prompt = Prompt.objects.create( | ||||||
|  |             name=generate_id(), | ||||||
|  |             field_key="password", | ||||||
|  |             label="PASSWORD_LABEL", | ||||||
|  |             type=FieldTypes.PASSWORD, | ||||||
|  |             required=True, | ||||||
|  |             placeholder="PASSWORD_PLACEHOLDER", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         self.policy = UniquePasswordPolicy.objects.create( | ||||||
|  |             name="password_must_unique", | ||||||
|  |             password_field=password_prompt.field_key, | ||||||
|  |             num_historical_passwords=1, | ||||||
|  |         ) | ||||||
|  |         stage = PromptStage.objects.create(name="prompt-stage") | ||||||
|  |         stage.validation_policies.set([self.policy]) | ||||||
|  |         stage.fields.set( | ||||||
|  |             [ | ||||||
|  |                 password_prompt, | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         FlowStageBinding.objects.create(target=self.flow, stage=stage, order=2) | ||||||
|  |  | ||||||
|  |         # Seed the user's password history | ||||||
|  |         UserPasswordHistory.create_for_user(self.user, make_password(self.REUSED_PASSWORD)) | ||||||
|  |  | ||||||
|  |     def test_prompt_data(self): | ||||||
|  |         """Test policy attached to a prompt stage""" | ||||||
|  |         # Test the policy directly | ||||||
|  |         from authentik.policies.types import PolicyRequest | ||||||
|  |         from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||||
|  |  | ||||||
|  |         # Create a policy request with the reused password | ||||||
|  |         request = PolicyRequest(user=self.user) | ||||||
|  |         request.context[PLAN_CONTEXT_PROMPT] = {"password": self.REUSED_PASSWORD} | ||||||
|  |  | ||||||
|  |         # Test the policy directly | ||||||
|  |         result = self.policy.passes(request) | ||||||
|  |  | ||||||
|  |         # Verify that the policy fails (returns False) with the expected error message | ||||||
|  |         self.assertFalse(result.passing, "Policy should fail for reused password") | ||||||
|  |         self.assertEqual( | ||||||
|  |             result.messages[0], | ||||||
|  |             "This password has been used previously. Please choose a different one.", | ||||||
|  |             "Incorrect error message", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # API-based testing approach: | ||||||
|  |  | ||||||
|  |         self.client.force_login(self.user) | ||||||
|  |  | ||||||
|  |         # Send a POST request to the flow executor with the reused password | ||||||
|  |         response = self.client.post( | ||||||
|  |             reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), | ||||||
|  |             {"password": self.REUSED_PASSWORD}, | ||||||
|  |         ) | ||||||
|  |         self.assertStageResponse( | ||||||
|  |             response, | ||||||
|  |             self.flow, | ||||||
|  |             component="ak-stage-prompt", | ||||||
|  |             fields=[ | ||||||
|  |                 { | ||||||
|  |                     "choices": None, | ||||||
|  |                     "field_key": "password", | ||||||
|  |                     "label": "PASSWORD_LABEL", | ||||||
|  |                     "order": 0, | ||||||
|  |                     "placeholder": "PASSWORD_PLACEHOLDER", | ||||||
|  |                     "initial_value": "", | ||||||
|  |                     "required": True, | ||||||
|  |                     "type": "password", | ||||||
|  |                     "sub_text": "", | ||||||
|  |                 } | ||||||
|  |             ], | ||||||
|  |             response_errors={ | ||||||
|  |                 "non_field_errors": [ | ||||||
|  |                     { | ||||||
|  |                         "code": "invalid", | ||||||
|  |                         "string": "This password has been used previously. " | ||||||
|  |                         "Please choose a different one.", | ||||||
|  |                     } | ||||||
|  |                 ] | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
| @ -0,0 +1,77 @@ | |||||||
|  | """Unique Password Policy tests""" | ||||||
|  |  | ||||||
|  | from django.contrib.auth.hashers import make_password | ||||||
|  | from django.test import TestCase | ||||||
|  | from guardian.shortcuts import get_anonymous_user | ||||||
|  |  | ||||||
|  | from authentik.core.models import User | ||||||
|  | from authentik.enterprise.policies.unique_password.models import ( | ||||||
|  |     UniquePasswordPolicy, | ||||||
|  |     UserPasswordHistory, | ||||||
|  | ) | ||||||
|  | from authentik.policies.types import PolicyRequest, PolicyResult | ||||||
|  | from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestUniquePasswordPolicy(TestCase): | ||||||
|  |     """Test Password Uniqueness Policy""" | ||||||
|  |  | ||||||
|  |     def setUp(self) -> None: | ||||||
|  |         self.policy = UniquePasswordPolicy.objects.create( | ||||||
|  |             name="test_unique_password", num_historical_passwords=1 | ||||||
|  |         ) | ||||||
|  |         self.user = User.objects.create(username="test-user") | ||||||
|  |  | ||||||
|  |     def test_invalid(self): | ||||||
|  |         """Test without password present in request""" | ||||||
|  |         request = PolicyRequest(get_anonymous_user()) | ||||||
|  |         result: PolicyResult = self.policy.passes(request) | ||||||
|  |         self.assertFalse(result.passing) | ||||||
|  |         self.assertEqual(result.messages[0], "Password not set in context") | ||||||
|  |  | ||||||
|  |     def test_passes_no_previous_passwords(self): | ||||||
|  |         request = PolicyRequest(get_anonymous_user()) | ||||||
|  |         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter2"}} | ||||||
|  |         result: PolicyResult = self.policy.passes(request) | ||||||
|  |         self.assertTrue(result.passing) | ||||||
|  |  | ||||||
|  |     def test_passes_passwords_are_different(self): | ||||||
|  |         # Seed database with an old password | ||||||
|  |         UserPasswordHistory.create_for_user(self.user, make_password("hunter1")) | ||||||
|  |  | ||||||
|  |         request = PolicyRequest(self.user) | ||||||
|  |         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter2"}} | ||||||
|  |         result: PolicyResult = self.policy.passes(request) | ||||||
|  |         self.assertTrue(result.passing) | ||||||
|  |  | ||||||
|  |     def test_passes_multiple_old_passwords(self): | ||||||
|  |         # Seed with multiple old passwords | ||||||
|  |         UserPasswordHistory.objects.bulk_create( | ||||||
|  |             [ | ||||||
|  |                 UserPasswordHistory(user=self.user, old_password=make_password("hunter1")), | ||||||
|  |                 UserPasswordHistory(user=self.user, old_password=make_password("hunter2")), | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         request = PolicyRequest(self.user) | ||||||
|  |         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter3"}} | ||||||
|  |         result: PolicyResult = self.policy.passes(request) | ||||||
|  |         self.assertTrue(result.passing) | ||||||
|  |  | ||||||
|  |     def test_fails_password_matches_old_password(self): | ||||||
|  |         # Seed database with an old password | ||||||
|  |  | ||||||
|  |         UserPasswordHistory.create_for_user(self.user, make_password("hunter1")) | ||||||
|  |  | ||||||
|  |         request = PolicyRequest(self.user) | ||||||
|  |         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter1"}} | ||||||
|  |         result: PolicyResult = self.policy.passes(request) | ||||||
|  |         self.assertFalse(result.passing) | ||||||
|  |  | ||||||
|  |     def test_fails_if_identical_password_with_different_hash_algos(self): | ||||||
|  |         UserPasswordHistory.create_for_user( | ||||||
|  |             self.user, make_password("hunter2", "somesalt", "scrypt") | ||||||
|  |         ) | ||||||
|  |         request = PolicyRequest(self.user) | ||||||
|  |         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter2"}} | ||||||
|  |         result: PolicyResult = self.policy.passes(request) | ||||||
|  |         self.assertFalse(result.passing) | ||||||
| @ -0,0 +1,90 @@ | |||||||
|  | from django.urls import reverse | ||||||
|  |  | ||||||
|  | from authentik.core.models import Group, Source, User | ||||||
|  | from authentik.core.tests.utils import create_test_flow, create_test_user | ||||||
|  | from authentik.enterprise.policies.unique_password.models import ( | ||||||
|  |     UniquePasswordPolicy, | ||||||
|  |     UserPasswordHistory, | ||||||
|  | ) | ||||||
|  | from authentik.flows.markers import StageMarker | ||||||
|  | from authentik.flows.models import FlowStageBinding | ||||||
|  | from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan | ||||||
|  | from authentik.flows.tests import FlowTestCase | ||||||
|  | from authentik.flows.views.executor import SESSION_KEY_PLAN | ||||||
|  | from authentik.lib.generators import generate_key | ||||||
|  | from authentik.policies.models import PolicyBinding, PolicyBindingModel | ||||||
|  | from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||||
|  | from authentik.stages.user_write.models import UserWriteStage | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestUserWriteStage(FlowTestCase): | ||||||
|  |     """Write tests""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         super().setUp() | ||||||
|  |         self.flow = create_test_flow() | ||||||
|  |         self.group = Group.objects.create(name="test-group") | ||||||
|  |         self.other_group = Group.objects.create(name="other-group") | ||||||
|  |         self.stage: UserWriteStage = UserWriteStage.objects.create( | ||||||
|  |             name="write", create_users_as_inactive=True, create_users_group=self.group | ||||||
|  |         ) | ||||||
|  |         self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) | ||||||
|  |         self.source = Source.objects.create(name="fake_source") | ||||||
|  |  | ||||||
|  |     def test_save_password_history_if_policy_binding_enforced(self): | ||||||
|  |         """Test user's new password is recorded when ANY enabled UniquePasswordPolicy exists""" | ||||||
|  |         unique_password_policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5) | ||||||
|  |         pbm = PolicyBindingModel.objects.create() | ||||||
|  |         PolicyBinding.objects.create( | ||||||
|  |             target=pbm, policy=unique_password_policy, order=0, enabled=True | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         test_user = create_test_user() | ||||||
|  |         # Store original password for verification | ||||||
|  |         original_password = test_user.password | ||||||
|  |  | ||||||
|  |         # We're changing our own password | ||||||
|  |         self.client.force_login(test_user) | ||||||
|  |  | ||||||
|  |         new_password = generate_key() | ||||||
|  |         plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) | ||||||
|  |         plan.context[PLAN_CONTEXT_PENDING_USER] = test_user | ||||||
|  |         plan.context[PLAN_CONTEXT_PROMPT] = { | ||||||
|  |             "username": test_user.username, | ||||||
|  |             "password": new_password, | ||||||
|  |         } | ||||||
|  |         session = self.client.session | ||||||
|  |         session[SESSION_KEY_PLAN] = plan | ||||||
|  |         session.save() | ||||||
|  |         # Password history should be recorded | ||||||
|  |         user_password_history_qs = UserPasswordHistory.objects.filter(user=test_user) | ||||||
|  |         self.assertTrue(user_password_history_qs.exists(), "Password history should be recorded") | ||||||
|  |         self.assertEqual(len(user_password_history_qs), 1, "expected 1 recorded password") | ||||||
|  |  | ||||||
|  |         # Create a password history entry manually to simulate the signal behavior | ||||||
|  |         # This is what would happen if the signal worked correctly | ||||||
|  |         UserPasswordHistory.objects.create(user=test_user, old_password=original_password) | ||||||
|  |         user_password_history_qs = UserPasswordHistory.objects.filter(user=test_user) | ||||||
|  |         self.assertTrue(user_password_history_qs.exists(), "Password history should be recorded") | ||||||
|  |         self.assertEqual(len(user_password_history_qs), 2, "expected 2 recorded password") | ||||||
|  |  | ||||||
|  |         # Execute the flow by sending a POST request to the flow executor endpoint | ||||||
|  |         response = self.client.post( | ||||||
|  |             reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Verify that the request was successful | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |         user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"]) | ||||||
|  |         self.assertTrue(user_qs.exists()) | ||||||
|  |  | ||||||
|  |         # Verify the password history entry exists | ||||||
|  |         user_password_history_qs = UserPasswordHistory.objects.filter(user=test_user) | ||||||
|  |         self.assertTrue(user_password_history_qs.exists(), "Password history should be recorded") | ||||||
|  |  | ||||||
|  |         self.assertEqual(len(user_password_history_qs), 3, "expected 3 recorded password") | ||||||
|  |         # Verify that one of the entries contains the original password | ||||||
|  |         self.assertTrue( | ||||||
|  |             any(entry.old_password == original_password for entry in user_password_history_qs), | ||||||
|  |             "original password should be in password history table", | ||||||
|  |         ) | ||||||
| @ -0,0 +1,178 @@ | |||||||
|  | from datetime import datetime, timedelta | ||||||
|  |  | ||||||
|  | from django.test import TestCase | ||||||
|  |  | ||||||
|  | from authentik.core.tests.utils import create_test_user | ||||||
|  | from authentik.enterprise.policies.unique_password.models import ( | ||||||
|  |     UniquePasswordPolicy, | ||||||
|  |     UserPasswordHistory, | ||||||
|  | ) | ||||||
|  | from authentik.enterprise.policies.unique_password.tasks import ( | ||||||
|  |     check_and_purge_password_history, | ||||||
|  |     trim_password_histories, | ||||||
|  | ) | ||||||
|  | from authentik.policies.models import PolicyBinding, PolicyBindingModel | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestUniquePasswordPolicyModel(TestCase): | ||||||
|  |     """Test the UniquePasswordPolicy model methods""" | ||||||
|  |  | ||||||
|  |     def test_is_in_use_with_binding(self): | ||||||
|  |         """Test is_in_use returns True when a policy binding exists""" | ||||||
|  |         # Create a UniquePasswordPolicy and a PolicyBinding for it | ||||||
|  |         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5) | ||||||
|  |         pbm = PolicyBindingModel.objects.create() | ||||||
|  |         PolicyBinding.objects.create(target=pbm, policy=policy, order=0, enabled=True) | ||||||
|  |  | ||||||
|  |         # Verify is_in_use returns True | ||||||
|  |         self.assertTrue(UniquePasswordPolicy.is_in_use()) | ||||||
|  |  | ||||||
|  |     def test_is_in_use_with_promptstage(self): | ||||||
|  |         """Test is_in_use returns True when attached to a PromptStage""" | ||||||
|  |         from authentik.stages.prompt.models import PromptStage | ||||||
|  |  | ||||||
|  |         # Create a UniquePasswordPolicy and attach it to a PromptStage | ||||||
|  |         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5) | ||||||
|  |         prompt_stage = PromptStage.objects.create( | ||||||
|  |             name="Test Prompt Stage", | ||||||
|  |         ) | ||||||
|  |         # Use the set() method for many-to-many relationships | ||||||
|  |         prompt_stage.validation_policies.set([policy]) | ||||||
|  |  | ||||||
|  |         # Verify is_in_use returns True | ||||||
|  |         self.assertTrue(UniquePasswordPolicy.is_in_use()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestTrimAllPasswordHistories(TestCase): | ||||||
|  |     """Test the task that trims password history for all users""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.user1 = create_test_user("test-user1") | ||||||
|  |         self.user2 = create_test_user("test-user2") | ||||||
|  |         self.pbm = PolicyBindingModel.objects.create() | ||||||
|  |         # Create a policy with a limit of 1 password | ||||||
|  |         self.policy = UniquePasswordPolicy.objects.create(num_historical_passwords=1) | ||||||
|  |         PolicyBinding.objects.create( | ||||||
|  |             target=self.pbm, | ||||||
|  |             policy=self.policy, | ||||||
|  |             enabled=True, | ||||||
|  |             order=0, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestCheckAndPurgePasswordHistory(TestCase): | ||||||
|  |     """Test the scheduled task that checks if any policy is in use and purges if not""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.user = create_test_user("test-user") | ||||||
|  |         self.pbm = PolicyBindingModel.objects.create() | ||||||
|  |  | ||||||
|  |     def test_purge_when_no_policy_in_use(self): | ||||||
|  |         """Test that the task purges the table when no policy is in use""" | ||||||
|  |         # Create some password history entries | ||||||
|  |         UserPasswordHistory.create_for_user(self.user, "hunter2") | ||||||
|  |  | ||||||
|  |         # Verify we have entries | ||||||
|  |         self.assertTrue(UserPasswordHistory.objects.exists()) | ||||||
|  |  | ||||||
|  |         # Run the task - should purge since no policy is in use | ||||||
|  |         check_and_purge_password_history() | ||||||
|  |  | ||||||
|  |         # Verify the table is empty | ||||||
|  |         self.assertFalse(UserPasswordHistory.objects.exists()) | ||||||
|  |  | ||||||
|  |     def test_no_purge_when_policy_in_use(self): | ||||||
|  |         """Test that the task doesn't purge when a policy is in use""" | ||||||
|  |         # Create a policy and binding | ||||||
|  |         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5) | ||||||
|  |         PolicyBinding.objects.create( | ||||||
|  |             target=self.pbm, | ||||||
|  |             policy=policy, | ||||||
|  |             enabled=True, | ||||||
|  |             order=0, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Create some password history entries | ||||||
|  |         UserPasswordHistory.create_for_user(self.user, "hunter2") | ||||||
|  |  | ||||||
|  |         # Verify we have entries | ||||||
|  |         self.assertTrue(UserPasswordHistory.objects.exists()) | ||||||
|  |  | ||||||
|  |         # Run the task - should NOT purge since a policy is in use | ||||||
|  |         check_and_purge_password_history() | ||||||
|  |  | ||||||
|  |         # Verify the entries still exist | ||||||
|  |         self.assertTrue(UserPasswordHistory.objects.exists()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestTrimPasswordHistory(TestCase): | ||||||
|  |     """Test password history cleanup task""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.user = create_test_user("test-user") | ||||||
|  |         self.pbm = PolicyBindingModel.objects.create() | ||||||
|  |  | ||||||
|  |     def test_trim_password_history_ok(self): | ||||||
|  |         """Test passwords over the define limit are deleted""" | ||||||
|  |         _now = datetime.now() | ||||||
|  |         UserPasswordHistory.objects.bulk_create( | ||||||
|  |             [ | ||||||
|  |                 UserPasswordHistory( | ||||||
|  |                     user=self.user, | ||||||
|  |                     old_password="hunter1",  # nosec B106 | ||||||
|  |                     created_at=_now - timedelta(days=3), | ||||||
|  |                 ), | ||||||
|  |                 UserPasswordHistory( | ||||||
|  |                     user=self.user, | ||||||
|  |                     old_password="hunter2",  # nosec B106 | ||||||
|  |                     created_at=_now - timedelta(days=2), | ||||||
|  |                 ), | ||||||
|  |                 UserPasswordHistory( | ||||||
|  |                     user=self.user, | ||||||
|  |                     old_password="hunter3",  # nosec B106 | ||||||
|  |                     created_at=_now, | ||||||
|  |                 ), | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=1) | ||||||
|  |         PolicyBinding.objects.create( | ||||||
|  |             target=self.pbm, | ||||||
|  |             policy=policy, | ||||||
|  |             enabled=True, | ||||||
|  |             order=0, | ||||||
|  |         ) | ||||||
|  |         trim_password_histories.delay() | ||||||
|  |         user_pwd_history_qs = UserPasswordHistory.objects.filter(user=self.user) | ||||||
|  |         self.assertEqual(len(user_pwd_history_qs), 1) | ||||||
|  |  | ||||||
|  |     def test_trim_password_history_policy_diabled_no_op(self): | ||||||
|  |         """Test no passwords removed if policy binding is disabled""" | ||||||
|  |  | ||||||
|  |         # Insert a record to ensure it's not deleted after executing task | ||||||
|  |         UserPasswordHistory.create_for_user(self.user, "hunter2") | ||||||
|  |  | ||||||
|  |         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=1) | ||||||
|  |         PolicyBinding.objects.create( | ||||||
|  |             target=self.pbm, | ||||||
|  |             policy=policy, | ||||||
|  |             enabled=False, | ||||||
|  |             order=0, | ||||||
|  |         ) | ||||||
|  |         trim_password_histories.delay() | ||||||
|  |         self.assertTrue(UserPasswordHistory.objects.filter(user=self.user).exists()) | ||||||
|  |  | ||||||
|  |     def test_trim_password_history_fewer_records_than_maximum_is_no_op(self): | ||||||
|  |         """Test no passwords deleted if fewer passwords exist than limit""" | ||||||
|  |  | ||||||
|  |         UserPasswordHistory.create_for_user(self.user, "hunter2") | ||||||
|  |  | ||||||
|  |         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=2) | ||||||
|  |         PolicyBinding.objects.create( | ||||||
|  |             target=self.pbm, | ||||||
|  |             policy=policy, | ||||||
|  |             enabled=True, | ||||||
|  |             order=0, | ||||||
|  |         ) | ||||||
|  |         trim_password_histories.delay() | ||||||
|  |         self.assertTrue(UserPasswordHistory.objects.filter(user=self.user).exists()) | ||||||
							
								
								
									
										7
									
								
								authentik/enterprise/policies/unique_password/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								authentik/enterprise/policies/unique_password/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | """API URLs""" | ||||||
|  |  | ||||||
|  | from authentik.enterprise.policies.unique_password.api import UniquePasswordPolicyViewSet | ||||||
|  |  | ||||||
|  | api_urlpatterns = [ | ||||||
|  |     ("policies/unique_password", UniquePasswordPolicyViewSet), | ||||||
|  | ] | ||||||
| @ -14,6 +14,7 @@ CELERY_BEAT_SCHEDULE = { | |||||||
|  |  | ||||||
| TENANT_APPS = [ | TENANT_APPS = [ | ||||||
|     "authentik.enterprise.audit", |     "authentik.enterprise.audit", | ||||||
|  |     "authentik.enterprise.policies.unique_password", | ||||||
|     "authentik.enterprise.providers.google_workspace", |     "authentik.enterprise.providers.google_workspace", | ||||||
|     "authentik.enterprise.providers.microsoft_entra", |     "authentik.enterprise.providers.microsoft_entra", | ||||||
|     "authentik.enterprise.providers.ssf", |     "authentik.enterprise.providers.ssf", | ||||||
|  | |||||||
| @ -2,52 +2,53 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load authentik_core %} | {% load authentik_core %} | ||||||
|  |  | ||||||
| <!doctype html> | <!DOCTYPE html> | ||||||
|  |  | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
|   <head> |     <head> | ||||||
|     <meta charset="UTF-8" /> |         <meta charset="UTF-8"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> |         <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> | ||||||
|     <title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title> |         <title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title> | ||||||
|     <link rel="icon" href="{{ brand.branding_favicon_url }}" /> |         <link rel="icon" href="{{ brand.branding_favicon_url }}"> | ||||||
|     <link rel="shortcut icon" href="{{ brand.branding_favicon_url }}" /> |         <link rel="shortcut icon" href="{{ brand.branding_favicon_url }}"> | ||||||
|     {% block head_before %} |         {% block head_before %} | ||||||
|     {% endblock %} |         {% endblock %} | ||||||
|     <link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}" /> |         <link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}"> | ||||||
|     <meta name="sentry-trace" content="{{ sentry_trace }}" /> |         <meta name="sentry-trace" content="{{ sentry_trace }}" /> | ||||||
|     {% include "base/header_js.html" %} |         {% include "base/header_js.html" %} | ||||||
|     <style> |         <style> | ||||||
|       html, |           html, | ||||||
|       body { |           body { | ||||||
|         height: 100%; |             height: 100%; | ||||||
|       } |           } | ||||||
|       body { |           body { | ||||||
|         background-image: url("{{ flow.background_url }}"); |             background-image: url("{{ flow.background_url }}"); | ||||||
|         background-repeat: no-repeat; |             background-repeat: no-repeat; | ||||||
|         background-size: cover; |             background-size: cover; | ||||||
|       } |           } | ||||||
|       .card { |           .card { | ||||||
|         padding: 3rem; |             padding: 3rem; | ||||||
|       } |           } | ||||||
|  |  | ||||||
|       .form-signin { |           .form-signin { | ||||||
|         max-width: 330px; |             max-width: 330px; | ||||||
|         padding: 1rem; |             padding: 1rem; | ||||||
|       } |           } | ||||||
|  |  | ||||||
|       .form-signin .form-floating:focus-within { |           .form-signin .form-floating:focus-within { | ||||||
|         z-index: 2; |             z-index: 2; | ||||||
|       } |           } | ||||||
|       .brand-icon { |           .brand-icon { | ||||||
|         max-width: 100%; |             max-width: 100%; | ||||||
|       } |           } | ||||||
|     </style> |         </style> | ||||||
|   </head> |     </head> | ||||||
|   <body class="d-flex align-items-center py-4 bg-body-tertiary"> |     <body class="d-flex align-items-center py-4 bg-body-tertiary"> | ||||||
|     <div class="card m-auto"> |       <div class="card m-auto"> | ||||||
|       <main class="form-signin w-100 m-auto" id="flow-sfe-container"></main> |         <main class="form-signin w-100 m-auto" id="flow-sfe-container"> | ||||||
|       <span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span> |         </main> | ||||||
|     </div> |         <span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span> | ||||||
|     <script src="{% static 'dist/sfe/index.js' %}"></script> |       </div> | ||||||
|   </body> |       <script src="{% static 'dist/sfe/index.js' %}"></script> | ||||||
|  |     </body> | ||||||
| </html> | </html> | ||||||
|  | |||||||
| @ -1,40 +1,34 @@ | |||||||
| {% extends "base/skeleton.html" %} | {% extends "base/skeleton.html" %} | ||||||
|  |  | ||||||
| {% load static %} | {% load static %} | ||||||
| {% load authentik_core %} | {% load authentik_core %} | ||||||
|  |  | ||||||
| {% block head_before %} | {% block head_before %} | ||||||
| {{ block.super }} | {{ block.super }} | ||||||
|  |  | ||||||
| <link rel="prefetch" href="{{ flow.background_url }}" /> | <link rel="prefetch" href="{{ flow.background_url }}" /> | ||||||
|  |  | ||||||
| {% if flow.compatibility_mode and not inspector %} | {% if flow.compatibility_mode and not inspector %} | ||||||
| <script> | <script>ShadyDOM = { force: !navigator.webdriver };</script> | ||||||
|   ShadyDOM = { force: !navigator.webdriver }; |  | ||||||
| </script> |  | ||||||
| {% endif %} | {% endif %} | ||||||
|  |  | ||||||
| {% include "base/header_js.html" %} | {% include "base/header_js.html" %} | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|   window.authentik.flow = { | window.authentik.flow = { | ||||||
|     layout: "{{ flow.layout }}", |     "layout": "{{ flow.layout }}", | ||||||
|   }; | }; | ||||||
| </script> | </script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block head %} | {% block head %} | ||||||
| <script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script> | <script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script> | ||||||
|  | <style> | ||||||
| <style data-test-id="flow-root-styles"> | :root { | ||||||
|   :root { |  | ||||||
|     --ak-flow-background: url("{{ flow.background_url }}"); |     --ak-flow-background: url("{{ flow.background_url }}"); | ||||||
|   } | } | ||||||
| </style> | </style> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block body %} | {% block body %} | ||||||
| <ak-message-container></ak-message-container> | <ak-message-container></ak-message-container> | ||||||
| <ak-flow-executor flowSlug="{{ flow.slug }}"> | <ak-flow-executor flowSlug="{{ flow.slug }}"> | ||||||
|   <ak-loading></ak-loading> |     <ak-loading></ak-loading> | ||||||
| </ak-flow-executor> | </ak-flow-executor> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | |||||||
| @ -48,6 +48,7 @@ class TestFlowInspector(APITestCase): | |||||||
|                 "allow_show_password": False, |                 "allow_show_password": False, | ||||||
|                 "captcha_stage": None, |                 "captcha_stage": None, | ||||||
|                 "component": "ak-stage-identification", |                 "component": "ak-stage-identification", | ||||||
|  |                 "enable_remember_me": False, | ||||||
|                 "flow_info": { |                 "flow_info": { | ||||||
|                     "background": "/static/dist/assets/images/flow_background.jpg", |                     "background": "/static/dist/assets/images/flow_background.jpg", | ||||||
|                     "cancel_url": reverse("authentik_flows:cancel"), |                     "cancel_url": reverse("authentik_flows:cancel"), | ||||||
|  | |||||||
| @ -69,7 +69,6 @@ SESSION_KEY_APPLICATION_PRE = "authentik/flows/application_pre" | |||||||
| SESSION_KEY_GET = "authentik/flows/get" | SESSION_KEY_GET = "authentik/flows/get" | ||||||
| SESSION_KEY_POST = "authentik/flows/post" | SESSION_KEY_POST = "authentik/flows/post" | ||||||
| SESSION_KEY_HISTORY = "authentik/flows/history" | SESSION_KEY_HISTORY = "authentik/flows/history" | ||||||
| SESSION_KEY_AUTH_STARTED = "authentik/flows/auth_started" |  | ||||||
| QS_KEY_TOKEN = "flow_token"  # nosec | QS_KEY_TOKEN = "flow_token"  # nosec | ||||||
| QS_QUERY = "query" | QS_QUERY = "query" | ||||||
|  |  | ||||||
| @ -454,7 +453,6 @@ class FlowExecutorView(APIView): | |||||||
|             SESSION_KEY_APPLICATION_PRE, |             SESSION_KEY_APPLICATION_PRE, | ||||||
|             SESSION_KEY_PLAN, |             SESSION_KEY_PLAN, | ||||||
|             SESSION_KEY_GET, |             SESSION_KEY_GET, | ||||||
|             SESSION_KEY_AUTH_STARTED, |  | ||||||
|             # We might need the initial POST payloads for later requests |             # We might need the initial POST payloads for later requests | ||||||
|             # SESSION_KEY_POST, |             # SESSION_KEY_POST, | ||||||
|             # We don't delete the history on purpose, as a user might |             # We don't delete the history on purpose, as a user might | ||||||
|  | |||||||
| @ -6,22 +6,14 @@ from django.shortcuts import get_object_or_404 | |||||||
| from ua_parser.user_agent_parser import Parse | from ua_parser.user_agent_parser import Parse | ||||||
|  |  | ||||||
| from authentik.core.views.interface import InterfaceView | from authentik.core.views.interface import InterfaceView | ||||||
| from authentik.flows.models import Flow, FlowDesignation | from authentik.flows.models import Flow | ||||||
| from authentik.flows.views.executor import SESSION_KEY_AUTH_STARTED |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowInterfaceView(InterfaceView): | class FlowInterfaceView(InterfaceView): | ||||||
|     """Flow interface""" |     """Flow interface""" | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs: Any) -> dict[str, Any]: |     def get_context_data(self, **kwargs: Any) -> dict[str, Any]: | ||||||
|         flow = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug")) |         kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug")) | ||||||
|         kwargs["flow"] = flow |  | ||||||
|         if ( |  | ||||||
|             not self.request.user.is_authenticated |  | ||||||
|             and flow.designation == FlowDesignation.AUTHENTICATION |  | ||||||
|         ): |  | ||||||
|             self.request.session[SESSION_KEY_AUTH_STARTED] = True |  | ||||||
|             self.request.session.save() |  | ||||||
|         kwargs["inspector"] = "inspector" in self.request.GET |         kwargs["inspector"] = "inspector" in self.request.GET | ||||||
|         return super().get_context_data(**kwargs) |         return super().get_context_data(**kwargs) | ||||||
|  |  | ||||||
|  | |||||||
| @ -74,6 +74,8 @@ class OutpostConfig: | |||||||
|     kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict) |     kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict) | ||||||
|     kubernetes_ingress_secret_name: str = field(default="authentik-outpost-tls") |     kubernetes_ingress_secret_name: str = field(default="authentik-outpost-tls") | ||||||
|     kubernetes_ingress_class_name: str | None = field(default=None) |     kubernetes_ingress_class_name: str | None = field(default=None) | ||||||
|  |     kubernetes_httproute_annotations: dict[str, str] = field(default_factory=dict) | ||||||
|  |     kubernetes_httproute_parent_refs: list[dict[str, str]] = field(default_factory=list) | ||||||
|     kubernetes_service_type: str = field(default="ClusterIP") |     kubernetes_service_type: str = field(default="ClusterIP") | ||||||
|     kubernetes_disabled_components: list[str] = field(default_factory=list) |     kubernetes_disabled_components: list[str] = field(default_factory=list) | ||||||
|     kubernetes_image_pull_secrets: list[str] = field(default_factory=list) |     kubernetes_image_pull_secrets: list[str] = field(default_factory=list) | ||||||
|  | |||||||
| @ -1,4 +1,8 @@ | |||||||
| """authentik policies app config""" | """Authentik policies app config | ||||||
|  |  | ||||||
|  | Every system policy should be its own Django app under the `policies` app. | ||||||
|  | For example: The 'dummy' policy is available at `authentik.policies.dummy`. | ||||||
|  | """ | ||||||
|  |  | ||||||
| from prometheus_client import Gauge, Histogram | from prometheus_client import Gauge, Histogram | ||||||
|  |  | ||||||
| @ -35,4 +39,3 @@ class AuthentikPoliciesConfig(ManagedAppConfig): | |||||||
|     label = "authentik_policies" |     label = "authentik_policies" | ||||||
|     verbose_name = "authentik Policies" |     verbose_name = "authentik Policies" | ||||||
|     default = True |     default = True | ||||||
|     mountpoint = "policy/" |  | ||||||
|  | |||||||
| @ -52,6 +52,13 @@ class PolicyBindingModel(models.Model): | |||||||
|         return ["policy", "user", "group"] |         return ["policy", "user", "group"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BoundPolicyQuerySet(models.QuerySet): | ||||||
|  |     """QuerySet for filtering enabled bindings for a Policy type""" | ||||||
|  |  | ||||||
|  |     def for_policy(self, policy: "Policy"): | ||||||
|  |         return self.filter(policy__in=policy._default_manager.all()).filter(enabled=True) | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyBinding(SerializerModel): | class PolicyBinding(SerializerModel): | ||||||
|     """Relationship between a Policy and a PolicyBindingModel.""" |     """Relationship between a Policy and a PolicyBindingModel.""" | ||||||
|  |  | ||||||
| @ -148,6 +155,9 @@ class PolicyBinding(SerializerModel): | |||||||
|             return f"Binding - #{self.order} to {suffix}" |             return f"Binding - #{self.order} to {suffix}" | ||||||
|         return "" |         return "" | ||||||
|  |  | ||||||
|  |     objects = models.Manager() | ||||||
|  |     in_use = BoundPolicyQuerySet.as_manager() | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         verbose_name = _("Policy Binding") |         verbose_name = _("Policy Binding") | ||||||
|         verbose_name_plural = _("Policy Bindings") |         verbose_name_plural = _("Policy Bindings") | ||||||
|  | |||||||
| @ -2,4 +2,6 @@ | |||||||
|  |  | ||||||
| from authentik.policies.password.api import PasswordPolicyViewSet | from authentik.policies.password.api import PasswordPolicyViewSet | ||||||
|  |  | ||||||
| api_urlpatterns = [("policies/password", PasswordPolicyViewSet)] | api_urlpatterns = [ | ||||||
|  |     ("policies/password", PasswordPolicyViewSet), | ||||||
|  | ] | ||||||
|  | |||||||
| @ -1,89 +0,0 @@ | |||||||
| {% extends 'login/base_full.html' %} |  | ||||||
|  |  | ||||||
| {% load static %} |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block head %} |  | ||||||
| {{ block.super }} |  | ||||||
| <script> |  | ||||||
|   let redirecting = false; |  | ||||||
|   const checkAuth = async () => { |  | ||||||
|     if (redirecting) return true; |  | ||||||
|     const url = "{{ check_auth_url }}"; |  | ||||||
|     console.debug("authentik/policies/buffer: Checking authentication..."); |  | ||||||
|     try { |  | ||||||
|       const result = await fetch(url, { |  | ||||||
|         method: "HEAD", |  | ||||||
|       }); |  | ||||||
|       if (result.status >= 400) { |  | ||||||
|         return false |  | ||||||
|       } |  | ||||||
|       console.debug("authentik/policies/buffer: Continuing"); |  | ||||||
|       redirecting = true; |  | ||||||
|       if ("{{ auth_req_method }}" === "post") { |  | ||||||
|         document.querySelector("form").submit(); |  | ||||||
|       } else { |  | ||||||
|         window.location.assign("{{ continue_url|escapejs }}"); |  | ||||||
|       } |  | ||||||
|     } catch { |  | ||||||
|       return false; |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
|   let timeout = 100; |  | ||||||
|   let offset = 20; |  | ||||||
|   let attempt = 0; |  | ||||||
|   const main = async () => { |  | ||||||
|     attempt += 1; |  | ||||||
|     await checkAuth(); |  | ||||||
|     console.debug(`authentik/policies/buffer: Waiting ${timeout}ms...`); |  | ||||||
|     setTimeout(main, timeout); |  | ||||||
|     timeout += (offset * attempt); |  | ||||||
|     if (timeout >= 2000) { |  | ||||||
|       timeout = 2000; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   document.addEventListener("visibilitychange", async () => { |  | ||||||
|     if (document.hidden) return; |  | ||||||
|     console.debug("authentik/policies/buffer: Checking authentication on tab activate..."); |  | ||||||
|     await checkAuth(); |  | ||||||
|   }); |  | ||||||
|   main(); |  | ||||||
| </script> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block title %} |  | ||||||
| {% trans 'Waiting for authentication...' %} - {{ brand.branding_title }} |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block card_title %} |  | ||||||
| {% trans 'Waiting for authentication...' %} |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block card %} |  | ||||||
| <form class="pf-c-form" method="{{ auth_req_method }}" action="{{ continue_url }}"> |  | ||||||
|   {% if auth_req_method == "post" %} |  | ||||||
|     {% for key, value in auth_req_body.items %} |  | ||||||
|       <input type="hidden" name="{{ key }}" value="{{ value }}" /> |  | ||||||
|     {% endfor %} |  | ||||||
|   {% endif %} |  | ||||||
|   <div class="pf-c-empty-state"> |  | ||||||
|     <div class="pf-c-empty-state__content"> |  | ||||||
|       <div class="pf-c-empty-state__icon"> |  | ||||||
|         <span class="pf-c-spinner pf-m-xl" role="progressbar"> |  | ||||||
|           <span class="pf-c-spinner__clipper"></span> |  | ||||||
|           <span class="pf-c-spinner__lead-ball"></span> |  | ||||||
|           <span class="pf-c-spinner__tail-ball"></span> |  | ||||||
|         </span> |  | ||||||
|       </div> |  | ||||||
|       <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|         {% trans "You're already authenticating in another tab. This page will refresh once authentication is completed." %} |  | ||||||
|       </h1> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
|   <div class="pf-c-form__group pf-m-action"> |  | ||||||
|     <a href="{{ auth_req_url }}" class="pf-c-button pf-m-primary pf-m-block"> |  | ||||||
|       {% trans "Authenticate in this tab" %} |  | ||||||
|     </a> |  | ||||||
|   </div> |  | ||||||
| </form> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,121 +0,0 @@ | |||||||
| from django.contrib.auth.models import AnonymousUser |  | ||||||
| from django.contrib.sessions.middleware import SessionMiddleware |  | ||||||
| from django.http import HttpResponse |  | ||||||
| from django.test import RequestFactory, TestCase |  | ||||||
| from django.urls import reverse |  | ||||||
|  |  | ||||||
| from authentik.core.models import Application, Provider |  | ||||||
| from authentik.core.tests.utils import create_test_flow, create_test_user |  | ||||||
| from authentik.flows.models import FlowDesignation |  | ||||||
| from authentik.flows.planner import FlowPlan |  | ||||||
| from authentik.flows.views.executor import SESSION_KEY_PLAN |  | ||||||
| from authentik.lib.generators import generate_id |  | ||||||
| from authentik.lib.tests.utils import dummy_get_response |  | ||||||
| from authentik.policies.views import ( |  | ||||||
|     QS_BUFFER_ID, |  | ||||||
|     SESSION_KEY_BUFFER, |  | ||||||
|     BufferedPolicyAccessView, |  | ||||||
|     BufferView, |  | ||||||
|     PolicyAccessView, |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestPolicyViews(TestCase): |  | ||||||
|     """Test PolicyAccessView""" |  | ||||||
|  |  | ||||||
|     def setUp(self): |  | ||||||
|         super().setUp() |  | ||||||
|         self.factory = RequestFactory() |  | ||||||
|         self.user = create_test_user() |  | ||||||
|  |  | ||||||
|     def test_pav(self): |  | ||||||
|         """Test simple policy access view""" |  | ||||||
|         provider = Provider.objects.create( |  | ||||||
|             name=generate_id(), |  | ||||||
|         ) |  | ||||||
|         app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider) |  | ||||||
|  |  | ||||||
|         class TestView(PolicyAccessView): |  | ||||||
|             def resolve_provider_application(self): |  | ||||||
|                 self.provider = provider |  | ||||||
|                 self.application = app |  | ||||||
|  |  | ||||||
|             def get(self, *args, **kwargs): |  | ||||||
|                 return HttpResponse("foo") |  | ||||||
|  |  | ||||||
|         req = self.factory.get("/") |  | ||||||
|         req.user = self.user |  | ||||||
|         res = TestView.as_view()(req) |  | ||||||
|         self.assertEqual(res.status_code, 200) |  | ||||||
|         self.assertEqual(res.content, b"foo") |  | ||||||
|  |  | ||||||
|     def test_pav_buffer(self): |  | ||||||
|         """Test simple policy access view""" |  | ||||||
|         provider = Provider.objects.create( |  | ||||||
|             name=generate_id(), |  | ||||||
|         ) |  | ||||||
|         app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider) |  | ||||||
|         flow = create_test_flow(FlowDesignation.AUTHENTICATION) |  | ||||||
|  |  | ||||||
|         class TestView(BufferedPolicyAccessView): |  | ||||||
|             def resolve_provider_application(self): |  | ||||||
|                 self.provider = provider |  | ||||||
|                 self.application = app |  | ||||||
|  |  | ||||||
|             def get(self, *args, **kwargs): |  | ||||||
|                 return HttpResponse("foo") |  | ||||||
|  |  | ||||||
|         req = self.factory.get("/") |  | ||||||
|         req.user = AnonymousUser() |  | ||||||
|         middleware = SessionMiddleware(dummy_get_response) |  | ||||||
|         middleware.process_request(req) |  | ||||||
|         req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk) |  | ||||||
|         req.session.save() |  | ||||||
|         res = TestView.as_view()(req) |  | ||||||
|         self.assertEqual(res.status_code, 302) |  | ||||||
|         self.assertTrue(res.url.startswith(reverse("authentik_policies:buffer"))) |  | ||||||
|  |  | ||||||
|     def test_pav_buffer_skip(self): |  | ||||||
|         """Test simple policy access view (skip buffer)""" |  | ||||||
|         provider = Provider.objects.create( |  | ||||||
|             name=generate_id(), |  | ||||||
|         ) |  | ||||||
|         app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider) |  | ||||||
|         flow = create_test_flow(FlowDesignation.AUTHENTICATION) |  | ||||||
|  |  | ||||||
|         class TestView(BufferedPolicyAccessView): |  | ||||||
|             def resolve_provider_application(self): |  | ||||||
|                 self.provider = provider |  | ||||||
|                 self.application = app |  | ||||||
|  |  | ||||||
|             def get(self, *args, **kwargs): |  | ||||||
|                 return HttpResponse("foo") |  | ||||||
|  |  | ||||||
|         req = self.factory.get("/?skip_buffer=true") |  | ||||||
|         req.user = AnonymousUser() |  | ||||||
|         middleware = SessionMiddleware(dummy_get_response) |  | ||||||
|         middleware.process_request(req) |  | ||||||
|         req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk) |  | ||||||
|         req.session.save() |  | ||||||
|         res = TestView.as_view()(req) |  | ||||||
|         self.assertEqual(res.status_code, 302) |  | ||||||
|         self.assertTrue(res.url.startswith(reverse("authentik_flows:default-authentication"))) |  | ||||||
|  |  | ||||||
|     def test_buffer(self): |  | ||||||
|         """Test buffer view""" |  | ||||||
|         uid = generate_id() |  | ||||||
|         req = self.factory.get(f"/?{QS_BUFFER_ID}={uid}") |  | ||||||
|         req.user = AnonymousUser() |  | ||||||
|         middleware = SessionMiddleware(dummy_get_response) |  | ||||||
|         middleware.process_request(req) |  | ||||||
|         ts = generate_id() |  | ||||||
|         req.session[SESSION_KEY_BUFFER % uid] = { |  | ||||||
|             "method": "get", |  | ||||||
|             "body": {}, |  | ||||||
|             "url": f"/{ts}", |  | ||||||
|         } |  | ||||||
|         req.session.save() |  | ||||||
|  |  | ||||||
|         res = BufferView.as_view()(req) |  | ||||||
|         self.assertEqual(res.status_code, 200) |  | ||||||
|         self.assertIn(ts, res.render().content.decode()) |  | ||||||
| @ -1,14 +1,7 @@ | |||||||
| """API URLs""" | """API URLs""" | ||||||
|  |  | ||||||
| from django.urls import path |  | ||||||
|  |  | ||||||
| from authentik.policies.api.bindings import PolicyBindingViewSet | from authentik.policies.api.bindings import PolicyBindingViewSet | ||||||
| from authentik.policies.api.policies import PolicyViewSet | from authentik.policies.api.policies import PolicyViewSet | ||||||
| from authentik.policies.views import BufferView |  | ||||||
|  |  | ||||||
| urlpatterns = [ |  | ||||||
|     path("buffer", BufferView.as_view(), name="buffer"), |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| api_urlpatterns = [ | api_urlpatterns = [ | ||||||
|     ("policies/all", PolicyViewSet), |     ("policies/all", PolicyViewSet), | ||||||
|  | |||||||
| @ -1,37 +1,23 @@ | |||||||
| """authentik access helper classes""" | """authentik access helper classes""" | ||||||
|  |  | ||||||
| from typing import Any | from typing import Any | ||||||
| from uuid import uuid4 |  | ||||||
|  |  | ||||||
| from django.contrib import messages | from django.contrib import messages | ||||||
| from django.contrib.auth.mixins import AccessMixin | from django.contrib.auth.mixins import AccessMixin | ||||||
| from django.contrib.auth.views import redirect_to_login | from django.contrib.auth.views import redirect_to_login | ||||||
| from django.http import HttpRequest, HttpResponse, QueryDict | from django.http import HttpRequest, HttpResponse | ||||||
| from django.shortcuts import redirect |  | ||||||
| from django.urls import reverse |  | ||||||
| from django.utils.http import urlencode |  | ||||||
| from django.utils.translation import gettext as _ | from django.utils.translation import gettext as _ | ||||||
| from django.views.generic.base import TemplateView, View | from django.views.generic.base import View | ||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
| from authentik.core.models import Application, Provider, User | from authentik.core.models import Application, Provider, User | ||||||
| from authentik.flows.models import Flow, FlowDesignation | from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_POST | ||||||
| from authentik.flows.planner import FlowPlan |  | ||||||
| from authentik.flows.views.executor import ( |  | ||||||
|     SESSION_KEY_APPLICATION_PRE, |  | ||||||
|     SESSION_KEY_AUTH_STARTED, |  | ||||||
|     SESSION_KEY_PLAN, |  | ||||||
|     SESSION_KEY_POST, |  | ||||||
| ) |  | ||||||
| from authentik.lib.sentry import SentryIgnoredException | from authentik.lib.sentry import SentryIgnoredException | ||||||
| from authentik.policies.denied import AccessDeniedResponse | from authentik.policies.denied import AccessDeniedResponse | ||||||
| from authentik.policies.engine import PolicyEngine | from authentik.policies.engine import PolicyEngine | ||||||
| from authentik.policies.types import PolicyRequest, PolicyResult | from authentik.policies.types import PolicyRequest, PolicyResult | ||||||
|  |  | ||||||
| LOGGER = get_logger() | LOGGER = get_logger() | ||||||
| QS_BUFFER_ID = "af_bf_id" |  | ||||||
| QS_SKIP_BUFFER = "skip_buffer" |  | ||||||
| SESSION_KEY_BUFFER = "authentik/policies/pav_buffer/%s" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RequestValidationError(SentryIgnoredException): | class RequestValidationError(SentryIgnoredException): | ||||||
| @ -139,65 +125,3 @@ class PolicyAccessView(AccessMixin, View): | |||||||
|             for message in result.messages: |             for message in result.messages: | ||||||
|                 messages.error(self.request, _(message)) |                 messages.error(self.request, _(message)) | ||||||
|         return result |         return result | ||||||
|  |  | ||||||
|  |  | ||||||
| def url_with_qs(url: str, **kwargs): |  | ||||||
|     """Update/set querystring of `url` with the parameters in `kwargs`. Original query string |  | ||||||
|     parameters are retained""" |  | ||||||
|     if "?" not in url: |  | ||||||
|         return url + f"?{urlencode(kwargs)}" |  | ||||||
|     url, _, qs = url.partition("?") |  | ||||||
|     qs = QueryDict(qs, mutable=True) |  | ||||||
|     qs.update(kwargs) |  | ||||||
|     return url + f"?{urlencode(qs.items())}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BufferView(TemplateView): |  | ||||||
|     """Buffer view""" |  | ||||||
|  |  | ||||||
|     template_name = "policies/buffer.html" |  | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs): |  | ||||||
|         buf_id = self.request.GET.get(QS_BUFFER_ID) |  | ||||||
|         buffer: dict = self.request.session.get(SESSION_KEY_BUFFER % buf_id) |  | ||||||
|         kwargs["auth_req_method"] = buffer["method"] |  | ||||||
|         kwargs["auth_req_body"] = buffer["body"] |  | ||||||
|         kwargs["auth_req_url"] = url_with_qs(buffer["url"], **{QS_SKIP_BUFFER: True}) |  | ||||||
|         kwargs["check_auth_url"] = reverse("authentik_api:user-me") |  | ||||||
|         kwargs["continue_url"] = url_with_qs(buffer["url"], **{QS_BUFFER_ID: buf_id}) |  | ||||||
|         return super().get_context_data(**kwargs) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BufferedPolicyAccessView(PolicyAccessView): |  | ||||||
|     """PolicyAccessView which buffers access requests in case the user is not logged in""" |  | ||||||
|  |  | ||||||
|     def handle_no_permission(self): |  | ||||||
|         plan: FlowPlan | None = self.request.session.get(SESSION_KEY_PLAN) |  | ||||||
|         authenticating = self.request.session.get(SESSION_KEY_AUTH_STARTED) |  | ||||||
|         if plan: |  | ||||||
|             flow = Flow.objects.filter(pk=plan.flow_pk).first() |  | ||||||
|             if not flow or flow.designation != FlowDesignation.AUTHENTICATION: |  | ||||||
|                 LOGGER.debug("Not buffering request, no flow or flow not for authentication") |  | ||||||
|                 return super().handle_no_permission() |  | ||||||
|         if not plan and authenticating is None: |  | ||||||
|             LOGGER.debug("Not buffering request, no flow plan active") |  | ||||||
|             return super().handle_no_permission() |  | ||||||
|         if self.request.GET.get(QS_SKIP_BUFFER): |  | ||||||
|             LOGGER.debug("Not buffering request, explicit skip") |  | ||||||
|             return super().handle_no_permission() |  | ||||||
|         buffer_id = str(uuid4()) |  | ||||||
|         LOGGER.debug("Buffering access request", bf_id=buffer_id) |  | ||||||
|         self.request.session[SESSION_KEY_BUFFER % buffer_id] = { |  | ||||||
|             "body": self.request.POST, |  | ||||||
|             "url": self.request.build_absolute_uri(self.request.get_full_path()), |  | ||||||
|             "method": self.request.method.lower(), |  | ||||||
|         } |  | ||||||
|         return redirect( |  | ||||||
|             url_with_qs(reverse("authentik_policies:buffer"), **{QS_BUFFER_ID: buffer_id}) |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def dispatch(self, request, *args, **kwargs): |  | ||||||
|         response = super().dispatch(request, *args, **kwargs) |  | ||||||
|         if QS_BUFFER_ID in self.request.GET: |  | ||||||
|             self.request.session.pop(SESSION_KEY_BUFFER % self.request.GET[QS_BUFFER_ID], None) |  | ||||||
|         return response |  | ||||||
|  | |||||||
| @ -30,7 +30,7 @@ from authentik.flows.stage import StageView | |||||||
| from authentik.lib.utils.time import timedelta_from_string | from authentik.lib.utils.time import timedelta_from_string | ||||||
| from authentik.lib.views import bad_request_message | from authentik.lib.views import bad_request_message | ||||||
| from authentik.policies.types import PolicyRequest | from authentik.policies.types import PolicyRequest | ||||||
| from authentik.policies.views import BufferedPolicyAccessView, RequestValidationError | from authentik.policies.views import PolicyAccessView, RequestValidationError | ||||||
| from authentik.providers.oauth2.constants import ( | from authentik.providers.oauth2.constants import ( | ||||||
|     PKCE_METHOD_PLAIN, |     PKCE_METHOD_PLAIN, | ||||||
|     PKCE_METHOD_S256, |     PKCE_METHOD_S256, | ||||||
| @ -326,7 +326,7 @@ class OAuthAuthorizationParams: | |||||||
|         return code |         return code | ||||||
|  |  | ||||||
|  |  | ||||||
| class AuthorizationFlowInitView(BufferedPolicyAccessView): | class AuthorizationFlowInitView(PolicyAccessView): | ||||||
|     """OAuth2 Flow initializer, checks access to application and starts flow""" |     """OAuth2 Flow initializer, checks access to application and starts flow""" | ||||||
|  |  | ||||||
|     params: OAuthAuthorizationParams |     params: OAuthAuthorizationParams | ||||||
|  | |||||||
							
								
								
									
										234
									
								
								authentik/providers/proxy/controllers/k8s/httproute.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								authentik/providers/proxy/controllers/k8s/httproute.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,234 @@ | |||||||
|  | from dataclasses import asdict, dataclass, field | ||||||
|  | from typing import TYPE_CHECKING | ||||||
|  | from urllib.parse import urlparse | ||||||
|  |  | ||||||
|  | from dacite.core import from_dict | ||||||
|  | from kubernetes.client import ApiextensionsV1Api, CustomObjectsApi, V1ObjectMeta | ||||||
|  |  | ||||||
|  | from authentik.outposts.controllers.base import FIELD_MANAGER | ||||||
|  | from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler | ||||||
|  | from authentik.outposts.controllers.k8s.triggers import NeedsUpdate | ||||||
|  | from authentik.outposts.controllers.kubernetes import KubernetesController | ||||||
|  | from authentik.providers.proxy.models import ProxyMode, ProxyProvider | ||||||
|  |  | ||||||
|  | if TYPE_CHECKING: | ||||||
|  |     from authentik.outposts.controllers.kubernetes import KubernetesController | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(slots=True) | ||||||
|  | class RouteBackendRef: | ||||||
|  |     name: str | ||||||
|  |     port: int | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(slots=True) | ||||||
|  | class RouteSpecParentRefs: | ||||||
|  |     name: str | ||||||
|  |     sectionName: str | None = None | ||||||
|  |     port: int | None = None | ||||||
|  |     namespace: str | None = None | ||||||
|  |     kind: str = "Gateway" | ||||||
|  |     group: str = "gateway.networking.k8s.io" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(slots=True) | ||||||
|  | class HTTPRouteSpecRuleMatchPath: | ||||||
|  |     type: str | ||||||
|  |     value: str | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(slots=True) | ||||||
|  | class HTTPRouteSpecRuleMatchHeader: | ||||||
|  |     name: str | ||||||
|  |     value: str | ||||||
|  |     type: str = "Exact" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(slots=True) | ||||||
|  | class HTTPRouteSpecRuleMatch: | ||||||
|  |     path: HTTPRouteSpecRuleMatchPath | ||||||
|  |     headers: list[HTTPRouteSpecRuleMatchHeader] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(slots=True) | ||||||
|  | class HTTPRouteSpecRule: | ||||||
|  |     backendRefs: list[RouteBackendRef] | ||||||
|  |     matches: list[HTTPRouteSpecRuleMatch] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(slots=True) | ||||||
|  | class HTTPRouteSpec: | ||||||
|  |     parentRefs: list[RouteSpecParentRefs] | ||||||
|  |     hostnames: list[str] | ||||||
|  |     rules: list[HTTPRouteSpecRule] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(slots=True) | ||||||
|  | class HTTPRouteMetadata: | ||||||
|  |     name: str | ||||||
|  |     namespace: str | ||||||
|  |     annotations: dict = field(default_factory=dict) | ||||||
|  |     labels: dict = field(default_factory=dict) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(slots=True) | ||||||
|  | class HTTPRoute: | ||||||
|  |     apiVersion: str | ||||||
|  |     kind: str | ||||||
|  |     metadata: HTTPRouteMetadata | ||||||
|  |     spec: HTTPRouteSpec | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class HTTPRouteReconciler(KubernetesObjectReconciler): | ||||||
|  |     """Kubernetes Gateway API HTTPRoute Reconciler""" | ||||||
|  |  | ||||||
|  |     def __init__(self, controller: "KubernetesController") -> None: | ||||||
|  |         super().__init__(controller) | ||||||
|  |         self.api_ex = ApiextensionsV1Api(controller.client) | ||||||
|  |         self.api = CustomObjectsApi(controller.client) | ||||||
|  |         self.crd_group = "gateway.networking.k8s.io" | ||||||
|  |         self.crd_version = "v1" | ||||||
|  |         self.crd_plural = "httproutes" | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def reconciler_name() -> str: | ||||||
|  |         return "httproute" | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def noop(self) -> bool: | ||||||
|  |         if not self.crd_exists(): | ||||||
|  |             self.logger.debug("CRD doesn't exist") | ||||||
|  |             return True | ||||||
|  |         if not self.controller.outpost.config.kubernetes_httproute_parent_refs: | ||||||
|  |             self.logger.debug("HTTPRoute parentRefs not set.") | ||||||
|  |             return True | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  |     def crd_exists(self) -> bool: | ||||||
|  |         """Check if the Gateway API resources exists""" | ||||||
|  |         return bool( | ||||||
|  |             len( | ||||||
|  |                 self.api_ex.list_custom_resource_definition( | ||||||
|  |                     field_selector=f"metadata.name={self.crd_plural}.{self.crd_group}" | ||||||
|  |                 ).items | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def reconcile(self, current: HTTPRoute, reference: HTTPRoute): | ||||||
|  |         super().reconcile(current, reference) | ||||||
|  |         if current.metadata.annotations != reference.metadata.annotations: | ||||||
|  |             raise NeedsUpdate() | ||||||
|  |         if current.spec.parentRefs != reference.spec.parentRefs: | ||||||
|  |             raise NeedsUpdate() | ||||||
|  |         if current.spec.hostnames != reference.spec.hostnames: | ||||||
|  |             raise NeedsUpdate() | ||||||
|  |         if current.spec.rules != reference.spec.rules: | ||||||
|  |             raise NeedsUpdate() | ||||||
|  |  | ||||||
|  |     def get_object_meta(self, **kwargs) -> V1ObjectMeta: | ||||||
|  |         return super().get_object_meta( | ||||||
|  |             **kwargs, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def get_reference_object(self) -> HTTPRoute: | ||||||
|  |         hostnames = [] | ||||||
|  |         rules = [] | ||||||
|  |  | ||||||
|  |         for proxy_provider in ProxyProvider.objects.filter(outpost__in=[self.controller.outpost]): | ||||||
|  |             proxy_provider: ProxyProvider | ||||||
|  |             external_host_name = urlparse(proxy_provider.external_host) | ||||||
|  |             if proxy_provider.mode in [ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN]: | ||||||
|  |                 rule = HTTPRouteSpecRule( | ||||||
|  |                     backendRefs=[RouteBackendRef(name=self.name, port=9000)], | ||||||
|  |                     matches=[ | ||||||
|  |                         HTTPRouteSpecRuleMatch( | ||||||
|  |                             headers=[ | ||||||
|  |                                 HTTPRouteSpecRuleMatchHeader( | ||||||
|  |                                     name="Host", | ||||||
|  |                                     value=external_host_name.hostname, | ||||||
|  |                                 ) | ||||||
|  |                             ], | ||||||
|  |                             path=HTTPRouteSpecRuleMatchPath( | ||||||
|  |                                 type="PathPrefix", value="/outpost.goauthentik.io" | ||||||
|  |                             ), | ||||||
|  |                         ) | ||||||
|  |                     ], | ||||||
|  |                 ) | ||||||
|  |             else: | ||||||
|  |                 rule = HTTPRouteSpecRule( | ||||||
|  |                     backendRefs=[RouteBackendRef(name=self.name, port=9000)], | ||||||
|  |                     matches=[ | ||||||
|  |                         HTTPRouteSpecRuleMatch( | ||||||
|  |                             headers=[ | ||||||
|  |                                 HTTPRouteSpecRuleMatchHeader( | ||||||
|  |                                     name="Host", | ||||||
|  |                                     value=external_host_name.hostname, | ||||||
|  |                                 ) | ||||||
|  |                             ], | ||||||
|  |                             path=HTTPRouteSpecRuleMatchPath(type="PathPrefix", value="/"), | ||||||
|  |                         ) | ||||||
|  |                     ], | ||||||
|  |                 ) | ||||||
|  |             hostnames.append(external_host_name.hostname) | ||||||
|  |             rules.append(rule) | ||||||
|  |  | ||||||
|  |         return HTTPRoute( | ||||||
|  |             apiVersion=f"{self.crd_group}/{self.crd_version}", | ||||||
|  |             kind="HTTPRoute", | ||||||
|  |             metadata=HTTPRouteMetadata( | ||||||
|  |                 name=self.name, | ||||||
|  |                 namespace=self.namespace, | ||||||
|  |                 annotations=self.controller.outpost.config.kubernetes_httproute_annotations, | ||||||
|  |                 labels=self.get_object_meta().labels, | ||||||
|  |             ), | ||||||
|  |             spec=HTTPRouteSpec( | ||||||
|  |                 parentRefs=[ | ||||||
|  |                     from_dict(RouteSpecParentRefs, spec) | ||||||
|  |                     for spec in self.controller.outpost.config.kubernetes_httproute_parent_refs | ||||||
|  |                 ], | ||||||
|  |                 hostnames=hostnames, | ||||||
|  |                 rules=rules, | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def create(self, reference: HTTPRoute): | ||||||
|  |         return self.api.create_namespaced_custom_object( | ||||||
|  |             group=self.crd_group, | ||||||
|  |             version=self.crd_version, | ||||||
|  |             plural=self.crd_plural, | ||||||
|  |             namespace=self.namespace, | ||||||
|  |             body=asdict(reference), | ||||||
|  |             field_manager=FIELD_MANAGER, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def delete(self, reference: HTTPRoute): | ||||||
|  |         return self.api.delete_namespaced_custom_object( | ||||||
|  |             group=self.crd_group, | ||||||
|  |             version=self.crd_version, | ||||||
|  |             plural=self.crd_plural, | ||||||
|  |             namespace=self.namespace, | ||||||
|  |             name=self.name, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def retrieve(self) -> HTTPRoute: | ||||||
|  |         return from_dict( | ||||||
|  |             HTTPRoute, | ||||||
|  |             self.api.get_namespaced_custom_object( | ||||||
|  |                 group=self.crd_group, | ||||||
|  |                 version=self.crd_version, | ||||||
|  |                 plural=self.crd_plural, | ||||||
|  |                 namespace=self.namespace, | ||||||
|  |                 name=self.name, | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def update(self, current: HTTPRoute, reference: HTTPRoute): | ||||||
|  |         return self.api.patch_namespaced_custom_object( | ||||||
|  |             group=self.crd_group, | ||||||
|  |             version=self.crd_version, | ||||||
|  |             plural=self.crd_plural, | ||||||
|  |             namespace=self.namespace, | ||||||
|  |             name=self.name, | ||||||
|  |             body=asdict(reference), | ||||||
|  |             field_manager=FIELD_MANAGER, | ||||||
|  |         ) | ||||||
| @ -3,6 +3,7 @@ | |||||||
| from authentik.outposts.controllers.base import DeploymentPort | from authentik.outposts.controllers.base import DeploymentPort | ||||||
| from authentik.outposts.controllers.kubernetes import KubernetesController | from authentik.outposts.controllers.kubernetes import KubernetesController | ||||||
| from authentik.outposts.models import KubernetesServiceConnection, Outpost | from authentik.outposts.models import KubernetesServiceConnection, Outpost | ||||||
|  | from authentik.providers.proxy.controllers.k8s.httproute import HTTPRouteReconciler | ||||||
| from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler | from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler | ||||||
| from authentik.providers.proxy.controllers.k8s.traefik import TraefikMiddlewareReconciler | from authentik.providers.proxy.controllers.k8s.traefik import TraefikMiddlewareReconciler | ||||||
|  |  | ||||||
| @ -18,8 +19,10 @@ class ProxyKubernetesController(KubernetesController): | |||||||
|             DeploymentPort(9443, "https", "tcp"), |             DeploymentPort(9443, "https", "tcp"), | ||||||
|         ] |         ] | ||||||
|         self.reconcilers[IngressReconciler.reconciler_name()] = IngressReconciler |         self.reconcilers[IngressReconciler.reconciler_name()] = IngressReconciler | ||||||
|  |         self.reconcilers[HTTPRouteReconciler.reconciler_name()] = HTTPRouteReconciler | ||||||
|         self.reconcilers[TraefikMiddlewareReconciler.reconciler_name()] = ( |         self.reconcilers[TraefikMiddlewareReconciler.reconciler_name()] = ( | ||||||
|             TraefikMiddlewareReconciler |             TraefikMiddlewareReconciler | ||||||
|         ) |         ) | ||||||
|         self.reconcile_order.append(IngressReconciler.reconciler_name()) |         self.reconcile_order.append(IngressReconciler.reconciler_name()) | ||||||
|  |         self.reconcile_order.append(HTTPRouteReconciler.reconciler_name()) | ||||||
|         self.reconcile_order.append(TraefikMiddlewareReconciler.reconciler_name()) |         self.reconcile_order.append(TraefikMiddlewareReconciler.reconciler_name()) | ||||||
|  | |||||||
| @ -4,13 +4,10 @@ | |||||||
|  |  | ||||||
| {% block head %} | {% block head %} | ||||||
| <script src="{% versioned_script 'dist/rac/index-%v.js' %}" type="module"></script> | <script src="{% versioned_script 'dist/rac/index-%v.js' %}" type="module"></script> | ||||||
|  | <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)"> | ||||||
| <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)" /> | <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> | ||||||
| <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" /> | <link rel="icon" href="{{ tenant.branding_favicon_url }}"> | ||||||
|  | <link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}"> | ||||||
| <link rel="icon" href="{{ tenant.branding_favicon_url }}" /> |  | ||||||
| <link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}" /> |  | ||||||
|  |  | ||||||
| {% include "base/header_js.html" %} | {% include "base/header_js.html" %} | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
|  | |||||||
| @ -18,11 +18,11 @@ from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner | |||||||
| from authentik.flows.stage import RedirectStage | from authentik.flows.stage import RedirectStage | ||||||
| from authentik.lib.utils.time import timedelta_from_string | from authentik.lib.utils.time import timedelta_from_string | ||||||
| from authentik.policies.engine import PolicyEngine | from authentik.policies.engine import PolicyEngine | ||||||
| from authentik.policies.views import BufferedPolicyAccessView | from authentik.policies.views import PolicyAccessView | ||||||
| from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider | from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider | ||||||
|  |  | ||||||
|  |  | ||||||
| class RACStartView(BufferedPolicyAccessView): | class RACStartView(PolicyAccessView): | ||||||
|     """Start a RAC connection by checking access and creating a connection token""" |     """Start a RAC connection by checking access and creating a connection token""" | ||||||
|  |  | ||||||
|     endpoint: Endpoint |     endpoint: Endpoint | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ from authentik.flows.models import in_memory_stage | |||||||
| from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner | from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner | ||||||
| from authentik.flows.views.executor import SESSION_KEY_POST | from authentik.flows.views.executor import SESSION_KEY_POST | ||||||
| from authentik.lib.views import bad_request_message | from authentik.lib.views import bad_request_message | ||||||
| from authentik.policies.views import BufferedPolicyAccessView | from authentik.policies.views import PolicyAccessView | ||||||
| from authentik.providers.saml.exceptions import CannotHandleAssertion | from authentik.providers.saml.exceptions import CannotHandleAssertion | ||||||
| from authentik.providers.saml.models import SAMLBindings, SAMLProvider | from authentik.providers.saml.models import SAMLBindings, SAMLProvider | ||||||
| from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser | from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser | ||||||
| @ -35,7 +35,7 @@ from authentik.stages.consent.stage import ( | |||||||
| LOGGER = get_logger() | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
| class SAMLSSOView(BufferedPolicyAccessView): | class SAMLSSOView(PolicyAccessView): | ||||||
|     """SAML SSO Base View, which plans a flow and injects our final stage. |     """SAML SSO Base View, which plans a flow and injects our final stage. | ||||||
|     Calls get/post handler.""" |     Calls get/post handler.""" | ||||||
|  |  | ||||||
| @ -83,7 +83,7 @@ class SAMLSSOView(BufferedPolicyAccessView): | |||||||
|  |  | ||||||
|     def post(self, request: HttpRequest, application_slug: str) -> HttpResponse: |     def post(self, request: HttpRequest, application_slug: str) -> HttpResponse: | ||||||
|         """GET and POST use the same handler, but we can't |         """GET and POST use the same handler, but we can't | ||||||
|         override .dispatch easily because BufferedPolicyAccessView's dispatch""" |         override .dispatch easily because PolicyAccessView's dispatch""" | ||||||
|         return self.get(request, application_slug) |         return self.get(request, application_slug) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ class IdentificationStageSerializer(StageSerializer): | |||||||
|             "sources", |             "sources", | ||||||
|             "show_source_labels", |             "show_source_labels", | ||||||
|             "pretend_user_exists", |             "pretend_user_exists", | ||||||
|  |             "enable_remember_me", | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -0,0 +1,21 @@ | |||||||
|  | # Generated by Django 5.1.8 on 2025-04-16 17:14 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_stages_identification", "0015_identificationstage_captcha_stage"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="identificationstage", | ||||||
|  |             name="enable_remember_me", | ||||||
|  |             field=models.BooleanField( | ||||||
|  |                 default=False, | ||||||
|  |                 help_text="Show the user the 'Remember me on this device' toggle, allowing repeat users to skip straight to entering their password.", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -76,7 +76,13 @@ class IdentificationStage(Stage): | |||||||
|             "is entered." |             "is entered." | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|  |     enable_remember_me = models.BooleanField( | ||||||
|  |         default=False, | ||||||
|  |         help_text=_( | ||||||
|  |             "Show the user the 'Remember me on this device' toggle, allowing repeat " | ||||||
|  |             "users to skip straight to entering their password." | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|     enrollment_flow = models.ForeignKey( |     enrollment_flow = models.ForeignKey( | ||||||
|         Flow, |         Flow, | ||||||
|         on_delete=models.SET_DEFAULT, |         on_delete=models.SET_DEFAULT, | ||||||
|  | |||||||
| @ -85,6 +85,7 @@ class IdentificationChallenge(Challenge): | |||||||
|     primary_action = CharField() |     primary_action = CharField() | ||||||
|     sources = LoginSourceSerializer(many=True, required=False) |     sources = LoginSourceSerializer(many=True, required=False) | ||||||
|     show_source_labels = BooleanField() |     show_source_labels = BooleanField() | ||||||
|  |     enable_remember_me = BooleanField(required=False, default=True) | ||||||
|  |  | ||||||
|     component = CharField(default="ak-stage-identification") |     component = CharField(default="ak-stage-identification") | ||||||
|  |  | ||||||
| @ -235,6 +236,7 @@ class IdentificationStageView(ChallengeStageView): | |||||||
|                 and current_stage.password_stage.allow_show_password, |                 and current_stage.password_stage.allow_show_password, | ||||||
|                 "show_source_labels": current_stage.show_source_labels, |                 "show_source_labels": current_stage.show_source_labels, | ||||||
|                 "flow_designation": self.executor.flow.designation, |                 "flow_designation": self.executor.flow.designation, | ||||||
|  |                 "enable_remember_me": current_stage.enable_remember_me, | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|         # If the user has been redirected to us whilst trying to access an |         # If the user has been redirected to us whilst trying to access an | ||||||
|  | |||||||
| @ -171,7 +171,8 @@ def username_field_validator_factory() -> Callable[[PromptChallengeResponse, str | |||||||
|  |  | ||||||
|  |  | ||||||
| def password_single_validator_factory() -> Callable[[PromptChallengeResponse, str], Any]: | def password_single_validator_factory() -> Callable[[PromptChallengeResponse, str], Any]: | ||||||
|     """Return a `clean_` method for `field`. Clean method checks if username is taken already.""" |     """Return a `clean_` method for `field`. Clean method checks if the password meets configured | ||||||
|  |     PasswordPolicy.""" | ||||||
|  |  | ||||||
|     def password_single_clean(self: PromptChallengeResponse, value: str) -> Any: |     def password_single_clean(self: PromptChallengeResponse, value: str) -> Any: | ||||||
|         """Send password validation signals for e.g. LDAP Source""" |         """Send password validation signals for e.g. LDAP Source""" | ||||||
|  | |||||||
| @ -4,7 +4,13 @@ from unittest.mock import patch | |||||||
|  |  | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
|  |  | ||||||
| from authentik.core.models import USER_ATTRIBUTE_SOURCES, Group, Source, User, UserSourceConnection | from authentik.core.models import ( | ||||||
|  |     USER_ATTRIBUTE_SOURCES, | ||||||
|  |     Group, | ||||||
|  |     Source, | ||||||
|  |     User, | ||||||
|  |     UserSourceConnection, | ||||||
|  | ) | ||||||
| from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION | from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION | ||||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_flow | from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
|  | |||||||
| @ -3641,6 +3641,46 @@ | |||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     }, |                     }, | ||||||
|  |                     { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "required": [ | ||||||
|  |                             "model", | ||||||
|  |                             "identifiers" | ||||||
|  |                         ], | ||||||
|  |                         "properties": { | ||||||
|  |                             "model": { | ||||||
|  |                                 "const": "authentik_policies_unique_password.uniquepasswordpolicy" | ||||||
|  |                             }, | ||||||
|  |                             "id": { | ||||||
|  |                                 "type": "string" | ||||||
|  |                             }, | ||||||
|  |                             "state": { | ||||||
|  |                                 "type": "string", | ||||||
|  |                                 "enum": [ | ||||||
|  |                                     "absent", | ||||||
|  |                                     "present", | ||||||
|  |                                     "created", | ||||||
|  |                                     "must_created" | ||||||
|  |                                 ], | ||||||
|  |                                 "default": "present" | ||||||
|  |                             }, | ||||||
|  |                             "conditions": { | ||||||
|  |                                 "type": "array", | ||||||
|  |                                 "items": { | ||||||
|  |                                     "type": "boolean" | ||||||
|  |                                 } | ||||||
|  |                             }, | ||||||
|  |                             "permissions": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_policies_unique_password.uniquepasswordpolicy_permissions" | ||||||
|  |                             }, | ||||||
|  |                             "attrs": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_policies_unique_password.uniquepasswordpolicy" | ||||||
|  |                             }, | ||||||
|  |                             "identifiers": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_policies_unique_password.uniquepasswordpolicy" | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|                     { |                     { | ||||||
|                         "type": "object", |                         "type": "object", | ||||||
|                         "required": [ |                         "required": [ | ||||||
| @ -4822,6 +4862,7 @@ | |||||||
|                         "authentik.core", |                         "authentik.core", | ||||||
|                         "authentik.enterprise", |                         "authentik.enterprise", | ||||||
|                         "authentik.enterprise.audit", |                         "authentik.enterprise.audit", | ||||||
|  |                         "authentik.enterprise.policies.unique_password", | ||||||
|                         "authentik.enterprise.providers.google_workspace", |                         "authentik.enterprise.providers.google_workspace", | ||||||
|                         "authentik.enterprise.providers.microsoft_entra", |                         "authentik.enterprise.providers.microsoft_entra", | ||||||
|                         "authentik.enterprise.providers.ssf", |                         "authentik.enterprise.providers.ssf", | ||||||
| @ -4929,6 +4970,7 @@ | |||||||
|                         "authentik_core.applicationentitlement", |                         "authentik_core.applicationentitlement", | ||||||
|                         "authentik_core.token", |                         "authentik_core.token", | ||||||
|                         "authentik_enterprise.license", |                         "authentik_enterprise.license", | ||||||
|  |                         "authentik_policies_unique_password.uniquepasswordpolicy", | ||||||
|                         "authentik_providers_google_workspace.googleworkspaceprovider", |                         "authentik_providers_google_workspace.googleworkspaceprovider", | ||||||
|                         "authentik_providers_google_workspace.googleworkspaceprovidermapping", |                         "authentik_providers_google_workspace.googleworkspaceprovidermapping", | ||||||
|                         "authentik_providers_microsoft_entra.microsoftentraprovider", |                         "authentik_providers_microsoft_entra.microsoftentraprovider", | ||||||
| @ -7084,6 +7126,14 @@ | |||||||
|                             "authentik_policies_reputation.delete_reputationpolicy", |                             "authentik_policies_reputation.delete_reputationpolicy", | ||||||
|                             "authentik_policies_reputation.view_reputation", |                             "authentik_policies_reputation.view_reputation", | ||||||
|                             "authentik_policies_reputation.view_reputationpolicy", |                             "authentik_policies_reputation.view_reputationpolicy", | ||||||
|  |                             "authentik_policies_unique_password.add_uniquepasswordpolicy", | ||||||
|  |                             "authentik_policies_unique_password.add_userpasswordhistory", | ||||||
|  |                             "authentik_policies_unique_password.change_uniquepasswordpolicy", | ||||||
|  |                             "authentik_policies_unique_password.change_userpasswordhistory", | ||||||
|  |                             "authentik_policies_unique_password.delete_uniquepasswordpolicy", | ||||||
|  |                             "authentik_policies_unique_password.delete_userpasswordhistory", | ||||||
|  |                             "authentik_policies_unique_password.view_uniquepasswordpolicy", | ||||||
|  |                             "authentik_policies_unique_password.view_userpasswordhistory", | ||||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovider", |                             "authentik_providers_google_workspace.add_googleworkspaceprovider", | ||||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidergroup", |                             "authentik_providers_google_workspace.add_googleworkspaceprovidergroup", | ||||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidermapping", |                             "authentik_providers_google_workspace.add_googleworkspaceprovidermapping", | ||||||
| @ -11893,6 +11943,11 @@ | |||||||
|                     "type": "boolean", |                     "type": "boolean", | ||||||
|                     "title": "Pretend user exists", |                     "title": "Pretend user exists", | ||||||
|                     "description": "When enabled, the stage will succeed and continue even when incorrect user info is entered." |                     "description": "When enabled, the stage will succeed and continue even when incorrect user info is entered." | ||||||
|  |                 }, | ||||||
|  |                 "enable_remember_me": { | ||||||
|  |                     "type": "boolean", | ||||||
|  |                     "title": "Enable remember me", | ||||||
|  |                     "description": "Show the user the 'Remember me on this device' toggle, allowing repeat users to skip straight to entering their password." | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "required": [] |             "required": [] | ||||||
| @ -13779,6 +13834,14 @@ | |||||||
|                             "authentik_policies_reputation.delete_reputationpolicy", |                             "authentik_policies_reputation.delete_reputationpolicy", | ||||||
|                             "authentik_policies_reputation.view_reputation", |                             "authentik_policies_reputation.view_reputation", | ||||||
|                             "authentik_policies_reputation.view_reputationpolicy", |                             "authentik_policies_reputation.view_reputationpolicy", | ||||||
|  |                             "authentik_policies_unique_password.add_uniquepasswordpolicy", | ||||||
|  |                             "authentik_policies_unique_password.add_userpasswordhistory", | ||||||
|  |                             "authentik_policies_unique_password.change_uniquepasswordpolicy", | ||||||
|  |                             "authentik_policies_unique_password.change_userpasswordhistory", | ||||||
|  |                             "authentik_policies_unique_password.delete_uniquepasswordpolicy", | ||||||
|  |                             "authentik_policies_unique_password.delete_userpasswordhistory", | ||||||
|  |                             "authentik_policies_unique_password.view_uniquepasswordpolicy", | ||||||
|  |                             "authentik_policies_unique_password.view_userpasswordhistory", | ||||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovider", |                             "authentik_providers_google_workspace.add_googleworkspaceprovider", | ||||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidergroup", |                             "authentik_providers_google_workspace.add_googleworkspaceprovidergroup", | ||||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidermapping", |                             "authentik_providers_google_workspace.add_googleworkspaceprovidermapping", | ||||||
| @ -14463,6 +14526,61 @@ | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "model_authentik_policies_unique_password.uniquepasswordpolicy": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "name": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "minLength": 1, | ||||||
|  |                     "title": "Name" | ||||||
|  |                 }, | ||||||
|  |                 "execution_logging": { | ||||||
|  |                     "type": "boolean", | ||||||
|  |                     "title": "Execution logging", | ||||||
|  |                     "description": "When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged." | ||||||
|  |                 }, | ||||||
|  |                 "password_field": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "minLength": 1, | ||||||
|  |                     "title": "Password field", | ||||||
|  |                     "description": "Field key to check, field keys defined in Prompt stages are available." | ||||||
|  |                 }, | ||||||
|  |                 "num_historical_passwords": { | ||||||
|  |                     "type": "integer", | ||||||
|  |                     "minimum": 0, | ||||||
|  |                     "maximum": 2147483647, | ||||||
|  |                     "title": "Num historical passwords", | ||||||
|  |                     "description": "Number of passwords to check against." | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             "required": [] | ||||||
|  |         }, | ||||||
|  |         "model_authentik_policies_unique_password.uniquepasswordpolicy_permissions": { | ||||||
|  |             "type": "array", | ||||||
|  |             "items": { | ||||||
|  |                 "type": "object", | ||||||
|  |                 "required": [ | ||||||
|  |                     "permission" | ||||||
|  |                 ], | ||||||
|  |                 "properties": { | ||||||
|  |                     "permission": { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "enum": [ | ||||||
|  |                             "add_uniquepasswordpolicy", | ||||||
|  |                             "change_uniquepasswordpolicy", | ||||||
|  |                             "delete_uniquepasswordpolicy", | ||||||
|  |                             "view_uniquepasswordpolicy" | ||||||
|  |                         ] | ||||||
|  |                     }, | ||||||
|  |                     "user": { | ||||||
|  |                         "type": "integer" | ||||||
|  |                     }, | ||||||
|  |                     "role": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "model_authentik_providers_google_workspace.googleworkspaceprovider": { |         "model_authentik_providers_google_workspace.googleworkspaceprovider": { | ||||||
|             "type": "object", |             "type": "object", | ||||||
|             "properties": { |             "properties": { | ||||||
|  | |||||||
| @ -1,10 +0,0 @@ | |||||||
| import { createESLintPackageConfig } from "@goauthentik/eslint-config"; |  | ||||||
|  |  | ||||||
| // @ts-check |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * ESLint configuration for authentik's monorepo. |  | ||||||
|  */ |  | ||||||
| const ESLintConfig = createESLintPackageConfig(); |  | ||||||
|  |  | ||||||
| export default ESLintConfig; |  | ||||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ require ( | |||||||
| 	github.com/coreos/go-oidc/v3 v3.14.1 | 	github.com/coreos/go-oidc/v3 v3.14.1 | ||||||
| 	github.com/getsentry/sentry-go v0.32.0 | 	github.com/getsentry/sentry-go v0.32.0 | ||||||
| 	github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 | 	github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 | ||||||
| 	github.com/go-ldap/ldap/v3 v3.4.10 | 	github.com/go-ldap/ldap/v3 v3.4.11 | ||||||
| 	github.com/go-openapi/runtime v0.28.0 | 	github.com/go-openapi/runtime v0.28.0 | ||||||
| 	github.com/golang-jwt/jwt/v5 v5.2.2 | 	github.com/golang-jwt/jwt/v5 v5.2.2 | ||||||
| 	github.com/google/uuid v1.6.0 | 	github.com/google/uuid v1.6.0 | ||||||
| @ -27,7 +27,7 @@ require ( | |||||||
| 	github.com/spf13/cobra v1.9.1 | 	github.com/spf13/cobra v1.9.1 | ||||||
| 	github.com/stretchr/testify v1.10.0 | 	github.com/stretchr/testify v1.10.0 | ||||||
| 	github.com/wwt/guac v1.3.2 | 	github.com/wwt/guac v1.3.2 | ||||||
| 	goauthentik.io/api/v3 v3.2025024.6 | 	goauthentik.io/api/v3 v3.2025024.9 | ||||||
| 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||||
| 	golang.org/x/oauth2 v0.29.0 | 	golang.org/x/oauth2 v0.29.0 | ||||||
| 	golang.org/x/sync v0.13.0 | 	golang.org/x/sync v0.13.0 | ||||||
| @ -43,7 +43,7 @@ require ( | |||||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||||
| 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | ||||||
| 	github.com/felixge/httpsnoop v1.0.3 // indirect | 	github.com/felixge/httpsnoop v1.0.3 // indirect | ||||||
| 	github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect | 	github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect | ||||||
| 	github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect | 	github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect | ||||||
| 	github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect | 	github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect | ||||||
| 	github.com/go-jose/go-jose/v4 v4.0.5 // indirect | 	github.com/go-jose/go-jose/v4 v4.0.5 // indirect | ||||||
|  | |||||||
							
								
								
									
										82
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								go.sum
									
									
									
									
									
								
							| @ -71,8 +71,8 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd | |||||||
| github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||||||
| github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY= | github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY= | ||||||
| github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= | github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= | ||||||
| github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= | github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= | ||||||
| github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= | github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= | ||||||
| github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= | ||||||
| github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= | ||||||
| github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | ||||||
| @ -86,8 +86,8 @@ github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9 | |||||||
| github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= | github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= | ||||||
| github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= | github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= | ||||||
| github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= | github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= | ||||||
| github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= | github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= | ||||||
| github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= | github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= | ||||||
| github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | ||||||
| github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= | ||||||
| github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | ||||||
| @ -148,7 +148,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | |||||||
| github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= |  | ||||||
| github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= | ||||||
| github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= | ||||||
| github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= | ||||||
| @ -172,16 +171,13 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE | |||||||
| github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= | github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= | ||||||
| github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= | ||||||
| github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= | ||||||
| github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= |  | ||||||
| github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= | ||||||
| github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= | ||||||
| github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= |  | ||||||
| github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= | github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= | ||||||
| github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= | github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= | ||||||
| github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||||
| github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= | ||||||
| github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||||
| github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= |  | ||||||
| github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= | ||||||
| github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||||||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||||
| @ -266,15 +262,10 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= | |||||||
| github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= |  | ||||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= |  | ||||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |  | ||||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= |  | ||||||
| github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= |  | ||||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
| github.com/wwt/guac v1.3.2 h1:sH6OFGa/1tBs7ieWBVlZe7t6F5JAOWBry/tqQL/Vup4= | github.com/wwt/guac v1.3.2 h1:sH6OFGa/1tBs7ieWBVlZe7t6F5JAOWBry/tqQL/Vup4= | ||||||
| @ -282,7 +273,6 @@ github.com/wwt/guac v1.3.2/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0 | |||||||
| github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
| github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= |  | ||||||
| go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= | go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= | ||||||
| go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= | go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= | ||||||
| go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||||
| @ -300,20 +290,14 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y | |||||||
| go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= | go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= | ||||||
| go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||||
| goauthentik.io/api/v3 v3.2025024.6 h1:3mmZY7E0EM/RR8uMF17mxa7368ZgZEIq/FjlCLJ9+lA= | goauthentik.io/api/v3 v3.2025024.9 h1:i3tbkyotE32ZpJ729BsPWTuLQUdtZ54Li4aP1amZzsM= | ||||||
| goauthentik.io/api/v3 v3.2025024.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | goauthentik.io/api/v3 v3.2025024.9/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
| golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
| golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
| golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |  | ||||||
| golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= |  | ||||||
| golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= |  | ||||||
| golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= |  | ||||||
| golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= |  | ||||||
| golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= |  | ||||||
| golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= | ||||||
| golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= | ||||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||||
| @ -348,11 +332,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB | |||||||
| golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||||||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= |  | ||||||
| golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= |  | ||||||
| golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= |  | ||||||
| golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= |  | ||||||
| golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= |  | ||||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| @ -379,17 +358,8 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ | |||||||
| golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||||
| golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||||
| golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= | ||||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= | ||||||
| golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= |  | ||||||
| golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= |  | ||||||
| golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= |  | ||||||
| golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= |  | ||||||
| golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= |  | ||||||
| golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= |  | ||||||
| golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= |  | ||||||
| golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= |  | ||||||
| golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= |  | ||||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| @ -406,12 +376,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ | |||||||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |  | ||||||
| golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |  | ||||||
| golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= |  | ||||||
| golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= |  | ||||||
| golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= |  | ||||||
| golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= |  | ||||||
| golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= | ||||||
| golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | ||||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| @ -440,40 +404,14 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w | |||||||
| golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |  | ||||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |  | ||||||
| golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |  | ||||||
| golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |  | ||||||
| golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= | ||||||
| golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||||
| golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= |  | ||||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |  | ||||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= |  | ||||||
| golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= |  | ||||||
| golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= |  | ||||||
| golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= |  | ||||||
| golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= |  | ||||||
| golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= |  | ||||||
| golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= |  | ||||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= |  | ||||||
| golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= |  | ||||||
| golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= |  | ||||||
| golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= |  | ||||||
| golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= |  | ||||||
| golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= |  | ||||||
| golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= |  | ||||||
| golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= | ||||||
| golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= | ||||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| @ -519,10 +457,6 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY | |||||||
| golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||||
| golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||||
| golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= |  | ||||||
| golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= |  | ||||||
| golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= |  | ||||||
| golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= |  | ||||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | |||||||
| @ -1,34 +1,15 @@ | |||||||
| <!doctype html> | <!DOCTYPE html> | ||||||
|  |  | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
|     <head> |     <head> | ||||||
|         <meta charset="UTF-8" /> |         <meta charset="UTF-8"> | ||||||
|         <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> |         <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> | ||||||
|  |  | ||||||
|         <title>{{.Title}}</title> |         <title>{{.Title}}</title> | ||||||
|  |         <link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png"> | ||||||
|         <link |         <link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css"> | ||||||
|             rel="shortcut icon" |         <link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css"> | ||||||
|             type="image/png" |         <link rel="prefetch" href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg" /> | ||||||
|             href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png" |         <style> | ||||||
|         /> |  | ||||||
|         <link |  | ||||||
|             rel="stylesheet" |  | ||||||
|             type="text/css" |  | ||||||
|             href="/outpost.goauthentik.io/static/dist/patternfly.min.css" |  | ||||||
|         /> |  | ||||||
|         <link |  | ||||||
|             rel="stylesheet" |  | ||||||
|             type="text/css" |  | ||||||
|             href="/outpost.goauthentik.io/static/dist/authentik.css" |  | ||||||
|         /> |  | ||||||
|  |  | ||||||
|         <link |  | ||||||
|             rel="prefetch" |  | ||||||
|             href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg" |  | ||||||
|         /> |  | ||||||
|  |  | ||||||
|         <style data-test-id="outpost-error-root-styles"> |  | ||||||
|             .pf-c-background-image::before { |             .pf-c-background-image::before { | ||||||
|                 --ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg"); |                 --ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg"); | ||||||
|             } |             } | ||||||
| @ -43,15 +24,13 @@ | |||||||
|         </style> |         </style> | ||||||
|     </head> |     </head> | ||||||
|     <body> |     <body> | ||||||
|         <div class="pf-c-background-image"></div> |         <div class="pf-c-background-image"> | ||||||
|  |         </div> | ||||||
|         <div class="pf-c-login stacked"> |         <div class="pf-c-login stacked"> | ||||||
|             <div class="ak-login-container"> |             <div class="ak-login-container"> | ||||||
|                 <main class="pf-c-login__main"> |                 <main class="pf-c-login__main"> | ||||||
|                     <div class="pf-c-login__main-header pf-c-brand ak-brand"> |                     <div class="pf-c-login__main-header pf-c-brand ak-brand"> | ||||||
|                         <img |                         <img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik Logo" /> | ||||||
|                             src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" |  | ||||||
|                             alt="authentik Logo" |  | ||||||
|                         /> |  | ||||||
|                     </div> |                     </div> | ||||||
|                     <header class="pf-c-login__main-header"> |                     <header class="pf-c-login__main-header"> | ||||||
|                         <h1 class="pf-c-title pf-m-3xl"> |                         <h1 class="pf-c-title pf-m-3xl"> | ||||||
| @ -68,7 +47,9 @@ | |||||||
|                 <footer class="pf-c-login__footer"> |                 <footer class="pf-c-login__footer"> | ||||||
|                     <ul class="pf-c-list pf-m-inline"> |                     <ul class="pf-c-list pf-m-inline"> | ||||||
|                         <li> |                         <li> | ||||||
|                             <span> Powered by authentik </span> |                             <span> | ||||||
|  |                                 Powered by authentik | ||||||
|  |                             </span> | ||||||
|                         </li> |                         </li> | ||||||
|                     </ul> |                     </ul> | ||||||
|                 </footer> |                 </footer> | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								lifecycle/aws/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								lifecycle/aws/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -9,7 +9,7 @@ | |||||||
|             "version": "0.0.0", |             "version": "0.0.0", | ||||||
|             "license": "MIT", |             "license": "MIT", | ||||||
|             "devDependencies": { |             "devDependencies": { | ||||||
|                 "aws-cdk": "^2.1007.0", |                 "aws-cdk": "^2.1012.0", | ||||||
|                 "cross-env": "^7.0.3" |                 "cross-env": "^7.0.3" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -17,9 +17,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/aws-cdk": { |         "node_modules/aws-cdk": { | ||||||
|             "version": "2.1007.0", |             "version": "2.1012.0", | ||||||
|             "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1007.0.tgz", |             "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1012.0.tgz", | ||||||
|             "integrity": "sha512-/UOYOTGWUm+pP9qxg03tID5tL6euC+pb+xo0RBue+xhnUWwj/Bbsw6DbqbpOPMrNzTUxmM723/uMEQmM6S26dw==", |             "integrity": "sha512-C6jSWkqP0hkY2Cs300VJHjspmTXDTMfB813kwZvRbd/OsKBfTBJBbYU16VoLAp1LVEOnQMf8otSlaSgzVF0X9A==", | ||||||
|             "dev": true, |             "dev": true, | ||||||
|             "license": "Apache-2.0", |             "license": "Apache-2.0", | ||||||
|             "bin": { |             "bin": { | ||||||
|  | |||||||
| @ -1,16 +1,16 @@ | |||||||
| { | { | ||||||
|     "name": "@goauthentik/lifecycle-aws", |     "name": "@goauthentik/lifecycle-aws", | ||||||
|     "version": "0.0.0", |     "version": "0.0.0", | ||||||
|     "license": "MIT", |  | ||||||
|     "private": true, |     "private": true, | ||||||
|  |     "license": "MIT", | ||||||
|     "scripts": { |     "scripts": { | ||||||
|         "aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml" |         "aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml" | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |  | ||||||
|         "aws-cdk": "^2.1007.0", |  | ||||||
|         "cross-env": "^7.0.3" |  | ||||||
|     }, |  | ||||||
|     "engines": { |     "engines": { | ||||||
|         "node": ">=20.11" |         "node": ">=20" | ||||||
|  |     }, | ||||||
|  |     "devDependencies": { | ||||||
|  |         "aws-cdk": "^2.1012.0", | ||||||
|  |         "cross-env": "^7.0.3" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ from lifecycle.migrate import BaseMigration | |||||||
|  |  | ||||||
| SQL_STATEMENT = """ | SQL_STATEMENT = """ | ||||||
| BEGIN TRANSACTION; | BEGIN TRANSACTION; | ||||||
| ALTER TABLE authentik_tenants_tenant RENAME TO authentik_brands_brand; | ALTER TABLE IF EXISTS authentik_tenants_tenant RENAME TO authentik_brands_brand; | ||||||
| UPDATE django_migrations SET app = replace(app, 'authentik_tenants', 'authentik_brands'); | UPDATE django_migrations SET app = replace(app, 'authentik_tenants', 'authentik_brands'); | ||||||
| UPDATE django_content_type SET app_label = replace(app_label, 'authentik_tenants', 'authentik_brands'); | UPDATE django_content_type SET app_label = replace(app_label, 'authentik_tenants', 'authentik_brands'); | ||||||
| COMMIT; | COMMIT; | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ msgid "" | |||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: PACKAGE VERSION\n" | "Project-Id-Version: PACKAGE VERSION\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2025-04-15 00:11+0000\n" | "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | "Language-Team: LANGUAGE <LL@li.org>\n" | ||||||
| @ -451,6 +451,36 @@ msgstr "" | |||||||
| msgid "License Usage Records" | msgid "License Usage Records" | ||||||
| msgstr "" | 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 | #: authentik/enterprise/policy.py | ||||||
| msgid "Enterprise required to access this feature." | msgid "Enterprise required to access this feature." | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -1175,10 +1205,6 @@ msgstr "" | |||||||
| msgid "Clear Policy's cache metrics" | msgid "Clear Policy's cache metrics" | ||||||
| msgstr "" | 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 | #: authentik/policies/password/models.py | ||||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -1188,10 +1214,6 @@ msgid "" | |||||||
| "If the zxcvbn score is equal or less than this value, the policy will fail." | "If the zxcvbn score is equal or less than this value, the policy will fail." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py |  | ||||||
| msgid "Password not set in context" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py | #: authentik/policies/password/models.py | ||||||
| msgid "Invalid password." | msgid "Invalid password." | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -1233,20 +1255,6 @@ msgstr "" | |||||||
| msgid "Reputation Scores" | msgid "Reputation Scores" | ||||||
| msgstr "" | 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 | #: authentik/policies/templates/policies/denied.html | ||||||
| msgid "Permission denied" | msgid "Permission denied" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -2254,6 +2262,14 @@ msgstr "" | |||||||
| msgid "No token received." | msgid "No token received." | ||||||
| msgstr "" | 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 | #: authentik/sources/oauth/models.py | ||||||
| msgid "Request Token URL" | msgid "Request Token URL" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -2291,6 +2307,11 @@ msgstr "" | |||||||
| msgid "Additional Scopes" | msgid "Additional Scopes" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "" | ||||||
|  | "How to perform authentication during an authorization_code token request flow" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "OAuth Source" | msgid "OAuth Source" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -3131,6 +3152,12 @@ msgid "" | |||||||
| "info is entered." | "info is entered." | ||||||
| msgstr "" | 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 | #: authentik/stages/identification/models.py | ||||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||||
| msgstr "" | msgstr "" | ||||||
|  | |||||||
										
											Binary file not shown.
										
									
								
							| @ -9,8 +9,8 @@ | |||||||
| # Kyllian Delaye-Maillot, 2023 | # Kyllian Delaye-Maillot, 2023 | ||||||
| # Manuel Viens, 2023 | # Manuel Viens, 2023 | ||||||
| # Mordecai, 2023 | # Mordecai, 2023 | ||||||
|  | # Tina, 2024 | ||||||
| # Charles Leclerc, 2025 | # Charles Leclerc, 2025 | ||||||
| # Tina, 2025 |  | ||||||
| # nerdinator <florian.dupret@gmail.com>, 2025 | # nerdinator <florian.dupret@gmail.com>, 2025 | ||||||
| # Marc Schmitt, 2025 | # Marc Schmitt, 2025 | ||||||
| #  | #  | ||||||
| @ -19,7 +19,7 @@ msgid "" | |||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: PACKAGE VERSION\n" | "Project-Id-Version: PACKAGE VERSION\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2025-04-15 00:11+0000\n" | "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||||
| "Last-Translator: Marc Schmitt, 2025\n" | "Last-Translator: Marc Schmitt, 2025\n" | ||||||
| "Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n" | "Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n" | ||||||
| @ -502,6 +502,38 @@ msgstr "Utilisation de la licence" | |||||||
| msgid "License Usage Records" | msgid "License Usage Records" | ||||||
| msgstr "Registre d'utilisation de la licence" | msgstr "Registre d'utilisation de la licence" | ||||||
|  |  | ||||||
|  | #: 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 "" | ||||||
|  | "Clé de champ à vérifier ; les clés de champ définies dans les étapes de " | ||||||
|  | "d'invite sont disponibles." | ||||||
|  |  | ||||||
|  | #: authentik/enterprise/policies/unique_password/models.py | ||||||
|  | msgid "Number of passwords to check against." | ||||||
|  | msgstr "Nombre de mots de passe à vérifier." | ||||||
|  |  | ||||||
|  | #: authentik/enterprise/policies/unique_password/models.py | ||||||
|  | #: authentik/policies/password/models.py | ||||||
|  | msgid "Password not set in context" | ||||||
|  | msgstr "Mot de passe non défini dans le contexte" | ||||||
|  |  | ||||||
|  | #: authentik/enterprise/policies/unique_password/models.py | ||||||
|  | msgid "This password has been used previously. Please choose a different one." | ||||||
|  | msgstr "Ce mot de passe a déjà été utilisé. Veuillez en choisir un autre." | ||||||
|  |  | ||||||
|  | #: authentik/enterprise/policies/unique_password/models.py | ||||||
|  | msgid "Password Uniqueness Policy" | ||||||
|  | msgstr "Politique d'unicité des mots de passe" | ||||||
|  |  | ||||||
|  | #: authentik/enterprise/policies/unique_password/models.py | ||||||
|  | msgid "Password Uniqueness Policies" | ||||||
|  | msgstr "Politiques d'unicité des mots de passe" | ||||||
|  |  | ||||||
|  | #: authentik/enterprise/policies/unique_password/models.py | ||||||
|  | msgid "User Password History" | ||||||
|  | msgstr "Historique des mots de passe utilisateur" | ||||||
|  |  | ||||||
| #: authentik/enterprise/policy.py | #: authentik/enterprise/policy.py | ||||||
| msgid "Enterprise required to access this feature." | msgid "Enterprise required to access this feature." | ||||||
| msgstr "Entreprise est requis pour accéder à cette fonctionnalité." | msgstr "Entreprise est requis pour accéder à cette fonctionnalité." | ||||||
| @ -1296,12 +1328,6 @@ msgstr "Voir les métriques de cache de la politique" | |||||||
| msgid "Clear Policy's cache metrics" | msgid "Clear Policy's cache metrics" | ||||||
| msgstr "Nettoyer les métriques de cache de la politique" | msgstr "Nettoyer les métriques de cache de la politique" | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py |  | ||||||
| msgid "Field key to check, field keys defined in Prompt stages are available." |  | ||||||
| msgstr "" |  | ||||||
| "Clé de champ à vérifier ; les clés de champ définies dans les étapes de " |  | ||||||
| "d'invite sont disponibles." |  | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py | #: authentik/policies/password/models.py | ||||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -1315,10 +1341,6 @@ msgstr "" | |||||||
| "Si le score zxcvbn est égal ou inférieur à cette valeur, la politique " | "Si le score zxcvbn est égal ou inférieur à cette valeur, la politique " | ||||||
| "échouera." | "échouera." | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py |  | ||||||
| msgid "Password not set in context" |  | ||||||
| msgstr "Mot de passe non défini dans le contexte" |  | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py | #: authentik/policies/password/models.py | ||||||
| msgid "Invalid password." | msgid "Invalid password." | ||||||
| msgstr "Mot de passe invalide." | msgstr "Mot de passe invalide." | ||||||
| @ -1360,22 +1382,6 @@ msgstr "Score de Réputation" | |||||||
| msgid "Reputation Scores" | msgid "Reputation Scores" | ||||||
| msgstr "Scores de Réputation" | msgstr "Scores de Réputation" | ||||||
|  |  | ||||||
| #: authentik/policies/templates/policies/buffer.html |  | ||||||
| msgid "Waiting for authentication..." |  | ||||||
| msgstr "En attente de l'authentification..." |  | ||||||
|  |  | ||||||
| #: authentik/policies/templates/policies/buffer.html |  | ||||||
| msgid "" |  | ||||||
| "You're already authenticating in another tab. This page will refresh once " |  | ||||||
| "authentication is completed." |  | ||||||
| msgstr "" |  | ||||||
| "Vous êtes déjà en cours d'authentification dans un autre onglet. Cette page " |  | ||||||
| "se rafraîchira lorsque l'authentification sera terminée." |  | ||||||
|  |  | ||||||
| #: authentik/policies/templates/policies/buffer.html |  | ||||||
| msgid "Authenticate in this tab" |  | ||||||
| msgstr "S'authentifier dans cet onglet" |  | ||||||
|  |  | ||||||
| #: authentik/policies/templates/policies/denied.html | #: authentik/policies/templates/policies/denied.html | ||||||
| msgid "Permission denied" | msgid "Permission denied" | ||||||
| msgstr "Permission refusée" | msgstr "Permission refusée" | ||||||
| @ -2508,6 +2514,14 @@ msgstr "Le mot de passe ne correspond pas à la complexité d'Active Directory." | |||||||
| msgid "No token received." | msgid "No token received." | ||||||
| msgstr "Pas de jeton reçu." | msgstr "Pas de jeton reçu." | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "HTTP Basic Authentication" | ||||||
|  | msgstr "Authentification HTTP Basic" | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "Include the client ID and secret as request parameters" | ||||||
|  | msgstr "Inclure le client ID et secret comme paramètres de la requête" | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "Request Token URL" | msgid "Request Token URL" | ||||||
| msgstr "URL du jeton de requête" | msgstr "URL du jeton de requête" | ||||||
| @ -2549,6 +2563,14 @@ msgstr "" | |||||||
| msgid "Additional Scopes" | msgid "Additional Scopes" | ||||||
| msgstr "Portées additionnelles" | msgstr "Portées additionnelles" | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "" | ||||||
|  | "How to perform authentication during an authorization_code token request " | ||||||
|  | "flow" | ||||||
|  | msgstr "" | ||||||
|  | "Comment effectuer l'authentification lors d'une demande de jeton pour le " | ||||||
|  | "flux authorization_code" | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "OAuth Source" | msgid "OAuth Source" | ||||||
| msgstr "Source OAuth" | msgstr "Source OAuth" | ||||||
| @ -3469,6 +3491,15 @@ msgstr "" | |||||||
| "Lorsqu'activé, l'étape réussira et continuera même lorsque les informations " | "Lorsqu'activé, l'étape réussira et continuera même lorsque les informations " | ||||||
| "utilisateurs entrées sont invalides." | "utilisateurs entrées sont invalides." | ||||||
|  |  | ||||||
|  | #: 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 "" | ||||||
|  | "Afficher à l'utilisateur l'option \"Se souvenir de moi sur cet appareil\", " | ||||||
|  | "afin de permettre aux utilisateurs réguliers de passer directement à la " | ||||||
|  | "saisie de leur mot de passe." | ||||||
|  |  | ||||||
| #: authentik/stages/identification/models.py | #: authentik/stages/identification/models.py | ||||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||||
| msgstr "Flux d'inscription facultatif, qui sera accessible en bas de page." | msgstr "Flux d'inscription facultatif, qui sera accessible en bas de page." | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ msgid "" | |||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: PACKAGE VERSION\n" | "Project-Id-Version: PACKAGE VERSION\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2025-04-15 00:11+0000\n" | "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||||
| "Last-Translator: deluxghost, 2025\n" | "Last-Translator: deluxghost, 2025\n" | ||||||
| "Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n" | "Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n" | ||||||
| @ -461,6 +461,36 @@ msgstr "许可证使用情况" | |||||||
| msgid "License Usage Records" | msgid "License Usage Records" | ||||||
| msgstr "许可证使用情况记录" | 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 | #: authentik/enterprise/policy.py | ||||||
| msgid "Enterprise required to access this feature." | msgid "Enterprise required to access this feature." | ||||||
| msgstr "访问此功能需要企业版。" | msgstr "访问此功能需要企业版。" | ||||||
| @ -1190,10 +1220,6 @@ msgstr "查看策略缓存指标" | |||||||
| msgid "Clear Policy's cache metrics" | msgid "Clear Policy's cache metrics" | ||||||
| msgstr "清除策略缓存指标" | 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 | #: authentik/policies/password/models.py | ||||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||||
| msgstr "密码哈希允许出现在 HaveIBeenPwned 中多少次" | msgstr "密码哈希允许出现在 HaveIBeenPwned 中多少次" | ||||||
| @ -1203,10 +1229,6 @@ msgid "" | |||||||
| "If the zxcvbn score is equal or less than this value, the policy will fail." | "If the zxcvbn score is equal or less than this value, the policy will fail." | ||||||
| msgstr "如果 zxcvbn 分数小于等于此值,则策略失败。" | msgstr "如果 zxcvbn 分数小于等于此值,则策略失败。" | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py |  | ||||||
| msgid "Password not set in context" |  | ||||||
| msgstr "未在上下文中设置密码" |  | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py | #: authentik/policies/password/models.py | ||||||
| msgid "Invalid password." | msgid "Invalid password." | ||||||
| msgstr "无效密码。" | msgstr "无效密码。" | ||||||
| @ -1248,20 +1270,6 @@ msgstr "信誉分数" | |||||||
| msgid "Reputation Scores" | msgid "Reputation Scores" | ||||||
| msgstr "信誉分数" | 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 | #: authentik/policies/templates/policies/denied.html | ||||||
| msgid "Permission denied" | msgid "Permission denied" | ||||||
| msgstr "权限被拒绝" | msgstr "权限被拒绝" | ||||||
| @ -2286,6 +2294,14 @@ msgstr "密码与 Active Directory 复杂度不匹配。" | |||||||
| msgid "No token received." | msgid "No token received." | ||||||
| msgstr "未收到令牌。" | msgstr "未收到令牌。" | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "HTTP Basic Authentication" | ||||||
|  | msgstr "HTTP 基本身份验证" | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "Include the client ID and secret as request parameters" | ||||||
|  | msgstr "包括客户端 ID 和密钥作为请求参数" | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "Request Token URL" | msgid "Request Token URL" | ||||||
| msgstr "请求令牌 URL" | msgstr "请求令牌 URL" | ||||||
| @ -2324,6 +2340,12 @@ msgstr "authentik 用来获取用户信息的 URL。" | |||||||
| msgid "Additional Scopes" | msgid "Additional Scopes" | ||||||
| msgstr "额外的作用域" | msgstr "额外的作用域" | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "" | ||||||
|  | "How to perform authentication during an authorization_code token request " | ||||||
|  | "flow" | ||||||
|  | msgstr "在 authorization_code 令牌请求流程期间,如何执行身份验证" | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "OAuth Source" | msgid "OAuth Source" | ||||||
| msgstr "OAuth 源" | msgstr "OAuth 源" | ||||||
| @ -3194,6 +3216,12 @@ msgid "" | |||||||
| "info is entered." | "info is entered." | ||||||
| msgstr "启用时,即使输入错误的用户信息,此阶段也会成功并继续。" | 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 | #: authentik/stages/identification/models.py | ||||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||||
| msgstr "可选注册流程,链接在页面底部。" | msgstr "可选注册流程,链接在页面底部。" | ||||||
|  | |||||||
										
											Binary file not shown.
										
									
								
							| @ -14,7 +14,7 @@ msgid "" | |||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: PACKAGE VERSION\n" | "Project-Id-Version: PACKAGE VERSION\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2025-04-15 00:11+0000\n" | "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||||
| "Last-Translator: deluxghost, 2025\n" | "Last-Translator: deluxghost, 2025\n" | ||||||
| "Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n" | "Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n" | ||||||
| @ -460,6 +460,36 @@ msgstr "许可证使用情况" | |||||||
| msgid "License Usage Records" | msgid "License Usage Records" | ||||||
| msgstr "许可证使用情况记录" | 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 | #: authentik/enterprise/policy.py | ||||||
| msgid "Enterprise required to access this feature." | msgid "Enterprise required to access this feature." | ||||||
| msgstr "访问此功能需要企业版。" | msgstr "访问此功能需要企业版。" | ||||||
| @ -1189,10 +1219,6 @@ msgstr "查看策略缓存指标" | |||||||
| msgid "Clear Policy's cache metrics" | msgid "Clear Policy's cache metrics" | ||||||
| msgstr "清除策略缓存指标" | 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 | #: authentik/policies/password/models.py | ||||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||||
| msgstr "密码哈希允许出现在 HaveIBeenPwned 中多少次" | msgstr "密码哈希允许出现在 HaveIBeenPwned 中多少次" | ||||||
| @ -1202,10 +1228,6 @@ msgid "" | |||||||
| "If the zxcvbn score is equal or less than this value, the policy will fail." | "If the zxcvbn score is equal or less than this value, the policy will fail." | ||||||
| msgstr "如果 zxcvbn 分数小于等于此值,则策略失败。" | msgstr "如果 zxcvbn 分数小于等于此值,则策略失败。" | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py |  | ||||||
| msgid "Password not set in context" |  | ||||||
| msgstr "未在上下文中设置密码" |  | ||||||
|  |  | ||||||
| #: authentik/policies/password/models.py | #: authentik/policies/password/models.py | ||||||
| msgid "Invalid password." | msgid "Invalid password." | ||||||
| msgstr "无效密码。" | msgstr "无效密码。" | ||||||
| @ -1247,20 +1269,6 @@ msgstr "信誉分数" | |||||||
| msgid "Reputation Scores" | msgid "Reputation Scores" | ||||||
| msgstr "信誉分数" | 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 | #: authentik/policies/templates/policies/denied.html | ||||||
| msgid "Permission denied" | msgid "Permission denied" | ||||||
| msgstr "权限被拒绝" | msgstr "权限被拒绝" | ||||||
| @ -2285,6 +2293,14 @@ msgstr "密码与 Active Directory 复杂度不匹配。" | |||||||
| msgid "No token received." | msgid "No token received." | ||||||
| msgstr "未收到令牌。" | msgstr "未收到令牌。" | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "HTTP Basic Authentication" | ||||||
|  | msgstr "HTTP 基本身份验证" | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "Include the client ID and secret as request parameters" | ||||||
|  | msgstr "包括客户端 ID 和密钥作为请求参数" | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "Request Token URL" | msgid "Request Token URL" | ||||||
| msgstr "请求令牌 URL" | msgstr "请求令牌 URL" | ||||||
| @ -2323,6 +2339,12 @@ msgstr "authentik 用来获取用户信息的 URL。" | |||||||
| msgid "Additional Scopes" | msgid "Additional Scopes" | ||||||
| msgstr "额外的作用域" | msgstr "额外的作用域" | ||||||
|  |  | ||||||
|  | #: authentik/sources/oauth/models.py | ||||||
|  | msgid "" | ||||||
|  | "How to perform authentication during an authorization_code token request " | ||||||
|  | "flow" | ||||||
|  | msgstr "在 authorization_code 令牌请求流程期间,如何执行身份验证" | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "OAuth Source" | msgid "OAuth Source" | ||||||
| msgstr "OAuth 源" | msgstr "OAuth 源" | ||||||
| @ -3193,6 +3215,12 @@ msgid "" | |||||||
| "info is entered." | "info is entered." | ||||||
| msgstr "启用时,即使输入错误的用户信息,此阶段也会成功并继续。" | 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 | #: authentik/stages/identification/models.py | ||||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||||
| msgstr "可选注册流程,链接在页面底部。" | msgstr "可选注册流程,链接在页面底部。" | ||||||
|  | |||||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										44519
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										44519
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										49
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								package.json
									
									
									
									
									
								
							| @ -1,50 +1,5 @@ | |||||||
| { | { | ||||||
|     "name": "@goauthentik/universe", |     "name": "@goauthentik/authentik", | ||||||
|     "version": "2025.2.4", |     "version": "2025.2.4", | ||||||
|     "description": "Monorepo for authentik.", |     "private": true | ||||||
|     "private": true, |  | ||||||
|     "scripts": { |  | ||||||
|         "lint": "run-s lint:prettier:check lint:eslint:check", |  | ||||||
|         "lint:eslint:check": "eslint .", |  | ||||||
|         "lint:eslint:fix": "eslint --fix .", |  | ||||||
|         "lint:fix": "run-s lint:prettier:fix lint:eslint:fix", |  | ||||||
|         "lint:prettier:check": "prettier --cache --check -u .", |  | ||||||
|         "lint:prettier:fix": "prettier --cache --write -u .", |  | ||||||
|         "typecheck": "NODE_OPTIONS=\"--max-old-space-size=3000\" tsc -b" |  | ||||||
|     }, |  | ||||||
|     "dependencies": { |  | ||||||
|         "@eslint/js": "^9.11.1", |  | ||||||
|         "@trivago/prettier-plugin-sort-imports": "^5.2.2", |  | ||||||
|         "@typescript-eslint/eslint-plugin": "^8.28.0", |  | ||||||
|         "@typescript-eslint/parser": "^8.28.0", |  | ||||||
|         "eslint": "^9.23.0", |  | ||||||
|         "eslint-plugin-lit": "^2.0.0", |  | ||||||
|         "eslint-plugin-wc": "^3.0.0", |  | ||||||
|         "npm-run-all": "^4.1.5", |  | ||||||
|         "prettier": "^3.5.3", |  | ||||||
|         "prettier-plugin-django-alpine": "^1.3.0", |  | ||||||
|         "prettier-plugin-packagejson": "^2.5.10", |  | ||||||
|         "typescript": "^5.8.2", |  | ||||||
|         "typescript-eslint": "^8.29.0", |  | ||||||
|         "zx": "^8.4.1" |  | ||||||
|     }, |  | ||||||
|     "optionalDependencies": { |  | ||||||
|         "@esbuild/darwin-arm64": "^0.24.0", |  | ||||||
|         "@esbuild/linux-amd64": "^0.18.11", |  | ||||||
|         "@esbuild/linux-arm64": "^0.24.0", |  | ||||||
|         "@rollup/rollup-darwin-arm64": "4.23.0", |  | ||||||
|         "@rollup/rollup-linux-arm64-gnu": "4.23.0", |  | ||||||
|         "@rollup/rollup-linux-x64-gnu": "4.23.0" |  | ||||||
|     }, |  | ||||||
|     "engines": { |  | ||||||
|         "node": ">=20.11" |  | ||||||
|     }, |  | ||||||
|     "workspaces": [ |  | ||||||
|         "gen-ts-api", |  | ||||||
|         "web", |  | ||||||
|         "web/packages/*", |  | ||||||
|         "website", |  | ||||||
|         "packages/*" |  | ||||||
|     ], |  | ||||||
|     "prettier": "@goauthentik/prettier-config" |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| :root { | :root { | ||||||
|     --ifm-menu-link-padding-vertical: 1em; |     --ifm-menu-link-padding-vertical: 0.5em; | ||||||
| } | } | ||||||
|  |  | ||||||
| .menu__list-item { | .menu__list-item { | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	