Compare commits
	
		
			150 Commits
		
	
	
		
			version/20
			...
			version-20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4a1acd377b | |||
| c5b84a91d1 | |||
| e77ecda3b8 | |||
| 4e317c10c5 | |||
| eb05a3ddb8 | |||
| a22d6a0924 | |||
| 3f0d67779a | |||
| 0a937ae8e9 | |||
| f8d94f3039 | |||
| 6bb261ac62 | |||
| 45f2c5bae7 | |||
| 5d8c1aa0b0 | |||
| 0101368369 | |||
| 4854f81592 | |||
| 4bed6e02e5 | |||
| 908f123d0e | |||
| 256dd24a1e | |||
| d4284407f9 | |||
| 80da5dfc52 | |||
| b6edf990e0 | |||
| a66dcf9382 | |||
| 9095a840d5 | |||
| 72259f6479 | |||
| 0973c74b9d | |||
| c7ed4f7ac1 | |||
| 3d577cf15e | |||
| 5474a32573 | |||
| a5940b88e3 | |||
| ff15716012 | |||
| c040b13b29 | |||
| 4915e980c5 | |||
| df362dd9ea | |||
| d4e4f93cb4 | |||
| 3af0de6a00 | |||
| 4f24d61290 | |||
| 4c5c4dcf2c | |||
| 660b5cb6c6 | |||
| 6ff1ea73a9 | |||
| 3de224690a | |||
| d4624b510a | |||
| 8856d762d0 | |||
| 5d1cbf14d1 | |||
| 6d5207f644 | |||
| 3b6497cd51 | |||
| ff7320b0f8 | |||
| e5a393c534 | |||
| bb4be944dc | |||
| 21efee8f44 | |||
| f61549a60f | |||
| 0a7bafd1b2 | |||
| b3987c5fa0 | |||
| 0da043a9fe | |||
| f336f204cb | |||
| 3bfcf18492 | |||
| dfafe8b43d | |||
| b5d43b15f8 | |||
| 2ccab75021 | |||
| 9070df6c26 | |||
| a1c8ad55ad | |||
| 872c05c690 | |||
| a9528dc1b5 | |||
| 0e59ade1f2 | |||
| 5ac49c695d | |||
| 3a30ecbe76 | |||
| 1f838bb2aa | |||
| cc42830e23 | |||
| 593eb959ca | |||
| 5bb6785ad6 | |||
| 535c11a729 | |||
| a0fa8d8524 | |||
| c14025c579 | |||
| 8bc3db7c90 | |||
| e194715c3e | |||
| 787f02d5dc | |||
| a0ed01a610 | |||
| 02ba493759 | |||
| a7fea5434d | |||
| 4fb783e953 | |||
| affbf85699 | |||
| 0d92112a3f | |||
| b1ad3ec9db | |||
| c0601baca6 | |||
| 057c5c5e9a | |||
| 05429ab848 | |||
| b66d51a699 | |||
| f834bc0ff2 | |||
| 93fd883d7a | |||
| 7e080d4d68 | |||
| 3e3ca22d04 | |||
| e741caa6b3 | |||
| 4343246a41 | |||
| 3f6f83b4b6 | |||
| c63e1c9b87 | |||
| f44cf06d22 | |||
| 3f609b8601 | |||
| edd89b44a4 | |||
| 3e58748862 | |||
| 7088a6b0e6 | |||
| 6c880e0e62 | |||
| cb1e70be7f | |||
| 6ba150f737 | |||
| 131769ea73 | |||
| e68adbb30d | |||
| f1eef09099 | |||
| 5ab3c7fa9f | |||
| d0cec39a0f | |||
| e15f53a39a | |||
| 25fb995663 | |||
| eac658c64f | |||
| 15e2032493 | |||
| c87f6cd9d9 | |||
| e758995458 | |||
| 20c284a188 | |||
| b0936ea8f3 | |||
| bfc0f4a413 | |||
| 1a9a90cf6a | |||
| 00f1a6fa48 | |||
| 33754a06d2 | |||
| 69b838e1cf | |||
| d5e04a2301 | |||
| fbf251280f | |||
| eaadf62f01 | |||
| 8c33e7a7c1 | |||
| a7d9a80a28 | |||
| 2ea5dce8d3 | |||
| 14bf01efe4 | |||
| 67b24a60e4 | |||
| e6775297cb | |||
| 4e4e2b36b6 | |||
| 3189c56fc3 | |||
| 5b5ea47b7a | |||
| caa382f898 | |||
| 2d63488197 | |||
| c1c8e4c8d4 | |||
| a0e451c5e5 | |||
| eaba8006e6 | |||
| 39ff202f8c | |||
| 654e0d6245 | |||
| ec04443493 | |||
| d247c262af | |||
| dff49b2bef | |||
| 50666a76fb | |||
| b51a7f9746 | |||
| 001dfd9f6c | |||
| 5e4fbeeb25 | |||
| 2c910bf6ca | |||
| 9b11319e81 | |||
| 40dc4b3fb8 | |||
| 0e37b98968 | |||
| 7e132eb014 | 
| @ -1,5 +1,5 @@ | |||||||
| [bumpversion] | [bumpversion] | ||||||
| current_version = 2022.1.5 | current_version = 2022.2.1 | ||||||
| tag = True | tag = True | ||||||
| commit = True | commit = True | ||||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -86,10 +86,9 @@ jobs: | |||||||
|           cp authentik/lib/default.yml local.env.yml |           cp authentik/lib/default.yml local.env.yml | ||||||
|           cp -R .github .. |           cp -R .github .. | ||||||
|           cp -R scripts .. |           cp -R scripts .. | ||||||
|           cp -R poetry.lock pyproject.toml .. |  | ||||||
|           git checkout $(git describe --abbrev=0 --match 'version/*') |           git checkout $(git describe --abbrev=0 --match 'version/*') | ||||||
|           rm -rf .github/ scripts/ |           rm -rf .github/ scripts/ | ||||||
|           mv ../.github ../scripts ../poetry.lock ../pyproject.toml . |           mv ../.github ../scripts . | ||||||
|       - name: prepare |       - name: prepare | ||||||
|         env: |         env: | ||||||
|           INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }} |           INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }} | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -30,14 +30,14 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           push: ${{ github.event_name == 'release' }} |           push: ${{ github.event_name == 'release' }} | ||||||
|           tags: | |           tags: | | ||||||
|             beryju/authentik:2022.1.5, |             beryju/authentik:2022.2.1, | ||||||
|             beryju/authentik:latest, |             beryju/authentik:latest, | ||||||
|             ghcr.io/goauthentik/server:2022.1.5, |             ghcr.io/goauthentik/server:2022.2.1, | ||||||
|             ghcr.io/goauthentik/server:latest |             ghcr.io/goauthentik/server:latest | ||||||
|           platforms: linux/amd64,linux/arm64 |           platforms: linux/amd64,linux/arm64 | ||||||
|           context: . |           context: . | ||||||
|       - name: Building Docker Image (stable) |       - name: Building Docker Image (stable) | ||||||
|         if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }} |         if: ${{ github.event_name == 'release' && !contains('2022.2.1', 'rc') }} | ||||||
|         run: | |         run: | | ||||||
|           docker pull beryju/authentik:latest |           docker pull beryju/authentik:latest | ||||||
|           docker tag beryju/authentik:latest beryju/authentik:stable |           docker tag beryju/authentik:latest beryju/authentik:stable | ||||||
| @ -78,14 +78,14 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           push: ${{ github.event_name == 'release' }} |           push: ${{ github.event_name == 'release' }} | ||||||
|           tags: | |           tags: | | ||||||
|             beryju/authentik-${{ matrix.type }}:2022.1.5, |             beryju/authentik-${{ matrix.type }}:2022.2.1, | ||||||
|             beryju/authentik-${{ matrix.type }}:latest, |             beryju/authentik-${{ matrix.type }}:latest, | ||||||
|             ghcr.io/goauthentik/${{ matrix.type }}:2022.1.5, |             ghcr.io/goauthentik/${{ matrix.type }}:2022.2.1, | ||||||
|             ghcr.io/goauthentik/${{ matrix.type }}:latest |             ghcr.io/goauthentik/${{ matrix.type }}:latest | ||||||
|           file: ${{ matrix.type }}.Dockerfile |           file: ${{ matrix.type }}.Dockerfile | ||||||
|           platforms: linux/amd64,linux/arm64 |           platforms: linux/amd64,linux/arm64 | ||||||
|       - name: Building Docker Image (stable) |       - name: Building Docker Image (stable) | ||||||
|         if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }} |         if: ${{ github.event_name == 'release' && !contains('2022.2.1', 'rc') }} | ||||||
|         run: | |         run: | | ||||||
|           docker pull beryju/authentik-${{ matrix.type }}:latest |           docker pull beryju/authentik-${{ matrix.type }}:latest | ||||||
|           docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable |           docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable | ||||||
| @ -170,7 +170,7 @@ jobs: | |||||||
|           SENTRY_PROJECT: authentik |           SENTRY_PROJECT: authentik | ||||||
|           SENTRY_URL: https://sentry.beryju.org |           SENTRY_URL: https://sentry.beryju.org | ||||||
|         with: |         with: | ||||||
|           version: authentik@2022.1.5 |           version: authentik@2022.2.1 | ||||||
|           environment: beryjuorg-prod |           environment: beryjuorg-prod | ||||||
|           sourcemaps: './web/dist' |           sourcemaps: './web/dist' | ||||||
|           url_prefix: '~/static/dist' |           url_prefix: '~/static/dist' | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/release-tag.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release-tag.yml
									
									
									
									
										vendored
									
									
								
							| @ -27,7 +27,7 @@ jobs: | |||||||
|           docker-compose run -u root server test |           docker-compose run -u root server test | ||||||
|       - name: Extract version number |       - name: Extract version number | ||||||
|         id: get_version |         id: get_version | ||||||
|         uses: actions/github-script@v5 |         uses: actions/github-script@v6 | ||||||
|         with: |         with: | ||||||
|           github-token: ${{ secrets.GITHUB_TOKEN }} |           github-token: ${{ secrets.GITHUB_TOKEN }} | ||||||
|           script: | |           script: | | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ ENV NODE_ENV=production | |||||||
| RUN cd /work/web && npm i && npm run build | RUN cd /work/web && npm i && npm run build | ||||||
|  |  | ||||||
| # Stage 3: Build go proxy | # Stage 3: Build go proxy | ||||||
| FROM docker.io/golang:1.17.6-bullseye AS builder | FROM docker.io/golang:1.17.7-bullseye AS builder | ||||||
|  |  | ||||||
| WORKDIR /work | WORKDIR /work | ||||||
|  |  | ||||||
|  | |||||||
| @ -6,8 +6,8 @@ | |||||||
|  |  | ||||||
| | Version    | Supported          | | | Version    | Supported          | | ||||||
| | ---------- | ------------------ | | | ---------- | ------------------ | | ||||||
| | 2021.10.x  | :white_check_mark: | | | 2022.1.x   | :white_check_mark: | | ||||||
| | 2021.12.x  | :white_check_mark: | | | 2022.2.x   | :white_check_mark: | | ||||||
|  |  | ||||||
| ## Reporting a Vulnerability | ## Reporting a Vulnerability | ||||||
|  |  | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| from os import environ | from os import environ | ||||||
| from typing import Optional | from typing import Optional | ||||||
|  |  | ||||||
| __version__ = "2022.1.5" | __version__ = "2022.2.1" | ||||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -12,10 +12,13 @@ from rest_framework.permissions import IsAdminUser | |||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.viewsets import ViewSet | from rest_framework.viewsets import ViewSet | ||||||
|  | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
| from authentik.core.api.utils import PassiveSerializer | from authentik.core.api.utils import PassiveSerializer | ||||||
| from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus | from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus | ||||||
|  |  | ||||||
|  | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
| class TaskSerializer(PassiveSerializer): | class TaskSerializer(PassiveSerializer): | ||||||
|     """Serialize TaskInfo and TaskResult""" |     """Serialize TaskInfo and TaskResult""" | ||||||
| @ -89,6 +92,7 @@ class TaskViewSet(ViewSet): | |||||||
|         try: |         try: | ||||||
|             task_module = import_module(task.task_call_module) |             task_module = import_module(task.task_call_module) | ||||||
|             task_func = getattr(task_module, task.task_call_func) |             task_func = getattr(task_module, task.task_call_func) | ||||||
|  |             LOGGER.debug("Running task", task=task_func) | ||||||
|             task_func.delay(*task.task_call_args, **task.task_call_kwargs) |             task_func.delay(*task.task_call_args, **task.task_call_kwargs) | ||||||
|             messages.success( |             messages.success( | ||||||
|                 self.request, |                 self.request, | ||||||
| @ -96,6 +100,7 @@ class TaskViewSet(ViewSet): | |||||||
|             ) |             ) | ||||||
|             return Response(status=204) |             return Response(status=204) | ||||||
|         except (ImportError, AttributeError):  # pragma: no cover |         except (ImportError, AttributeError):  # pragma: no cover | ||||||
|  |             LOGGER.warning("Failed to run task, remove state", task=task) | ||||||
|             # if we get an import error, the module path has probably changed |             # if we get an import error, the module path has probably changed | ||||||
|             task.delete() |             task.delete() | ||||||
|             return Response(status=500) |             return Response(status=500) | ||||||
|  | |||||||
| @ -1,10 +1,9 @@ | |||||||
| """core Configs API""" | """core Configs API""" | ||||||
| from os import environ, path | from os import path | ||||||
|  |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.db import models | from django.db import models | ||||||
| from drf_spectacular.utils import extend_schema | from drf_spectacular.utils import extend_schema | ||||||
| from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME |  | ||||||
| from rest_framework.fields import ( | from rest_framework.fields import ( | ||||||
|     BooleanField, |     BooleanField, | ||||||
|     CharField, |     CharField, | ||||||
| @ -28,7 +27,6 @@ class Capabilities(models.TextChoices): | |||||||
|  |  | ||||||
|     CAN_SAVE_MEDIA = "can_save_media" |     CAN_SAVE_MEDIA = "can_save_media" | ||||||
|     CAN_GEO_IP = "can_geo_ip" |     CAN_GEO_IP = "can_geo_ip" | ||||||
|     CAN_BACKUP = "can_backup" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ErrorReportingConfigSerializer(PassiveSerializer): | class ErrorReportingConfigSerializer(PassiveSerializer): | ||||||
| @ -65,13 +63,6 @@ class ConfigView(APIView): | |||||||
|             caps.append(Capabilities.CAN_SAVE_MEDIA) |             caps.append(Capabilities.CAN_SAVE_MEDIA) | ||||||
|         if GEOIP_READER.enabled: |         if GEOIP_READER.enabled: | ||||||
|             caps.append(Capabilities.CAN_GEO_IP) |             caps.append(Capabilities.CAN_GEO_IP) | ||||||
|         if SERVICE_HOST_ENV_NAME in environ: |  | ||||||
|             # Running in k8s, only s3 backup is supported |  | ||||||
|             if CONFIG.y("postgresql.s3_backup"): |  | ||||||
|                 caps.append(Capabilities.CAN_BACKUP) |  | ||||||
|         else: |  | ||||||
|             # Running in compose, backup is always supported |  | ||||||
|             caps.append(Capabilities.CAN_BACKUP) |  | ||||||
|         return caps |         return caps | ||||||
|  |  | ||||||
|     @extend_schema(responses={200: ConfigSerializer(many=False)}) |     @extend_schema(responses={200: ConfigSerializer(many=False)}) | ||||||
|  | |||||||
| @ -56,7 +56,11 @@ class ApplicationSerializer(ModelSerializer): | |||||||
|         if isinstance(user, SimpleLazyObject): |         if isinstance(user, SimpleLazyObject): | ||||||
|             user._setup() |             user._setup() | ||||||
|             user = user._wrapped |             user = user._wrapped | ||||||
|         return url % user.__dict__ |         try: | ||||||
|  |             return url % user.__dict__ | ||||||
|  |         except ValueError as exc: | ||||||
|  |             LOGGER.warning("Failed to format launch url", exc=exc) | ||||||
|  |             return url | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,17 +1,7 @@ | |||||||
| """authentik core tasks""" | """authentik core tasks""" | ||||||
| from datetime import datetime |  | ||||||
| from io import StringIO |  | ||||||
| from os import environ |  | ||||||
|  |  | ||||||
| from boto3.exceptions import Boto3Error |  | ||||||
| from botocore.exceptions import BotoCoreError, ClientError |  | ||||||
| from dbbackup.db.exceptions import CommandConnectorError |  | ||||||
| from django.contrib.humanize.templatetags.humanize import naturaltime |  | ||||||
| from django.contrib.sessions.backends.cache import KEY_PREFIX | from django.contrib.sessions.backends.cache import KEY_PREFIX | ||||||
| from django.core import management |  | ||||||
| from django.core.cache import cache | from django.core.cache import cache | ||||||
| from django.utils.timezone import now | from django.utils.timezone import now | ||||||
| from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME |  | ||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
| from authentik.core.models import AuthenticatedSession, ExpiringModel | from authentik.core.models import AuthenticatedSession, ExpiringModel | ||||||
| @ -21,7 +11,6 @@ from authentik.events.monitored_tasks import ( | |||||||
|     TaskResultStatus, |     TaskResultStatus, | ||||||
|     prefill_task, |     prefill_task, | ||||||
| ) | ) | ||||||
| from authentik.lib.config import CONFIG |  | ||||||
| from authentik.root.celery import CELERY_APP | from authentik.root.celery import CELERY_APP | ||||||
|  |  | ||||||
| LOGGER = get_logger() | LOGGER = get_logger() | ||||||
| @ -53,46 +42,3 @@ def clean_expired_models(self: MonitoredTask): | |||||||
|     LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount) |     LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount) | ||||||
|     messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}") |     messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}") | ||||||
|     self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages)) |     self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def should_backup() -> bool: |  | ||||||
|     """Check if we should be doing backups""" |  | ||||||
|     if SERVICE_HOST_ENV_NAME in environ and not CONFIG.y("postgresql.s3_backup.bucket"): |  | ||||||
|         LOGGER.info("Running in k8s and s3 backups are not configured, skipping") |  | ||||||
|         return False |  | ||||||
|     if not CONFIG.y_bool("postgresql.backup.enabled"): |  | ||||||
|         return False |  | ||||||
|     return True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @CELERY_APP.task(bind=True, base=MonitoredTask) |  | ||||||
| @prefill_task |  | ||||||
| def backup_database(self: MonitoredTask):  # pragma: no cover |  | ||||||
|     """Database backup""" |  | ||||||
|     self.result_timeout_hours = 25 |  | ||||||
|     if not should_backup(): |  | ||||||
|         self.set_status(TaskResult(TaskResultStatus.UNKNOWN, ["Backups are not configured."])) |  | ||||||
|         return |  | ||||||
|     try: |  | ||||||
|         start = datetime.now() |  | ||||||
|         out = StringIO() |  | ||||||
|         management.call_command("dbbackup", quiet=True, stdout=out) |  | ||||||
|         self.set_status( |  | ||||||
|             TaskResult( |  | ||||||
|                 TaskResultStatus.SUCCESSFUL, |  | ||||||
|                 [ |  | ||||||
|                     f"Successfully finished database backup {naturaltime(start)} {out.getvalue()}", |  | ||||||
|                 ], |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|         LOGGER.info("Successfully backed up database.") |  | ||||||
|     except ( |  | ||||||
|         IOError, |  | ||||||
|         BotoCoreError, |  | ||||||
|         ClientError, |  | ||||||
|         Boto3Error, |  | ||||||
|         PermissionError, |  | ||||||
|         CommandConnectorError, |  | ||||||
|         ValueError, |  | ||||||
|     ) as exc: |  | ||||||
|         self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc)) |  | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ | |||||||
|         {% block head_before %} |         {% block head_before %} | ||||||
|         {% endblock %} |         {% endblock %} | ||||||
|         <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}"> |         <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}"> | ||||||
|  |         <link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}"> | ||||||
|         <script src="{% static 'dist/poly.js' %}" type="module"></script> |         <script src="{% static 'dist/poly.js' %}" type="module"></script> | ||||||
|         {% block head %} |         {% block head %} | ||||||
|         {% endblock %} |         {% endblock %} | ||||||
|  | |||||||
| @ -1,7 +1,5 @@ | |||||||
| """events GeoIP Reader""" | """events GeoIP Reader""" | ||||||
| from datetime import datetime |  | ||||||
| from os import stat | from os import stat | ||||||
| from time import time |  | ||||||
| from typing import Optional, TypedDict | from typing import Optional, TypedDict | ||||||
|  |  | ||||||
| from geoip2.database import Reader | from geoip2.database import Reader | ||||||
| @ -46,14 +44,18 @@ class GeoIPReader: | |||||||
|             LOGGER.warning("Failed to load GeoIP database", exc=exc) |             LOGGER.warning("Failed to load GeoIP database", exc=exc) | ||||||
|  |  | ||||||
|     def __check_expired(self): |     def __check_expired(self): | ||||||
|         """Check if the geoip database has been opened longer than 8 hours, |         """Check if the modification date of the GeoIP database has | ||||||
|         and re-open it, as it will probably will have been re-downloaded""" |         changed, and reload it if so""" | ||||||
|         now = time() |         path = CONFIG.y("geoip") | ||||||
|         diff = datetime.fromtimestamp(now) - datetime.fromtimestamp(self.__last_mtime) |         try: | ||||||
|         diff_hours = diff.total_seconds() // 3600 |             mtime = stat(path).st_mtime | ||||||
|         if diff_hours >= 8: |             diff = self.__last_mtime < mtime | ||||||
|             LOGGER.info("GeoIP databased loaded too long, re-opening", diff=diff) |             if diff > 0: | ||||||
|             self.__open() |                 LOGGER.info("Found new GeoIP Database, reopening", diff=diff) | ||||||
|  |                 self.__open() | ||||||
|  |         except OSError as exc: | ||||||
|  |             LOGGER.warning("Failed to check GeoIP age", exc=exc) | ||||||
|  |             return | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def enabled(self) -> bool: |     def enabled(self) -> bool: | ||||||
|  | |||||||
| @ -5,16 +5,6 @@ postgresql: | |||||||
|   user: authentik |   user: authentik | ||||||
|   port: 5432 |   port: 5432 | ||||||
|   password: 'env://POSTGRES_PASSWORD' |   password: 'env://POSTGRES_PASSWORD' | ||||||
|   backup: |  | ||||||
|     enabled: false |  | ||||||
|   s3_backup: |  | ||||||
|     access_key: "" |  | ||||||
|     secret_key: "" |  | ||||||
|     bucket: "" |  | ||||||
|     region: eu-central-1 |  | ||||||
|     host: "" |  | ||||||
|     location: "" |  | ||||||
|     insecure_skip_verify: false |  | ||||||
|  |  | ||||||
| web: | web: | ||||||
|   listen: 0.0.0.0:9000 |   listen: 0.0.0.0:9000 | ||||||
| @ -65,6 +55,7 @@ outposts: | |||||||
|   # %(version)s: Current version; 2021.4.1 |   # %(version)s: Current version; 2021.4.1 | ||||||
|   # %(build_hash)s: Build hash if you're running a beta version |   # %(build_hash)s: Build hash if you're running a beta version | ||||||
|   container_image_base: ghcr.io/goauthentik/%(type)s:%(version)s |   container_image_base: ghcr.io/goauthentik/%(type)s:%(version)s | ||||||
|  |   discover: true | ||||||
|  |  | ||||||
| cookie_domain: null | cookie_domain: null | ||||||
| disable_update_check: false | disable_update_check: false | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								authentik/lib/merge.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								authentik/lib/merge.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | """merge utils""" | ||||||
|  | from deepmerge import Merger | ||||||
|  |  | ||||||
|  | MERGE_LIST_UNIQUE = Merger( | ||||||
|  |     [(list, ["append_unique"]), (dict, ["merge"]), (set, ["union"])], ["override"], ["override"] | ||||||
|  | ) | ||||||
| @ -3,8 +3,6 @@ from typing import Optional | |||||||
|  |  | ||||||
| from aioredis.errors import ConnectionClosedError, ReplyError | from aioredis.errors import ConnectionClosedError, ReplyError | ||||||
| from billiard.exceptions import SoftTimeLimitExceeded, WorkerLostError | from billiard.exceptions import SoftTimeLimitExceeded, WorkerLostError | ||||||
| from botocore.client import ClientError |  | ||||||
| from botocore.exceptions import BotoCoreError |  | ||||||
| from celery.exceptions import CeleryError | from celery.exceptions import CeleryError | ||||||
| from channels.middleware import BaseMiddleware | from channels.middleware import BaseMiddleware | ||||||
| from channels_redis.core import ChannelFull | from channels_redis.core import ChannelFull | ||||||
| @ -81,9 +79,6 @@ def before_send(event: dict, hint: dict) -> Optional[dict]: | |||||||
|         WorkerLostError, |         WorkerLostError, | ||||||
|         CeleryError, |         CeleryError, | ||||||
|         SoftTimeLimitExceeded, |         SoftTimeLimitExceeded, | ||||||
|         # S3 errors |  | ||||||
|         BotoCoreError, |  | ||||||
|         ClientError, |  | ||||||
|         # custom baseclass |         # custom baseclass | ||||||
|         SentryIgnoredException, |         SentryIgnoredException, | ||||||
|         # ldap errors |         # ldap errors | ||||||
| @ -101,8 +96,6 @@ def before_send(event: dict, hint: dict) -> Optional[dict]: | |||||||
|             return None |             return None | ||||||
|     if "logger" in event: |     if "logger" in event: | ||||||
|         if event["logger"] in [ |         if event["logger"] in [ | ||||||
|             "dbbackup", |  | ||||||
|             "botocore", |  | ||||||
|             "kombu", |             "kombu", | ||||||
|             "asyncio", |             "asyncio", | ||||||
|             "multiprocessing", |             "multiprocessing", | ||||||
|  | |||||||
| @ -3,6 +3,8 @@ import os | |||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from tempfile import gettempdir | from tempfile import gettempdir | ||||||
|  |  | ||||||
|  | from docker.errors import DockerException | ||||||
|  |  | ||||||
| from authentik.crypto.models import CertificateKeyPair | from authentik.crypto.models import CertificateKeyPair | ||||||
|  |  | ||||||
| HEADER = "### Managed by authentik" | HEADER = "### Managed by authentik" | ||||||
| @ -27,6 +29,8 @@ class DockerInlineSSH: | |||||||
|     def __init__(self, host: str, keypair: CertificateKeyPair) -> None: |     def __init__(self, host: str, keypair: CertificateKeyPair) -> None: | ||||||
|         self.host = host |         self.host = host | ||||||
|         self.keypair = keypair |         self.keypair = keypair | ||||||
|  |         if not self.keypair: | ||||||
|  |             raise DockerException("keypair must be set for SSH connections") | ||||||
|         self.config_path = Path("~/.ssh/config").expanduser() |         self.config_path = Path("~/.ssh/config").expanduser() | ||||||
|         self.header = f"{HEADER} - {self.host}\n" |         self.header = f"{HEADER} - {self.host}\n" | ||||||
|  |  | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ from authentik.events.monitored_tasks import ( | |||||||
|     TaskResultStatus, |     TaskResultStatus, | ||||||
|     prefill_task, |     prefill_task, | ||||||
| ) | ) | ||||||
|  | from authentik.lib.config import CONFIG | ||||||
| from authentik.lib.utils.reflection import path_to_class | from authentik.lib.utils.reflection import path_to_class | ||||||
| from authentik.outposts.controllers.base import BaseController, ControllerException | from authentik.outposts.controllers.base import BaseController, ControllerException | ||||||
| from authentik.outposts.controllers.docker import DockerClient | from authentik.outposts.controllers.docker import DockerClient | ||||||
| @ -231,6 +232,9 @@ def _outpost_single_update(outpost: Outpost, layer=None): | |||||||
| @CELERY_APP.task() | @CELERY_APP.task() | ||||||
| def outpost_local_connection(): | def outpost_local_connection(): | ||||||
|     """Checks the local environment and create Service connections.""" |     """Checks the local environment and create Service connections.""" | ||||||
|  |     if not CONFIG.y_bool("outposts.discover"): | ||||||
|  |         LOGGER.debug("outpost integration discovery is disabled") | ||||||
|  |         return | ||||||
|     # Explicitly check against token filename, as that's |     # Explicitly check against token filename, as that's | ||||||
|     # only present when the integration is enabled |     # only present when the integration is enabled | ||||||
|     if Path(SERVICE_TOKEN_FILENAME).exists(): |     if Path(SERVICE_TOKEN_FILENAME).exists(): | ||||||
|  | |||||||
| @ -45,6 +45,13 @@ class GrantTypes(models.TextChoices): | |||||||
|     HYBRID = "hybrid" |     HYBRID = "hybrid" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ResponseMode(models.TextChoices): | ||||||
|  |     """https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#OAuth.Post""" | ||||||
|  |  | ||||||
|  |     QUERY = "query" | ||||||
|  |     FRAGMENT = "fragment" | ||||||
|  |  | ||||||
|  |  | ||||||
| class SubModes(models.TextChoices): | class SubModes(models.TextChoices): | ||||||
|     """Mode after which 'sub' attribute is generateed, for compatibility reasons""" |     """Mode after which 'sub' attribute is generateed, for compatibility reasons""" | ||||||
|  |  | ||||||
|  | |||||||
| @ -43,7 +43,7 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             name="test", |             name="test", | ||||||
|             client_id="test", |             client_id="test", | ||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris="http://local.invalid", |             redirect_uris="http://local.invalid/Foo", | ||||||
|         ) |         ) | ||||||
|         with self.assertRaises(AuthorizeError): |         with self.assertRaises(AuthorizeError): | ||||||
|             request = self.factory.get( |             request = self.factory.get( | ||||||
| @ -51,7 +51,7 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 data={ |                 data={ | ||||||
|                     "response_type": "code", |                     "response_type": "code", | ||||||
|                     "client_id": "test", |                     "client_id": "test", | ||||||
|                     "redirect_uri": "http://local.invalid", |                     "redirect_uri": "http://local.invalid/Foo", | ||||||
|                     "request": "foo", |                     "request": "foo", | ||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
| @ -105,26 +105,30 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             name="test", |             name="test", | ||||||
|             client_id="test", |             client_id="test", | ||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris="http://local.invalid", |             redirect_uris="http://local.invalid/Foo", | ||||||
|         ) |         ) | ||||||
|         request = self.factory.get( |         request = self.factory.get( | ||||||
|             "/", |             "/", | ||||||
|             data={ |             data={ | ||||||
|                 "response_type": "code", |                 "response_type": "code", | ||||||
|                 "client_id": "test", |                 "client_id": "test", | ||||||
|                 "redirect_uri": "http://local.invalid", |                 "redirect_uri": "http://local.invalid/Foo", | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             OAuthAuthorizationParams.from_request(request).grant_type, |             OAuthAuthorizationParams.from_request(request).grant_type, | ||||||
|             GrantTypes.AUTHORIZATION_CODE, |             GrantTypes.AUTHORIZATION_CODE, | ||||||
|         ) |         ) | ||||||
|  |         self.assertEqual( | ||||||
|  |             OAuthAuthorizationParams.from_request(request).redirect_uri, | ||||||
|  |             "http://local.invalid/Foo", | ||||||
|  |         ) | ||||||
|         request = self.factory.get( |         request = self.factory.get( | ||||||
|             "/", |             "/", | ||||||
|             data={ |             data={ | ||||||
|                 "response_type": "id_token", |                 "response_type": "id_token", | ||||||
|                 "client_id": "test", |                 "client_id": "test", | ||||||
|                 "redirect_uri": "http://local.invalid", |                 "redirect_uri": "http://local.invalid/Foo", | ||||||
|                 "scope": "openid", |                 "scope": "openid", | ||||||
|                 "state": "foo", |                 "state": "foo", | ||||||
|             }, |             }, | ||||||
| @ -140,7 +144,7 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 data={ |                 data={ | ||||||
|                     "response_type": "id_token", |                     "response_type": "id_token", | ||||||
|                     "client_id": "test", |                     "client_id": "test", | ||||||
|                     "redirect_uri": "http://local.invalid", |                     "redirect_uri": "http://local.invalid/Foo", | ||||||
|                     "state": "foo", |                     "state": "foo", | ||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
| @ -153,7 +157,7 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             data={ |             data={ | ||||||
|                 "response_type": "code token", |                 "response_type": "code token", | ||||||
|                 "client_id": "test", |                 "client_id": "test", | ||||||
|                 "redirect_uri": "http://local.invalid", |                 "redirect_uri": "http://local.invalid/Foo", | ||||||
|                 "scope": "openid", |                 "scope": "openid", | ||||||
|                 "state": "foo", |                 "state": "foo", | ||||||
|             }, |             }, | ||||||
| @ -167,7 +171,7 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 data={ |                 data={ | ||||||
|                     "response_type": "invalid", |                     "response_type": "invalid", | ||||||
|                     "client_id": "test", |                     "client_id": "test", | ||||||
|                     "redirect_uri": "http://local.invalid", |                     "redirect_uri": "http://local.invalid/Foo", | ||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|             OAuthAuthorizationParams.from_request(request) |             OAuthAuthorizationParams.from_request(request) | ||||||
|  | |||||||
| @ -44,6 +44,7 @@ from authentik.providers.oauth2.models import ( | |||||||
|     AuthorizationCode, |     AuthorizationCode, | ||||||
|     GrantTypes, |     GrantTypes, | ||||||
|     OAuth2Provider, |     OAuth2Provider, | ||||||
|  |     ResponseMode, | ||||||
|     ResponseTypes, |     ResponseTypes, | ||||||
| ) | ) | ||||||
| from authentik.providers.oauth2.utils import HttpResponseRedirectScheme | from authentik.providers.oauth2.utils import HttpResponseRedirectScheme | ||||||
| @ -99,7 +100,7 @@ class OAuthAuthorizationParams: | |||||||
|         # and POST request. |         # and POST request. | ||||||
|         query_dict = request.POST if request.method == "POST" else request.GET |         query_dict = request.POST if request.method == "POST" else request.GET | ||||||
|         state = query_dict.get("state") |         state = query_dict.get("state") | ||||||
|         redirect_uri = query_dict.get("redirect_uri", "").lower() |         redirect_uri = query_dict.get("redirect_uri", "") | ||||||
|  |  | ||||||
|         response_type = query_dict.get("response_type", "") |         response_type = query_dict.get("response_type", "") | ||||||
|         grant_type = None |         grant_type = None | ||||||
| @ -153,7 +154,10 @@ class OAuthAuthorizationParams: | |||||||
|     def check_redirect_uri(self): |     def check_redirect_uri(self): | ||||||
|         """Redirect URI validation.""" |         """Redirect URI validation.""" | ||||||
|         allowed_redirect_urls = self.provider.redirect_uris.split() |         allowed_redirect_urls = self.provider.redirect_uris.split() | ||||||
|         if not self.redirect_uri: |         # We don't want to actually lowercase the final URL we redirect to, | ||||||
|  |         # we only lowercase it for comparison | ||||||
|  |         redirect_uri = self.redirect_uri.lower() | ||||||
|  |         if not redirect_uri: | ||||||
|             LOGGER.warning("Missing redirect uri.") |             LOGGER.warning("Missing redirect uri.") | ||||||
|             raise RedirectUriError("", allowed_redirect_urls) |             raise RedirectUriError("", allowed_redirect_urls) | ||||||
|  |  | ||||||
| @ -169,7 +173,7 @@ class OAuthAuthorizationParams: | |||||||
|                 allow=self.redirect_uri, |                 allow=self.redirect_uri, | ||||||
|             ) |             ) | ||||||
|             return |             return | ||||||
|         if self.redirect_uri not in [x.lower() for x in allowed_redirect_urls]: |         if redirect_uri not in [x.lower() for x in allowed_redirect_urls]: | ||||||
|             LOGGER.warning( |             LOGGER.warning( | ||||||
|                 "Invalid redirect uri", |                 "Invalid redirect uri", | ||||||
|                 redirect_uri=self.redirect_uri, |                 redirect_uri=self.redirect_uri, | ||||||
| @ -299,13 +303,23 @@ class OAuthFulfillmentStage(StageView): | |||||||
|                 code = self.params.create_code(self.request) |                 code = self.params.create_code(self.request) | ||||||
|                 code.save(force_insert=True) |                 code.save(force_insert=True) | ||||||
|  |  | ||||||
|             if self.params.grant_type == GrantTypes.AUTHORIZATION_CODE: |             query_dict = self.request.POST if self.request.method == "POST" else self.request.GET | ||||||
|  |             response_mode = ResponseMode.QUERY | ||||||
|  |             # Get response mode from url param, otherwise decide based on grant type | ||||||
|  |             if "response_mode" in query_dict: | ||||||
|  |                 response_mode = query_dict["response_mode"] | ||||||
|  |             elif self.params.grant_type == GrantTypes.AUTHORIZATION_CODE: | ||||||
|  |                 response_mode = ResponseMode.QUERY | ||||||
|  |             elif self.params.grant_type in [GrantTypes.IMPLICIT, GrantTypes.HYBRID]: | ||||||
|  |                 response_mode = ResponseMode.FRAGMENT | ||||||
|  |  | ||||||
|  |             if response_mode == ResponseMode.QUERY: | ||||||
|                 query_params["code"] = code.code |                 query_params["code"] = code.code | ||||||
|                 query_params["state"] = [str(self.params.state) if self.params.state else ""] |                 query_params["state"] = [str(self.params.state) if self.params.state else ""] | ||||||
|  |  | ||||||
|                 uri = uri._replace(query=urlencode(query_params, doseq=True)) |                 uri = uri._replace(query=urlencode(query_params, doseq=True)) | ||||||
|                 return urlunsplit(uri) |                 return urlunsplit(uri) | ||||||
|             if self.params.grant_type in [GrantTypes.IMPLICIT, GrantTypes.HYBRID]: |             if response_mode == ResponseMode.FRAGMENT: | ||||||
|                 query_fragment = self.create_implicit_response(code) |                 query_fragment = self.create_implicit_response(code) | ||||||
|  |  | ||||||
|                 uri = uri._replace( |                 uri = uri._replace( | ||||||
|  | |||||||
| @ -12,4 +12,8 @@ class AuthentikProviderProxyConfig(AppConfig): | |||||||
|     verbose_name = "authentik Providers.Proxy" |     verbose_name = "authentik Providers.Proxy" | ||||||
|  |  | ||||||
|     def ready(self) -> None: |     def ready(self) -> None: | ||||||
|  |         from authentik.providers.proxy.tasks import proxy_set_defaults | ||||||
|  |  | ||||||
|         import_module("authentik.providers.proxy.managed") |         import_module("authentik.providers.proxy.managed") | ||||||
|  |  | ||||||
|  |         proxy_set_defaults.delay() | ||||||
|  | |||||||
| @ -28,12 +28,12 @@ class ProxyDockerController(DockerController): | |||||||
|         labels["traefik.enable"] = "true" |         labels["traefik.enable"] = "true" | ||||||
|         labels[ |         labels[ | ||||||
|             f"traefik.http.routers.{traefik_name}-router.rule" |             f"traefik.http.routers.{traefik_name}-router.rule" | ||||||
|         ] = f"Host({','.join(hosts)}) && PathPrefix(`/akprox`)" |         ] = f"Host({','.join(hosts)}) && PathPrefix(`/outpost.goauthentik.io`)" | ||||||
|         labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true" |         labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true" | ||||||
|         labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service" |         labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service" | ||||||
|         labels[ |         labels[ | ||||||
|             f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path" |             f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path" | ||||||
|         ] = "/akprox/ping" |         ] = "/outpost.goauthentik.io/ping" | ||||||
|         labels[ |         labels[ | ||||||
|             f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.port" |             f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.port" | ||||||
|         ] = "9300" |         ] = "9300" | ||||||
|  | |||||||
| @ -92,6 +92,8 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]): | |||||||
|             # Buffer sizes for large headers with JWTs |             # Buffer sizes for large headers with JWTs | ||||||
|             "nginx.ingress.kubernetes.io/proxy-buffers-number": "4", |             "nginx.ingress.kubernetes.io/proxy-buffers-number": "4", | ||||||
|             "nginx.ingress.kubernetes.io/proxy-buffer-size": "16k", |             "nginx.ingress.kubernetes.io/proxy-buffer-size": "16k", | ||||||
|  |             # Enable TLS in traefik | ||||||
|  |             "traefik.ingress.kubernetes.io/router.tls": "true", | ||||||
|         } |         } | ||||||
|         annotations.update(self.controller.outpost.config.kubernetes_ingress_annotations) |         annotations.update(self.controller.outpost.config.kubernetes_ingress_annotations) | ||||||
|         return annotations |         return annotations | ||||||
| @ -126,7 +128,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]): | |||||||
|                                         port=V1ServiceBackendPort(name="http"), |                                         port=V1ServiceBackendPort(name="http"), | ||||||
|                                     ), |                                     ), | ||||||
|                                 ), |                                 ), | ||||||
|                                 path="/akprox", |                                 path="/outpost.goauthentik.io", | ||||||
|                                 path_type="ImplementationSpecific", |                                 path_type="ImplementationSpecific", | ||||||
|                             ) |                             ) | ||||||
|                         ] |                         ] | ||||||
|  | |||||||
| @ -119,7 +119,10 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]) | |||||||
|             ), |             ), | ||||||
|             spec=TraefikMiddlewareSpec( |             spec=TraefikMiddlewareSpec( | ||||||
|                 forwardAuth=TraefikMiddlewareSpecForwardAuth( |                 forwardAuth=TraefikMiddlewareSpecForwardAuth( | ||||||
|                     address=f"http://{self.name}.{self.namespace}:9000/akprox/auth/traefik", |                     address=( | ||||||
|  |                         f"http://{self.name}.{self.namespace}:9000/" | ||||||
|  |                         "outpost.goauthentik.io/auth/traefik" | ||||||
|  |                     ), | ||||||
|                     authResponseHeaders=[ |                     authResponseHeaders=[ | ||||||
|                         "X-authentik-username", |                         "X-authentik-username", | ||||||
|                         "X-authentik-groups", |                         "X-authentik-groups", | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ def get_cookie_secret(): | |||||||
|  |  | ||||||
|  |  | ||||||
| def _get_callback_url(uri: str) -> str: | def _get_callback_url(uri: str) -> str: | ||||||
|     return urljoin(uri, "/akprox/callback") |     return urljoin(uri, "outpost.goauthentik.io/callback") | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProxyMode(models.TextChoices): | class ProxyMode(models.TextChoices): | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								authentik/providers/proxy/tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								authentik/providers/proxy/tasks.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | """proxy provider tasks""" | ||||||
|  | from authentik.providers.proxy.models import ProxyProvider | ||||||
|  | from authentik.root.celery import CELERY_APP | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @CELERY_APP.task() | ||||||
|  | def proxy_set_defaults(): | ||||||
|  |     """Ensure correct defaults are set for all providers""" | ||||||
|  |     for provider in ProxyProvider.objects.all(): | ||||||
|  |         provider.set_oauth_defaults() | ||||||
|  |         provider.save() | ||||||
| @ -6,7 +6,6 @@ import os | |||||||
| import sys | import sys | ||||||
| from hashlib import sha512 | from hashlib import sha512 | ||||||
| from json import dumps | from json import dumps | ||||||
| from tempfile import gettempdir |  | ||||||
| from time import time | from time import time | ||||||
| from urllib.parse import quote_plus | from urllib.parse import quote_plus | ||||||
|  |  | ||||||
| @ -137,7 +136,6 @@ INSTALLED_APPS = [ | |||||||
|     "guardian", |     "guardian", | ||||||
|     "django_prometheus", |     "django_prometheus", | ||||||
|     "channels", |     "channels", | ||||||
|     "dbbackup", |  | ||||||
| ] | ] | ||||||
|  |  | ||||||
| GUARDIAN_MONKEY_PATCH = False | GUARDIAN_MONKEY_PATCH = False | ||||||
| @ -357,32 +355,6 @@ CELERY_RESULT_BACKEND = ( | |||||||
|     f"{_redis_url}/{CONFIG.y('redis.message_queue_db')}{REDIS_CELERY_TLS_REQUIREMENTS}" |     f"{_redis_url}/{CONFIG.y('redis.message_queue_db')}{REDIS_CELERY_TLS_REQUIREMENTS}" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| # Database backup |  | ||||||
| DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage" |  | ||||||
| DBBACKUP_STORAGE_OPTIONS = {"location": "./backups" if DEBUG else "/backups"} |  | ||||||
| DBBACKUP_FILENAME_TEMPLATE = f"authentik-backup-{__version__}-{{datetime}}.sql" |  | ||||||
| DBBACKUP_CONNECTOR_MAPPING = { |  | ||||||
|     "django_prometheus.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpConnector", |  | ||||||
| } |  | ||||||
| DBBACKUP_TMP_DIR = gettempdir() if DEBUG else "/tmp"  # nosec |  | ||||||
| DBBACKUP_CLEANUP_KEEP = 10 |  | ||||||
| if CONFIG.y("postgresql.s3_backup.bucket", "") != "": |  | ||||||
|     DBBACKUP_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" |  | ||||||
|     DBBACKUP_STORAGE_OPTIONS = { |  | ||||||
|         "access_key": CONFIG.y("postgresql.s3_backup.access_key"), |  | ||||||
|         "secret_key": CONFIG.y("postgresql.s3_backup.secret_key"), |  | ||||||
|         "bucket_name": CONFIG.y("postgresql.s3_backup.bucket"), |  | ||||||
|         "region_name": CONFIG.y("postgresql.s3_backup.region", "eu-central-1"), |  | ||||||
|         "default_acl": "private", |  | ||||||
|         "endpoint_url": CONFIG.y("postgresql.s3_backup.host"), |  | ||||||
|         "location": CONFIG.y("postgresql.s3_backup.location", ""), |  | ||||||
|         "verify": not CONFIG.y_bool("postgresql.s3_backup.insecure_skip_verify", False), |  | ||||||
|     } |  | ||||||
|     j_print( |  | ||||||
|         "Database backup to S3 is configured", |  | ||||||
|         host=CONFIG.y("postgresql.s3_backup.host"), |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
| # Sentry integration | # Sentry integration | ||||||
| SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8" | SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8" | ||||||
|  |  | ||||||
| @ -493,12 +465,9 @@ _LOGGING_HANDLER_MAP = { | |||||||
|     "urllib3": "WARNING", |     "urllib3": "WARNING", | ||||||
|     "websockets": "WARNING", |     "websockets": "WARNING", | ||||||
|     "daphne": "WARNING", |     "daphne": "WARNING", | ||||||
|     "dbbackup": "ERROR", |  | ||||||
|     "kubernetes": "INFO", |     "kubernetes": "INFO", | ||||||
|     "asyncio": "WARNING", |     "asyncio": "WARNING", | ||||||
|     "aioredis": "WARNING", |     "aioredis": "WARNING", | ||||||
|     "s3transfer": "WARNING", |  | ||||||
|     "botocore": "WARNING", |  | ||||||
| } | } | ||||||
| for handler_name, level in _LOGGING_HANDLER_MAP.items(): | for handler_name, level in _LOGGING_HANDLER_MAP.items(): | ||||||
|     # pyright: reportGeneralTypeIssues=false |     # pyright: reportGeneralTypeIssues=false | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| """Sync LDAP Users and groups into authentik""" | """Sync LDAP Users and groups into authentik""" | ||||||
| from typing import Any | from typing import Any | ||||||
|  |  | ||||||
| from deepmerge import always_merger |  | ||||||
| from django.db.models.base import Model | from django.db.models.base import Model | ||||||
| from django.db.models.query import QuerySet | from django.db.models.query import QuerySet | ||||||
| from structlog.stdlib import BoundLogger, get_logger | from structlog.stdlib import BoundLogger, get_logger | ||||||
|  |  | ||||||
| from authentik.core.exceptions import PropertyMappingExpressionException | from authentik.core.exceptions import PropertyMappingExpressionException | ||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
|  | from authentik.lib.merge import MERGE_LIST_UNIQUE | ||||||
| from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME | from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME | ||||||
| from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource | from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource | ||||||
|  |  | ||||||
| @ -123,8 +123,8 @@ class BaseLDAPSynchronizer: | |||||||
|                 continue |                 continue | ||||||
|             setattr(instance, key, value) |             setattr(instance, key, value) | ||||||
|         final_atttributes = {} |         final_atttributes = {} | ||||||
|         always_merger.merge(final_atttributes, instance.attributes) |         MERGE_LIST_UNIQUE.merge(final_atttributes, instance.attributes) | ||||||
|         always_merger.merge(final_atttributes, data.get("attributes", {})) |         MERGE_LIST_UNIQUE.merge(final_atttributes, data.get("attributes", {})) | ||||||
|         instance.attributes = final_atttributes |         instance.attributes = final_atttributes | ||||||
|         instance.save() |         instance.save() | ||||||
|         return (instance, False) |         return (instance, False) | ||||||
|  | |||||||
| @ -13,8 +13,8 @@ class AuthenticatorValidateStageSerializer(StageSerializer): | |||||||
|  |  | ||||||
|     def validate_not_configured_action(self, value): |     def validate_not_configured_action(self, value): | ||||||
|         """Ensure that a configuration stage is set when not_configured_action is configure""" |         """Ensure that a configuration stage is set when not_configured_action is configure""" | ||||||
|         configuration_stage = self.initial_data.get("configuration_stage") |         configuration_stages = self.initial_data.get("configuration_stages") | ||||||
|         if value == NotConfiguredAction.CONFIGURE and configuration_stage is None: |         if value == NotConfiguredAction.CONFIGURE and configuration_stages is None: | ||||||
|             raise ValidationError( |             raise ValidationError( | ||||||
|                 ( |                 ( | ||||||
|                     'When "Not configured action" is set to "Configure", ' |                     'When "Not configured action" is set to "Configure", ' | ||||||
| @ -29,7 +29,7 @@ class AuthenticatorValidateStageSerializer(StageSerializer): | |||||||
|         fields = StageSerializer.Meta.fields + [ |         fields = StageSerializer.Meta.fields + [ | ||||||
|             "not_configured_action", |             "not_configured_action", | ||||||
|             "device_classes", |             "device_classes", | ||||||
|             "configuration_stage", |             "configuration_stages", | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -38,5 +38,5 @@ class AuthenticatorValidateStageViewSet(UsedByMixin, ModelViewSet): | |||||||
|  |  | ||||||
|     queryset = AuthenticatorValidateStage.objects.all() |     queryset = AuthenticatorValidateStage.objects.all() | ||||||
|     serializer_class = AuthenticatorValidateStageSerializer |     serializer_class = AuthenticatorValidateStageSerializer | ||||||
|     filterset_fields = ["name", "not_configured_action", "configuration_stage"] |     filterset_fields = ["name", "not_configured_action", "configuration_stages"] | ||||||
|     ordering = ["name"] |     ordering = ["name"] | ||||||
|  | |||||||
| @ -0,0 +1,44 @@ | |||||||
|  | # Generated by Django 4.0.1 on 2022-01-05 22:09 | ||||||
|  |  | ||||||
|  | from django.apps.registry import Apps | ||||||
|  | from django.db import migrations, models | ||||||
|  | from django.db.backends.base.schema import BaseDatabaseSchemaEditor | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def migrate_configuration_stage(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): | ||||||
|  |     db_alias = schema_editor.connection.alias | ||||||
|  |     AuthenticatorValidateStage = apps.get_model( | ||||||
|  |         "authentik_stages_authenticator_validate", "AuthenticatorValidateStage" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     for stage in AuthenticatorValidateStage.objects.using(db_alias).all(): | ||||||
|  |         if stage.configuration_stage: | ||||||
|  |             stage.configuration_stages.set([stage.configuration_stage]) | ||||||
|  |             stage.save() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_flows", "0021_auto_20211227_2103"), | ||||||
|  |         ("authentik_stages_authenticator_validate", "0009_default_stage"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="authenticatorvalidatestage", | ||||||
|  |             name="configuration_stages", | ||||||
|  |             field=models.ManyToManyField( | ||||||
|  |                 blank=True, | ||||||
|  |                 default=None, | ||||||
|  |                 help_text="Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.", | ||||||
|  |                 related_name="+", | ||||||
|  |                 to="authentik_flows.Stage", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |         migrations.RunPython(migrate_configuration_stage), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name="authenticatorvalidatestage", | ||||||
|  |             name="configuration_stage", | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -38,16 +38,14 @@ class AuthenticatorValidateStage(Stage): | |||||||
|         choices=NotConfiguredAction.choices, default=NotConfiguredAction.SKIP |         choices=NotConfiguredAction.choices, default=NotConfiguredAction.SKIP | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     configuration_stage = models.ForeignKey( |     configuration_stages = models.ManyToManyField( | ||||||
|         Stage, |         Stage, | ||||||
|         null=True, |  | ||||||
|         blank=True, |         blank=True, | ||||||
|         default=None, |         default=None, | ||||||
|         on_delete=models.SET_DEFAULT, |  | ||||||
|         related_name="+", |         related_name="+", | ||||||
|         help_text=_( |         help_text=_( | ||||||
|             ( |             ( | ||||||
|                 "Stage used to configure Authenticator when user doesn't have any compatible " |                 "Stages used to configure Authenticator when user doesn't have any compatible " | ||||||
|                 "devices. After this configuration Stage passes, the user is not prompted again." |                 "devices. After this configuration Stage passes, the user is not prompted again." | ||||||
|             ) |             ) | ||||||
|         ), |         ), | ||||||
|  | |||||||
| @ -1,10 +1,12 @@ | |||||||
| """Authenticator Validation""" | """Authenticator Validation""" | ||||||
| from django.http import HttpRequest, HttpResponse | from django.http import HttpRequest, HttpResponse | ||||||
| from django_otp import devices_for_user | from django_otp import devices_for_user | ||||||
| from rest_framework.fields import CharField, IntegerField, JSONField, ListField | from rest_framework.fields import CharField, IntegerField, JSONField, ListField, UUIDField | ||||||
| from rest_framework.serializers import ValidationError | from rest_framework.serializers import ValidationError | ||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
|  | from authentik.core.api.utils import PassiveSerializer | ||||||
|  | from authentik.core.models import User | ||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
| from authentik.events.utils import cleanse_dict, sanitize_dict | from authentik.events.utils import cleanse_dict, sanitize_dict | ||||||
| from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge | from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge | ||||||
| @ -26,6 +28,18 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice | |||||||
| from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS | from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS | ||||||
|  |  | ||||||
| LOGGER = get_logger() | LOGGER = get_logger() | ||||||
|  | SESSION_STAGES = "goauthentik.io/stages/authenticator_validate/stages" | ||||||
|  | SESSION_SELECTED_STAGE = "goauthentik.io/stages/authenticator_validate/selected_stage" | ||||||
|  | SESSION_DEVICE_CHALLENGES = "goauthentik.io/stages/authenticator_validate/device_challenges" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SelectableStageSerializer(PassiveSerializer): | ||||||
|  |     """Serializer for stages which can be selected by users""" | ||||||
|  |  | ||||||
|  |     pk = UUIDField() | ||||||
|  |     name = CharField() | ||||||
|  |     verbose_name = CharField() | ||||||
|  |     meta_model_name = CharField() | ||||||
|  |  | ||||||
|  |  | ||||||
| class AuthenticatorValidationChallenge(WithUserInfoChallenge): | class AuthenticatorValidationChallenge(WithUserInfoChallenge): | ||||||
| @ -33,12 +47,14 @@ class AuthenticatorValidationChallenge(WithUserInfoChallenge): | |||||||
|  |  | ||||||
|     device_challenges = ListField(child=DeviceChallenge()) |     device_challenges = ListField(child=DeviceChallenge()) | ||||||
|     component = CharField(default="ak-stage-authenticator-validate") |     component = CharField(default="ak-stage-authenticator-validate") | ||||||
|  |     configuration_stages = ListField(child=SelectableStageSerializer()) | ||||||
|  |  | ||||||
|  |  | ||||||
| class AuthenticatorValidationChallengeResponse(ChallengeResponse): | class AuthenticatorValidationChallengeResponse(ChallengeResponse): | ||||||
|     """Challenge used for Code-based and WebAuthn authenticators""" |     """Challenge used for Code-based and WebAuthn authenticators""" | ||||||
|  |  | ||||||
|     selected_challenge = DeviceChallenge(required=False) |     selected_challenge = DeviceChallenge(required=False) | ||||||
|  |     selected_stage = CharField(required=False) | ||||||
|  |  | ||||||
|     code = CharField(required=False) |     code = CharField(required=False) | ||||||
|     webauthn = JSONField(required=False) |     webauthn = JSONField(required=False) | ||||||
| @ -46,7 +62,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse): | |||||||
|     component = CharField(default="ak-stage-authenticator-validate") |     component = CharField(default="ak-stage-authenticator-validate") | ||||||
|  |  | ||||||
|     def _challenge_allowed(self, classes: list): |     def _challenge_allowed(self, classes: list): | ||||||
|         device_challenges: list[dict] = self.stage.request.session.get("device_challenges") |         device_challenges: list[dict] = self.stage.request.session.get(SESSION_DEVICE_CHALLENGES) | ||||||
|         if not any(x["device_class"] in classes for x in device_challenges): |         if not any(x["device_class"] in classes for x in device_challenges): | ||||||
|             raise ValidationError("No compatible device class allowed") |             raise ValidationError("No compatible device class allowed") | ||||||
|  |  | ||||||
| @ -71,7 +87,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse): | |||||||
|     def validate_selected_challenge(self, challenge: dict) -> dict: |     def validate_selected_challenge(self, challenge: dict) -> dict: | ||||||
|         """Check which challenge the user has selected. Actual logic only used for SMS stage.""" |         """Check which challenge the user has selected. Actual logic only used for SMS stage.""" | ||||||
|         # First check if the challenge is valid |         # First check if the challenge is valid | ||||||
|         for device_challenge in self.stage.request.session.get("device_challenges"): |         for device_challenge in self.stage.request.session.get(SESSION_DEVICE_CHALLENGES): | ||||||
|             if device_challenge.get("device_class", "") != challenge.get("device_class", ""): |             if device_challenge.get("device_class", "") != challenge.get("device_class", ""): | ||||||
|                 raise ValidationError("invalid challenge selected") |                 raise ValidationError("invalid challenge selected") | ||||||
|             if device_challenge.get("device_uid", "") != challenge.get("device_uid", ""): |             if device_challenge.get("device_uid", "") != challenge.get("device_uid", ""): | ||||||
| @ -84,6 +100,15 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse): | |||||||
|         select_challenge(self.stage.request, devices.first()) |         select_challenge(self.stage.request, devices.first()) | ||||||
|         return challenge |         return challenge | ||||||
|  |  | ||||||
|  |     def validate_selected_stage(self, stage_pk: str) -> str: | ||||||
|  |         """Check that the selected stage is valid""" | ||||||
|  |         stages = self.stage.request.session.get(SESSION_STAGES, []) | ||||||
|  |         if not any(str(stage.pk) == stage_pk for stage in stages): | ||||||
|  |             raise ValidationError("Selected stage is invalid") | ||||||
|  |         LOGGER.debug("Setting selected stage to ", stage=stage_pk) | ||||||
|  |         self.stage.request.session[SESSION_SELECTED_STAGE] = stage_pk | ||||||
|  |         return stage_pk | ||||||
|  |  | ||||||
|     def validate(self, attrs: dict): |     def validate(self, attrs: dict): | ||||||
|         # Checking if the given data is from a valid device class is done above |         # Checking if the given data is from a valid device class is done above | ||||||
|         # Here we only check if the any data was sent at all |         # Here we only check if the any data was sent at all | ||||||
| @ -164,7 +189,7 @@ class AuthenticatorValidateStageView(ChallengeStageView): | |||||||
|             else: |             else: | ||||||
|                 LOGGER.debug("No pending user, continuing") |                 LOGGER.debug("No pending user, continuing") | ||||||
|                 return self.executor.stage_ok() |                 return self.executor.stage_ok() | ||||||
|         self.request.session["device_challenges"] = challenges |         self.request.session[SESSION_DEVICE_CHALLENGES] = challenges | ||||||
|  |  | ||||||
|         # No allowed devices |         # No allowed devices | ||||||
|         if len(challenges) < 1: |         if len(challenges) < 1: | ||||||
| @ -175,35 +200,74 @@ class AuthenticatorValidateStageView(ChallengeStageView): | |||||||
|                 LOGGER.debug("Authenticator not configured, denying") |                 LOGGER.debug("Authenticator not configured, denying") | ||||||
|                 return self.executor.stage_invalid() |                 return self.executor.stage_invalid() | ||||||
|             if stage.not_configured_action == NotConfiguredAction.CONFIGURE: |             if stage.not_configured_action == NotConfiguredAction.CONFIGURE: | ||||||
|                 if not stage.configuration_stage: |                 LOGGER.debug("Authenticator not configured, forcing configure") | ||||||
|                     Event.new( |                 return self.prepare_stages(user) | ||||||
|                         EventAction.CONFIGURATION_ERROR, |  | ||||||
|                         message=( |  | ||||||
|                             "Authenticator validation stage is set to configure user " |  | ||||||
|                             "but no configuration flow is set." |  | ||||||
|                         ), |  | ||||||
|                         stage=self, |  | ||||||
|                     ).from_http(self.request).set_user(user).save() |  | ||||||
|                     return self.executor.stage_invalid() |  | ||||||
|                 LOGGER.debug("Authenticator not configured, sending user to configure") |  | ||||||
|                 # Because the foreign key to stage.configuration_stage points to |  | ||||||
|                 # a base stage class, we need to do another lookup |  | ||||||
|                 stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk) |  | ||||||
|                 # plan.insert inserts at 1 index, so when stage_ok pops 0, |  | ||||||
|                 # the configuration stage is next |  | ||||||
|                 self.executor.plan.insert_stage(stage) |  | ||||||
|                 return self.executor.stage_ok() |  | ||||||
|         return super().get(request, *args, **kwargs) |         return super().get(request, *args, **kwargs) | ||||||
|  |  | ||||||
|     def get_challenge(self) -> AuthenticatorValidationChallenge: |     def prepare_stages(self, user: User, *args, **kwargs) -> HttpResponse: | ||||||
|         challenges = self.request.session.get("device_challenges") |         """Check how the user can configure themselves. If no stages are set, return an error. | ||||||
|         if not challenges: |         If a single stage is set, insert that stage directly. If multiple are selected, include | ||||||
|             LOGGER.debug("Authenticator Validation stage ran without challenges") |         them in the challenge.""" | ||||||
|  |         stage: AuthenticatorValidateStage = self.executor.current_stage | ||||||
|  |         if not stage.configuration_stages.exists(): | ||||||
|  |             Event.new( | ||||||
|  |                 EventAction.CONFIGURATION_ERROR, | ||||||
|  |                 message=( | ||||||
|  |                     "Authenticator validation stage is set to configure user " | ||||||
|  |                     "but no configuration flow is set." | ||||||
|  |                 ), | ||||||
|  |                 stage=self, | ||||||
|  |             ).from_http(self.request).set_user(user).save() | ||||||
|             return self.executor.stage_invalid() |             return self.executor.stage_invalid() | ||||||
|  |         if stage.configuration_stages.count() == 1: | ||||||
|  |             next_stage = Stage.objects.get_subclass(pk=stage.configuration_stages.first().pk) | ||||||
|  |             LOGGER.debug("Single stage configured, auto-selecting", stage=next_stage) | ||||||
|  |             self.request.session[SESSION_SELECTED_STAGE] = next_stage | ||||||
|  |             # Because that normal insetion only happens on post, we directly inject it here and | ||||||
|  |             # return it | ||||||
|  |             self.executor.plan.insert_stage(next_stage) | ||||||
|  |             return self.executor.stage_ok() | ||||||
|  |         stages = Stage.objects.filter(pk__in=stage.configuration_stages.all()).select_subclasses() | ||||||
|  |         self.request.session[SESSION_STAGES] = stages | ||||||
|  |         return super().get(self.request, *args, **kwargs) | ||||||
|  |  | ||||||
|  |     def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||||
|  |         res = super().post(request, *args, **kwargs) | ||||||
|  |         if ( | ||||||
|  |             SESSION_SELECTED_STAGE in self.request.session | ||||||
|  |             and self.executor.current_stage.not_configured_action == NotConfiguredAction.CONFIGURE | ||||||
|  |         ): | ||||||
|  |             LOGGER.debug("Got selected stage in session, running that") | ||||||
|  |             stage_pk = self.request.session.get(SESSION_SELECTED_STAGE) | ||||||
|  |             # Because the foreign key to stage.configuration_stage points to | ||||||
|  |             # a base stage class, we need to do another lookup | ||||||
|  |             stage = Stage.objects.get_subclass(pk=stage_pk) | ||||||
|  |             # plan.insert inserts at 1 index, so when stage_ok pops 0, | ||||||
|  |             # the configuration stage is next | ||||||
|  |             self.executor.plan.insert_stage(stage) | ||||||
|  |             return self.executor.stage_ok() | ||||||
|  |         return res | ||||||
|  |  | ||||||
|  |     def get_challenge(self) -> AuthenticatorValidationChallenge: | ||||||
|  |         challenges = self.request.session.get(SESSION_DEVICE_CHALLENGES, []) | ||||||
|  |         stages = self.request.session.get(SESSION_STAGES, []) | ||||||
|  |         stage_challenges = [] | ||||||
|  |         for stage in stages: | ||||||
|  |             serializer = SelectableStageSerializer( | ||||||
|  |                 data={ | ||||||
|  |                     "pk": stage.pk, | ||||||
|  |                     "name": stage.name, | ||||||
|  |                     "verbose_name": str(stage._meta.verbose_name), | ||||||
|  |                     "meta_model_name": f"{stage._meta.app_label}.{stage._meta.model_name}", | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |             serializer.is_valid() | ||||||
|  |             stage_challenges.append(serializer.data) | ||||||
|         return AuthenticatorValidationChallenge( |         return AuthenticatorValidationChallenge( | ||||||
|             data={ |             data={ | ||||||
|                 "type": ChallengeTypes.NATIVE.value, |                 "type": ChallengeTypes.NATIVE.value, | ||||||
|                 "device_challenges": challenges, |                 "device_challenges": challenges, | ||||||
|  |                 "configuration_stages": stage_challenges, | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | |||||||
| @ -43,8 +43,8 @@ class AuthenticatorValidateStageTests(FlowTestCase): | |||||||
|         stage = AuthenticatorValidateStage.objects.create( |         stage = AuthenticatorValidateStage.objects.create( | ||||||
|             name="foo", |             name="foo", | ||||||
|             not_configured_action=NotConfiguredAction.CONFIGURE, |             not_configured_action=NotConfiguredAction.CONFIGURE, | ||||||
|             configuration_stage=conf_stage, |  | ||||||
|         ) |         ) | ||||||
|  |         stage.configuration_stages.set([conf_stage]) | ||||||
|         flow = Flow.objects.create(name="test", slug="test", title="test") |         flow = Flow.objects.create(name="test", slug="test", title="test") | ||||||
|         FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0) |         FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0) | ||||||
|         FlowStageBinding.objects.create(target=flow, stage=stage, order=1) |         FlowStageBinding.objects.create(target=flow, stage=stage, order=1) | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ services: | |||||||
|     image: redis:alpine |     image: redis:alpine | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|   server: |   server: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.5} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.2.1} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: server |     command: server | ||||||
|     environment: |     environment: | ||||||
| @ -38,7 +38,7 @@ services: | |||||||
|       - "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000" |       - "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000" | ||||||
|       - "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443" |       - "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443" | ||||||
|   worker: |   worker: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.5} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.2.1} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: worker |     command: worker | ||||||
|     environment: |     environment: | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							| @ -8,16 +8,16 @@ require ( | |||||||
| 	github.com/coreos/go-oidc v2.2.1+incompatible | 	github.com/coreos/go-oidc v2.2.1+incompatible | ||||||
| 	github.com/garyburd/redigo v1.6.2 // indirect | 	github.com/garyburd/redigo v1.6.2 // indirect | ||||||
| 	github.com/getsentry/sentry-go v0.12.0 | 	github.com/getsentry/sentry-go v0.12.0 | ||||||
| 	github.com/go-ldap/ldap/v3 v3.4.1 | 	github.com/go-ldap/ldap/v3 v3.4.2 | ||||||
| 	github.com/go-openapi/runtime v0.22.0 | 	github.com/go-openapi/runtime v0.23.0 | ||||||
| 	github.com/go-openapi/strfmt v0.21.1 | 	github.com/go-openapi/strfmt v0.21.2 | ||||||
| 	github.com/golang-jwt/jwt v3.2.2+incompatible | 	github.com/golang-jwt/jwt v3.2.2+incompatible | ||||||
| 	github.com/google/uuid v1.3.0 | 	github.com/google/uuid v1.3.0 | ||||||
| 	github.com/gorilla/handlers v1.5.1 | 	github.com/gorilla/handlers v1.5.1 | ||||||
| 	github.com/gorilla/mux v1.8.0 | 	github.com/gorilla/mux v1.8.0 | ||||||
| 	github.com/gorilla/securecookie v1.1.1 | 	github.com/gorilla/securecookie v1.1.1 | ||||||
| 	github.com/gorilla/sessions v1.2.1 | 	github.com/gorilla/sessions v1.2.1 | ||||||
| 	github.com/gorilla/websocket v1.4.2 | 	github.com/gorilla/websocket v1.5.0 | ||||||
| 	github.com/imdario/mergo v0.3.12 | 	github.com/imdario/mergo v0.3.12 | ||||||
| 	github.com/mailru/easyjson v0.7.7 // indirect | 	github.com/mailru/easyjson v0.7.7 // indirect | ||||||
| 	github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 | 	github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								go.sum
									
									
									
									
									
								
							| @ -125,8 +125,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 | |||||||
| github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||||||
| github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||||||
| github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= | ||||||
| github.com/go-ldap/ldap/v3 v3.4.1 h1:fU/0xli6HY02ocbMuozHAYsaHLcnkLjvho2r5a34BUU= | github.com/go-ldap/ldap/v3 v3.4.2 h1:zFZKcXKLqZpFMrMQGHeHWKXbDTdNCmhGY9AK41zPh+8= | ||||||
| github.com/go-ldap/ldap/v3 v3.4.1/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= | github.com/go-ldap/ldap/v3 v3.4.2/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= | ||||||
| github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||||||
| github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | ||||||
| github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= | ||||||
| @ -183,8 +183,8 @@ github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29g | |||||||
| github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= | github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= | ||||||
| github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= | github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= | ||||||
| github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= | github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= | ||||||
| github.com/go-openapi/runtime v0.22.0 h1:vY2D0u807kkcwidaj0YJuq4zyAWQnjLNDpJcVBrUFNs= | github.com/go-openapi/runtime v0.23.0 h1:HX6ET2sHCIvaKeDDQoU01CtO1ekg5EkekHSkLTtWXH0= | ||||||
| github.com/go-openapi/runtime v0.22.0/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs= | github.com/go-openapi/runtime v0.23.0/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs= | ||||||
| github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= | github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= | ||||||
| github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= | github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= | ||||||
| github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= | github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= | ||||||
| @ -208,8 +208,8 @@ github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLs | |||||||
| github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= | github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= | ||||||
| github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk= | github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk= | ||||||
| github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= | github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= | ||||||
| github.com/go-openapi/strfmt v0.21.1 h1:G6s2t5V5kGCHLVbSdZ/6lI8Wm4OzoPFkc3/cjAsKQrM= | github.com/go-openapi/strfmt v0.21.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f+os= | ||||||
| github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= | github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= | ||||||
| github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= | ||||||
| github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= | github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= | ||||||
| github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= | github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= | ||||||
| @ -334,8 +334,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ | |||||||
| github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= | ||||||
| github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= | ||||||
| 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.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= | ||||||
| github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||||
| github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | ||||||
| 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= | ||||||
| github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||||
|  | |||||||
| @ -25,4 +25,4 @@ func OutpostUserAgent() string { | |||||||
| 	return fmt.Sprintf("authentik-outpost@%s", FullVersion()) | 	return fmt.Sprintf("authentik-outpost@%s", FullVersion()) | ||||||
| } | } | ||||||
|  |  | ||||||
| const VERSION = "2022.1.5" | const VERSION = "2022.2.1" | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ var ( | |||||||
| func RunServer() { | func RunServer() { | ||||||
| 	m := mux.NewRouter() | 	m := mux.NewRouter() | ||||||
| 	l := log.WithField("logger", "authentik.outpost.metrics") | 	l := log.WithField("logger", "authentik.outpost.metrics") | ||||||
| 	m.HandleFunc("/akprox/ping", func(rw http.ResponseWriter, r *http.Request) { | 	m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) { | ||||||
| 		rw.WriteHeader(204) | 		rw.WriteHeader(204) | ||||||
| 	}) | 	}) | ||||||
| 	m.Path("/metrics").Handler(promhttp.Handler()) | 	m.Path("/metrics").Handler(promhttp.Handler()) | ||||||
|  | |||||||
| @ -78,7 +78,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore | |||||||
| 	oauth2Config := oauth2.Config{ | 	oauth2Config := oauth2.Config{ | ||||||
| 		ClientID:     *p.ClientId, | 		ClientID:     *p.ClientId, | ||||||
| 		ClientSecret: *p.ClientSecret, | 		ClientSecret: *p.ClientSecret, | ||||||
| 		RedirectURL:  urlJoin(p.ExternalHost, "/akprox/callback"), | 		RedirectURL:  urlJoin(p.ExternalHost, "/outpost.goauthentik.io/callback"), | ||||||
| 		Endpoint:     endpoint.Endpoint, | 		Endpoint:     endpoint.Endpoint, | ||||||
| 		Scopes:       p.ScopesToRequest, | 		Scopes:       p.ScopesToRequest, | ||||||
| 	} | 	} | ||||||
| @ -145,10 +145,10 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore | |||||||
| 	mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle) | 	mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle) | ||||||
|  |  | ||||||
| 	// Support /start and /sign_in for backwards compatibility | 	// Support /start and /sign_in for backwards compatibility | ||||||
| 	mux.HandleFunc("/akprox/start", a.handleRedirect) | 	mux.HandleFunc("/outpost.goauthentik.io/start", a.handleRedirect) | ||||||
| 	mux.HandleFunc("/akprox/sign_in", a.handleRedirect) | 	mux.HandleFunc("/outpost.goauthentik.io/sign_in", a.handleRedirect) | ||||||
| 	mux.HandleFunc("/akprox/callback", a.handleCallback) | 	mux.HandleFunc("/outpost.goauthentik.io/callback", a.handleCallback) | ||||||
| 	mux.HandleFunc("/akprox/sign_out", a.handleSignOut) | 	mux.HandleFunc("/outpost.goauthentik.io/sign_out", a.handleSignOut) | ||||||
| 	switch *p.Mode { | 	switch *p.Mode { | ||||||
| 	case api.PROXYMODE_PROXY: | 	case api.PROXYMODE_PROXY: | ||||||
| 		err = a.configureProxy() | 		err = a.configureProxy() | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ func (a *Application) ErrorPage(rw http.ResponseWriter, r *http.Request, err str | |||||||
| 	data := ErrorPageData{ | 	data := ErrorPageData{ | ||||||
| 		Title:       "Bad Gateway", | 		Title:       "Bad Gateway", | ||||||
| 		Message:     "Error proxying to upstream server", | 		Message:     "Error proxying to upstream server", | ||||||
| 		ProxyPrefix: "/akprox", | 		ProxyPrefix: "/outpost.goauthentik.io", | ||||||
| 	} | 	} | ||||||
| 	if claims != nil && len(err) > 0 { | 	if claims != nil && len(err) > 0 { | ||||||
| 		data.Message = err | 		data.Message = err | ||||||
|  | |||||||
| @ -12,15 +12,15 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func (a *Application) configureForward() error { | func (a *Application) configureForward() error { | ||||||
| 	a.mux.HandleFunc("/akprox/auth", func(rw http.ResponseWriter, r *http.Request) { | 	a.mux.HandleFunc("/outpost.goauthentik.io/auth", func(rw http.ResponseWriter, r *http.Request) { | ||||||
| 		if _, ok := r.URL.Query()["traefik"]; ok { | 		if _, ok := r.URL.Query()["traefik"]; ok { | ||||||
| 			a.forwardHandleTraefik(rw, r) | 			a.forwardHandleTraefik(rw, r) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		a.forwardHandleNginx(rw, r) | 		a.forwardHandleNginx(rw, r) | ||||||
| 	}) | 	}) | ||||||
| 	a.mux.HandleFunc("/akprox/auth/traefik", a.forwardHandleTraefik) | 	a.mux.HandleFunc("/outpost.goauthentik.io/auth/traefik", a.forwardHandleTraefik) | ||||||
| 	a.mux.HandleFunc("/akprox/auth/nginx", a.forwardHandleNginx) | 	a.mux.HandleFunc("/outpost.goauthentik.io/auth/nginx", a.forwardHandleNginx) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -49,8 +49,8 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque | |||||||
| 		a.log.Trace("path can be accessed without authentication") | 		a.log.Trace("path can be accessed without authentication") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if strings.HasPrefix(r.Header.Get("X-Forwarded-Uri"), "/akprox") { | 	if strings.HasPrefix(r.Header.Get("X-Forwarded-Uri"), "/outpost.goauthentik.io") { | ||||||
| 		a.log.WithField("url", r.URL.String()).Trace("path begins with /akprox, allowing access") | 		a.log.WithField("url", r.URL.String()).Trace("path begins with /outpost.goauthentik.io, allowing access") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	host := "" | 	host := "" | ||||||
| @ -80,7 +80,7 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque | |||||||
| 	if proto != "" { | 	if proto != "" { | ||||||
| 		proto = proto + ":" | 		proto = proto + ":" | ||||||
| 	} | 	} | ||||||
| 	rdFinal := fmt.Sprintf("%s//%s%s", proto, host, "/akprox/start") | 	rdFinal := fmt.Sprintf("%s//%s%s", proto, host, "/outpost.goauthentik.io/start") | ||||||
| 	a.log.WithField("url", rdFinal).Debug("Redirecting to login") | 	a.log.WithField("url", rdFinal).Debug("Redirecting to login") | ||||||
| 	http.Redirect(rw, r, rdFinal, http.StatusTemporaryRedirect) | 	http.Redirect(rw, r, rdFinal, http.StatusTemporaryRedirect) | ||||||
| } | } | ||||||
| @ -119,8 +119,8 @@ func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if fwd.String() != r.URL.String() { | 	if fwd.String() != r.URL.String() { | ||||||
| 		if strings.HasPrefix(fwd.Path, "/akprox") { | 		if strings.HasPrefix(fwd.Path, "/outpost.goauthentik.io") { | ||||||
| 			a.log.WithField("url", r.URL.String()).Trace("path begins with /akprox, allowing access") | 			a.log.WithField("url", r.URL.String()).Trace("path begins with /outpost.goauthentik.io, allowing access") | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ import ( | |||||||
|  |  | ||||||
| func TestForwardHandleNginx_Single_Blank(t *testing.T) { | func TestForwardHandleNginx_Single_Blank(t *testing.T) { | ||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil) | ||||||
|  |  | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| 	a.forwardHandleNginx(rr, req) | 	a.forwardHandleNginx(rr, req) | ||||||
| @ -22,7 +22,7 @@ func TestForwardHandleNginx_Single_Blank(t *testing.T) { | |||||||
|  |  | ||||||
| func TestForwardHandleNginx_Single_Skip(t *testing.T) { | func TestForwardHandleNginx_Single_Skip(t *testing.T) { | ||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil) | ||||||
| 	req.Header.Set("X-Original-URL", "http://test.goauthentik.io/skip") | 	req.Header.Set("X-Original-URL", "http://test.goauthentik.io/skip") | ||||||
|  |  | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| @ -33,7 +33,7 @@ func TestForwardHandleNginx_Single_Skip(t *testing.T) { | |||||||
|  |  | ||||||
| func TestForwardHandleNginx_Single_Headers(t *testing.T) { | func TestForwardHandleNginx_Single_Headers(t *testing.T) { | ||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil) | ||||||
| 	req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app") | 	req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app") | ||||||
|  |  | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| @ -47,7 +47,7 @@ func TestForwardHandleNginx_Single_Headers(t *testing.T) { | |||||||
|  |  | ||||||
| func TestForwardHandleNginx_Single_URI(t *testing.T) { | func TestForwardHandleNginx_Single_URI(t *testing.T) { | ||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	req, _ := http.NewRequest("GET", "https://foo.bar/akprox/auth/nginx", nil) | 	req, _ := http.NewRequest("GET", "https://foo.bar/outpost.goauthentik.io/auth/nginx", nil) | ||||||
| 	req.Header.Set("X-Original-URI", "/app") | 	req.Header.Set("X-Original-URI", "/app") | ||||||
|  |  | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| @ -61,7 +61,7 @@ func TestForwardHandleNginx_Single_URI(t *testing.T) { | |||||||
|  |  | ||||||
| func TestForwardHandleNginx_Single_Claims(t *testing.T) { | func TestForwardHandleNginx_Single_Claims(t *testing.T) { | ||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil) | ||||||
| 	req.Header.Set("X-Original-URI", "/") | 	req.Header.Set("X-Original-URI", "/") | ||||||
|  |  | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| @ -108,7 +108,7 @@ func TestForwardHandleNginx_Domain_Blank(t *testing.T) { | |||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() | 	a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() | ||||||
| 	a.proxyConfig.CookieDomain = api.PtrString("foo") | 	a.proxyConfig.CookieDomain = api.PtrString("foo") | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil) | ||||||
|  |  | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| 	a.forwardHandleNginx(rr, req) | 	a.forwardHandleNginx(rr, req) | ||||||
| @ -121,7 +121,7 @@ func TestForwardHandleNginx_Domain_Header(t *testing.T) { | |||||||
| 	a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() | 	a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() | ||||||
| 	a.proxyConfig.CookieDomain = api.PtrString("foo") | 	a.proxyConfig.CookieDomain = api.PtrString("foo") | ||||||
| 	a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io" | 	a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io" | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil) | ||||||
| 	req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app") | 	req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app") | ||||||
|  |  | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ import ( | |||||||
|  |  | ||||||
| func TestForwardHandleTraefik_Single_Blank(t *testing.T) { | func TestForwardHandleTraefik_Single_Blank(t *testing.T) { | ||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil) | ||||||
|  |  | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| 	a.forwardHandleTraefik(rr, req) | 	a.forwardHandleTraefik(rr, req) | ||||||
| @ -22,7 +22,7 @@ func TestForwardHandleTraefik_Single_Blank(t *testing.T) { | |||||||
|  |  | ||||||
| func TestForwardHandleTraefik_Single_Skip(t *testing.T) { | func TestForwardHandleTraefik_Single_Skip(t *testing.T) { | ||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil) | ||||||
| 	req.Header.Set("X-Forwarded-Proto", "http") | 	req.Header.Set("X-Forwarded-Proto", "http") | ||||||
| 	req.Header.Set("X-Forwarded-Host", "test.goauthentik.io") | 	req.Header.Set("X-Forwarded-Host", "test.goauthentik.io") | ||||||
| 	req.Header.Set("X-Forwarded-Uri", "/skip") | 	req.Header.Set("X-Forwarded-Uri", "/skip") | ||||||
| @ -35,7 +35,7 @@ func TestForwardHandleTraefik_Single_Skip(t *testing.T) { | |||||||
|  |  | ||||||
| func TestForwardHandleTraefik_Single_Headers(t *testing.T) { | func TestForwardHandleTraefik_Single_Headers(t *testing.T) { | ||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil) | ||||||
| 	req.Header.Set("X-Forwarded-Proto", "http") | 	req.Header.Set("X-Forwarded-Proto", "http") | ||||||
| 	req.Header.Set("X-Forwarded-Host", "test.goauthentik.io") | 	req.Header.Set("X-Forwarded-Host", "test.goauthentik.io") | ||||||
| 	req.Header.Set("X-Forwarded-Uri", "/app") | 	req.Header.Set("X-Forwarded-Uri", "/app") | ||||||
| @ -45,7 +45,7 @@ func TestForwardHandleTraefik_Single_Headers(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, rr.Code, http.StatusTemporaryRedirect) | 	assert.Equal(t, rr.Code, http.StatusTemporaryRedirect) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, loc.String(), "http://test.goauthentik.io/akprox/start") | 	assert.Equal(t, loc.String(), "http://test.goauthentik.io/outpost.goauthentik.io/start") | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SeesionName) | 	s, _ := a.sessions.Get(req, constants.SeesionName) | ||||||
| 	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect]) | ||||||
| @ -53,7 +53,7 @@ func TestForwardHandleTraefik_Single_Headers(t *testing.T) { | |||||||
|  |  | ||||||
| func TestForwardHandleTraefik_Single_Claims(t *testing.T) { | func TestForwardHandleTraefik_Single_Claims(t *testing.T) { | ||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil) | ||||||
| 	req.Header.Set("X-Forwarded-Proto", "http") | 	req.Header.Set("X-Forwarded-Proto", "http") | ||||||
| 	req.Header.Set("X-Forwarded-Host", "test.goauthentik.io") | 	req.Header.Set("X-Forwarded-Host", "test.goauthentik.io") | ||||||
| 	req.Header.Set("X-Forwarded-Uri", "/app") | 	req.Header.Set("X-Forwarded-Uri", "/app") | ||||||
| @ -102,7 +102,7 @@ func TestForwardHandleTraefik_Domain_Blank(t *testing.T) { | |||||||
| 	a := newTestApplication() | 	a := newTestApplication() | ||||||
| 	a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() | 	a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() | ||||||
| 	a.proxyConfig.CookieDomain = api.PtrString("foo") | 	a.proxyConfig.CookieDomain = api.PtrString("foo") | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil) | ||||||
|  |  | ||||||
| 	rr := httptest.NewRecorder() | 	rr := httptest.NewRecorder() | ||||||
| 	a.forwardHandleTraefik(rr, req) | 	a.forwardHandleTraefik(rr, req) | ||||||
| @ -115,7 +115,7 @@ func TestForwardHandleTraefik_Domain_Header(t *testing.T) { | |||||||
| 	a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() | 	a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() | ||||||
| 	a.proxyConfig.CookieDomain = api.PtrString("foo") | 	a.proxyConfig.CookieDomain = api.PtrString("foo") | ||||||
| 	a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io" | 	a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io" | ||||||
| 	req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil) | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil) | ||||||
| 	req.Header.Set("X-Forwarded-Proto", "http") | 	req.Header.Set("X-Forwarded-Proto", "http") | ||||||
| 	req.Header.Set("X-Forwarded-Host", "test.goauthentik.io") | 	req.Header.Set("X-Forwarded-Host", "test.goauthentik.io") | ||||||
| 	req.Header.Set("X-Forwarded-Uri", "/app") | 	req.Header.Set("X-Forwarded-Uri", "/app") | ||||||
| @ -125,7 +125,7 @@ func TestForwardHandleTraefik_Domain_Header(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusTemporaryRedirect, rr.Code) | 	assert.Equal(t, http.StatusTemporaryRedirect, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, "http://auth.test.goauthentik.io/akprox/start", loc.String()) | 	assert.Equal(t, "http://auth.test.goauthentik.io/outpost.goauthentik.io/start", loc.String()) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SeesionName) | 	s, _ := a.sessions.Get(req, constants.SeesionName) | ||||||
| 	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect]) | ||||||
|  | |||||||
| @ -73,10 +73,10 @@ func (a *Application) configureProxy() error { | |||||||
|  |  | ||||||
| func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) { | func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) { | ||||||
| 	return func(r *http.Request) { | 	return func(r *http.Request) { | ||||||
|  | 		r.Header.Set("X-Forwarded-Host", r.Host) | ||||||
| 		claims, _ := a.getClaims(r) | 		claims, _ := a.getClaims(r) | ||||||
| 		r.URL.Scheme = ou.Scheme | 		r.URL.Scheme = ou.Scheme | ||||||
| 		r.URL.Host = ou.Host | 		r.URL.Host = ou.Host | ||||||
| 		r.Host = ou.Host |  | ||||||
| 		if claims != nil && claims.Proxy != nil && claims.Proxy.BackendOverride != "" { | 		if claims != nil && claims.Proxy != nil && claims.Proxy.BackendOverride != "" { | ||||||
| 			u, err := url.Parse(claims.Proxy.BackendOverride) | 			u, err := url.Parse(claims.Proxy.BackendOverride) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @ -84,7 +84,6 @@ func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) { | |||||||
| 			} else { | 			} else { | ||||||
| 				r.URL.Scheme = u.Scheme | 				r.URL.Scheme = u.Scheme | ||||||
| 				r.URL.Host = u.Host | 				r.URL.Host = u.Host | ||||||
| 				r.Host = u.Host |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		a.log.WithField("upstream_url", r.URL.String()).Trace("final upstream url") | 		a.log.WithField("upstream_url", r.URL.String()).Trace("final upstream url") | ||||||
|  | |||||||
| @ -19,9 +19,10 @@ func TestProxy_ModifyRequest(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| 	a.proxyModifyRequest(u)(req) | 	a.proxyModifyRequest(u)(req) | ||||||
|  |  | ||||||
|  | 	assert.Equal(t, "frontend", req.Header.Get("X-Forwarded-Host")) | ||||||
| 	assert.Equal(t, "/foo", req.URL.Path) | 	assert.Equal(t, "/foo", req.URL.Path) | ||||||
| 	assert.Equal(t, "backend:8012", req.URL.Host) | 	assert.Equal(t, "backend:8012", req.URL.Host) | ||||||
| 	assert.Equal(t, "backend:8012", req.Host) | 	assert.Equal(t, "frontend", req.Host) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestProxy_ModifyRequest_Claims(t *testing.T) { | func TestProxy_ModifyRequest_Claims(t *testing.T) { | ||||||
| @ -49,7 +50,7 @@ func TestProxy_ModifyRequest_Claims(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, "/foo", req.URL.Path) | 	assert.Equal(t, "/foo", req.URL.Path) | ||||||
| 	assert.Equal(t, "other-backend:8123", req.URL.Host) | 	assert.Equal(t, "other-backend:8123", req.URL.Host) | ||||||
| 	assert.Equal(t, "other-backend:8123", req.Host) | 	assert.Equal(t, "frontend", req.Host) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestProxy_ModifyRequest_Claims_Invalid(t *testing.T) { | func TestProxy_ModifyRequest_Claims_Invalid(t *testing.T) { | ||||||
| @ -77,5 +78,5 @@ func TestProxy_ModifyRequest_Claims_Invalid(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, "/foo", req.URL.Path) | 	assert.Equal(t, "/foo", req.URL.Path) | ||||||
| 	assert.Equal(t, "backend:8012", req.URL.Host) | 	assert.Equal(t, "backend:8012", req.URL.Host) | ||||||
| 	assert.Equal(t, "backend:8012", req.Host) | 	assert.Equal(t, "frontend", req.Host) | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,14 +3,46 @@ package application | |||||||
| import ( | import ( | ||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/gorilla/securecookie" | 	"github.com/gorilla/securecookie" | ||||||
|  | 	"goauthentik.io/api" | ||||||
| 	"goauthentik.io/internal/outpost/proxyv2/constants" | 	"goauthentik.io/internal/outpost/proxyv2/constants" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	redirectParam = "rd" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (a *Application) checkRedirectParam(r *http.Request) (string, bool) { | ||||||
|  | 	rd := r.URL.Query().Get(redirectParam) | ||||||
|  | 	if rd == "" { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 	u, err := url.Parse(rd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		a.log.WithError(err).Warning("Failed to parse redirect URL") | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 	// Check to make sure we only redirect to allowed places | ||||||
|  | 	if a.Mode() == api.PROXYMODE_PROXY || a.Mode() == api.PROXYMODE_FORWARD_SINGLE { | ||||||
|  | 		if !strings.Contains(u.String(), a.proxyConfig.ExternalHost) { | ||||||
|  | 			a.log.WithField("url", u.String()).WithField("ext", a.proxyConfig.ExternalHost).Warning("redirect URI did not contain external host") | ||||||
|  | 			return "", false | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if !strings.HasSuffix(u.Host, *a.proxyConfig.CookieDomain) { | ||||||
|  | 			a.log.WithField("host", u.Host).WithField("dom", *a.proxyConfig.CookieDomain).Warning("redirect URI Host was not included in cookie domain") | ||||||
|  | 			return "", false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return u.String(), true | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) { | func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	newState := base64.RawStdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) | 	newState := base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) | ||||||
| 	s, err := a.sessions.Get(r, constants.SeesionName) | 	s, err := a.sessions.Get(r, constants.SeesionName) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		s.Values[constants.SessionOAuthState] = []string{} | 		s.Values[constants.SessionOAuthState] = []string{} | ||||||
| @ -20,6 +52,11 @@ func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) { | |||||||
| 		s.Values[constants.SessionOAuthState] = []string{} | 		s.Values[constants.SessionOAuthState] = []string{} | ||||||
| 		state = []string{} | 		state = []string{} | ||||||
| 	} | 	} | ||||||
|  | 	rd, ok := a.checkRedirectParam(r) | ||||||
|  | 	if ok { | ||||||
|  | 		s.Values[constants.SessionRedirect] = rd | ||||||
|  | 		a.log.WithField("rd", rd).Trace("Setting redirect") | ||||||
|  | 	} | ||||||
| 	s.Values[constants.SessionOAuthState] = append(state, newState) | 	s.Values[constants.SessionOAuthState] = append(state, newState) | ||||||
| 	err = s.Save(r, rw) | 	err = s.Save(r, rw) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -29,7 +66,10 @@ func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (a *Application) handleCallback(rw http.ResponseWriter, r *http.Request) { | func (a *Application) handleCallback(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	s, _ := a.sessions.Get(r, constants.SeesionName) | 	s, err := a.sessions.Get(r, constants.SeesionName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		a.log.WithError(err).Trace("failed to get session") | ||||||
|  | 	} | ||||||
| 	state, ok := s.Values[constants.SessionOAuthState] | 	state, ok := s.Values[constants.SessionOAuthState] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		a.log.Warning("No state saved in session") | 		a.log.Warning("No state saved in session") | ||||||
| @ -62,8 +102,8 @@ func (a *Application) handleCallback(rw http.ResponseWriter, r *http.Request) { | |||||||
| 	redirect := a.proxyConfig.ExternalHost | 	redirect := a.proxyConfig.ExternalHost | ||||||
| 	redirectR, ok := s.Values[constants.SessionRedirect] | 	redirectR, ok := s.Values[constants.SessionRedirect] | ||||||
| 	if ok { | 	if ok { | ||||||
| 		a.log.WithField("redirect", redirectR).Trace("got final redirect from session") |  | ||||||
| 		redirect = redirectR.(string) | 		redirect = redirectR.(string) | ||||||
| 	} | 	} | ||||||
|  | 	a.log.WithField("redirect", redirect).Trace("final redirect") | ||||||
| 	http.Redirect(rw, r, redirect, http.StatusFound) | 	http.Redirect(rw, r, redirect, http.StatusFound) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										51
									
								
								internal/outpost/proxyv2/application/oauth_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								internal/outpost/proxyv2/application/oauth_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"goauthentik.io/api" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestCheckRedirectParam(t *testing.T) { | ||||||
|  | 	a := newTestApplication() | ||||||
|  | 	req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/start", nil) | ||||||
|  |  | ||||||
|  | 	rd, ok := a.checkRedirectParam(req) | ||||||
|  |  | ||||||
|  | 	assert.Equal(t, false, ok) | ||||||
|  | 	assert.Equal(t, "", rd) | ||||||
|  |  | ||||||
|  | 	req, _ = http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://google.com", nil) | ||||||
|  |  | ||||||
|  | 	rd, ok = a.checkRedirectParam(req) | ||||||
|  |  | ||||||
|  | 	assert.Equal(t, false, ok) | ||||||
|  | 	assert.Equal(t, "", rd) | ||||||
|  |  | ||||||
|  | 	req, _ = http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://ext.t.goauthentik.io/test", nil) | ||||||
|  |  | ||||||
|  | 	rd, ok = a.checkRedirectParam(req) | ||||||
|  |  | ||||||
|  | 	assert.Equal(t, true, ok) | ||||||
|  | 	assert.Equal(t, "https://ext.t.goauthentik.io/test", rd) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestCheckRedirectParam_Domain(t *testing.T) { | ||||||
|  | 	a := newTestApplication() | ||||||
|  | 	a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() | ||||||
|  | 	a.proxyConfig.CookieDomain = api.PtrString("t.goauthentik.io") | ||||||
|  | 	req, _ := http.NewRequest("GET", "https://a.t.goauthentik.io/outpost.goauthentik.io/auth/start", nil) | ||||||
|  |  | ||||||
|  | 	rd, ok := a.checkRedirectParam(req) | ||||||
|  |  | ||||||
|  | 	assert.Equal(t, false, ok) | ||||||
|  | 	assert.Equal(t, "", rd) | ||||||
|  | 	req, _ = http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://ext.t.goauthentik.io/test", nil) | ||||||
|  |  | ||||||
|  | 	rd, ok = a.checkRedirectParam(req) | ||||||
|  |  | ||||||
|  | 	assert.Equal(t, true, ok) | ||||||
|  | 	assert.Equal(t, "https://ext.t.goauthentik.io/test", rd) | ||||||
|  | } | ||||||
| @ -15,6 +15,7 @@ func newTestApplication() *Application { | |||||||
| 			ClientId:                   api.PtrString(ak.TestSecret()), | 			ClientId:                   api.PtrString(ak.TestSecret()), | ||||||
| 			ClientSecret:               api.PtrString(ak.TestSecret()), | 			ClientSecret:               api.PtrString(ak.TestSecret()), | ||||||
| 			CookieSecret:               api.PtrString(ak.TestSecret()), | 			CookieSecret:               api.PtrString(ak.TestSecret()), | ||||||
|  | 			ExternalHost:               "https://ext.t.goauthentik.io", | ||||||
| 			CookieDomain:               api.PtrString(""), | 			CookieDomain:               api.PtrString(""), | ||||||
| 			Mode:                       api.PROXYMODE_FORWARD_SINGLE.Ptr(), | 			Mode:                       api.PROXYMODE_FORWARD_SINGLE.Ptr(), | ||||||
| 			SkipPathRegex:              api.PtrString("/skip.*"), | 			SkipPathRegex:              api.PtrString("/skip.*"), | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) { | |||||||
| 		a.log.WithError(err).Warning("failed to save session before redirect") | 		a.log.WithError(err).Warning("failed to save session before redirect") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	authUrl := urlJoin(a.proxyConfig.ExternalHost, "/akprox/start") | 	authUrl := urlJoin(a.proxyConfig.ExternalHost, "/outpost.goauthentik.io/start") | ||||||
| 	http.Redirect(rw, r, authUrl, http.StatusFound) | 	http.Redirect(rw, r, authUrl, http.StatusFound) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ func TestRedirectToStart_Proxy(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String()) | 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start", loc.String()) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SeesionName) | 	s, _ := a.sessions.Get(req, constants.SeesionName) | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect]) | ||||||
| @ -38,7 +38,7 @@ func TestRedirectToStart_Forward(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String()) | 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start", loc.String()) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SeesionName) | 	s, _ := a.sessions.Get(req, constants.SeesionName) | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect]) | ||||||
| @ -56,7 +56,7 @@ func TestRedirectToStart_Forward_Domain_Invalid(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String()) | 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start", loc.String()) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SeesionName) | 	s, _ := a.sessions.Get(req, constants.SeesionName) | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect]) | ||||||
| @ -74,7 +74,7 @@ func TestRedirectToStart_Forward_Domain(t *testing.T) { | |||||||
|  |  | ||||||
| 	assert.Equal(t, http.StatusFound, rr.Code) | 	assert.Equal(t, http.StatusFound, rr.Code) | ||||||
| 	loc, _ := rr.Result().Location() | 	loc, _ := rr.Result().Location() | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String()) | 	assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start", loc.String()) | ||||||
|  |  | ||||||
| 	s, _ := a.sessions.Get(req, constants.SeesionName) | 	s, _ := a.sessions.Get(req, constants.SeesionName) | ||||||
| 	assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect]) | 	assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect]) | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ func (ps *ProxyServer) HandlePing(rw http.ResponseWriter, r *http.Request) { | |||||||
|  |  | ||||||
| func (ps *ProxyServer) HandleStatic(rw http.ResponseWriter, r *http.Request) { | func (ps *ProxyServer) HandleStatic(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	before := time.Now() | 	before := time.Now() | ||||||
| 	web.DisableIndex(http.StripPrefix("/akprox/static/dist", staticWeb.StaticHandler)).ServeHTTP(rw, r) | 	web.DisableIndex(http.StripPrefix("/outpost.goauthentik.io/static/dist", staticWeb.StaticHandler)).ServeHTTP(rw, r) | ||||||
| 	after := time.Since(before) | 	after := time.Since(before) | ||||||
| 	metrics.Requests.With(prometheus.Labels{ | 	metrics.Requests.With(prometheus.Labels{ | ||||||
| 		"outpost_name": ps.akAPI.Outpost.Name, | 		"outpost_name": ps.akAPI.Outpost.Name, | ||||||
| @ -90,11 +90,11 @@ func (ps *ProxyServer) lookupApp(r *http.Request) (*application.Application, str | |||||||
| } | } | ||||||
|  |  | ||||||
| func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) { | func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	if strings.HasPrefix(r.URL.Path, "/akprox/static") { | 	if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/static") { | ||||||
| 		ps.HandleStatic(rw, r) | 		ps.HandleStatic(rw, r) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if strings.HasPrefix(r.URL.Path, "/akprox/ping") { | 	if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/ping") { | ||||||
| 		ps.HandlePing(rw, r) | 		ps.HandlePing(rw, r) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @ -109,6 +109,7 @@ func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		ps.log.WithField("headers", r.Header).Trace("tracing headers for no hostname match") | ||||||
| 		ps.log.WithField("host", host).Warning("no app for hostname") | 		ps.log.WithField("host", host).Warning("no app for hostname") | ||||||
|  |  | ||||||
| 		rw.Header().Set("Content-Type", "application/json") | 		rw.Header().Set("Content-Type", "application/json") | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ var ( | |||||||
| func RunServer() { | func RunServer() { | ||||||
| 	m := mux.NewRouter() | 	m := mux.NewRouter() | ||||||
| 	l := log.WithField("logger", "authentik.outpost.metrics") | 	l := log.WithField("logger", "authentik.outpost.metrics") | ||||||
| 	m.HandleFunc("/akprox/ping", func(rw http.ResponseWriter, r *http.Request) { | 	m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) { | ||||||
| 		rw.WriteHeader(204) | 		rw.WriteHeader(204) | ||||||
| 	}) | 	}) | ||||||
| 	m.Path("/metrics").Handler(promhttp.Handler()) | 	m.Path("/metrics").Handler(promhttp.Handler()) | ||||||
|  | |||||||
| @ -64,8 +64,8 @@ func NewProxyServer(ac *ak.APIController, portOffset int) *ProxyServer { | |||||||
| 		akAPI:       ac, | 		akAPI:       ac, | ||||||
| 		defaultCert: defaultCert, | 		defaultCert: defaultCert, | ||||||
| 	} | 	} | ||||||
| 	globalMux.PathPrefix("/akprox/static").HandlerFunc(s.HandleStatic) | 	globalMux.PathPrefix("/outpost.goauthentik.io/static").HandlerFunc(s.HandleStatic) | ||||||
| 	globalMux.Path("/akprox/ping").HandlerFunc(s.HandlePing) | 	globalMux.Path("/outpost.goauthentik.io/ping").HandlerFunc(s.HandlePing) | ||||||
| 	rootMux.PathPrefix("/").HandlerFunc(s.Handle) | 	rootMux.PathPrefix("/").HandlerFunc(s.Handle) | ||||||
| 	return s | 	return s | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,12 +5,13 @@ | |||||||
|         <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="/akprox/static/dist/assets/icons/icon.png"> |         <link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png"> | ||||||
|         <link rel="stylesheet" type="text/css" href="/akprox/static/dist/patternfly.min.css"> |         <link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css"> | ||||||
|         <link rel="stylesheet" type="text/css" href="/akprox/static/dist/authentik.css"> |         <link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css"> | ||||||
|  |         <link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/custom.css"> | ||||||
|         <style> |         <style> | ||||||
|             .pf-c-background-image::before { |             .pf-c-background-image::before { | ||||||
|                 --ak-flow-background: url("/akprox/static/dist/assets/images/flow_background.jpg"); |                 --ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg"); | ||||||
|             } |             } | ||||||
|         </style> |         </style> | ||||||
|     </head> |     </head> | ||||||
| @ -32,7 +33,7 @@ | |||||||
|             <div class="ak-login-container"> |             <div class="ak-login-container"> | ||||||
|                 <header class="pf-c-login__header"> |                 <header class="pf-c-login__header"> | ||||||
|                     <div class="pf-c-brand ak-brand"> |                     <div class="pf-c-brand ak-brand"> | ||||||
|                         <img src="/akprox/static/dist/assets/icons/icon_left_brand.svg" alt="authentik icon" /> |                         <img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik icon" /> | ||||||
|                     </div> |                     </div> | ||||||
|                 </header> |                 </header> | ||||||
|                 <main class="pf-c-login__main"> |                 <main class="pf-c-login__main"> | ||||||
|  | |||||||
| @ -3,7 +3,8 @@ package templates | |||||||
| import ( | import ( | ||||||
| 	_ "embed" | 	_ "embed" | ||||||
| 	"html/template" | 	"html/template" | ||||||
| 	"log" |  | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| //go:embed error.html | //go:embed error.html | ||||||
|  | |||||||
| @ -30,7 +30,7 @@ func (ws *WebServer) configureProxy() { | |||||||
| 	rp := &httputil.ReverseProxy{Director: director} | 	rp := &httputil.ReverseProxy{Director: director} | ||||||
| 	rp.ErrorHandler = ws.proxyErrorHandler | 	rp.ErrorHandler = ws.proxyErrorHandler | ||||||
| 	rp.ModifyResponse = ws.proxyModifyResponse | 	rp.ModifyResponse = ws.proxyModifyResponse | ||||||
| 	ws.m.PathPrefix("/akprox").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | 	ws.m.PathPrefix("/outpost.goauthentik.io").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||||
| 		if ws.ProxyServer != nil { | 		if ws.ProxyServer != nil { | ||||||
| 			before := time.Now() | 			before := time.Now() | ||||||
| 			ws.ProxyServer.Handle(rw, r) | 			ws.ProxyServer.Handle(rw, r) | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| # Stage 1: Build | # Stage 1: Build | ||||||
| FROM docker.io/golang:1.17.6-bullseye AS builder | FROM docker.io/golang:1.17.7-bullseye AS builder | ||||||
|  |  | ||||||
| WORKDIR /go/src/goauthentik.io | WORKDIR /go/src/goauthentik.io | ||||||
|  |  | ||||||
| @ -19,7 +19,7 @@ ENV GIT_BUILD_HASH=$GIT_BUILD_HASH | |||||||
|  |  | ||||||
| COPY --from=builder /go/ldap / | COPY --from=builder /go/ldap / | ||||||
|  |  | ||||||
| HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/akprox/ping" ] | HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/outpost.goauthentik.io/ping" ] | ||||||
|  |  | ||||||
| EXPOSE 3389 6636 9300 | EXPOSE 3389 6636 9300 | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								lifecycle/ak
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								lifecycle/ak
									
									
									
									
									
								
							| @ -32,30 +32,6 @@ function check_if_root { | |||||||
|     chpst -u authentik:$GROUP env HOME=/authentik $1 |     chpst -u authentik:$GROUP env HOME=/authentik $1 | ||||||
| } | } | ||||||
|  |  | ||||||
| function prefixwith { |  | ||||||
|     local prefix="$1" |  | ||||||
|     shift |  | ||||||
|     "$@" > >(sed "s/^/$prefix: /") 2> >(sed "s/^/$prefix (err): /" >&2) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function restore { |  | ||||||
|     PG_HOST=$(python -m authentik.lib.config postgresql.host 2> /dev/null) |  | ||||||
|     PG_NAME=$(python -m authentik.lib.config postgresql.name 2> /dev/null) |  | ||||||
|     PG_USER=$(python -m authentik.lib.config postgresql.user 2> /dev/null) |  | ||||||
|     PG_PORT=$(python -m authentik.lib.config postgresql.port 2> /dev/null) |  | ||||||
|     export PGPASSWORD=$(python -m authentik.lib.config postgresql.password 2> /dev/null) |  | ||||||
|     log "Ensuring no one can connect to the database" |  | ||||||
|     prefixwith "psql" psql -h"${PG_HOST}" -U"${PG_USER}" -c"UPDATE pg_database SET datallowconn = 'false' WHERE datname = '${PG_NAME}';" "postgres" |  | ||||||
|     prefixwith "psql" psql -h"${PG_HOST}" -U"${PG_USER}" -c"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${PG_NAME}';" "postgres" |  | ||||||
|     log "deleting and re-creating database" |  | ||||||
|     prefixwith "psql" dropdb -h"${PG_HOST}" -U"${PG_USER}" "${PG_NAME}" || trueacku |  | ||||||
|     prefixwith "psql" createdb -h"${PG_HOST}" -U"${PG_USER}" "${PG_NAME}" |  | ||||||
|     log "running initial migrations" |  | ||||||
|     prefixwith "migrate" python -m lifecycle.migrate 2> /dev/null |  | ||||||
|     log "restoring database" |  | ||||||
|     prefixwith "restore" python -m manage dbrestore -i ${@:2} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MODE_FILE="/tmp/authentik-mode" | MODE_FILE="/tmp/authentik-mode" | ||||||
|  |  | ||||||
| if [[ "$1" == "server" ]]; then | if [[ "$1" == "server" ]]; then | ||||||
| @ -75,12 +51,6 @@ elif [[ "$1" == "worker" ]]; then | |||||||
| elif [[ "$1" == "flower" ]]; then | elif [[ "$1" == "flower" ]]; then | ||||||
|     echo "flower" > $MODE_FILE |     echo "flower" > $MODE_FILE | ||||||
|     celery -A authentik.root.celery flower |     celery -A authentik.root.celery flower | ||||||
| elif [[ "$1" == "backup" ]]; then |  | ||||||
|     wait_for_db |  | ||||||
|     python -m manage dbbackup --clean |  | ||||||
| elif [[ "$1" == "restore" ]]; then |  | ||||||
|     wait_for_db |  | ||||||
|     restore $@ |  | ||||||
| elif [[ "$1" == "bash" ]]; then | elif [[ "$1" == "bash" ]]; then | ||||||
|     /bin/bash |     /bin/bash | ||||||
| elif [[ "$1" == "test" ]]; then | elif [[ "$1" == "test" ]]; then | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								locale/zh-Hans/LC_MESSAGES/django.mo
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								locale/zh-Hans/LC_MESSAGES/django.mo
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1749
									
								
								locale/zh-Hans/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1749
									
								
								locale/zh-Hans/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								locale/zh-Hant/LC_MESSAGES/django.mo
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								locale/zh-Hant/LC_MESSAGES/django.mo
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1749
									
								
								locale/zh-Hant/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1749
									
								
								locale/zh-Hant/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								locale/zh_TW/LC_MESSAGES/django.mo
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								locale/zh_TW/LC_MESSAGES/django.mo
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1749
									
								
								locale/zh_TW/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1749
									
								
								locale/zh_TW/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -22,13 +22,6 @@ warnings.filterwarnings( | |||||||
|         "efault_app_config." |         "efault_app_config." | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
| warnings.filterwarnings( |  | ||||||
|     "ignore", |  | ||||||
|     message=( |  | ||||||
|         "'dbbackup' defines default_app_config = 'dbbackup.apps.DbbackupConfig'. Django now det" |  | ||||||
|         "ects this configuration automatically. You can remove default_app_config." |  | ||||||
|     ), |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| defuse_stdlib() | defuse_stdlib() | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										631
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										631
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -7,7 +7,7 @@ ENV NODE_ENV=production | |||||||
| RUN cd /static && npm i && npm run build-proxy | RUN cd /static && npm i && npm run build-proxy | ||||||
|  |  | ||||||
| # Stage 2: Build | # Stage 2: Build | ||||||
| FROM docker.io/golang:1.17.6-bullseye AS builder | FROM docker.io/golang:1.17.7-bullseye AS builder | ||||||
|  |  | ||||||
| WORKDIR /go/src/goauthentik.io | WORKDIR /go/src/goauthentik.io | ||||||
|  |  | ||||||
| @ -32,7 +32,7 @@ COPY --from=web-builder /static/security.txt /web/security.txt | |||||||
| COPY --from=web-builder /static/dist/ /web/dist/ | COPY --from=web-builder /static/dist/ /web/dist/ | ||||||
| COPY --from=web-builder /static/authentik/ /web/authentik/ | COPY --from=web-builder /static/authentik/ /web/authentik/ | ||||||
|  |  | ||||||
| HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/akprox/ping" ] | HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/outpost.goauthentik.io/ping" ] | ||||||
|  |  | ||||||
| EXPOSE 9000 9300 9443 | EXPOSE 9000 9300 9443 | ||||||
|  |  | ||||||
|  | |||||||
| @ -92,12 +92,11 @@ addopts = "-p no:celery --junitxml=unittest.xml" | |||||||
|  |  | ||||||
| [tool.poetry] | [tool.poetry] | ||||||
| name = "authentik" | name = "authentik" | ||||||
| version = "2022.1.5" | version = "2022.2.1" | ||||||
| description = "" | description = "" | ||||||
| authors = ["Jens Langhammer <jens.langhammer@beryju.org>"] | authors = ["Jens Langhammer <jens.langhammer@beryju.org>"] | ||||||
|  |  | ||||||
| [tool.poetry.dependencies] | [tool.poetry.dependencies] | ||||||
| boto3 = "*" |  | ||||||
| celery = "*" | celery = "*" | ||||||
| channels = "*" | channels = "*" | ||||||
| channels-redis = "*" | channels-redis = "*" | ||||||
| @ -107,14 +106,12 @@ dacite = "*" | |||||||
| deepmerge = "*" | deepmerge = "*" | ||||||
| defusedxml = "*" | defusedxml = "*" | ||||||
| django = "*" | django = "*" | ||||||
| django-dbbackup = "=4.0.0b0" |  | ||||||
| django-filter = "*" | django-filter = "*" | ||||||
| django-guardian = "*" | django-guardian = "*" | ||||||
| django-model-utils = "*" | django-model-utils = "*" | ||||||
| django-otp = "*" | django-otp = "*" | ||||||
| django-prometheus = "*" | django-prometheus = "*" | ||||||
| django-redis = "*" | django-redis = "*" | ||||||
| django-storages = "*" |  | ||||||
| djangorestframework = "*" | djangorestframework = "*" | ||||||
| djangorestframework-guardian = "*" | djangorestframework-guardian = "*" | ||||||
| docker = "*" | docker = "*" | ||||||
|  | |||||||
							
								
								
									
										72
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								schema.yml
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | |||||||
| openapi: 3.0.3 | openapi: 3.0.3 | ||||||
| info: | info: | ||||||
|   title: authentik |   title: authentik | ||||||
|   version: 2022.1.5 |   version: 2022.2.1 | ||||||
|   description: Making authentication simple. |   description: Making authentication simple. | ||||||
|   contact: |   contact: | ||||||
|     email: hello@beryju.org |     email: hello@beryju.org | ||||||
| @ -15045,10 +15045,14 @@ paths: | |||||||
|       description: AuthenticatorValidateStage Viewset |       description: AuthenticatorValidateStage Viewset | ||||||
|       parameters: |       parameters: | ||||||
|       - in: query |       - in: query | ||||||
|         name: configuration_stage |         name: configuration_stages | ||||||
|         schema: |         schema: | ||||||
|           type: string |           type: array | ||||||
|           format: uuid |           items: | ||||||
|  |             type: string | ||||||
|  |             format: uuid | ||||||
|  |         explode: true | ||||||
|  |         style: form | ||||||
|       - in: query |       - in: query | ||||||
|         name: name |         name: name | ||||||
|         schema: |         schema: | ||||||
| @ -19826,11 +19830,12 @@ components: | |||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/DeviceClassesEnum' |             $ref: '#/components/schemas/DeviceClassesEnum' | ||||||
|           description: Device classes which can be used to authenticate |           description: Device classes which can be used to authenticate | ||||||
|         configuration_stage: |         configuration_stages: | ||||||
|           type: string |           type: array | ||||||
|           format: uuid |           items: | ||||||
|           nullable: true |             type: string | ||||||
|           description: Stage used to configure Authenticator when user doesn't have |             format: uuid | ||||||
|  |           description: Stages used to configure Authenticator when user doesn't have | ||||||
|             any compatible devices. After this configuration Stage passes, the user |             any compatible devices. After this configuration Stage passes, the user | ||||||
|             is not prompted again. |             is not prompted again. | ||||||
|       required: |       required: | ||||||
| @ -19858,11 +19863,12 @@ components: | |||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/DeviceClassesEnum' |             $ref: '#/components/schemas/DeviceClassesEnum' | ||||||
|           description: Device classes which can be used to authenticate |           description: Device classes which can be used to authenticate | ||||||
|         configuration_stage: |         configuration_stages: | ||||||
|           type: string |           type: array | ||||||
|           format: uuid |           items: | ||||||
|           nullable: true |             type: string | ||||||
|           description: Stage used to configure Authenticator when user doesn't have |             format: uuid | ||||||
|  |           description: Stages used to configure Authenticator when user doesn't have | ||||||
|             any compatible devices. After this configuration Stage passes, the user |             any compatible devices. After this configuration Stage passes, the user | ||||||
|             is not prompted again. |             is not prompted again. | ||||||
|       required: |       required: | ||||||
| @ -19892,7 +19898,12 @@ components: | |||||||
|           type: array |           type: array | ||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/DeviceChallenge' |             $ref: '#/components/schemas/DeviceChallenge' | ||||||
|  |         configuration_stages: | ||||||
|  |           type: array | ||||||
|  |           items: | ||||||
|  |             $ref: '#/components/schemas/SelectableStage' | ||||||
|       required: |       required: | ||||||
|  |       - configuration_stages | ||||||
|       - device_challenges |       - device_challenges | ||||||
|       - pending_user |       - pending_user | ||||||
|       - pending_user_avatar |       - pending_user_avatar | ||||||
| @ -19907,6 +19918,9 @@ components: | |||||||
|           default: ak-stage-authenticator-validate |           default: ak-stage-authenticator-validate | ||||||
|         selected_challenge: |         selected_challenge: | ||||||
|           $ref: '#/components/schemas/DeviceChallengeRequest' |           $ref: '#/components/schemas/DeviceChallengeRequest' | ||||||
|  |         selected_stage: | ||||||
|  |           type: string | ||||||
|  |           minLength: 1 | ||||||
|         code: |         code: | ||||||
|           type: string |           type: string | ||||||
|           minLength: 1 |           minLength: 1 | ||||||
| @ -20017,7 +20031,6 @@ components: | |||||||
|       enum: |       enum: | ||||||
|       - can_save_media |       - can_save_media | ||||||
|       - can_geo_ip |       - can_geo_ip | ||||||
|       - can_backup |  | ||||||
|       type: string |       type: string | ||||||
|     CaptchaChallenge: |     CaptchaChallenge: | ||||||
|       type: object |       type: object | ||||||
| @ -26678,11 +26691,12 @@ components: | |||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/DeviceClassesEnum' |             $ref: '#/components/schemas/DeviceClassesEnum' | ||||||
|           description: Device classes which can be used to authenticate |           description: Device classes which can be used to authenticate | ||||||
|         configuration_stage: |         configuration_stages: | ||||||
|           type: string |           type: array | ||||||
|           format: uuid |           items: | ||||||
|           nullable: true |             type: string | ||||||
|           description: Stage used to configure Authenticator when user doesn't have |             format: uuid | ||||||
|  |           description: Stages used to configure Authenticator when user doesn't have | ||||||
|             any compatible devices. After this configuration Stage passes, the user |             any compatible devices. After this configuration Stage passes, the user | ||||||
|             is not prompted again. |             is not prompted again. | ||||||
|     PatchedCaptchaStageRequest: |     PatchedCaptchaStageRequest: | ||||||
| @ -30018,6 +30032,24 @@ components: | |||||||
|       - direct |       - direct | ||||||
|       - cached |       - cached | ||||||
|       type: string |       type: string | ||||||
|  |     SelectableStage: | ||||||
|  |       type: object | ||||||
|  |       description: Serializer for stages which can be selected by users | ||||||
|  |       properties: | ||||||
|  |         pk: | ||||||
|  |           type: string | ||||||
|  |           format: uuid | ||||||
|  |         name: | ||||||
|  |           type: string | ||||||
|  |         verbose_name: | ||||||
|  |           type: string | ||||||
|  |         meta_model_name: | ||||||
|  |           type: string | ||||||
|  |       required: | ||||||
|  |       - meta_model_name | ||||||
|  |       - name | ||||||
|  |       - pk | ||||||
|  |       - verbose_name | ||||||
|     ServiceConnection: |     ServiceConnection: | ||||||
|       type: object |       type: object | ||||||
|       description: ServiceConnection Serializer |       description: ServiceConnection Serializer | ||||||
|  | |||||||
| @ -105,7 +105,7 @@ class TestProviderProxy(SeleniumTestCase): | |||||||
|         self.assertIn(f"X-Authentik-Username: {self.user.username}", full_body_text) |         self.assertIn(f"X-Authentik-Username: {self.user.username}", full_body_text) | ||||||
|         self.assertIn("X-Foo: bar", full_body_text) |         self.assertIn("X-Foo: bar", full_body_text) | ||||||
|  |  | ||||||
|         self.driver.get("http://localhost:9000/akprox/sign_out") |         self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out") | ||||||
|         sleep(2) |         sleep(2) | ||||||
|         full_body_text = self.driver.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text |         full_body_text = self.driver.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text | ||||||
|         self.assertIn("You've logged out of proxy.", full_body_text) |         self.assertIn("You've logged out of proxy.", full_body_text) | ||||||
|  | |||||||
							
								
								
									
										873
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										873
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -22,7 +22,11 @@ | |||||||
|             "fr_FR", |             "fr_FR", | ||||||
|             "tr", |             "tr", | ||||||
|             "es", |             "es", | ||||||
|             "pl" |             "pl", | ||||||
|  |             "zh_TW", | ||||||
|  |             "zh-Hans", | ||||||
|  |             "zh-Hant", | ||||||
|  |             "de" | ||||||
|         ], |         ], | ||||||
|         "formatOptions": { |         "formatOptions": { | ||||||
|             "lineNumbers": false |             "lineNumbers": false | ||||||
| @ -48,14 +52,14 @@ | |||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|         "@babel/core": "^7.16.12", |         "@babel/core": "^7.17.4", | ||||||
|         "@babel/plugin-proposal-decorators": "^7.16.7", |         "@babel/plugin-proposal-decorators": "^7.17.2", | ||||||
|         "@babel/plugin-transform-runtime": "^7.16.10", |         "@babel/plugin-transform-runtime": "^7.17.0", | ||||||
|         "@babel/preset-env": "^7.16.11", |         "@babel/preset-env": "^7.16.11", | ||||||
|         "@babel/preset-typescript": "^7.16.7", |         "@babel/preset-typescript": "^7.16.7", | ||||||
|         "@formatjs/intl-listformat": "^6.5.1", |         "@formatjs/intl-listformat": "^6.5.2", | ||||||
|         "@fortawesome/fontawesome-free": "^5.15.4", |         "@fortawesome/fontawesome-free": "^6.0.0", | ||||||
|         "@goauthentik/api": "^2022.1.3-1643236150", |         "@goauthentik/api": "^2022.1.5-1644681372", | ||||||
|         "@jackfranklin/rollup-plugin-markdown": "^0.3.0", |         "@jackfranklin/rollup-plugin-markdown": "^0.3.0", | ||||||
|         "@lingui/cli": "^3.13.2", |         "@lingui/cli": "^3.13.2", | ||||||
|         "@lingui/core": "^3.13.2", |         "@lingui/core": "^3.13.2", | ||||||
| @ -67,36 +71,36 @@ | |||||||
|         "@rollup/plugin-babel": "^5.3.0", |         "@rollup/plugin-babel": "^5.3.0", | ||||||
|         "@rollup/plugin-commonjs": "^21.0.1", |         "@rollup/plugin-commonjs": "^21.0.1", | ||||||
|         "@rollup/plugin-node-resolve": "^13.1.3", |         "@rollup/plugin-node-resolve": "^13.1.3", | ||||||
|         "@rollup/plugin-replace": "^3.0.1", |         "@rollup/plugin-replace": "^3.1.0", | ||||||
|         "@rollup/plugin-typescript": "^8.3.0", |         "@rollup/plugin-typescript": "^8.3.0", | ||||||
|         "@sentry/browser": "^6.17.3", |         "@sentry/browser": "^6.17.8", | ||||||
|         "@sentry/tracing": "^6.17.3", |         "@sentry/tracing": "^6.17.8", | ||||||
|         "@squoosh/cli": "^0.7.2", |         "@squoosh/cli": "^0.7.2", | ||||||
|         "@trivago/prettier-plugin-sort-imports": "^3.1.1", |         "@trivago/prettier-plugin-sort-imports": "^3.2.0", | ||||||
|         "@types/chart.js": "^2.9.35", |         "@types/chart.js": "^2.9.35", | ||||||
|         "@types/codemirror": "5.60.5", |         "@types/codemirror": "5.60.5", | ||||||
|         "@types/grecaptcha": "^3.0.3", |         "@types/grecaptcha": "^3.0.3", | ||||||
|         "@typescript-eslint/eslint-plugin": "^5.10.2", |         "@typescript-eslint/eslint-plugin": "^5.12.0", | ||||||
|         "@typescript-eslint/parser": "^5.10.2", |         "@typescript-eslint/parser": "^5.12.0", | ||||||
|         "@webcomponents/webcomponentsjs": "^2.6.0", |         "@webcomponents/webcomponentsjs": "^2.6.0", | ||||||
|         "babel-plugin-macros": "^3.1.0", |         "babel-plugin-macros": "^3.1.0", | ||||||
|         "base64-js": "^1.5.1", |         "base64-js": "^1.5.1", | ||||||
|         "chart.js": "^3.7.0", |         "chart.js": "^3.7.1", | ||||||
|         "chartjs-adapter-moment": "^1.0.0", |         "chartjs-adapter-moment": "^1.0.0", | ||||||
|         "codemirror": "^5.65.1", |         "codemirror": "^5.65.1", | ||||||
|         "construct-style-sheets-polyfill": "^3.1.0", |         "construct-style-sheets-polyfill": "^3.1.0", | ||||||
|         "country-flag-icons": "^1.4.20", |         "country-flag-icons": "^1.4.21", | ||||||
|         "eslint": "^8.8.0", |         "eslint": "^8.9.0", | ||||||
|         "eslint-config-google": "^0.14.0", |         "eslint-config-google": "^0.14.0", | ||||||
|         "eslint-plugin-custom-elements": "0.0.4", |         "eslint-plugin-custom-elements": "0.0.4", | ||||||
|         "eslint-plugin-lit": "^1.6.1", |         "eslint-plugin-lit": "^1.6.1", | ||||||
|         "flowchart.js": "^1.17.0", |         "flowchart.js": "^1.17.1", | ||||||
|         "fuse.js": "^6.5.3", |         "fuse.js": "^6.5.3", | ||||||
|         "lit": "^2.1.2", |         "lit": "^2.1.4", | ||||||
|         "moment": "^2.29.1", |         "moment": "^2.29.1", | ||||||
|         "prettier": "^2.5.1", |         "prettier": "^2.5.1", | ||||||
|         "rapidoc": "^9.1.4", |         "rapidoc": "^9.1.4", | ||||||
|         "rollup": "^2.66.1", |         "rollup": "^2.67.2", | ||||||
|         "rollup-plugin-copy": "^3.4.0", |         "rollup-plugin-copy": "^3.4.0", | ||||||
|         "rollup-plugin-cssimport": "^1.0.2", |         "rollup-plugin-cssimport": "^1.0.2", | ||||||
|         "rollup-plugin-minify-html-literals": "^1.2.6", |         "rollup-plugin-minify-html-literals": "^1.2.6", | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ export const resources = [ | |||||||
|         dest: "dist/", |         dest: "dist/", | ||||||
|     }, |     }, | ||||||
|     { src: "src/authentik.css", dest: "dist/" }, |     { src: "src/authentik.css", dest: "dist/" }, | ||||||
|  |     { src: "src/custom.css", dest: "dist/" }, | ||||||
|  |  | ||||||
|     { |     { | ||||||
|         src: "node_modules/@patternfly/patternfly/assets/*", |         src: "node_modules/@patternfly/patternfly/assets/*", | ||||||
|  | |||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 704 KiB After Width: | Height: | Size: 580 KiB | 
| @ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; | |||||||
| export const ERROR_CLASS = "pf-m-danger"; | export const ERROR_CLASS = "pf-m-danger"; | ||||||
| export const PROGRESS_CLASS = "pf-m-in-progress"; | export const PROGRESS_CLASS = "pf-m-in-progress"; | ||||||
| export const CURRENT_CLASS = "pf-m-current"; | export const CURRENT_CLASS = "pf-m-current"; | ||||||
| export const VERSION = "2022.1.5"; | export const VERSION = "2022.2.1"; | ||||||
| export const TITLE_DEFAULT = "authentik"; | export const TITLE_DEFAULT = "authentik"; | ||||||
| export const ROUTE_SEPARATOR = ";"; | export const ROUTE_SEPARATOR = ";"; | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								web/src/custom.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/src/custom.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | /* User customisable */ | ||||||
| @ -433,8 +433,7 @@ export class FlowExecutor extends LitElement implements StageHost { | |||||||
|                                                 ) |                                                 ) | ||||||
|                                                     ? html` |                                                     ? html` | ||||||
|                                                           <li> |                                                           <li> | ||||||
|                                                               <a |                                                               <a href="https://unsplash.com/@trime" | ||||||
|                                                                   href="https://unsplash.com/@kimonmaritz" |  | ||||||
|                                                                   >${t`Background image`}</a |                                                                   >${t`Background image`}</a | ||||||
|                                                               > |                                                               > | ||||||
|                                                           </li> |                                                           </li> | ||||||
|  | |||||||
| @ -67,7 +67,7 @@ export class AuthenticatorValidateStage | |||||||
|         return this._selectedDeviceChallenge; |         return this._selectedDeviceChallenge; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<void> { |     submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<boolean> { | ||||||
|         return this.host?.submit(payload) || Promise.resolve(); |         return this.host?.submit(payload) || Promise.resolve(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -140,7 +140,7 @@ export class AuthenticatorValidateStage | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderDevicePicker(): TemplateResult { |     renderDevicePicker(): TemplateResult { | ||||||
|         return html` <ul> |         return html`<ul> | ||||||
|             ${this.challenge?.deviceChallenges.map((challenges) => { |             ${this.challenge?.deviceChallenges.map((challenges) => { | ||||||
|                 return html`<li> |                 return html`<li> | ||||||
|                     <button |                     <button | ||||||
| @ -157,6 +157,30 @@ export class AuthenticatorValidateStage | |||||||
|         </ul>`; |         </ul>`; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     renderStagePicker(): TemplateResult { | ||||||
|  |         return html`<ul> | ||||||
|  |             ${this.challenge?.configurationStages.map((stage) => { | ||||||
|  |                 return html`<li> | ||||||
|  |                     <button | ||||||
|  |                         class="pf-c-button authenticator-button" | ||||||
|  |                         type="button" | ||||||
|  |                         @click=${() => { | ||||||
|  |                             this.submit({ | ||||||
|  |                                 component: this.challenge.component || "", | ||||||
|  |                                 selectedStage: stage.pk, | ||||||
|  |                             }); | ||||||
|  |                         }} | ||||||
|  |                     > | ||||||
|  |                         <div class="right"> | ||||||
|  |                             <p>${stage.name}</p> | ||||||
|  |                             <small>${stage.verboseName}</small> | ||||||
|  |                         </div> | ||||||
|  |                     </button> | ||||||
|  |                 </li>`; | ||||||
|  |             })} | ||||||
|  |         </ul>`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     renderDeviceChallenge(): TemplateResult { |     renderDeviceChallenge(): TemplateResult { | ||||||
|         if (!this.selectedDeviceChallenge) { |         if (!this.selectedDeviceChallenge) { | ||||||
|             return html``; |             return html``; | ||||||
| @ -242,6 +266,9 @@ export class AuthenticatorValidateStage | |||||||
|                               ${this.selectedDeviceChallenge |                               ${this.selectedDeviceChallenge | ||||||
|                                   ? "" |                                   ? "" | ||||||
|                                   : html`<p>${t`Select an authentication method.`}</p>`} |                                   : html`<p>${t`Select an authentication method.`}</p>`} | ||||||
|  |                               ${this.challenge.configurationStages.length > 0 | ||||||
|  |                                   ? this.renderStagePicker() | ||||||
|  |                                   : html``} | ||||||
|                           </form> |                           </form> | ||||||
|                           ${this.renderDevicePicker()} |                           ${this.renderDevicePicker()} | ||||||
|                       </div> |                       </div> | ||||||
|  | |||||||
| @ -1,15 +1,19 @@ | |||||||
| import { en, es, fr, pl, tr } from "make-plural/plurals"; | import { de, en, es, fr, pl, tr, zh } from "make-plural/plurals"; | ||||||
|  |  | ||||||
| import { Messages, i18n } from "@lingui/core"; | import { Messages, i18n } from "@lingui/core"; | ||||||
| import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale"; | import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale"; | ||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
|  |  | ||||||
|  | import { messages as localeDE } from "../locales/de"; | ||||||
| import { messages as localeEN } from "../locales/en"; | import { messages as localeEN } from "../locales/en"; | ||||||
| import { messages as localeES } from "../locales/es"; | import { messages as localeES } from "../locales/es"; | ||||||
| import { messages as localeFR_FR } from "../locales/fr_FR"; | import { messages as localeFR_FR } from "../locales/fr_FR"; | ||||||
| import { messages as localePL } from "../locales/pl"; | import { messages as localePL } from "../locales/pl"; | ||||||
| import { messages as localeDEBUG } from "../locales/pseudo-LOCALE"; | import { messages as localeDEBUG } from "../locales/pseudo-LOCALE"; | ||||||
| import { messages as localeTR } from "../locales/tr"; | import { messages as localeTR } from "../locales/tr"; | ||||||
|  | import { messages as localeZH_Hans } from "../locales/zh-Hans"; | ||||||
|  | import { messages as localeZH_Hant } from "../locales/zh-Hant"; | ||||||
|  | import { messages as localeZH_TW } from "../locales/zh_TW"; | ||||||
|  |  | ||||||
| export const LOCALES: { | export const LOCALES: { | ||||||
|     code: string; |     code: string; | ||||||
| @ -54,6 +58,30 @@ export const LOCALES: { | |||||||
|         label: t`Polish`, |         label: t`Polish`, | ||||||
|         locale: localePL, |         locale: localePL, | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |         code: "zh_TW", | ||||||
|  |         plurals: zh, | ||||||
|  |         label: t`Taiwanese Mandarin`, | ||||||
|  |         locale: localeZH_TW, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         code: "zh-Hans", | ||||||
|  |         plurals: zh, | ||||||
|  |         label: t`Chinese (simplified)`, | ||||||
|  |         locale: localeZH_Hans, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         code: "zh-Hant", | ||||||
|  |         plurals: zh, | ||||||
|  |         label: t`Chinese (traditional)`, | ||||||
|  |         locale: localeZH_Hant, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         code: "de", | ||||||
|  |         plurals: de, | ||||||
|  |         label: t`German`, | ||||||
|  |         locale: localeDE, | ||||||
|  |     }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| LOCALES.forEach((locale) => { | LOCALES.forEach((locale) => { | ||||||
|  | |||||||
							
								
								
									
										6125
									
								
								web/src/locales/de.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6125
									
								
								web/src/locales/de.po
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -563,16 +563,16 @@ msgid "Background shown during execution." | |||||||
| msgstr "Background shown during execution." | msgstr "Background shown during execution." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with errors." | #~ msgid "Backup finished with errors." | ||||||
| msgstr "Backup finished with errors." | #~ msgstr "Backup finished with errors." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with warnings/backup not supported." | #~ msgid "Backup finished with warnings/backup not supported." | ||||||
| msgstr "Backup finished with warnings/backup not supported." | #~ msgstr "Backup finished with warnings/backup not supported." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/AdminOverviewPage.ts | #: src/pages/admin-overview/AdminOverviewPage.ts | ||||||
| msgid "Backup status" | #~ msgid "Backup status" | ||||||
| msgstr "Backup status" | #~ msgstr "Backup status" | ||||||
|  |  | ||||||
| #: src/pages/providers/ldap/LDAPProviderForm.ts | #: src/pages/providers/ldap/LDAPProviderForm.ts | ||||||
| #: src/pages/providers/ldap/LDAPProviderViewPage.ts | #: src/pages/providers/ldap/LDAPProviderViewPage.ts | ||||||
| @ -844,6 +844,14 @@ msgstr "Checks if the request's user's password has been changed in the last x d | |||||||
| msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | ||||||
| msgstr "Checks the value from the policy request against several rules, mostly used to ensure password strength." | msgstr "Checks the value from the policy request against several rules, mostly used to ensure password strength." | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (simplified)" | ||||||
|  | msgstr "Chinese (simplified)" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (traditional)" | ||||||
|  | msgstr "Chinese (traditional)" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts | #: src/pages/flows/FlowListPage.ts | ||||||
| msgid "Clear Flow cache" | msgid "Clear Flow cache" | ||||||
| msgstr "Clear Flow cache" | msgstr "Clear Flow cache" | ||||||
| @ -948,8 +956,12 @@ msgid "Configuration flow" | |||||||
| msgstr "Configuration flow" | msgstr "Configuration flow" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Configuration stage" | #~ msgid "Configuration stage" | ||||||
| msgstr "Configuration stage" | #~ msgstr "Configuration stage" | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Configuration stages" | ||||||
|  | msgstr "Configuration stages" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| #~ msgid "Configure WebAuthn" | #~ msgid "Configure WebAuthn" | ||||||
| @ -2205,6 +2217,10 @@ msgstr "Generic" | |||||||
| msgid "Generic OpenID Connect" | msgid "Generic OpenID Connect" | ||||||
| msgstr "Generic OpenID Connect" | msgstr "Generic OpenID Connect" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "German" | ||||||
|  | msgstr "German" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| msgid "Get this value from https://console.twilio.com" | msgid "Get this value from https://console.twilio.com" | ||||||
| @ -4483,8 +4499,8 @@ msgid "Stage type" | |||||||
| msgstr "Stage type" | msgstr "Stage type" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | #~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
| msgstr "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | #~ msgstr "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | ||||||
| msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | ||||||
| @ -4543,6 +4559,10 @@ msgstr "Stages" | |||||||
| msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | ||||||
| msgstr "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | msgstr "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
|  | msgstr "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
|  |  | ||||||
| #: src/pages/outposts/ServiceConnectionListPage.ts | #: src/pages/outposts/ServiceConnectionListPage.ts | ||||||
| msgid "State" | msgid "State" | ||||||
| msgstr "State" | msgstr "State" | ||||||
| @ -5014,6 +5034,10 @@ msgstr "TOTP Device" | |||||||
| msgid "TOTP authenticator" | msgid "TOTP authenticator" | ||||||
| msgstr "TOTP authenticator" | msgstr "TOTP authenticator" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Taiwanese Mandarin" | ||||||
|  | msgstr "Taiwanese Mandarin" | ||||||
|  |  | ||||||
| #: src/pages/flows/StageBindingForm.ts | #: src/pages/flows/StageBindingForm.ts | ||||||
| msgid "Target" | msgid "Target" | ||||||
| msgstr "Target" | msgstr "Target" | ||||||
| @ -5673,8 +5697,8 @@ msgid "Use the username and password below to authenticate. The password can be | |||||||
| msgstr "Use the username and password below to authenticate. The password can be retrieved later on the Tokens page." | msgstr "Use the username and password below to authenticate. The password can be retrieved later on the Tokens page." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)." | ||||||
| msgstr "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." | msgstr "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | ||||||
| @ -6036,6 +6060,10 @@ msgstr "When enabled, the invitation will be deleted after usage." | |||||||
| msgid "When enabled, user fields are matched regardless of their casing." | msgid "When enabled, user fields are matched regardless of their casing." | ||||||
| msgstr "When enabled, user fields are matched regardless of their casing." | msgstr "When enabled, user fields are matched regardless of their casing." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "When multiple stages are selected, the user can choose which one they want to enroll." | ||||||
|  | msgstr "When multiple stages are selected, the user can choose which one they want to enroll." | ||||||
|  |  | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | ||||||
| msgstr "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | msgstr "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | ||||||
|  | |||||||
| @ -561,16 +561,16 @@ msgid "Background shown during execution." | |||||||
| msgstr "Se muestra el fondo durante la ejecución." | msgstr "Se muestra el fondo durante la ejecución." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with errors." | #~ msgid "Backup finished with errors." | ||||||
| msgstr "La copia de seguridad terminó con errores." | #~ msgstr "La copia de seguridad terminó con errores." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with warnings/backup not supported." | #~ msgid "Backup finished with warnings/backup not supported." | ||||||
| msgstr "La copia de seguridad finalizó con advertencias o copias de seguridad no compatibles" | #~ msgstr "La copia de seguridad finalizó con advertencias o copias de seguridad no compatibles" | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/AdminOverviewPage.ts | #: src/pages/admin-overview/AdminOverviewPage.ts | ||||||
| msgid "Backup status" | #~ msgid "Backup status" | ||||||
| msgstr "Estado de respaldo" | #~ msgstr "Estado de respaldo" | ||||||
|  |  | ||||||
| #: src/pages/providers/ldap/LDAPProviderForm.ts | #: src/pages/providers/ldap/LDAPProviderForm.ts | ||||||
| #: src/pages/providers/ldap/LDAPProviderViewPage.ts | #: src/pages/providers/ldap/LDAPProviderViewPage.ts | ||||||
| @ -838,6 +838,14 @@ msgstr "Comprueba si la contraseña del usuario de la solicitud se ha cambiado e | |||||||
| msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | ||||||
| msgstr "Comprueba el valor de la solicitud de política en relación con varias reglas, que se utilizan principalmente para garantizar la seguridad de la contraseña." | msgstr "Comprueba el valor de la solicitud de política en relación con varias reglas, que se utilizan principalmente para garantizar la seguridad de la contraseña." | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (simplified)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (traditional)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts | #: src/pages/flows/FlowListPage.ts | ||||||
| msgid "Clear Flow cache" | msgid "Clear Flow cache" | ||||||
| msgstr "Borrar caché de flujo" | msgstr "Borrar caché de flujo" | ||||||
| @ -942,8 +950,12 @@ msgid "Configuration flow" | |||||||
| msgstr "Flujo de configuración" | msgstr "Flujo de configuración" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Configuration stage" | #~ msgid "Configuration stage" | ||||||
| msgstr "Etapa de configuración" | #~ msgstr "Etapa de configuración" | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Configuration stages" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #~ msgid "Configure WebAuthn" | #~ msgid "Configure WebAuthn" | ||||||
| #~ msgstr "Configurar WebAuthn" | #~ msgstr "Configurar WebAuthn" | ||||||
| @ -2166,6 +2178,10 @@ msgstr "Genérico" | |||||||
| msgid "Generic OpenID Connect" | msgid "Generic OpenID Connect" | ||||||
| msgstr "Conexión OpenID genérica" | msgstr "Conexión OpenID genérica" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "German" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| msgid "Get this value from https://console.twilio.com" | msgid "Get this value from https://console.twilio.com" | ||||||
| @ -4391,8 +4407,8 @@ msgid "Stage type" | |||||||
| msgstr "Tipo de escenario" | msgstr "Tipo de escenario" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | #~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
| msgstr "Etapa utilizada para configurar Authenticator cuando el usuario no tiene ningún dispositivo compatible. Una vez superada esta etapa de configuración, no se volverá a preguntar al usuario." | #~ msgstr "Etapa utilizada para configurar Authenticator cuando el usuario no tiene ningún dispositivo compatible. Una vez superada esta etapa de configuración, no se volverá a preguntar al usuario." | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | ||||||
| msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | ||||||
| @ -4451,6 +4467,10 @@ msgstr "Etapas" | |||||||
| msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | ||||||
| msgstr "Las etapas son pasos individuales de un flujo por los que se guía al usuario. Una etapa solo se puede ejecutar desde dentro de un flujo." | msgstr "Las etapas son pasos individuales de un flujo por los que se guía al usuario. Una etapa solo se puede ejecutar desde dentro de un flujo." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/outposts/ServiceConnectionListPage.ts | #: src/pages/outposts/ServiceConnectionListPage.ts | ||||||
| msgid "State" | msgid "State" | ||||||
| msgstr "Estado" | msgstr "Estado" | ||||||
| @ -4905,6 +4925,10 @@ msgstr "Dispositivo TOTP" | |||||||
| msgid "TOTP authenticator" | msgid "TOTP authenticator" | ||||||
| msgstr "Autenticador TOTP" | msgstr "Autenticador TOTP" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Taiwanese Mandarin" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/StageBindingForm.ts | #: src/pages/flows/StageBindingForm.ts | ||||||
| msgid "Target" | msgid "Target" | ||||||
| msgstr "Objetivo" | msgstr "Objetivo" | ||||||
| @ -5556,8 +5580,8 @@ msgid "Use the username and password below to authenticate. The password can be | |||||||
| msgstr "Use el nombre de usuario y la contraseña a continuación para autenticarse. La contraseña se puede recuperar más adelante en la página Tokens." | msgstr "Use el nombre de usuario y la contraseña a continuación para autenticarse. La contraseña se puede recuperar más adelante en la página Tokens." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)." | ||||||
| msgstr "Use este proveedor con auth_request de nginx o ForwardAuth de traefik. Cada aplicación/dominio necesita su propio proveedor. Además, en cada dominio, /akprox debe enrutarse al puesto avanzado (cuando se usa un puesto avanzado administrado, esto se hace por usted)." | msgstr "Use este proveedor con auth_request de nginx o ForwardAuth de traefik. Cada aplicación/dominio necesita su propio proveedor. Además, en cada dominio, /outpost.goauthentik.io debe enrutarse al puesto avanzado (cuando se usa un puesto avanzado administrado, esto se hace por usted)." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | ||||||
| @ -5912,6 +5936,10 @@ msgstr "Cuando se habilita, la invitación se eliminará después de su uso." | |||||||
| msgid "When enabled, user fields are matched regardless of their casing." | msgid "When enabled, user fields are matched regardless of their casing." | ||||||
| msgstr "Cuando se habilita, los campos de usuario coinciden independientemente de su carcasa." | msgstr "Cuando se habilita, los campos de usuario coinciden independientemente de su carcasa." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "When multiple stages are selected, the user can choose which one they want to enroll." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | ||||||
| msgstr "Cuando se selecciona, se muestra un campo de contraseña en la misma página en lugar de en una página separada. Esto evita ataques de enumeración de nombres de usuario." | msgstr "Cuando se selecciona, se muestra un campo de contraseña en la misma página en lugar de en una página separada. Esto evita ataques de enumeración de nombres de usuario." | ||||||
|  | |||||||
| @ -567,16 +567,16 @@ msgid "Background shown during execution." | |||||||
| msgstr "Arrière-plan utilisé durant l'exécution." | msgstr "Arrière-plan utilisé durant l'exécution." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with errors." | #~ msgid "Backup finished with errors." | ||||||
| msgstr "Sauvegarde terminée avec des erreurs." | #~ msgstr "Sauvegarde terminée avec des erreurs." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with warnings/backup not supported." | #~ msgid "Backup finished with warnings/backup not supported." | ||||||
| msgstr "Sauvegarde terminée avec avertissements/sauvegarde non supportée." | #~ msgstr "Sauvegarde terminée avec avertissements/sauvegarde non supportée." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/AdminOverviewPage.ts | #: src/pages/admin-overview/AdminOverviewPage.ts | ||||||
| msgid "Backup status" | #~ msgid "Backup status" | ||||||
| msgstr "État de la sauvegarde" | #~ msgstr "État de la sauvegarde" | ||||||
|  |  | ||||||
| #: src/pages/providers/ldap/LDAPProviderForm.ts | #: src/pages/providers/ldap/LDAPProviderForm.ts | ||||||
| #: src/pages/providers/ldap/LDAPProviderViewPage.ts | #: src/pages/providers/ldap/LDAPProviderViewPage.ts | ||||||
| @ -846,6 +846,14 @@ msgstr "Vérifie si le mot de passe de l'usager a été changé dans les X derni | |||||||
| msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | ||||||
| msgstr "Vérifie la valeur de la requête via plusieurs règles, principalement utilisé pour  s'assurer de la robustesse des mots de passe." | msgstr "Vérifie la valeur de la requête via plusieurs règles, principalement utilisé pour  s'assurer de la robustesse des mots de passe." | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (simplified)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (traditional)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts | #: src/pages/flows/FlowListPage.ts | ||||||
| msgid "Clear Flow cache" | msgid "Clear Flow cache" | ||||||
| msgstr "Vider le cache des flux" | msgstr "Vider le cache des flux" | ||||||
| @ -950,8 +958,12 @@ msgid "Configuration flow" | |||||||
| msgstr "Flux de configuration" | msgstr "Flux de configuration" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Configuration stage" | #~ msgid "Configuration stage" | ||||||
| msgstr "Étape de configuration" | #~ msgstr "Étape de configuration" | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Configuration stages" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| #~ msgid "Configure WebAuthn" | #~ msgid "Configure WebAuthn" | ||||||
| @ -2191,6 +2203,10 @@ msgstr "" | |||||||
| msgid "Generic OpenID Connect" | msgid "Generic OpenID Connect" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "German" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| msgid "Get this value from https://console.twilio.com" | msgid "Get this value from https://console.twilio.com" | ||||||
| @ -4444,8 +4460,8 @@ msgid "Stage type" | |||||||
| msgstr "Type d'étape" | msgstr "Type d'étape" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | #~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
| msgstr "Étape utilisée pour configurer l'Authenticator lorsqu'un utilisateur n'a pas d'appareil compatible. Une fois cette étape franchie, l'utilisateur ne sera plus sollicité." | #~ msgstr "Étape utilisée pour configurer l'Authenticator lorsqu'un utilisateur n'a pas d'appareil compatible. Une fois cette étape franchie, l'utilisateur ne sera plus sollicité." | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | ||||||
| msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | ||||||
| @ -4504,6 +4520,10 @@ msgstr "Étapes" | |||||||
| msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | ||||||
| msgstr "Les étapes sont des étapes simples d'un flux au travers duquel un utilisateur est guidé. Une étape peut être uniquement exécutée à l'intérieur d'un flux." | msgstr "Les étapes sont des étapes simples d'un flux au travers duquel un utilisateur est guidé. Une étape peut être uniquement exécutée à l'intérieur d'un flux." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/outposts/ServiceConnectionListPage.ts | #: src/pages/outposts/ServiceConnectionListPage.ts | ||||||
| msgid "State" | msgid "State" | ||||||
| msgstr "État" | msgstr "État" | ||||||
| @ -4969,6 +4989,10 @@ msgstr "" | |||||||
| msgid "TOTP authenticator" | msgid "TOTP authenticator" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Taiwanese Mandarin" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/StageBindingForm.ts | #: src/pages/flows/StageBindingForm.ts | ||||||
| msgid "Target" | msgid "Target" | ||||||
| msgstr "Cible" | msgstr "Cible" | ||||||
| @ -5614,8 +5638,8 @@ msgid "Use the username and password below to authenticate. The password can be | |||||||
| msgstr "Utilisez le nom d'utilisateur et le mot de passe ci-dessous pour vous authentifier. Le mot de passe peut être récupéré plus tard sur la page Jetons." | msgstr "Utilisez le nom d'utilisateur et le mot de passe ci-dessous pour vous authentifier. Le mot de passe peut être récupéré plus tard sur la page Jetons." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)." | ||||||
| msgstr "Utilisez ce fournisseur avec auth_request de nginx ou forwardAuth de traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, /akprox doit être routé vers l'avant-poste (si vous utilisez un avant-poste géré, cela est fait pour vous)." | msgstr "Utilisez ce fournisseur avec auth_request de nginx ou forwardAuth de traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, /outpost.goauthentik.io doit être routé vers l'avant-poste (si vous utilisez un avant-poste géré, cela est fait pour vous)." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | ||||||
| @ -5975,6 +5999,10 @@ msgstr "Si activée, l'invitation sera supprimée après utilisation." | |||||||
| msgid "When enabled, user fields are matched regardless of their casing." | msgid "When enabled, user fields are matched regardless of their casing." | ||||||
| msgstr "Si activé, les champs de l'utilisateur sont mis en correspondance en ignorant leur casse." | msgstr "Si activé, les champs de l'utilisateur sont mis en correspondance en ignorant leur casse." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "When multiple stages are selected, the user can choose which one they want to enroll." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | ||||||
| msgstr "Si activée, un champ de mot de passe est affiché sur la même page au lieu d'une page séparée. Cela permet d'éviter les attaques par énumération de noms d'utilisateur." | msgstr "Si activée, un champ de mot de passe est affiché sur la même page au lieu d'une page séparée. Cela permet d'éviter les attaques par énumération de noms d'utilisateur." | ||||||
|  | |||||||
| @ -560,17 +560,14 @@ msgstr "Obraz tła" | |||||||
| msgid "Background shown during execution." | msgid "Background shown during execution." | ||||||
| msgstr "Tło pokazywane podczas wykonywania." | msgstr "Tło pokazywane podczas wykonywania." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #~ msgid "Backup finished with errors." | ||||||
| msgid "Backup finished with errors." | #~ msgstr "Kopia zapasowa zakończona z błędami." | ||||||
| msgstr "Kopia zapasowa zakończona z błędami." |  | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #~ msgid "Backup finished with warnings/backup not supported." | ||||||
| msgid "Backup finished with warnings/backup not supported." | #~ msgstr "Tworzenie kopii zapasowej zakończone z ostrzeżeniami/kopia zapasowa nie jest obsługiwana." | ||||||
| msgstr "Tworzenie kopii zapasowej zakończone z ostrzeżeniami/kopia zapasowa nie jest obsługiwana." |  | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/AdminOverviewPage.ts | #~ msgid "Backup status" | ||||||
| msgid "Backup status" | #~ msgstr "Stan kopii zapasowej" | ||||||
| msgstr "Stan kopii zapasowej" |  | ||||||
|  |  | ||||||
| #: src/pages/providers/ldap/LDAPProviderForm.ts | #: src/pages/providers/ldap/LDAPProviderForm.ts | ||||||
| #: src/pages/providers/ldap/LDAPProviderViewPage.ts | #: src/pages/providers/ldap/LDAPProviderViewPage.ts | ||||||
| @ -838,6 +835,14 @@ msgstr "Sprawdza, czy żądanego użytkownika hasło zostało zmienione w ciągu | |||||||
| msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | ||||||
| msgstr "Sprawdza wartość z żądania zasad pod kątem kilku reguł, używanych głównie w celu zapewnienia siły hasła." | msgstr "Sprawdza wartość z żądania zasad pod kątem kilku reguł, używanych głównie w celu zapewnienia siły hasła." | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (simplified)" | ||||||
|  | msgstr "Chiński (uproszczony)" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (traditional)" | ||||||
|  | msgstr "Chiński (tradycyjny)" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts | #: src/pages/flows/FlowListPage.ts | ||||||
| msgid "Clear Flow cache" | msgid "Clear Flow cache" | ||||||
| msgstr "Wyczyść pamięć podręczną przepływu" | msgstr "Wyczyść pamięć podręczną przepływu" | ||||||
| @ -942,8 +947,12 @@ msgid "Configuration flow" | |||||||
| msgstr "Przepływ konfiguracji" | msgstr "Przepływ konfiguracji" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Configuration stage" | #~ msgid "Configuration stage" | ||||||
| msgstr "Etap konfiguracji" | #~ msgstr "Etap konfiguracji" | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Configuration stages" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #~ msgid "Configure WebAuthn" | #~ msgid "Configure WebAuthn" | ||||||
| #~ msgstr "Skonfiguruj WebAuthn" | #~ msgstr "Skonfiguruj WebAuthn" | ||||||
| @ -2166,6 +2175,10 @@ msgstr "Ogólny" | |||||||
| msgid "Generic OpenID Connect" | msgid "Generic OpenID Connect" | ||||||
| msgstr "Ogólny OpenID Connect" | msgstr "Ogólny OpenID Connect" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "German" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| msgid "Get this value from https://console.twilio.com" | msgid "Get this value from https://console.twilio.com" | ||||||
| @ -4391,8 +4404,8 @@ msgid "Stage type" | |||||||
| msgstr "Typ etapu" | msgstr "Typ etapu" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | #~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
| msgstr "Etap używany do konfiguracji uwierzytelniacza, gdy użytkownik nie ma żadnych kompatybilnych urządzeń. Po zakończeniu tego etapu konfiguracji użytkownik nie jest ponownie pytany." | #~ msgstr "Etap używany do konfiguracji uwierzytelniacza, gdy użytkownik nie ma żadnych kompatybilnych urządzeń. Po zakończeniu tego etapu konfiguracji użytkownik nie jest ponownie pytany." | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | ||||||
| msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | ||||||
| @ -4451,6 +4464,10 @@ msgstr "Etapy" | |||||||
| msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | ||||||
| msgstr "Etapy to pojedyncze kroki przepływu, przez które prowadzony jest użytkownik. Etap można wykonać tylko z przepływu." | msgstr "Etapy to pojedyncze kroki przepływu, przez które prowadzony jest użytkownik. Etap można wykonać tylko z przepływu." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/outposts/ServiceConnectionListPage.ts | #: src/pages/outposts/ServiceConnectionListPage.ts | ||||||
| msgid "State" | msgid "State" | ||||||
| msgstr "Stan" | msgstr "Stan" | ||||||
| @ -4905,6 +4922,10 @@ msgstr "Urządzenie TOTP" | |||||||
| msgid "TOTP authenticator" | msgid "TOTP authenticator" | ||||||
| msgstr "Uwierzytelniacz TOTP" | msgstr "Uwierzytelniacz TOTP" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Taiwanese Mandarin" | ||||||
|  | msgstr "Tajwański mandaryński" | ||||||
|  |  | ||||||
| #: src/pages/flows/StageBindingForm.ts | #: src/pages/flows/StageBindingForm.ts | ||||||
| msgid "Target" | msgid "Target" | ||||||
| msgstr "Cel" | msgstr "Cel" | ||||||
| @ -5556,8 +5577,8 @@ msgid "Use the username and password below to authenticate. The password can be | |||||||
| msgstr "Użyj poniższej nazwy użytkownika i hasła do uwierzytelnienia. Hasło można później odzyskać na stronie Tokeny." | msgstr "Użyj poniższej nazwy użytkownika i hasła do uwierzytelnienia. Hasło można później odzyskać na stronie Tokeny." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)." | ||||||
| msgstr "Użyj tego dostawcy z auth_request nginx lub forwardAuth traefik. Każda aplikacja/domena potrzebuje własnego dostawcy. Dodatkowo w każdej domenie /akprox musi być przekierowany do placówki (w przypadku korzystania z zarządzanej placówki jest to zrobione za Ciebie)." | msgstr "Użyj tego dostawcy z auth_request nginx lub forwardAuth traefik. Każda aplikacja/domena potrzebuje własnego dostawcy. Dodatkowo w każdej domenie /outpost.goauthentik.io musi być przekierowany do placówki (w przypadku korzystania z zarządzanej placówki jest to zrobione za Ciebie)." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | ||||||
| @ -5912,6 +5933,10 @@ msgstr "Po włączeniu zaproszenie zostanie usunięte po użyciu." | |||||||
| msgid "When enabled, user fields are matched regardless of their casing." | msgid "When enabled, user fields are matched regardless of their casing." | ||||||
| msgstr "Po włączeniu pola użytkownika są dopasowywane niezależnie od wielkości liter." | msgstr "Po włączeniu pola użytkownika są dopasowywane niezależnie od wielkości liter." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "When multiple stages are selected, the user can choose which one they want to enroll." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | ||||||
| msgstr "Po wybraniu pole hasła jest wyświetlane na tej samej stronie zamiast na osobnej stronie. Zapobiega to atakom polegającym na wyliczaniu nazw użytkowników." | msgstr "Po wybraniu pole hasła jest wyświetlane na tej samej stronie zamiast na osobnej stronie. Zapobiega to atakom polegającym na wyliczaniu nazw użytkowników." | ||||||
|  | |||||||
							
								
								
									
										6648
									
								
								web/src/locales/pl_PL.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6648
									
								
								web/src/locales/pl_PL.po
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -559,16 +559,16 @@ msgid "Background shown during execution." | |||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with errors." | #~ msgid "Backup finished with errors." | ||||||
| msgstr "" | #~ msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with warnings/backup not supported." | #~ msgid "Backup finished with warnings/backup not supported." | ||||||
| msgstr "" | #~ msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/AdminOverviewPage.ts | #: src/pages/admin-overview/AdminOverviewPage.ts | ||||||
| msgid "Backup status" | #~ msgid "Backup status" | ||||||
| msgstr "" | #~ msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/providers/ldap/LDAPProviderForm.ts | #: src/pages/providers/ldap/LDAPProviderForm.ts | ||||||
| #: src/pages/providers/ldap/LDAPProviderViewPage.ts | #: src/pages/providers/ldap/LDAPProviderViewPage.ts | ||||||
| @ -838,6 +838,14 @@ msgstr "" | |||||||
| msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (simplified)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (traditional)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts | #: src/pages/flows/FlowListPage.ts | ||||||
| msgid "Clear Flow cache" | msgid "Clear Flow cache" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -942,7 +950,11 @@ msgid "Configuration flow" | |||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Configuration stage" | #~ msgid "Configuration stage" | ||||||
|  | #~ msgstr "" | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Configuration stages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| @ -2197,6 +2209,10 @@ msgstr "" | |||||||
| msgid "Generic OpenID Connect" | msgid "Generic OpenID Connect" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "German" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| msgid "Get this value from https://console.twilio.com" | msgid "Get this value from https://console.twilio.com" | ||||||
| @ -4473,8 +4489,8 @@ msgid "Stage type" | |||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | #~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
| msgstr "" | #~ msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | ||||||
| msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | ||||||
| @ -4533,6 +4549,10 @@ msgstr "" | |||||||
| msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/outposts/ServiceConnectionListPage.ts | #: src/pages/outposts/ServiceConnectionListPage.ts | ||||||
| msgid "State" | msgid "State" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -5004,6 +5024,10 @@ msgstr "" | |||||||
| msgid "TOTP authenticator" | msgid "TOTP authenticator" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Taiwanese Mandarin" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/StageBindingForm.ts | #: src/pages/flows/StageBindingForm.ts | ||||||
| msgid "Target" | msgid "Target" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -5653,7 +5677,7 @@ msgid "Use the username and password below to authenticate. The password can be | |||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| @ -6016,6 +6040,10 @@ msgstr "" | |||||||
| msgid "When enabled, user fields are matched regardless of their casing." | msgid "When enabled, user fields are matched regardless of their casing." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "When multiple stages are selected, the user can choose which one they want to enroll." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | ||||||
| msgstr "" | msgstr "" | ||||||
|  | |||||||
| @ -561,16 +561,16 @@ msgid "Background shown during execution." | |||||||
| msgstr "Yürütme sırasında arka plan gösterilir." | msgstr "Yürütme sırasında arka plan gösterilir." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with errors." | #~ msgid "Backup finished with errors." | ||||||
| msgstr "Yedekleme hatalarla tamamlandı." | #~ msgstr "Yedekleme hatalarla tamamlandı." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/cards/BackupStatusCard.ts | #: src/pages/admin-overview/cards/BackupStatusCard.ts | ||||||
| msgid "Backup finished with warnings/backup not supported." | #~ msgid "Backup finished with warnings/backup not supported." | ||||||
| msgstr "Yedekleme desteklenmeyen uyarılar/yedekleme ile tamamlandı." | #~ msgstr "Yedekleme desteklenmeyen uyarılar/yedekleme ile tamamlandı." | ||||||
|  |  | ||||||
| #: src/pages/admin-overview/AdminOverviewPage.ts | #: src/pages/admin-overview/AdminOverviewPage.ts | ||||||
| msgid "Backup status" | #~ msgid "Backup status" | ||||||
| msgstr "Yedekleme durumu" | #~ msgstr "Yedekleme durumu" | ||||||
|  |  | ||||||
| #: src/pages/providers/ldap/LDAPProviderForm.ts | #: src/pages/providers/ldap/LDAPProviderForm.ts | ||||||
| #: src/pages/providers/ldap/LDAPProviderViewPage.ts | #: src/pages/providers/ldap/LDAPProviderViewPage.ts | ||||||
| @ -838,6 +838,14 @@ msgstr "İsteğin kullanıcı parolasının son x gün içinde değiştirilip de | |||||||
| msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength." | ||||||
| msgstr "İlke isteğindeki değeri, çoğunlukla parola gücünü sağlamak için kullanılan çeşitli kurallara göre denetler." | msgstr "İlke isteğindeki değeri, çoğunlukla parola gücünü sağlamak için kullanılan çeşitli kurallara göre denetler." | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (simplified)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Chinese (traditional)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts | #: src/pages/flows/FlowListPage.ts | ||||||
| msgid "Clear Flow cache" | msgid "Clear Flow cache" | ||||||
| msgstr "Akış önbelleğini temizleme" | msgstr "Akış önbelleğini temizleme" | ||||||
| @ -942,8 +950,12 @@ msgid "Configuration flow" | |||||||
| msgstr "Yapılandırma akışı" | msgstr "Yapılandırma akışı" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Configuration stage" | #~ msgid "Configuration stage" | ||||||
| msgstr "Yapılandırma aşamasında" | #~ msgstr "Yapılandırma aşamasında" | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Configuration stages" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #~ msgid "Configure WebAuthn" | #~ msgid "Configure WebAuthn" | ||||||
| #~ msgstr "WebAuthn'i Yapılandır" | #~ msgstr "WebAuthn'i Yapılandır" | ||||||
| @ -2166,6 +2178,10 @@ msgstr "Jenerik" | |||||||
| msgid "Generic OpenID Connect" | msgid "Generic OpenID Connect" | ||||||
| msgstr "Genel OpenID Connect" | msgstr "Genel OpenID Connect" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "German" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||||
| msgid "Get this value from https://console.twilio.com" | msgid "Get this value from https://console.twilio.com" | ||||||
| @ -4393,8 +4409,8 @@ msgid "Stage type" | |||||||
| msgstr "Aşama türü" | msgstr "Aşama türü" | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
| msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | #~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
| msgstr "Kullanıcının uyumlu bir aygıtı olmadığında Kimlik Doğrulayıcısı'nı yapılandırmak için kullanılan Aşama. Bu yapılandırma Stage geçtikten sonra kullanıcıya yeniden istenmez." | #~ msgstr "Kullanıcının uyumlu bir aygıtı olmadığında Kimlik Doğrulayıcısı'nı yapılandırmak için kullanılan Aşama. Bu yapılandırma Stage geçtikten sonra kullanıcıya yeniden istenmez." | ||||||
|  |  | ||||||
| #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | ||||||
| msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)." | ||||||
| @ -4453,6 +4469,10 @@ msgstr "Aşamalar" | |||||||
| msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow." | ||||||
| msgstr "Aşamalar, bir Akış'ın kullanıcının yönlendirildiği tek adımlardır. Bir aşama yalnızca bir akış içinden yürütülebilir." | msgstr "Aşamalar, bir Akış'ın kullanıcının yönlendirildiği tek adımlardır. Bir aşama yalnızca bir akış içinden yürütülebilir." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/outposts/ServiceConnectionListPage.ts | #: src/pages/outposts/ServiceConnectionListPage.ts | ||||||
| msgid "State" | msgid "State" | ||||||
| msgstr "Eyalet" | msgstr "Eyalet" | ||||||
| @ -4907,6 +4927,10 @@ msgstr "TOTP Cihazı" | |||||||
| msgid "TOTP authenticator" | msgid "TOTP authenticator" | ||||||
| msgstr "TOTP kimlik doğrulayıcı" | msgstr "TOTP kimlik doğrulayıcı" | ||||||
|  |  | ||||||
|  | #: src/interfaces/locale.ts | ||||||
|  | msgid "Taiwanese Mandarin" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/StageBindingForm.ts | #: src/pages/flows/StageBindingForm.ts | ||||||
| msgid "Target" | msgid "Target" | ||||||
| msgstr "Hedef" | msgstr "Hedef" | ||||||
| @ -5558,8 +5582,8 @@ msgid "Use the username and password below to authenticate. The password can be | |||||||
| msgstr "Kimlik doğrulaması için aşağıdaki kullanıcı adı ve parolayı kullanın. Parola daha sonra Belirteçler sayfasından alınabilir." | msgstr "Kimlik doğrulaması için aşağıdaki kullanıcı adı ve parolayı kullanın. Parola daha sonra Belirteçler sayfasından alınabilir." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)." | ||||||
| msgstr "Bu sağlayıcıyı nginx'in auth_request veya traefik's forwardAuth ile kullanın. Her uygulama/etki alanının kendi sağlayıcısına ihtiyacı vardır. Ayrıca, her etki alanında /akprox üsse yönlendirilmelidir (manged bir üs kullanırken, bu sizin için yapılır)." | msgstr "Bu sağlayıcıyı nginx'in auth_request veya traefik's forwardAuth ile kullanın. Her uygulama/etki alanının kendi sağlayıcısına ihtiyacı vardır. Ayrıca, her etki alanında /outpost.goauthentik.io üsse yönlendirilmelidir (manged bir üs kullanırken, bu sizin için yapılır)." | ||||||
|  |  | ||||||
| #: src/pages/providers/proxy/ProxyProviderForm.ts | #: src/pages/providers/proxy/ProxyProviderForm.ts | ||||||
| msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." | ||||||
| @ -5914,6 +5938,10 @@ msgstr "Etkinleştirildiğinde, davetiye kullanımdan sonra silinir." | |||||||
| msgid "When enabled, user fields are matched regardless of their casing." | msgid "When enabled, user fields are matched regardless of their casing." | ||||||
| msgstr "Etkinleştirildiğinde, kullanıcı alanları muhafazası ne olursa olsun eşleştirilir." | msgstr "Etkinleştirildiğinde, kullanıcı alanları muhafazası ne olursa olsun eşleştirilir." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||||
|  | msgid "When multiple stages are selected, the user can choose which one they want to enroll." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks." | ||||||
| msgstr "Seçildiğinde, ayrı bir sayfa yerine aynı sayfada bir parola alanı gösterilir. Bu, kullanıcı adı numaralandırma saldırılarını engeller." | msgstr "Seçildiğinde, ayrı bir sayfa yerine aynı sayfada bir parola alanı gösterilir. Bu, kullanıcı adı numaralandırma saldırılarını engeller." | ||||||
|  | |||||||
							
								
								
									
										6117
									
								
								web/src/locales/zh-Hans.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6117
									
								
								web/src/locales/zh-Hans.po
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6117
									
								
								web/src/locales/zh-Hant.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6117
									
								
								web/src/locales/zh-Hant.po
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6117
									
								
								web/src/locales/zh_TW.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6117
									
								
								web/src/locales/zh_TW.po
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -17,7 +17,6 @@ import "../../elements/charts/AdminLoginsChart"; | |||||||
| import { paramURL } from "../../elements/router/RouterOutlet"; | import { paramURL } from "../../elements/router/RouterOutlet"; | ||||||
| import "./TopApplicationsTable"; | import "./TopApplicationsTable"; | ||||||
| import "./cards/AdminStatusCard"; | import "./cards/AdminStatusCard"; | ||||||
| import "./cards/BackupStatusCard"; |  | ||||||
| import "./cards/SystemStatusCard"; | import "./cards/SystemStatusCard"; | ||||||
| import "./cards/VersionStatusCard"; | import "./cards/VersionStatusCard"; | ||||||
| import "./cards/WorkerStatusCard"; | import "./cards/WorkerStatusCard"; | ||||||
| @ -166,7 +165,7 @@ export class AdminOverviewPage extends LitElement { | |||||||
|                     </div> |                     </div> | ||||||
|                     <!-- row 2 --> |                     <!-- row 2 --> | ||||||
|                     <div |                     <div | ||||||
|                         class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container" |                         class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container" | ||||||
|                     > |                     > | ||||||
|                         <ak-admin-status-system |                         <ak-admin-status-system | ||||||
|                             icon="pf-icon pf-icon-server" |                             icon="pf-icon pf-icon-server" | ||||||
| @ -175,7 +174,7 @@ export class AdminOverviewPage extends LitElement { | |||||||
|                         </ak-admin-status-system> |                         </ak-admin-status-system> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div |                     <div | ||||||
|                         class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl card-container" |                         class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container" | ||||||
|                     > |                     > | ||||||
|                         <ak-admin-status-version |                         <ak-admin-status-version | ||||||
|                             icon="pf-icon pf-icon-bundle" |                             icon="pf-icon pf-icon-bundle" | ||||||
| @ -185,17 +184,7 @@ export class AdminOverviewPage extends LitElement { | |||||||
|                         </ak-admin-status-version> |                         </ak-admin-status-version> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div |                     <div | ||||||
|                         class="pf-l-grid__item pf-m-6-col pf-m-2-col-on-md pf-m-3-col-on-xl card-container" |                         class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container" | ||||||
|                     > |  | ||||||
|                         <ak-admin-status-card-backup |  | ||||||
|                             icon="fa fa-database" |  | ||||||
|                             header=${t`Backup status`} |  | ||||||
|                             headerLink="#/administration/system-tasks" |  | ||||||
|                         > |  | ||||||
|                         </ak-admin-status-card-backup> |  | ||||||
|                     </div> |  | ||||||
|                     <div |  | ||||||
|                         class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container" |  | ||||||
|                     > |                     > | ||||||
|                         <ak-admin-status-card-workers |                         <ak-admin-status-card-workers | ||||||
|                             icon="pf-icon pf-icon-server" |                             icon="pf-icon pf-icon-server" | ||||||
|  | |||||||
| @ -1,56 +0,0 @@ | |||||||
| import { t } from "@lingui/macro"; |  | ||||||
|  |  | ||||||
| import { TemplateResult, html } from "lit"; |  | ||||||
| import { customElement } from "lit/decorators.js"; |  | ||||||
|  |  | ||||||
| import { AdminApi, CapabilitiesEnum, StatusEnum } from "@goauthentik/api"; |  | ||||||
|  |  | ||||||
| import { DEFAULT_CONFIG, config } from "../../../api/Config"; |  | ||||||
| import { convertToTitle } from "../../../utils"; |  | ||||||
| import { AdminStatus, AdminStatusCard } from "./AdminStatusCard"; |  | ||||||
|  |  | ||||||
| @customElement("ak-admin-status-card-backup") |  | ||||||
| export class BackupStatusCard extends AdminStatusCard<StatusEnum> { |  | ||||||
|     getPrimaryValue(): Promise<StatusEnum> { |  | ||||||
|         return new AdminApi(DEFAULT_CONFIG) |  | ||||||
|             .adminSystemTasksRetrieve({ |  | ||||||
|                 id: "backup_database", |  | ||||||
|             }) |  | ||||||
|             .then((value) => { |  | ||||||
|                 return value.status; |  | ||||||
|             }) |  | ||||||
|             .catch(() => { |  | ||||||
|                 // On error (probably 404), check the config and see if the server |  | ||||||
|                 // can even backup |  | ||||||
|                 return config().then((c) => { |  | ||||||
|                     if (c.capabilities.includes(CapabilitiesEnum.Backup)) { |  | ||||||
|                         return StatusEnum.Error; |  | ||||||
|                     } |  | ||||||
|                     return StatusEnum.Warning; |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     renderValue(): TemplateResult { |  | ||||||
|         return html`${convertToTitle(this.value?.toString() || "")}`; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     getStatus(value: StatusEnum): Promise<AdminStatus> { |  | ||||||
|         switch (value) { |  | ||||||
|             case StatusEnum.Successful: |  | ||||||
|                 return Promise.resolve<AdminStatus>({ |  | ||||||
|                     icon: "fa fa-check-circle pf-m-success", |  | ||||||
|                 }); |  | ||||||
|             case StatusEnum.Error: |  | ||||||
|                 return Promise.resolve<AdminStatus>({ |  | ||||||
|                     icon: "fa fa-times-circle pf-m-danger", |  | ||||||
|                     message: html`${t`Backup finished with errors.`}`, |  | ||||||
|                 }); |  | ||||||
|             default: |  | ||||||
|                 return Promise.resolve<AdminStatus>({ |  | ||||||
|                     icon: "fa fa-exclamation-triangle pf-m-warning", |  | ||||||
|                     message: html`${t`Backup finished with warnings/backup not supported.`}`, |  | ||||||
|                 }); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -214,7 +214,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> { | |||||||
|                     </ak-form-element-horizontal>`; |                     </ak-form-element-horizontal>`; | ||||||
|             case ProxyMode.ForwardSingle: |             case ProxyMode.ForwardSingle: | ||||||
|                 return html`<p class="pf-u-mb-xl"> |                 return html`<p class="pf-u-mb-xl"> | ||||||
|                         ${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you).`} |                         ${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you).`} | ||||||
|                     </p> |                     </p> | ||||||
|                     <ak-form-element-horizontal |                     <ak-form-element-horizontal | ||||||
|                         label=${t`External host`} |                         label=${t`External host`} | ||||||
|  | |||||||
| @ -92,16 +92,19 @@ export class ProxyProviderViewPage extends LitElement { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderConfigTemplate(markdown: MarkdownDocument): MarkdownDocument { |     renderConfigTemplate(markdown: MarkdownDocument): MarkdownDocument { | ||||||
|  |         const extHost = new URL(this.provider?.externalHost || "http://a"); | ||||||
|         // See website/docs/providers/proxy/forward_auth.mdx |         // See website/docs/providers/proxy/forward_auth.mdx | ||||||
|         if (this.provider?.mode === ProxyMode.ForwardSingle) { |         if (this.provider?.mode === ProxyMode.ForwardSingle) { | ||||||
|             markdown.html = markdown.html |             markdown.html = markdown.html | ||||||
|                 .replaceAll("authentik.company", window.location.hostname) |                 .replaceAll("authentik.company", window.location.hostname) | ||||||
|                 .replaceAll("outpost.company", window.location.hostname) |                 .replaceAll("http://outpost.company:9000", window.location.hostname) | ||||||
|                 .replaceAll("app.company", this.provider?.externalHost || ""); |                 .replaceAll("https://app.company", extHost.toString()) | ||||||
|  |                 .replaceAll("app.company", extHost.hostname); | ||||||
|         } else if (this.provider?.mode == ProxyMode.ForwardDomain) { |         } else if (this.provider?.mode == ProxyMode.ForwardDomain) { | ||||||
|             markdown.html = markdown.html |             markdown.html = markdown.html | ||||||
|                 .replaceAll("authentik.company", window.location.hostname) |                 .replaceAll("authentik.company", window.location.hostname) | ||||||
|                 .replaceAll("outpost.company", this.provider?.externalHost || ""); |                 .replaceAll("https://app.company", extHost.hostname) | ||||||
|  |                 .replaceAll("http://outpost.company:9000", extHost.toString()); | ||||||
|         } |         } | ||||||
|         return markdown; |         return markdown; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -25,14 +25,14 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid | |||||||
|                 stageUuid: pk, |                 stageUuid: pk, | ||||||
|             }) |             }) | ||||||
|             .then((stage) => { |             .then((stage) => { | ||||||
|                 this.showConfigurationStage = |                 this.showConfigurationStages = | ||||||
|                     stage.notConfiguredAction === NotConfiguredActionEnum.Configure; |                     stage.notConfiguredAction === NotConfiguredActionEnum.Configure; | ||||||
|                 return stage; |                 return stage; | ||||||
|             }); |             }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @property({ type: Boolean }) |     @property({ type: Boolean }) | ||||||
|     showConfigurationStage = true; |     showConfigurationStages = true; | ||||||
|  |  | ||||||
|     getSuccessMessage(): string { |     getSuccessMessage(): string { | ||||||
|         if (this.instance) { |         if (this.instance) { | ||||||
| @ -136,9 +136,9 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid | |||||||
|                                     target.selectedOptions[0].value === |                                     target.selectedOptions[0].value === | ||||||
|                                     NotConfiguredActionEnum.Configure |                                     NotConfiguredActionEnum.Configure | ||||||
|                                 ) { |                                 ) { | ||||||
|                                     this.showConfigurationStage = true; |                                     this.showConfigurationStages = true; | ||||||
|                                 } else { |                                 } else { | ||||||
|                                     this.showConfigurationStage = false; |                                     this.showConfigurationStages = false; | ||||||
|                                 } |                                 } | ||||||
|                             }} |                             }} | ||||||
|                         > |                         > | ||||||
| @ -165,21 +165,13 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid | |||||||
|                             </option> |                             </option> | ||||||
|                         </select> |                         </select> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|                     ${this.showConfigurationStage |                     ${this.showConfigurationStages | ||||||
|                         ? html` |                         ? html` | ||||||
|                               <ak-form-element-horizontal |                               <ak-form-element-horizontal | ||||||
|                                   label=${t`Configuration stage`} |                                   label=${t`Configuration stages`} | ||||||
|                                   ?required=${true} |                                   name="configurationStages" | ||||||
|                                   name="configurationStage" |  | ||||||
|                               > |                               > | ||||||
|                                   <select class="pf-c-form-control"> |                                   <select class="pf-c-form-control" multiple> | ||||||
|                                       <option |  | ||||||
|                                           value="" |  | ||||||
|                                           ?selected=${this.instance?.configurationStage === |  | ||||||
|                                           undefined} |  | ||||||
|                                       > |  | ||||||
|                                           --------- |  | ||||||
|                                       </option> |  | ||||||
|                                       ${until( |                                       ${until( | ||||||
|                                           new StagesApi(DEFAULT_CONFIG) |                                           new StagesApi(DEFAULT_CONFIG) | ||||||
|                                               .stagesAllList({ |                                               .stagesAllList({ | ||||||
| @ -187,9 +179,11 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid | |||||||
|                                               }) |                                               }) | ||||||
|                                               .then((stages) => { |                                               .then((stages) => { | ||||||
|                                                   return stages.results.map((stage) => { |                                                   return stages.results.map((stage) => { | ||||||
|                                                       const selected = |                                                       const selected = Array.from( | ||||||
|                                                           this.instance?.configurationStage === |                                                           this.instance?.configurationStages || [], | ||||||
|                                                           stage.pk; |                                                       ).some((su) => { | ||||||
|  |                                                           return su == stage.pk; | ||||||
|  |                                                       }); | ||||||
|                                                       return html`<option |                                                       return html`<option | ||||||
|                                                           value=${ifDefined(stage.pk)} |                                                           value=${ifDefined(stage.pk)} | ||||||
|                                                           ?selected=${selected} |                                                           ?selected=${selected} | ||||||
| @ -202,7 +196,10 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid | |||||||
|                                       )} |                                       )} | ||||||
|                                   </select> |                                   </select> | ||||||
|                                   <p class="pf-c-form__helper-text"> |                                   <p class="pf-c-form__helper-text"> | ||||||
|                                       ${t`Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.`} |                                       ${t`Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.`} | ||||||
|  |                                   </p> | ||||||
|  |                                   <p class="pf-c-form__helper-text"> | ||||||
|  |                                       ${t`When multiple stages are selected, the user can choose which one they want to enroll.`} | ||||||
|                                   </p> |                                   </p> | ||||||
|                               </ak-form-element-horizontal> |                               </ak-form-element-horizontal> | ||||||
|                           ` |                           ` | ||||||
|  | |||||||
| @ -153,7 +153,7 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri | |||||||
|                         ?required=${true} |                         ?required=${true} | ||||||
|                         name="sources" |                         name="sources" | ||||||
|                     > |                     > | ||||||
|                         <select name="users" class="pf-c-form-control" multiple> |                         <select class="pf-c-form-control" multiple> | ||||||
|                             ${until( |                             ${until( | ||||||
|                                 new SourcesApi(DEFAULT_CONFIG) |                                 new SourcesApi(DEFAULT_CONFIG) | ||||||
|                                     .sourcesAllList({}) |                                     .sourcesAllList({}) | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ Applications are used to configure and separate the authorization / access contr | |||||||
|  |  | ||||||
| ## Authorization | ## Authorization | ||||||
|  |  | ||||||
| Application access can be configured using (Policy) Bindings. You can use this to grant access to one or multiple users/groups, or dynamically give access using policies. | Application access can be configured using (Policy) Bindings. Click on an application in the applications list, and select the *Policy / Group / User Bindings* tab. There you can bind users/groups/policies to grant them access. When nothing is bound, everyone has access. You can use this to grant access to one or multiple users/groups, or dynamically give access using policies. | ||||||
|  |  | ||||||
| By default, all users can access applications when no policies are bound. | By default, all users can access applications when no policies are bound. | ||||||
|  |  | ||||||
|  | |||||||
| @ -23,27 +23,6 @@ All of these variables can be set to values, but you can also use a URI-like for | |||||||
| - `AUTHENTIK_POSTGRESQL__PORT`: Database port, defaults to 5432 | - `AUTHENTIK_POSTGRESQL__PORT`: Database port, defaults to 5432 | ||||||
| - `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD` | - `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD` | ||||||
|  |  | ||||||
| ### PostgreSQL Backup Settings |  | ||||||
|  |  | ||||||
| :::info |  | ||||||
| The integrated backup is deprecated in 2022.1 and will be removed in a future version. |  | ||||||
| ::: |  | ||||||
|  |  | ||||||
| - `AUTHENTIK_POSTGRESQL__BACKUP__ENABLED`: Controls if the inbuilt backup-mechanism is enabled, defaults to false (new in 2021.10). |  | ||||||
|  |  | ||||||
| Optionally enable automated database backups to S3 or S3-compatible storages. |  | ||||||
|  |  | ||||||
| - `AUTHENTIK_POSTGRESQL__S3_BACKUP__ACCESS_KEY`: S3 Access Key |  | ||||||
| - `AUTHENTIK_POSTGRESQL__S3_BACKUP__SECRET_KEY`: S3 Secret Key |  | ||||||
| - `AUTHENTIK_POSTGRESQL__S3_BACKUP__BUCKET`: S3 Bucket |  | ||||||
| - `AUTHENTIK_POSTGRESQL__S3_BACKUP__REGION`: S3 Region, defaults to `eu-central-1` |  | ||||||
| - `AUTHENTIK_POSTGRESQL__S3_BACKUP__LOCATION`: Relative Location of the files to the bucket. Defaults to the root of the bucket. |  | ||||||
|  |  | ||||||
| To use an S3-compatible storage, set the following settings. |  | ||||||
|  |  | ||||||
| - `AUTHENTIK_POSTGRESQL__S3_BACKUP__HOST`: URL to the Service, for example `https://play.min.io` |  | ||||||
| - `AUTHENTIK_POSTGRESQL__S3_BACKUP__INSECURE_SKIP_VERIFY`: Set to `true` to disable SSL Certificate verification. |  | ||||||
|  |  | ||||||
| ## Redis Settings | ## Redis Settings | ||||||
|  |  | ||||||
| - `AUTHENTIK_REDIS__HOST`: Hostname of your Redis Server | - `AUTHENTIK_REDIS__HOST`: Hostname of your Redis Server | ||||||
| @ -142,6 +121,16 @@ Disable the inbuilt update-checker. Defaults to `false`. | |||||||
|  |  | ||||||
|   Placeholder for outpost docker images. Default: `ghcr.io/goauthentik/%(type)s:%(version)s`. |   Placeholder for outpost docker images. Default: `ghcr.io/goauthentik/%(type)s:%(version)s`. | ||||||
|  |  | ||||||
|  | - `AUTHENTIK_OUTPOSTS__DISCOVER` | ||||||
|  |  | ||||||
|  |   Configure the automatic discovery of integrations. Defaults to `true`. | ||||||
|  |  | ||||||
|  |   By default, the following is discovered: | ||||||
|  |  | ||||||
|  |     - Kubernetes in-cluster config | ||||||
|  |     - Kubeconfig | ||||||
|  |     - Existence of a docker socket | ||||||
|  |  | ||||||
| ### AUTHENTIK_AVATARS | ### AUTHENTIK_AVATARS | ||||||
|  |  | ||||||
| Configure how authentik should show avatars for users. Following values can be set: | Configure how authentik should show avatars for users. Following values can be set: | ||||||
|  | |||||||
| @ -21,8 +21,8 @@ If this is a fresh authentik install run the following commands to generate a pa | |||||||
| sudo apt-get install -y pwgen | sudo apt-get install -y pwgen | ||||||
| # Because of a PostgreSQL limitation, only passwords up to 99 chars are supported | # Because of a PostgreSQL limitation, only passwords up to 99 chars are supported | ||||||
| # See https://www.postgresql.org/message-id/09512C4F-8CB9-4021-B455-EF4C4F0D55A0@amazon.com | # See https://www.postgresql.org/message-id/09512C4F-8CB9-4021-B455-EF4C4F0D55A0@amazon.com | ||||||
| echo "PG_PASS=$(pwgen 40 1)" >> .env | echo "PG_PASS=$(pwgen -s 40 1)" >> .env | ||||||
| echo "AUTHENTIK_SECRET_KEY=$(pwgen 50 1)" >> .env | echo "AUTHENTIK_SECRET_KEY=$(pwgen -s 50 1)" >> .env | ||||||
| # Skip if you don't want to enable error reporting | # Skip if you don't want to enable error reporting | ||||||
| echo "AUTHENTIK_ERROR_REPORTING__ENABLED=true" >> .env | echo "AUTHENTIK_ERROR_REPORTING__ENABLED=true" >> .env | ||||||
| ``` | ``` | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	