Compare commits
	
		
			111 Commits
		
	
	
		
			monorepo-v
			...
			website/do
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 595e0b2702 | |||
| 4454592442 | |||
| 593c953ecc | |||
| bcefe7123c | |||
| 812cf6c4f2 | |||
| 73b6ef6a73 | |||
| b58ebcddbf | |||
| 8b6ac3c806 | |||
| c6aa792076 | |||
| ee4792734e | |||
| 445f11ca6b | |||
| 8e4810fb20 | |||
| 96a122c5d1 | |||
| 3c6b8b10e5 | |||
| 15999caa5d | |||
| 57d8375de1 | |||
| 07ec787076 | |||
| bc96bef097 | |||
| 28869858b5 | |||
| cbc5a1c39d | |||
| 5f6b69c998 | |||
| cf065db3d5 | |||
| 86c65325ce | |||
| 2b8e10e979 | |||
| 9298807275 | |||
| ed56d6ac50 | |||
| 8c07b385ad | |||
| 880db7a86c | |||
| 99c1250ba5 | |||
| 5ce126ac83 | |||
| dfa21d0725 | |||
| e7e4af3894 | |||
| 931d6ec579 | |||
| ff45acb25c | |||
| c96557ff2d | |||
| 734feac4ae | |||
| b17a9ed145 | |||
| 2bef7695db | |||
| df472dd842 | |||
| 98d201d34c | |||
| 47e89602ab | |||
| ceb0851452 | |||
| cac2593658 | |||
| 1c9705bfaa | |||
| 9e2566cec4 | |||
| 5bdef1c4f6 | |||
| ae41ccd862 | |||
| 337956672f | |||
| cf160f800d | |||
| e9822cd937 | |||
| 5244f64be4 | |||
| 0df4824fd4 | |||
| ea22abc75d | |||
| b09bab7543 | |||
| 5aedc8a5f2 | |||
| 2f3ae0f607 | |||
| e3674426b7 | |||
| df915d3a5e | |||
| 4949c31860 | |||
| 4580dec06b | |||
| 56de969640 | |||
| 413902508d | |||
| 64af0ccba6 | |||
| 673db53777 | |||
| 8df7716d90 | |||
| 19bb2de13f | |||
| a218fd7628 | |||
| 78cfb50a90 | |||
| 2033d52dc2 | |||
| be00f47ddc | |||
| 2cc5f4b273 | |||
| 4e8f3407a4 | |||
| 7f861cc2a1 | |||
| 7bf58d0ba2 | |||
| fffcb00f39 | |||
| 77ee868573 | |||
| 6aaec08496 | |||
| cc15584650 | |||
| e55e446b89 | |||
| 76088e48b5 | |||
| 4165a0a6b2 | |||
| 647fefe5ce | |||
| 723dccdae3 | |||
| c82f747e5e | |||
| 43406e2464 | |||
| a0ff0bef85 | |||
| bedf548a5f | |||
| 976e81c1dd | |||
| ad733033d7 | |||
| ba686f6a93 | |||
| dc50be1e13 | |||
| 205686d252 | |||
| 6d589013e6 | |||
| 2d6433ca9a | |||
| b5f07acb26 | |||
| ea8702077c | |||
| 6593357115 | |||
| 6daed865c1 | |||
| c48a21707a | |||
| e857770c0a | |||
| add74c8799 | |||
| 12d854035d | |||
| 57dd4ae91d | |||
| 37fbc98177 | |||
| 14f216eb40 | |||
| 1209dd022e | |||
| c96f13ac66 | |||
| 5e6874cc1f | |||
| fb5053ec83 | |||
| 6f7dc2c543 | |||
| 542b69b224 | 
							
								
								
									
										8
									
								
								.github/workflows/packages-npm-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/packages-npm-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -3,10 +3,10 @@ on: | ||||
|   push: | ||||
|     branches: [main] | ||||
|     paths: | ||||
|       - packages/docusaurus-config | ||||
|       - packages/eslint-config | ||||
|       - packages/prettier-config | ||||
|       - packages/tsconfig | ||||
|       - packages/docusaurus-config/** | ||||
|       - packages/eslint-config/** | ||||
|       - packages/prettier-config/** | ||||
|       - packages/tsconfig/** | ||||
|   workflow_dispatch: | ||||
| jobs: | ||||
|   publish: | ||||
|  | ||||
| @ -94,7 +94,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \ | ||||
|     /bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0" | ||||
|  | ||||
| # Stage 5: Download uv | ||||
| FROM ghcr.io/astral-sh/uv:0.6.14 AS uv | ||||
| FROM ghcr.io/astral-sh/uv:0.6.17 AS uv | ||||
| # Stage 6: Base python image | ||||
| FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base | ||||
|  | ||||
|  | ||||
| @ -20,8 +20,8 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni | ||||
|  | ||||
| | Version   | Supported | | ||||
| | --------- | --------- | | ||||
| | 2024.12.x | ✅        | | ||||
| | 2025.2.x  | ✅        | | ||||
| | 2025.4.x  | ✅        | | ||||
|  | ||||
| ## Reporting a Vulnerability | ||||
|  | ||||
|  | ||||
| @ -13,7 +13,10 @@ from authentik.core.models import ( | ||||
|     TokenIntents, | ||||
|     User, | ||||
| ) | ||||
| from authentik.core.tasks import clean_expired_models, clean_temporary_users | ||||
| from authentik.core.tasks import ( | ||||
|     clean_expired_models, | ||||
|     clean_temporary_users, | ||||
| ) | ||||
| from authentik.core.tests.utils import create_test_admin_user | ||||
| from authentik.lib.generators import generate_id | ||||
|  | ||||
|  | ||||
							
								
								
									
										0
									
								
								authentik/enterprise/policies/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								authentik/enterprise/policies/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										27
									
								
								authentik/enterprise/policies/unique_password/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								authentik/enterprise/policies/unique_password/api.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.enterprise.api import EnterpriseRequiredMixin | ||||
| from authentik.enterprise.policies.unique_password.models import UniquePasswordPolicy | ||||
| from authentik.policies.api.policies import PolicySerializer | ||||
|  | ||||
|  | ||||
| class UniquePasswordPolicySerializer(EnterpriseRequiredMixin, PolicySerializer): | ||||
|     """Password Uniqueness Policy Serializer""" | ||||
|  | ||||
|     class Meta: | ||||
|         model = UniquePasswordPolicy | ||||
|         fields = PolicySerializer.Meta.fields + [ | ||||
|             "password_field", | ||||
|             "num_historical_passwords", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class UniquePasswordPolicyViewSet(UsedByMixin, ModelViewSet): | ||||
|     """Password Uniqueness Policy Viewset""" | ||||
|  | ||||
|     queryset = UniquePasswordPolicy.objects.all() | ||||
|     serializer_class = UniquePasswordPolicySerializer | ||||
|     filterset_fields = "__all__" | ||||
|     ordering = ["name"] | ||||
|     search_fields = ["name"] | ||||
							
								
								
									
										10
									
								
								authentik/enterprise/policies/unique_password/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								authentik/enterprise/policies/unique_password/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| """authentik Unique Password policy app config""" | ||||
|  | ||||
| from authentik.enterprise.apps import EnterpriseConfig | ||||
|  | ||||
|  | ||||
| class AuthentikEnterprisePoliciesUniquePasswordConfig(EnterpriseConfig): | ||||
|     name = "authentik.enterprise.policies.unique_password" | ||||
|     label = "authentik_policies_unique_password" | ||||
|     verbose_name = "authentik Enterprise.Policies.Unique Password" | ||||
|     default = True | ||||
| @ -0,0 +1,81 @@ | ||||
| # Generated by Django 5.0.13 on 2025-03-26 23:02 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.conf import settings | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_policies", "0011_policybinding_failure_result_and_more"), | ||||
|         migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name="UniquePasswordPolicy", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "policy_ptr", | ||||
|                     models.OneToOneField( | ||||
|                         auto_created=True, | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         parent_link=True, | ||||
|                         primary_key=True, | ||||
|                         serialize=False, | ||||
|                         to="authentik_policies.policy", | ||||
|                     ), | ||||
|                 ), | ||||
|                 ( | ||||
|                     "password_field", | ||||
|                     models.TextField( | ||||
|                         default="password", | ||||
|                         help_text="Field key to check, field keys defined in Prompt stages are available.", | ||||
|                     ), | ||||
|                 ), | ||||
|                 ( | ||||
|                     "num_historical_passwords", | ||||
|                     models.PositiveIntegerField( | ||||
|                         default=1, help_text="Number of passwords to check against." | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|             options={ | ||||
|                 "verbose_name": "Password Uniqueness Policy", | ||||
|                 "verbose_name_plural": "Password Uniqueness Policies", | ||||
|                 "indexes": [ | ||||
|                     models.Index(fields=["policy_ptr_id"], name="authentik_p_policy__f559aa_idx") | ||||
|                 ], | ||||
|             }, | ||||
|             bases=("authentik_policies.policy",), | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name="UserPasswordHistory", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "id", | ||||
|                     models.AutoField( | ||||
|                         auto_created=True, primary_key=True, serialize=False, verbose_name="ID" | ||||
|                     ), | ||||
|                 ), | ||||
|                 ("old_password", models.CharField(max_length=128)), | ||||
|                 ("created_at", models.DateTimeField(auto_now_add=True)), | ||||
|                 ("hibp_prefix_sha1", models.CharField(max_length=5)), | ||||
|                 ("hibp_pw_hash", models.TextField()), | ||||
|                 ( | ||||
|                     "user", | ||||
|                     models.ForeignKey( | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         related_name="old_passwords", | ||||
|                         to=settings.AUTH_USER_MODEL, | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|             options={ | ||||
|                 "verbose_name": "User Password History", | ||||
|             }, | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										151
									
								
								authentik/enterprise/policies/unique_password/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								authentik/enterprise/policies/unique_password/models.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | ||||
| from hashlib import sha1 | ||||
|  | ||||
| from django.contrib.auth.hashers import identify_hasher, make_password | ||||
| from django.db import models | ||||
| from django.utils.translation import gettext as _ | ||||
| from rest_framework.serializers import BaseSerializer | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.policies.models import Policy | ||||
| from authentik.policies.types import PolicyRequest, PolicyResult | ||||
| from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class UniquePasswordPolicy(Policy): | ||||
|     """This policy prevents users from reusing old passwords.""" | ||||
|  | ||||
|     password_field = models.TextField( | ||||
|         default="password", | ||||
|         help_text=_("Field key to check, field keys defined in Prompt stages are available."), | ||||
|     ) | ||||
|  | ||||
|     # Limit on the number of previous passwords the policy evaluates | ||||
|     # Also controls number of old passwords the system stores. | ||||
|     num_historical_passwords = models.PositiveIntegerField( | ||||
|         default=1, | ||||
|         help_text=_("Number of passwords to check against."), | ||||
|     ) | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> type[BaseSerializer]: | ||||
|         from authentik.enterprise.policies.unique_password.api import UniquePasswordPolicySerializer | ||||
|  | ||||
|         return UniquePasswordPolicySerializer | ||||
|  | ||||
|     @property | ||||
|     def component(self) -> str: | ||||
|         return "ak-policy-password-uniqueness-form" | ||||
|  | ||||
|     def passes(self, request: PolicyRequest) -> PolicyResult: | ||||
|         from authentik.enterprise.policies.unique_password.models import UserPasswordHistory | ||||
|  | ||||
|         password = request.context.get(PLAN_CONTEXT_PROMPT, {}).get( | ||||
|             self.password_field, request.context.get(self.password_field) | ||||
|         ) | ||||
|         if not password: | ||||
|             LOGGER.warning( | ||||
|                 "Password field not found in request when checking UniquePasswordPolicy", | ||||
|                 field=self.password_field, | ||||
|                 fields=request.context.keys(), | ||||
|             ) | ||||
|             return PolicyResult(False, _("Password not set in context")) | ||||
|         password = str(password) | ||||
|  | ||||
|         if not self.num_historical_passwords: | ||||
|             # Policy not configured to check against any passwords | ||||
|             return PolicyResult(True) | ||||
|  | ||||
|         num_to_check = self.num_historical_passwords | ||||
|         password_history = UserPasswordHistory.objects.filter(user=request.user).order_by( | ||||
|             "-created_at" | ||||
|         )[:num_to_check] | ||||
|  | ||||
|         if not password_history: | ||||
|             return PolicyResult(True) | ||||
|  | ||||
|         for record in password_history: | ||||
|             if not record.old_password: | ||||
|                 continue | ||||
|  | ||||
|             if self._passwords_match(new_password=password, old_password=record.old_password): | ||||
|                 # Return on first match. Authentik does not consider timing attacks | ||||
|                 # on old passwords to be an attack surface. | ||||
|                 return PolicyResult( | ||||
|                     False, | ||||
|                     _("This password has been used previously. Please choose a different one."), | ||||
|                 ) | ||||
|  | ||||
|         return PolicyResult(True) | ||||
|  | ||||
|     def _passwords_match(self, *, new_password: str, old_password: str) -> bool: | ||||
|         try: | ||||
|             hasher = identify_hasher(old_password) | ||||
|         except ValueError: | ||||
|             LOGGER.warning( | ||||
|                 "Skipping password; could not load hash algorithm", | ||||
|             ) | ||||
|             return False | ||||
|  | ||||
|         return hasher.verify(new_password, old_password) | ||||
|  | ||||
|     @classmethod | ||||
|     def is_in_use(cls): | ||||
|         """Check if any UniquePasswordPolicy is in use, either through policy bindings | ||||
|         or direct attachment to a PromptStage. | ||||
|  | ||||
|         Returns: | ||||
|             bool: True if any policy is in use, False otherwise | ||||
|         """ | ||||
|         from authentik.policies.models import PolicyBinding | ||||
|  | ||||
|         # Check if any policy is in use through bindings | ||||
|         if PolicyBinding.in_use.for_policy(cls).exists(): | ||||
|             return True | ||||
|  | ||||
|         # Check if any policy is attached to a PromptStage | ||||
|         if cls.objects.filter(promptstage__isnull=False).exists(): | ||||
|             return True | ||||
|  | ||||
|         return False | ||||
|  | ||||
|     class Meta(Policy.PolicyMeta): | ||||
|         verbose_name = _("Password Uniqueness Policy") | ||||
|         verbose_name_plural = _("Password Uniqueness Policies") | ||||
|  | ||||
|  | ||||
| class UserPasswordHistory(models.Model): | ||||
|     user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="old_passwords") | ||||
|     # Mimic's column type of AbstractBaseUser.password | ||||
|     old_password = models.CharField(max_length=128) | ||||
|     created_at = models.DateTimeField(auto_now_add=True) | ||||
|  | ||||
|     hibp_prefix_sha1 = models.CharField(max_length=5) | ||||
|     hibp_pw_hash = models.TextField() | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("User Password History") | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
|         timestamp = f"{self.created_at:%Y/%m/%d %X}" if self.created_at else "N/A" | ||||
|         return f"Previous Password (user: {self.user_id}, recorded: {timestamp})" | ||||
|  | ||||
|     @classmethod | ||||
|     def create_for_user(cls, user: User, password: str): | ||||
|         # To check users' passwords against Have I been Pwned, we need the first 5 chars | ||||
|         # of the password hashed with SHA1 without a salt... | ||||
|         pw_hash_sha1 = sha1(password.encode("utf-8")).hexdigest()  # nosec | ||||
|         # ...however that'll give us a list of hashes from HIBP, and to compare that we still | ||||
|         # need a full unsalted SHA1 of the password. We don't want to save that directly in | ||||
|         # the database, so we hash that SHA1 again with a modern hashing alg, | ||||
|         # and then when we check users' passwords against HIBP we can use `check_password` | ||||
|         # which will take care of this. | ||||
|         hibp_hash_hash = make_password(pw_hash_sha1) | ||||
|         return cls.objects.create( | ||||
|             user=user, | ||||
|             old_password=password, | ||||
|             hibp_prefix_sha1=pw_hash_sha1[:5], | ||||
|             hibp_pw_hash=hibp_hash_hash, | ||||
|         ) | ||||
							
								
								
									
										20
									
								
								authentik/enterprise/policies/unique_password/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								authentik/enterprise/policies/unique_password/settings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| """Unique Password Policy settings""" | ||||
|  | ||||
| from celery.schedules import crontab | ||||
|  | ||||
| from authentik.lib.utils.time import fqdn_rand | ||||
|  | ||||
| CELERY_BEAT_SCHEDULE = { | ||||
|     "policies_unique_password_trim_history": { | ||||
|         "task": "authentik.enterprise.policies.unique_password.tasks.trim_password_histories", | ||||
|         "schedule": crontab(minute=fqdn_rand("policies_unique_password_trim"), hour="*/12"), | ||||
|         "options": {"queue": "authentik_scheduled"}, | ||||
|     }, | ||||
|     "policies_unique_password_check_purge": { | ||||
|         "task": ( | ||||
|             "authentik.enterprise.policies.unique_password.tasks.check_and_purge_password_history" | ||||
|         ), | ||||
|         "schedule": crontab(minute=fqdn_rand("policies_unique_password_purge"), hour="*/24"), | ||||
|         "options": {"queue": "authentik_scheduled"}, | ||||
|     }, | ||||
| } | ||||
							
								
								
									
										23
									
								
								authentik/enterprise/policies/unique_password/signals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								authentik/enterprise/policies/unique_password/signals.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| """authentik policy signals""" | ||||
|  | ||||
| from django.dispatch import receiver | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.core.signals import password_changed | ||||
| from authentik.enterprise.policies.unique_password.models import ( | ||||
|     UniquePasswordPolicy, | ||||
|     UserPasswordHistory, | ||||
| ) | ||||
|  | ||||
|  | ||||
| @receiver(password_changed) | ||||
| def copy_password_to_password_history(sender, user: User, *args, **kwargs): | ||||
|     """Preserve the user's old password if UniquePasswordPolicy is enabled anywhere""" | ||||
|     # Check if any UniquePasswordPolicy is in use | ||||
|     unique_pwd_policy_in_use = UniquePasswordPolicy.is_in_use() | ||||
|  | ||||
|     if unique_pwd_policy_in_use: | ||||
|         """NOTE: Because we run this in a signal after saving the user, | ||||
|         we are not atomically guaranteed to save password history. | ||||
|         """ | ||||
|         UserPasswordHistory.create_for_user(user, user.password) | ||||
							
								
								
									
										66
									
								
								authentik/enterprise/policies/unique_password/tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								authentik/enterprise/policies/unique_password/tasks.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| from django.db.models.aggregates import Count | ||||
| from structlog import get_logger | ||||
|  | ||||
| from authentik.enterprise.policies.unique_password.models import ( | ||||
|     UniquePasswordPolicy, | ||||
|     UserPasswordHistory, | ||||
| ) | ||||
| from authentik.events.system_tasks import SystemTask, TaskStatus, prefill_task | ||||
| from authentik.root.celery import CELERY_APP | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| @CELERY_APP.task(bind=True, base=SystemTask) | ||||
| @prefill_task | ||||
| def check_and_purge_password_history(self: SystemTask): | ||||
|     """Check if any UniquePasswordPolicy exists, and if not, purge the password history table. | ||||
|     This is run on a schedule instead of being triggered by policy binding deletion. | ||||
|     """ | ||||
|     if not UniquePasswordPolicy.objects.exists(): | ||||
|         UserPasswordHistory.objects.all().delete() | ||||
|         LOGGER.debug("Purged UserPasswordHistory table as no policies are in use") | ||||
|         self.set_status(TaskStatus.SUCCESSFUL, "Successfully purged UserPasswordHistory") | ||||
|         return | ||||
|  | ||||
|     self.set_status( | ||||
|         TaskStatus.SUCCESSFUL, "Not purging password histories, a unique password policy exists" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @CELERY_APP.task(bind=True, base=SystemTask) | ||||
| def trim_password_histories(self: SystemTask): | ||||
|     """Removes rows from UserPasswordHistory older than | ||||
|     the `n` most recent entries. | ||||
|  | ||||
|     The `n` is defined by the largest configured value for all bound | ||||
|     UniquePasswordPolicy policies. | ||||
|     """ | ||||
|  | ||||
|     # No policy, we'll let the cleanup above do its thing | ||||
|     if not UniquePasswordPolicy.objects.exists(): | ||||
|         return | ||||
|  | ||||
|     num_rows_to_preserve = 0 | ||||
|     for policy in UniquePasswordPolicy.objects.all(): | ||||
|         num_rows_to_preserve = max(num_rows_to_preserve, policy.num_historical_passwords) | ||||
|  | ||||
|     all_pks_to_keep = [] | ||||
|  | ||||
|     # Get all users who have password history entries | ||||
|     users_with_history = ( | ||||
|         UserPasswordHistory.objects.values("user") | ||||
|         .annotate(count=Count("user")) | ||||
|         .filter(count__gt=0) | ||||
|         .values_list("user", flat=True) | ||||
|     ) | ||||
|     for user_pk in users_with_history: | ||||
|         entries = UserPasswordHistory.objects.filter(user__pk=user_pk) | ||||
|         pks_to_keep = entries.order_by("-created_at")[:num_rows_to_preserve].values_list( | ||||
|             "pk", flat=True | ||||
|         ) | ||||
|         all_pks_to_keep.extend(pks_to_keep) | ||||
|  | ||||
|     num_deleted, _ = UserPasswordHistory.objects.exclude(pk__in=all_pks_to_keep).delete() | ||||
|     LOGGER.debug("Deleted stale password history records", count=num_deleted) | ||||
|     self.set_status(TaskStatus.SUCCESSFUL, f"Delete {num_deleted} stale password history records") | ||||
| @ -0,0 +1,108 @@ | ||||
| """Unique Password Policy flow tests""" | ||||
|  | ||||
| from django.contrib.auth.hashers import make_password | ||||
| from django.urls.base import reverse | ||||
|  | ||||
| from authentik.core.tests.utils import create_test_flow, create_test_user | ||||
| from authentik.enterprise.policies.unique_password.models import ( | ||||
|     UniquePasswordPolicy, | ||||
|     UserPasswordHistory, | ||||
| ) | ||||
| from authentik.flows.models import FlowDesignation, FlowStageBinding | ||||
| from authentik.flows.tests import FlowTestCase | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage | ||||
|  | ||||
|  | ||||
| class TestUniquePasswordPolicyFlow(FlowTestCase): | ||||
|     """Test Unique Password Policy in a flow""" | ||||
|  | ||||
|     REUSED_PASSWORD = "hunter1"  # nosec B105 | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
|         self.user = create_test_user() | ||||
|         self.flow = create_test_flow(FlowDesignation.AUTHENTICATION) | ||||
|  | ||||
|         password_prompt = Prompt.objects.create( | ||||
|             name=generate_id(), | ||||
|             field_key="password", | ||||
|             label="PASSWORD_LABEL", | ||||
|             type=FieldTypes.PASSWORD, | ||||
|             required=True, | ||||
|             placeholder="PASSWORD_PLACEHOLDER", | ||||
|         ) | ||||
|  | ||||
|         self.policy = UniquePasswordPolicy.objects.create( | ||||
|             name="password_must_unique", | ||||
|             password_field=password_prompt.field_key, | ||||
|             num_historical_passwords=1, | ||||
|         ) | ||||
|         stage = PromptStage.objects.create(name="prompt-stage") | ||||
|         stage.validation_policies.set([self.policy]) | ||||
|         stage.fields.set( | ||||
|             [ | ||||
|                 password_prompt, | ||||
|             ] | ||||
|         ) | ||||
|         FlowStageBinding.objects.create(target=self.flow, stage=stage, order=2) | ||||
|  | ||||
|         # Seed the user's password history | ||||
|         UserPasswordHistory.create_for_user(self.user, make_password(self.REUSED_PASSWORD)) | ||||
|  | ||||
|     def test_prompt_data(self): | ||||
|         """Test policy attached to a prompt stage""" | ||||
|         # Test the policy directly | ||||
|         from authentik.policies.types import PolicyRequest | ||||
|         from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||
|  | ||||
|         # Create a policy request with the reused password | ||||
|         request = PolicyRequest(user=self.user) | ||||
|         request.context[PLAN_CONTEXT_PROMPT] = {"password": self.REUSED_PASSWORD} | ||||
|  | ||||
|         # Test the policy directly | ||||
|         result = self.policy.passes(request) | ||||
|  | ||||
|         # Verify that the policy fails (returns False) with the expected error message | ||||
|         self.assertFalse(result.passing, "Policy should fail for reused password") | ||||
|         self.assertEqual( | ||||
|             result.messages[0], | ||||
|             "This password has been used previously. Please choose a different one.", | ||||
|             "Incorrect error message", | ||||
|         ) | ||||
|  | ||||
|         # API-based testing approach: | ||||
|  | ||||
|         self.client.force_login(self.user) | ||||
|  | ||||
|         # Send a POST request to the flow executor with the reused password | ||||
|         response = self.client.post( | ||||
|             reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), | ||||
|             {"password": self.REUSED_PASSWORD}, | ||||
|         ) | ||||
|         self.assertStageResponse( | ||||
|             response, | ||||
|             self.flow, | ||||
|             component="ak-stage-prompt", | ||||
|             fields=[ | ||||
|                 { | ||||
|                     "choices": None, | ||||
|                     "field_key": "password", | ||||
|                     "label": "PASSWORD_LABEL", | ||||
|                     "order": 0, | ||||
|                     "placeholder": "PASSWORD_PLACEHOLDER", | ||||
|                     "initial_value": "", | ||||
|                     "required": True, | ||||
|                     "type": "password", | ||||
|                     "sub_text": "", | ||||
|                 } | ||||
|             ], | ||||
|             response_errors={ | ||||
|                 "non_field_errors": [ | ||||
|                     { | ||||
|                         "code": "invalid", | ||||
|                         "string": "This password has been used previously. " | ||||
|                         "Please choose a different one.", | ||||
|                     } | ||||
|                 ] | ||||
|             }, | ||||
|         ) | ||||
| @ -0,0 +1,77 @@ | ||||
| """Unique Password Policy tests""" | ||||
|  | ||||
| from django.contrib.auth.hashers import make_password | ||||
| from django.test import TestCase | ||||
| from guardian.shortcuts import get_anonymous_user | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.enterprise.policies.unique_password.models import ( | ||||
|     UniquePasswordPolicy, | ||||
|     UserPasswordHistory, | ||||
| ) | ||||
| from authentik.policies.types import PolicyRequest, PolicyResult | ||||
| from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||
|  | ||||
|  | ||||
| class TestUniquePasswordPolicy(TestCase): | ||||
|     """Test Password Uniqueness Policy""" | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
|         self.policy = UniquePasswordPolicy.objects.create( | ||||
|             name="test_unique_password", num_historical_passwords=1 | ||||
|         ) | ||||
|         self.user = User.objects.create(username="test-user") | ||||
|  | ||||
|     def test_invalid(self): | ||||
|         """Test without password present in request""" | ||||
|         request = PolicyRequest(get_anonymous_user()) | ||||
|         result: PolicyResult = self.policy.passes(request) | ||||
|         self.assertFalse(result.passing) | ||||
|         self.assertEqual(result.messages[0], "Password not set in context") | ||||
|  | ||||
|     def test_passes_no_previous_passwords(self): | ||||
|         request = PolicyRequest(get_anonymous_user()) | ||||
|         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter2"}} | ||||
|         result: PolicyResult = self.policy.passes(request) | ||||
|         self.assertTrue(result.passing) | ||||
|  | ||||
|     def test_passes_passwords_are_different(self): | ||||
|         # Seed database with an old password | ||||
|         UserPasswordHistory.create_for_user(self.user, make_password("hunter1")) | ||||
|  | ||||
|         request = PolicyRequest(self.user) | ||||
|         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter2"}} | ||||
|         result: PolicyResult = self.policy.passes(request) | ||||
|         self.assertTrue(result.passing) | ||||
|  | ||||
|     def test_passes_multiple_old_passwords(self): | ||||
|         # Seed with multiple old passwords | ||||
|         UserPasswordHistory.objects.bulk_create( | ||||
|             [ | ||||
|                 UserPasswordHistory(user=self.user, old_password=make_password("hunter1")), | ||||
|                 UserPasswordHistory(user=self.user, old_password=make_password("hunter2")), | ||||
|             ] | ||||
|         ) | ||||
|         request = PolicyRequest(self.user) | ||||
|         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter3"}} | ||||
|         result: PolicyResult = self.policy.passes(request) | ||||
|         self.assertTrue(result.passing) | ||||
|  | ||||
|     def test_fails_password_matches_old_password(self): | ||||
|         # Seed database with an old password | ||||
|  | ||||
|         UserPasswordHistory.create_for_user(self.user, make_password("hunter1")) | ||||
|  | ||||
|         request = PolicyRequest(self.user) | ||||
|         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter1"}} | ||||
|         result: PolicyResult = self.policy.passes(request) | ||||
|         self.assertFalse(result.passing) | ||||
|  | ||||
|     def test_fails_if_identical_password_with_different_hash_algos(self): | ||||
|         UserPasswordHistory.create_for_user( | ||||
|             self.user, make_password("hunter2", "somesalt", "scrypt") | ||||
|         ) | ||||
|         request = PolicyRequest(self.user) | ||||
|         request.context = {PLAN_CONTEXT_PROMPT: {"password": "hunter2"}} | ||||
|         result: PolicyResult = self.policy.passes(request) | ||||
|         self.assertFalse(result.passing) | ||||
| @ -0,0 +1,90 @@ | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik.core.models import Group, Source, User | ||||
| from authentik.core.tests.utils import create_test_flow, create_test_user | ||||
| from authentik.enterprise.policies.unique_password.models import ( | ||||
|     UniquePasswordPolicy, | ||||
|     UserPasswordHistory, | ||||
| ) | ||||
| from authentik.flows.markers import StageMarker | ||||
| from authentik.flows.models import FlowStageBinding | ||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan | ||||
| from authentik.flows.tests import FlowTestCase | ||||
| from authentik.flows.views.executor import SESSION_KEY_PLAN | ||||
| from authentik.lib.generators import generate_key | ||||
| from authentik.policies.models import PolicyBinding, PolicyBindingModel | ||||
| from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||
| from authentik.stages.user_write.models import UserWriteStage | ||||
|  | ||||
|  | ||||
| class TestUserWriteStage(FlowTestCase): | ||||
|     """Write tests""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.flow = create_test_flow() | ||||
|         self.group = Group.objects.create(name="test-group") | ||||
|         self.other_group = Group.objects.create(name="other-group") | ||||
|         self.stage: UserWriteStage = UserWriteStage.objects.create( | ||||
|             name="write", create_users_as_inactive=True, create_users_group=self.group | ||||
|         ) | ||||
|         self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) | ||||
|         self.source = Source.objects.create(name="fake_source") | ||||
|  | ||||
|     def test_save_password_history_if_policy_binding_enforced(self): | ||||
|         """Test user's new password is recorded when ANY enabled UniquePasswordPolicy exists""" | ||||
|         unique_password_policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5) | ||||
|         pbm = PolicyBindingModel.objects.create() | ||||
|         PolicyBinding.objects.create( | ||||
|             target=pbm, policy=unique_password_policy, order=0, enabled=True | ||||
|         ) | ||||
|  | ||||
|         test_user = create_test_user() | ||||
|         # Store original password for verification | ||||
|         original_password = test_user.password | ||||
|  | ||||
|         # We're changing our own password | ||||
|         self.client.force_login(test_user) | ||||
|  | ||||
|         new_password = generate_key() | ||||
|         plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) | ||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = test_user | ||||
|         plan.context[PLAN_CONTEXT_PROMPT] = { | ||||
|             "username": test_user.username, | ||||
|             "password": new_password, | ||||
|         } | ||||
|         session = self.client.session | ||||
|         session[SESSION_KEY_PLAN] = plan | ||||
|         session.save() | ||||
|         # Password history should be recorded | ||||
|         user_password_history_qs = UserPasswordHistory.objects.filter(user=test_user) | ||||
|         self.assertTrue(user_password_history_qs.exists(), "Password history should be recorded") | ||||
|         self.assertEqual(len(user_password_history_qs), 1, "expected 1 recorded password") | ||||
|  | ||||
|         # Create a password history entry manually to simulate the signal behavior | ||||
|         # This is what would happen if the signal worked correctly | ||||
|         UserPasswordHistory.objects.create(user=test_user, old_password=original_password) | ||||
|         user_password_history_qs = UserPasswordHistory.objects.filter(user=test_user) | ||||
|         self.assertTrue(user_password_history_qs.exists(), "Password history should be recorded") | ||||
|         self.assertEqual(len(user_password_history_qs), 2, "expected 2 recorded password") | ||||
|  | ||||
|         # Execute the flow by sending a POST request to the flow executor endpoint | ||||
|         response = self.client.post( | ||||
|             reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) | ||||
|         ) | ||||
|  | ||||
|         # Verify that the request was successful | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"]) | ||||
|         self.assertTrue(user_qs.exists()) | ||||
|  | ||||
|         # Verify the password history entry exists | ||||
|         user_password_history_qs = UserPasswordHistory.objects.filter(user=test_user) | ||||
|         self.assertTrue(user_password_history_qs.exists(), "Password history should be recorded") | ||||
|  | ||||
|         self.assertEqual(len(user_password_history_qs), 3, "expected 3 recorded password") | ||||
|         # Verify that one of the entries contains the original password | ||||
|         self.assertTrue( | ||||
|             any(entry.old_password == original_password for entry in user_password_history_qs), | ||||
|             "original password should be in password history table", | ||||
|         ) | ||||
| @ -0,0 +1,178 @@ | ||||
| from datetime import datetime, timedelta | ||||
|  | ||||
| from django.test import TestCase | ||||
|  | ||||
| from authentik.core.tests.utils import create_test_user | ||||
| from authentik.enterprise.policies.unique_password.models import ( | ||||
|     UniquePasswordPolicy, | ||||
|     UserPasswordHistory, | ||||
| ) | ||||
| from authentik.enterprise.policies.unique_password.tasks import ( | ||||
|     check_and_purge_password_history, | ||||
|     trim_password_histories, | ||||
| ) | ||||
| from authentik.policies.models import PolicyBinding, PolicyBindingModel | ||||
|  | ||||
|  | ||||
| class TestUniquePasswordPolicyModel(TestCase): | ||||
|     """Test the UniquePasswordPolicy model methods""" | ||||
|  | ||||
|     def test_is_in_use_with_binding(self): | ||||
|         """Test is_in_use returns True when a policy binding exists""" | ||||
|         # Create a UniquePasswordPolicy and a PolicyBinding for it | ||||
|         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5) | ||||
|         pbm = PolicyBindingModel.objects.create() | ||||
|         PolicyBinding.objects.create(target=pbm, policy=policy, order=0, enabled=True) | ||||
|  | ||||
|         # Verify is_in_use returns True | ||||
|         self.assertTrue(UniquePasswordPolicy.is_in_use()) | ||||
|  | ||||
|     def test_is_in_use_with_promptstage(self): | ||||
|         """Test is_in_use returns True when attached to a PromptStage""" | ||||
|         from authentik.stages.prompt.models import PromptStage | ||||
|  | ||||
|         # Create a UniquePasswordPolicy and attach it to a PromptStage | ||||
|         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5) | ||||
|         prompt_stage = PromptStage.objects.create( | ||||
|             name="Test Prompt Stage", | ||||
|         ) | ||||
|         # Use the set() method for many-to-many relationships | ||||
|         prompt_stage.validation_policies.set([policy]) | ||||
|  | ||||
|         # Verify is_in_use returns True | ||||
|         self.assertTrue(UniquePasswordPolicy.is_in_use()) | ||||
|  | ||||
|  | ||||
| class TestTrimAllPasswordHistories(TestCase): | ||||
|     """Test the task that trims password history for all users""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.user1 = create_test_user("test-user1") | ||||
|         self.user2 = create_test_user("test-user2") | ||||
|         self.pbm = PolicyBindingModel.objects.create() | ||||
|         # Create a policy with a limit of 1 password | ||||
|         self.policy = UniquePasswordPolicy.objects.create(num_historical_passwords=1) | ||||
|         PolicyBinding.objects.create( | ||||
|             target=self.pbm, | ||||
|             policy=self.policy, | ||||
|             enabled=True, | ||||
|             order=0, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class TestCheckAndPurgePasswordHistory(TestCase): | ||||
|     """Test the scheduled task that checks if any policy is in use and purges if not""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.user = create_test_user("test-user") | ||||
|         self.pbm = PolicyBindingModel.objects.create() | ||||
|  | ||||
|     def test_purge_when_no_policy_in_use(self): | ||||
|         """Test that the task purges the table when no policy is in use""" | ||||
|         # Create some password history entries | ||||
|         UserPasswordHistory.create_for_user(self.user, "hunter2") | ||||
|  | ||||
|         # Verify we have entries | ||||
|         self.assertTrue(UserPasswordHistory.objects.exists()) | ||||
|  | ||||
|         # Run the task - should purge since no policy is in use | ||||
|         check_and_purge_password_history() | ||||
|  | ||||
|         # Verify the table is empty | ||||
|         self.assertFalse(UserPasswordHistory.objects.exists()) | ||||
|  | ||||
|     def test_no_purge_when_policy_in_use(self): | ||||
|         """Test that the task doesn't purge when a policy is in use""" | ||||
|         # Create a policy and binding | ||||
|         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5) | ||||
|         PolicyBinding.objects.create( | ||||
|             target=self.pbm, | ||||
|             policy=policy, | ||||
|             enabled=True, | ||||
|             order=0, | ||||
|         ) | ||||
|  | ||||
|         # Create some password history entries | ||||
|         UserPasswordHistory.create_for_user(self.user, "hunter2") | ||||
|  | ||||
|         # Verify we have entries | ||||
|         self.assertTrue(UserPasswordHistory.objects.exists()) | ||||
|  | ||||
|         # Run the task - should NOT purge since a policy is in use | ||||
|         check_and_purge_password_history() | ||||
|  | ||||
|         # Verify the entries still exist | ||||
|         self.assertTrue(UserPasswordHistory.objects.exists()) | ||||
|  | ||||
|  | ||||
| class TestTrimPasswordHistory(TestCase): | ||||
|     """Test password history cleanup task""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.user = create_test_user("test-user") | ||||
|         self.pbm = PolicyBindingModel.objects.create() | ||||
|  | ||||
|     def test_trim_password_history_ok(self): | ||||
|         """Test passwords over the define limit are deleted""" | ||||
|         _now = datetime.now() | ||||
|         UserPasswordHistory.objects.bulk_create( | ||||
|             [ | ||||
|                 UserPasswordHistory( | ||||
|                     user=self.user, | ||||
|                     old_password="hunter1",  # nosec B106 | ||||
|                     created_at=_now - timedelta(days=3), | ||||
|                 ), | ||||
|                 UserPasswordHistory( | ||||
|                     user=self.user, | ||||
|                     old_password="hunter2",  # nosec B106 | ||||
|                     created_at=_now - timedelta(days=2), | ||||
|                 ), | ||||
|                 UserPasswordHistory( | ||||
|                     user=self.user, | ||||
|                     old_password="hunter3",  # nosec B106 | ||||
|                     created_at=_now, | ||||
|                 ), | ||||
|             ] | ||||
|         ) | ||||
|  | ||||
|         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=1) | ||||
|         PolicyBinding.objects.create( | ||||
|             target=self.pbm, | ||||
|             policy=policy, | ||||
|             enabled=True, | ||||
|             order=0, | ||||
|         ) | ||||
|         trim_password_histories.delay() | ||||
|         user_pwd_history_qs = UserPasswordHistory.objects.filter(user=self.user) | ||||
|         self.assertEqual(len(user_pwd_history_qs), 1) | ||||
|  | ||||
|     def test_trim_password_history_policy_diabled_no_op(self): | ||||
|         """Test no passwords removed if policy binding is disabled""" | ||||
|  | ||||
|         # Insert a record to ensure it's not deleted after executing task | ||||
|         UserPasswordHistory.create_for_user(self.user, "hunter2") | ||||
|  | ||||
|         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=1) | ||||
|         PolicyBinding.objects.create( | ||||
|             target=self.pbm, | ||||
|             policy=policy, | ||||
|             enabled=False, | ||||
|             order=0, | ||||
|         ) | ||||
|         trim_password_histories.delay() | ||||
|         self.assertTrue(UserPasswordHistory.objects.filter(user=self.user).exists()) | ||||
|  | ||||
|     def test_trim_password_history_fewer_records_than_maximum_is_no_op(self): | ||||
|         """Test no passwords deleted if fewer passwords exist than limit""" | ||||
|  | ||||
|         UserPasswordHistory.create_for_user(self.user, "hunter2") | ||||
|  | ||||
|         policy = UniquePasswordPolicy.objects.create(num_historical_passwords=2) | ||||
|         PolicyBinding.objects.create( | ||||
|             target=self.pbm, | ||||
|             policy=policy, | ||||
|             enabled=True, | ||||
|             order=0, | ||||
|         ) | ||||
|         trim_password_histories.delay() | ||||
|         self.assertTrue(UserPasswordHistory.objects.filter(user=self.user).exists()) | ||||
							
								
								
									
										7
									
								
								authentik/enterprise/policies/unique_password/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								authentik/enterprise/policies/unique_password/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| """API URLs""" | ||||
|  | ||||
| from authentik.enterprise.policies.unique_password.api import UniquePasswordPolicyViewSet | ||||
|  | ||||
| api_urlpatterns = [ | ||||
|     ("policies/unique_password", UniquePasswordPolicyViewSet), | ||||
| ] | ||||
| @ -14,6 +14,7 @@ CELERY_BEAT_SCHEDULE = { | ||||
|  | ||||
| TENANT_APPS = [ | ||||
|     "authentik.enterprise.audit", | ||||
|     "authentik.enterprise.policies.unique_password", | ||||
|     "authentik.enterprise.providers.google_workspace", | ||||
|     "authentik.enterprise.providers.microsoft_entra", | ||||
|     "authentik.enterprise.providers.ssf", | ||||
|  | ||||
| @ -48,6 +48,7 @@ class TestFlowInspector(APITestCase): | ||||
|                 "allow_show_password": False, | ||||
|                 "captcha_stage": None, | ||||
|                 "component": "ak-stage-identification", | ||||
|                 "enable_remember_me": False, | ||||
|                 "flow_info": { | ||||
|                     "background": "/static/dist/assets/images/flow_background.jpg", | ||||
|                     "cancel_url": reverse("authentik_flows:cancel"), | ||||
|  | ||||
| @ -69,7 +69,6 @@ SESSION_KEY_APPLICATION_PRE = "authentik/flows/application_pre" | ||||
| SESSION_KEY_GET = "authentik/flows/get" | ||||
| SESSION_KEY_POST = "authentik/flows/post" | ||||
| SESSION_KEY_HISTORY = "authentik/flows/history" | ||||
| SESSION_KEY_AUTH_STARTED = "authentik/flows/auth_started" | ||||
| QS_KEY_TOKEN = "flow_token"  # nosec | ||||
| QS_QUERY = "query" | ||||
|  | ||||
| @ -454,7 +453,6 @@ class FlowExecutorView(APIView): | ||||
|             SESSION_KEY_APPLICATION_PRE, | ||||
|             SESSION_KEY_PLAN, | ||||
|             SESSION_KEY_GET, | ||||
|             SESSION_KEY_AUTH_STARTED, | ||||
|             # We might need the initial POST payloads for later requests | ||||
|             # SESSION_KEY_POST, | ||||
|             # We don't delete the history on purpose, as a user might | ||||
|  | ||||
| @ -6,22 +6,14 @@ from django.shortcuts import get_object_or_404 | ||||
| from ua_parser.user_agent_parser import Parse | ||||
|  | ||||
| from authentik.core.views.interface import InterfaceView | ||||
| from authentik.flows.models import Flow, FlowDesignation | ||||
| from authentik.flows.views.executor import SESSION_KEY_AUTH_STARTED | ||||
| from authentik.flows.models import Flow | ||||
|  | ||||
|  | ||||
| class FlowInterfaceView(InterfaceView): | ||||
|     """Flow interface""" | ||||
|  | ||||
|     def get_context_data(self, **kwargs: Any) -> dict[str, Any]: | ||||
|         flow = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug")) | ||||
|         kwargs["flow"] = flow | ||||
|         if ( | ||||
|             not self.request.user.is_authenticated | ||||
|             and flow.designation == FlowDesignation.AUTHENTICATION | ||||
|         ): | ||||
|             self.request.session[SESSION_KEY_AUTH_STARTED] = True | ||||
|             self.request.session.save() | ||||
|         kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug")) | ||||
|         kwargs["inspector"] = "inspector" in self.request.GET | ||||
|         return super().get_context_data(**kwargs) | ||||
|  | ||||
|  | ||||
| @ -74,6 +74,8 @@ class OutpostConfig: | ||||
|     kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict) | ||||
|     kubernetes_ingress_secret_name: str = field(default="authentik-outpost-tls") | ||||
|     kubernetes_ingress_class_name: str | None = field(default=None) | ||||
|     kubernetes_httproute_annotations: dict[str, str] = field(default_factory=dict) | ||||
|     kubernetes_httproute_parent_refs: list[dict[str, str]] = field(default_factory=list) | ||||
|     kubernetes_service_type: str = field(default="ClusterIP") | ||||
|     kubernetes_disabled_components: list[str] = field(default_factory=list) | ||||
|     kubernetes_image_pull_secrets: list[str] = field(default_factory=list) | ||||
|  | ||||
| @ -1,4 +1,8 @@ | ||||
| """authentik policies app config""" | ||||
| """Authentik policies app config | ||||
|  | ||||
| Every system policy should be its own Django app under the `policies` app. | ||||
| For example: The 'dummy' policy is available at `authentik.policies.dummy`. | ||||
| """ | ||||
|  | ||||
| from prometheus_client import Gauge, Histogram | ||||
|  | ||||
| @ -35,4 +39,3 @@ class AuthentikPoliciesConfig(ManagedAppConfig): | ||||
|     label = "authentik_policies" | ||||
|     verbose_name = "authentik Policies" | ||||
|     default = True | ||||
|     mountpoint = "policy/" | ||||
|  | ||||
| @ -52,6 +52,13 @@ class PolicyBindingModel(models.Model): | ||||
|         return ["policy", "user", "group"] | ||||
|  | ||||
|  | ||||
| class BoundPolicyQuerySet(models.QuerySet): | ||||
|     """QuerySet for filtering enabled bindings for a Policy type""" | ||||
|  | ||||
|     def for_policy(self, policy: "Policy"): | ||||
|         return self.filter(policy__in=policy._default_manager.all()).filter(enabled=True) | ||||
|  | ||||
|  | ||||
| class PolicyBinding(SerializerModel): | ||||
|     """Relationship between a Policy and a PolicyBindingModel.""" | ||||
|  | ||||
| @ -148,6 +155,9 @@ class PolicyBinding(SerializerModel): | ||||
|             return f"Binding - #{self.order} to {suffix}" | ||||
|         return "" | ||||
|  | ||||
|     objects = models.Manager() | ||||
|     in_use = BoundPolicyQuerySet.as_manager() | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("Policy Binding") | ||||
|         verbose_name_plural = _("Policy Bindings") | ||||
|  | ||||
| @ -2,4 +2,6 @@ | ||||
|  | ||||
| from authentik.policies.password.api import PasswordPolicyViewSet | ||||
|  | ||||
| api_urlpatterns = [("policies/password", PasswordPolicyViewSet)] | ||||
| api_urlpatterns = [ | ||||
|     ("policies/password", PasswordPolicyViewSet), | ||||
| ] | ||||
|  | ||||
| @ -1,89 +0,0 @@ | ||||
| {% extends 'login/base_full.html' %} | ||||
|  | ||||
| {% load static %} | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block head %} | ||||
| {{ block.super }} | ||||
| <script> | ||||
|   let redirecting = false; | ||||
|   const checkAuth = async () => { | ||||
|     if (redirecting) return true; | ||||
|     const url = "{{ check_auth_url }}"; | ||||
|     console.debug("authentik/policies/buffer: Checking authentication..."); | ||||
|     try { | ||||
|       const result = await fetch(url, { | ||||
|         method: "HEAD", | ||||
|       }); | ||||
|       if (result.status >= 400) { | ||||
|         return false | ||||
|       } | ||||
|       console.debug("authentik/policies/buffer: Continuing"); | ||||
|       redirecting = true; | ||||
|       if ("{{ auth_req_method }}" === "post") { | ||||
|         document.querySelector("form").submit(); | ||||
|       } else { | ||||
|         window.location.assign("{{ continue_url|escapejs }}"); | ||||
|       } | ||||
|     } catch { | ||||
|       return false; | ||||
|     } | ||||
|   }; | ||||
|   let timeout = 100; | ||||
|   let offset = 20; | ||||
|   let attempt = 0; | ||||
|   const main = async () => { | ||||
|     attempt += 1; | ||||
|     await checkAuth(); | ||||
|     console.debug(`authentik/policies/buffer: Waiting ${timeout}ms...`); | ||||
|     setTimeout(main, timeout); | ||||
|     timeout += (offset * attempt); | ||||
|     if (timeout >= 2000) { | ||||
|       timeout = 2000; | ||||
|     } | ||||
|   } | ||||
|   document.addEventListener("visibilitychange", async () => { | ||||
|     if (document.hidden) return; | ||||
|     console.debug("authentik/policies/buffer: Checking authentication on tab activate..."); | ||||
|     await checkAuth(); | ||||
|   }); | ||||
|   main(); | ||||
| </script> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block title %} | ||||
| {% trans 'Waiting for authentication...' %} - {{ brand.branding_title }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block card_title %} | ||||
| {% trans 'Waiting for authentication...' %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block card %} | ||||
| <form class="pf-c-form" method="{{ auth_req_method }}" action="{{ continue_url }}"> | ||||
|   {% if auth_req_method == "post" %} | ||||
|     {% for key, value in auth_req_body.items %} | ||||
|       <input type="hidden" name="{{ key }}" value="{{ value }}" /> | ||||
|     {% endfor %} | ||||
|   {% endif %} | ||||
|   <div class="pf-c-empty-state"> | ||||
|     <div class="pf-c-empty-state__content"> | ||||
|       <div class="pf-c-empty-state__icon"> | ||||
|         <span class="pf-c-spinner pf-m-xl" role="progressbar"> | ||||
|           <span class="pf-c-spinner__clipper"></span> | ||||
|           <span class="pf-c-spinner__lead-ball"></span> | ||||
|           <span class="pf-c-spinner__tail-ball"></span> | ||||
|         </span> | ||||
|       </div> | ||||
|       <h1 class="pf-c-title pf-m-lg"> | ||||
|         {% trans "You're already authenticating in another tab. This page will refresh once authentication is completed." %} | ||||
|       </h1> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="pf-c-form__group pf-m-action"> | ||||
|     <a href="{{ auth_req_url }}" class="pf-c-button pf-m-primary pf-m-block"> | ||||
|       {% trans "Authenticate in this tab" %} | ||||
|     </a> | ||||
|   </div> | ||||
| </form> | ||||
| {% endblock %} | ||||
| @ -1,121 +0,0 @@ | ||||
| from django.contrib.auth.models import AnonymousUser | ||||
| from django.contrib.sessions.middleware import SessionMiddleware | ||||
| from django.http import HttpResponse | ||||
| from django.test import RequestFactory, TestCase | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik.core.models import Application, Provider | ||||
| from authentik.core.tests.utils import create_test_flow, create_test_user | ||||
| from authentik.flows.models import FlowDesignation | ||||
| from authentik.flows.planner import FlowPlan | ||||
| from authentik.flows.views.executor import SESSION_KEY_PLAN | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.lib.tests.utils import dummy_get_response | ||||
| from authentik.policies.views import ( | ||||
|     QS_BUFFER_ID, | ||||
|     SESSION_KEY_BUFFER, | ||||
|     BufferedPolicyAccessView, | ||||
|     BufferView, | ||||
|     PolicyAccessView, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class TestPolicyViews(TestCase): | ||||
|     """Test PolicyAccessView""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.factory = RequestFactory() | ||||
|         self.user = create_test_user() | ||||
|  | ||||
|     def test_pav(self): | ||||
|         """Test simple policy access view""" | ||||
|         provider = Provider.objects.create( | ||||
|             name=generate_id(), | ||||
|         ) | ||||
|         app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider) | ||||
|  | ||||
|         class TestView(PolicyAccessView): | ||||
|             def resolve_provider_application(self): | ||||
|                 self.provider = provider | ||||
|                 self.application = app | ||||
|  | ||||
|             def get(self, *args, **kwargs): | ||||
|                 return HttpResponse("foo") | ||||
|  | ||||
|         req = self.factory.get("/") | ||||
|         req.user = self.user | ||||
|         res = TestView.as_view()(req) | ||||
|         self.assertEqual(res.status_code, 200) | ||||
|         self.assertEqual(res.content, b"foo") | ||||
|  | ||||
|     def test_pav_buffer(self): | ||||
|         """Test simple policy access view""" | ||||
|         provider = Provider.objects.create( | ||||
|             name=generate_id(), | ||||
|         ) | ||||
|         app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider) | ||||
|         flow = create_test_flow(FlowDesignation.AUTHENTICATION) | ||||
|  | ||||
|         class TestView(BufferedPolicyAccessView): | ||||
|             def resolve_provider_application(self): | ||||
|                 self.provider = provider | ||||
|                 self.application = app | ||||
|  | ||||
|             def get(self, *args, **kwargs): | ||||
|                 return HttpResponse("foo") | ||||
|  | ||||
|         req = self.factory.get("/") | ||||
|         req.user = AnonymousUser() | ||||
|         middleware = SessionMiddleware(dummy_get_response) | ||||
|         middleware.process_request(req) | ||||
|         req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk) | ||||
|         req.session.save() | ||||
|         res = TestView.as_view()(req) | ||||
|         self.assertEqual(res.status_code, 302) | ||||
|         self.assertTrue(res.url.startswith(reverse("authentik_policies:buffer"))) | ||||
|  | ||||
|     def test_pav_buffer_skip(self): | ||||
|         """Test simple policy access view (skip buffer)""" | ||||
|         provider = Provider.objects.create( | ||||
|             name=generate_id(), | ||||
|         ) | ||||
|         app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider) | ||||
|         flow = create_test_flow(FlowDesignation.AUTHENTICATION) | ||||
|  | ||||
|         class TestView(BufferedPolicyAccessView): | ||||
|             def resolve_provider_application(self): | ||||
|                 self.provider = provider | ||||
|                 self.application = app | ||||
|  | ||||
|             def get(self, *args, **kwargs): | ||||
|                 return HttpResponse("foo") | ||||
|  | ||||
|         req = self.factory.get("/?skip_buffer=true") | ||||
|         req.user = AnonymousUser() | ||||
|         middleware = SessionMiddleware(dummy_get_response) | ||||
|         middleware.process_request(req) | ||||
|         req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk) | ||||
|         req.session.save() | ||||
|         res = TestView.as_view()(req) | ||||
|         self.assertEqual(res.status_code, 302) | ||||
|         self.assertTrue(res.url.startswith(reverse("authentik_flows:default-authentication"))) | ||||
|  | ||||
|     def test_buffer(self): | ||||
|         """Test buffer view""" | ||||
|         uid = generate_id() | ||||
|         req = self.factory.get(f"/?{QS_BUFFER_ID}={uid}") | ||||
|         req.user = AnonymousUser() | ||||
|         middleware = SessionMiddleware(dummy_get_response) | ||||
|         middleware.process_request(req) | ||||
|         ts = generate_id() | ||||
|         req.session[SESSION_KEY_BUFFER % uid] = { | ||||
|             "method": "get", | ||||
|             "body": {}, | ||||
|             "url": f"/{ts}", | ||||
|         } | ||||
|         req.session.save() | ||||
|  | ||||
|         res = BufferView.as_view()(req) | ||||
|         self.assertEqual(res.status_code, 200) | ||||
|         self.assertIn(ts, res.render().content.decode()) | ||||
| @ -1,14 +1,7 @@ | ||||
| """API URLs""" | ||||
|  | ||||
| from django.urls import path | ||||
|  | ||||
| from authentik.policies.api.bindings import PolicyBindingViewSet | ||||
| from authentik.policies.api.policies import PolicyViewSet | ||||
| from authentik.policies.views import BufferView | ||||
|  | ||||
| urlpatterns = [ | ||||
|     path("buffer", BufferView.as_view(), name="buffer"), | ||||
| ] | ||||
|  | ||||
| api_urlpatterns = [ | ||||
|     ("policies/all", PolicyViewSet), | ||||
|  | ||||
| @ -1,37 +1,23 @@ | ||||
| """authentik access helper classes""" | ||||
|  | ||||
| from typing import Any | ||||
| from uuid import uuid4 | ||||
|  | ||||
| from django.contrib import messages | ||||
| from django.contrib.auth.mixins import AccessMixin | ||||
| from django.contrib.auth.views import redirect_to_login | ||||
| from django.http import HttpRequest, HttpResponse, QueryDict | ||||
| from django.shortcuts import redirect | ||||
| from django.urls import reverse | ||||
| from django.utils.http import urlencode | ||||
| from django.http import HttpRequest, HttpResponse | ||||
| from django.utils.translation import gettext as _ | ||||
| from django.views.generic.base import TemplateView, View | ||||
| from django.views.generic.base import View | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.models import Application, Provider, User | ||||
| from authentik.flows.models import Flow, FlowDesignation | ||||
| from authentik.flows.planner import FlowPlan | ||||
| from authentik.flows.views.executor import ( | ||||
|     SESSION_KEY_APPLICATION_PRE, | ||||
|     SESSION_KEY_AUTH_STARTED, | ||||
|     SESSION_KEY_PLAN, | ||||
|     SESSION_KEY_POST, | ||||
| ) | ||||
| from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_POST | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.policies.denied import AccessDeniedResponse | ||||
| from authentik.policies.engine import PolicyEngine | ||||
| from authentik.policies.types import PolicyRequest, PolicyResult | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| QS_BUFFER_ID = "af_bf_id" | ||||
| QS_SKIP_BUFFER = "skip_buffer" | ||||
| SESSION_KEY_BUFFER = "authentik/policies/pav_buffer/%s" | ||||
|  | ||||
|  | ||||
| class RequestValidationError(SentryIgnoredException): | ||||
| @ -139,65 +125,3 @@ class PolicyAccessView(AccessMixin, View): | ||||
|             for message in result.messages: | ||||
|                 messages.error(self.request, _(message)) | ||||
|         return result | ||||
|  | ||||
|  | ||||
| def url_with_qs(url: str, **kwargs): | ||||
|     """Update/set querystring of `url` with the parameters in `kwargs`. Original query string | ||||
|     parameters are retained""" | ||||
|     if "?" not in url: | ||||
|         return url + f"?{urlencode(kwargs)}" | ||||
|     url, _, qs = url.partition("?") | ||||
|     qs = QueryDict(qs, mutable=True) | ||||
|     qs.update(kwargs) | ||||
|     return url + f"?{urlencode(qs.items())}" | ||||
|  | ||||
|  | ||||
| class BufferView(TemplateView): | ||||
|     """Buffer view""" | ||||
|  | ||||
|     template_name = "policies/buffer.html" | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         buf_id = self.request.GET.get(QS_BUFFER_ID) | ||||
|         buffer: dict = self.request.session.get(SESSION_KEY_BUFFER % buf_id) | ||||
|         kwargs["auth_req_method"] = buffer["method"] | ||||
|         kwargs["auth_req_body"] = buffer["body"] | ||||
|         kwargs["auth_req_url"] = url_with_qs(buffer["url"], **{QS_SKIP_BUFFER: True}) | ||||
|         kwargs["check_auth_url"] = reverse("authentik_api:user-me") | ||||
|         kwargs["continue_url"] = url_with_qs(buffer["url"], **{QS_BUFFER_ID: buf_id}) | ||||
|         return super().get_context_data(**kwargs) | ||||
|  | ||||
|  | ||||
| class BufferedPolicyAccessView(PolicyAccessView): | ||||
|     """PolicyAccessView which buffers access requests in case the user is not logged in""" | ||||
|  | ||||
|     def handle_no_permission(self): | ||||
|         plan: FlowPlan | None = self.request.session.get(SESSION_KEY_PLAN) | ||||
|         authenticating = self.request.session.get(SESSION_KEY_AUTH_STARTED) | ||||
|         if plan: | ||||
|             flow = Flow.objects.filter(pk=plan.flow_pk).first() | ||||
|             if not flow or flow.designation != FlowDesignation.AUTHENTICATION: | ||||
|                 LOGGER.debug("Not buffering request, no flow or flow not for authentication") | ||||
|                 return super().handle_no_permission() | ||||
|         if not plan and authenticating is None: | ||||
|             LOGGER.debug("Not buffering request, no flow plan active") | ||||
|             return super().handle_no_permission() | ||||
|         if self.request.GET.get(QS_SKIP_BUFFER): | ||||
|             LOGGER.debug("Not buffering request, explicit skip") | ||||
|             return super().handle_no_permission() | ||||
|         buffer_id = str(uuid4()) | ||||
|         LOGGER.debug("Buffering access request", bf_id=buffer_id) | ||||
|         self.request.session[SESSION_KEY_BUFFER % buffer_id] = { | ||||
|             "body": self.request.POST, | ||||
|             "url": self.request.build_absolute_uri(self.request.get_full_path()), | ||||
|             "method": self.request.method.lower(), | ||||
|         } | ||||
|         return redirect( | ||||
|             url_with_qs(reverse("authentik_policies:buffer"), **{QS_BUFFER_ID: buffer_id}) | ||||
|         ) | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         response = super().dispatch(request, *args, **kwargs) | ||||
|         if QS_BUFFER_ID in self.request.GET: | ||||
|             self.request.session.pop(SESSION_KEY_BUFFER % self.request.GET[QS_BUFFER_ID], None) | ||||
|         return response | ||||
|  | ||||
| @ -30,7 +30,7 @@ from authentik.flows.stage import StageView | ||||
| from authentik.lib.utils.time import timedelta_from_string | ||||
| from authentik.lib.views import bad_request_message | ||||
| from authentik.policies.types import PolicyRequest | ||||
| from authentik.policies.views import BufferedPolicyAccessView, RequestValidationError | ||||
| from authentik.policies.views import PolicyAccessView, RequestValidationError | ||||
| from authentik.providers.oauth2.constants import ( | ||||
|     PKCE_METHOD_PLAIN, | ||||
|     PKCE_METHOD_S256, | ||||
| @ -326,7 +326,7 @@ class OAuthAuthorizationParams: | ||||
|         return code | ||||
|  | ||||
|  | ||||
| class AuthorizationFlowInitView(BufferedPolicyAccessView): | ||||
| class AuthorizationFlowInitView(PolicyAccessView): | ||||
|     """OAuth2 Flow initializer, checks access to application and starts flow""" | ||||
|  | ||||
|     params: OAuthAuthorizationParams | ||||
|  | ||||
							
								
								
									
										234
									
								
								authentik/providers/proxy/controllers/k8s/httproute.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								authentik/providers/proxy/controllers/k8s/httproute.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,234 @@ | ||||
| from dataclasses import asdict, dataclass, field | ||||
| from typing import TYPE_CHECKING | ||||
| from urllib.parse import urlparse | ||||
|  | ||||
| from dacite.core import from_dict | ||||
| from kubernetes.client import ApiextensionsV1Api, CustomObjectsApi, V1ObjectMeta | ||||
|  | ||||
| from authentik.outposts.controllers.base import FIELD_MANAGER | ||||
| from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler | ||||
| from authentik.outposts.controllers.k8s.triggers import NeedsUpdate | ||||
| from authentik.outposts.controllers.kubernetes import KubernetesController | ||||
| from authentik.providers.proxy.models import ProxyMode, ProxyProvider | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from authentik.outposts.controllers.kubernetes import KubernetesController | ||||
|  | ||||
|  | ||||
| @dataclass(slots=True) | ||||
| class RouteBackendRef: | ||||
|     name: str | ||||
|     port: int | ||||
|  | ||||
|  | ||||
| @dataclass(slots=True) | ||||
| class RouteSpecParentRefs: | ||||
|     name: str | ||||
|     sectionName: str | None = None | ||||
|     port: int | None = None | ||||
|     namespace: str | None = None | ||||
|     kind: str = "Gateway" | ||||
|     group: str = "gateway.networking.k8s.io" | ||||
|  | ||||
|  | ||||
| @dataclass(slots=True) | ||||
| class HTTPRouteSpecRuleMatchPath: | ||||
|     type: str | ||||
|     value: str | ||||
|  | ||||
|  | ||||
| @dataclass(slots=True) | ||||
| class HTTPRouteSpecRuleMatchHeader: | ||||
|     name: str | ||||
|     value: str | ||||
|     type: str = "Exact" | ||||
|  | ||||
|  | ||||
| @dataclass(slots=True) | ||||
| class HTTPRouteSpecRuleMatch: | ||||
|     path: HTTPRouteSpecRuleMatchPath | ||||
|     headers: list[HTTPRouteSpecRuleMatchHeader] | ||||
|  | ||||
|  | ||||
| @dataclass(slots=True) | ||||
| class HTTPRouteSpecRule: | ||||
|     backendRefs: list[RouteBackendRef] | ||||
|     matches: list[HTTPRouteSpecRuleMatch] | ||||
|  | ||||
|  | ||||
| @dataclass(slots=True) | ||||
| class HTTPRouteSpec: | ||||
|     parentRefs: list[RouteSpecParentRefs] | ||||
|     hostnames: list[str] | ||||
|     rules: list[HTTPRouteSpecRule] | ||||
|  | ||||
|  | ||||
| @dataclass(slots=True) | ||||
| class HTTPRouteMetadata: | ||||
|     name: str | ||||
|     namespace: str | ||||
|     annotations: dict = field(default_factory=dict) | ||||
|     labels: dict = field(default_factory=dict) | ||||
|  | ||||
|  | ||||
| @dataclass(slots=True) | ||||
| class HTTPRoute: | ||||
|     apiVersion: str | ||||
|     kind: str | ||||
|     metadata: HTTPRouteMetadata | ||||
|     spec: HTTPRouteSpec | ||||
|  | ||||
|  | ||||
| class HTTPRouteReconciler(KubernetesObjectReconciler): | ||||
|     """Kubernetes Gateway API HTTPRoute Reconciler""" | ||||
|  | ||||
|     def __init__(self, controller: "KubernetesController") -> None: | ||||
|         super().__init__(controller) | ||||
|         self.api_ex = ApiextensionsV1Api(controller.client) | ||||
|         self.api = CustomObjectsApi(controller.client) | ||||
|         self.crd_group = "gateway.networking.k8s.io" | ||||
|         self.crd_version = "v1" | ||||
|         self.crd_plural = "httproutes" | ||||
|  | ||||
|     @staticmethod | ||||
|     def reconciler_name() -> str: | ||||
|         return "httproute" | ||||
|  | ||||
|     @property | ||||
|     def noop(self) -> bool: | ||||
|         if not self.crd_exists(): | ||||
|             self.logger.debug("CRD doesn't exist") | ||||
|             return True | ||||
|         if not self.controller.outpost.config.kubernetes_httproute_parent_refs: | ||||
|             self.logger.debug("HTTPRoute parentRefs not set.") | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     def crd_exists(self) -> bool: | ||||
|         """Check if the Gateway API resources exists""" | ||||
|         return bool( | ||||
|             len( | ||||
|                 self.api_ex.list_custom_resource_definition( | ||||
|                     field_selector=f"metadata.name={self.crd_plural}.{self.crd_group}" | ||||
|                 ).items | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     def reconcile(self, current: HTTPRoute, reference: HTTPRoute): | ||||
|         super().reconcile(current, reference) | ||||
|         if current.metadata.annotations != reference.metadata.annotations: | ||||
|             raise NeedsUpdate() | ||||
|         if current.spec.parentRefs != reference.spec.parentRefs: | ||||
|             raise NeedsUpdate() | ||||
|         if current.spec.hostnames != reference.spec.hostnames: | ||||
|             raise NeedsUpdate() | ||||
|         if current.spec.rules != reference.spec.rules: | ||||
|             raise NeedsUpdate() | ||||
|  | ||||
|     def get_object_meta(self, **kwargs) -> V1ObjectMeta: | ||||
|         return super().get_object_meta( | ||||
|             **kwargs, | ||||
|         ) | ||||
|  | ||||
|     def get_reference_object(self) -> HTTPRoute: | ||||
|         hostnames = [] | ||||
|         rules = [] | ||||
|  | ||||
|         for proxy_provider in ProxyProvider.objects.filter(outpost__in=[self.controller.outpost]): | ||||
|             proxy_provider: ProxyProvider | ||||
|             external_host_name = urlparse(proxy_provider.external_host) | ||||
|             if proxy_provider.mode in [ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN]: | ||||
|                 rule = HTTPRouteSpecRule( | ||||
|                     backendRefs=[RouteBackendRef(name=self.name, port=9000)], | ||||
|                     matches=[ | ||||
|                         HTTPRouteSpecRuleMatch( | ||||
|                             headers=[ | ||||
|                                 HTTPRouteSpecRuleMatchHeader( | ||||
|                                     name="Host", | ||||
|                                     value=external_host_name.hostname, | ||||
|                                 ) | ||||
|                             ], | ||||
|                             path=HTTPRouteSpecRuleMatchPath( | ||||
|                                 type="PathPrefix", value="/outpost.goauthentik.io" | ||||
|                             ), | ||||
|                         ) | ||||
|                     ], | ||||
|                 ) | ||||
|             else: | ||||
|                 rule = HTTPRouteSpecRule( | ||||
|                     backendRefs=[RouteBackendRef(name=self.name, port=9000)], | ||||
|                     matches=[ | ||||
|                         HTTPRouteSpecRuleMatch( | ||||
|                             headers=[ | ||||
|                                 HTTPRouteSpecRuleMatchHeader( | ||||
|                                     name="Host", | ||||
|                                     value=external_host_name.hostname, | ||||
|                                 ) | ||||
|                             ], | ||||
|                             path=HTTPRouteSpecRuleMatchPath(type="PathPrefix", value="/"), | ||||
|                         ) | ||||
|                     ], | ||||
|                 ) | ||||
|             hostnames.append(external_host_name.hostname) | ||||
|             rules.append(rule) | ||||
|  | ||||
|         return HTTPRoute( | ||||
|             apiVersion=f"{self.crd_group}/{self.crd_version}", | ||||
|             kind="HTTPRoute", | ||||
|             metadata=HTTPRouteMetadata( | ||||
|                 name=self.name, | ||||
|                 namespace=self.namespace, | ||||
|                 annotations=self.controller.outpost.config.kubernetes_httproute_annotations, | ||||
|                 labels=self.get_object_meta().labels, | ||||
|             ), | ||||
|             spec=HTTPRouteSpec( | ||||
|                 parentRefs=[ | ||||
|                     from_dict(RouteSpecParentRefs, spec) | ||||
|                     for spec in self.controller.outpost.config.kubernetes_httproute_parent_refs | ||||
|                 ], | ||||
|                 hostnames=hostnames, | ||||
|                 rules=rules, | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|     def create(self, reference: HTTPRoute): | ||||
|         return self.api.create_namespaced_custom_object( | ||||
|             group=self.crd_group, | ||||
|             version=self.crd_version, | ||||
|             plural=self.crd_plural, | ||||
|             namespace=self.namespace, | ||||
|             body=asdict(reference), | ||||
|             field_manager=FIELD_MANAGER, | ||||
|         ) | ||||
|  | ||||
|     def delete(self, reference: HTTPRoute): | ||||
|         return self.api.delete_namespaced_custom_object( | ||||
|             group=self.crd_group, | ||||
|             version=self.crd_version, | ||||
|             plural=self.crd_plural, | ||||
|             namespace=self.namespace, | ||||
|             name=self.name, | ||||
|         ) | ||||
|  | ||||
|     def retrieve(self) -> HTTPRoute: | ||||
|         return from_dict( | ||||
|             HTTPRoute, | ||||
|             self.api.get_namespaced_custom_object( | ||||
|                 group=self.crd_group, | ||||
|                 version=self.crd_version, | ||||
|                 plural=self.crd_plural, | ||||
|                 namespace=self.namespace, | ||||
|                 name=self.name, | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|     def update(self, current: HTTPRoute, reference: HTTPRoute): | ||||
|         return self.api.patch_namespaced_custom_object( | ||||
|             group=self.crd_group, | ||||
|             version=self.crd_version, | ||||
|             plural=self.crd_plural, | ||||
|             namespace=self.namespace, | ||||
|             name=self.name, | ||||
|             body=asdict(reference), | ||||
|             field_manager=FIELD_MANAGER, | ||||
|         ) | ||||
| @ -3,6 +3,7 @@ | ||||
| from authentik.outposts.controllers.base import DeploymentPort | ||||
| from authentik.outposts.controllers.kubernetes import KubernetesController | ||||
| from authentik.outposts.models import KubernetesServiceConnection, Outpost | ||||
| from authentik.providers.proxy.controllers.k8s.httproute import HTTPRouteReconciler | ||||
| from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler | ||||
| from authentik.providers.proxy.controllers.k8s.traefik import TraefikMiddlewareReconciler | ||||
|  | ||||
| @ -18,8 +19,10 @@ class ProxyKubernetesController(KubernetesController): | ||||
|             DeploymentPort(9443, "https", "tcp"), | ||||
|         ] | ||||
|         self.reconcilers[IngressReconciler.reconciler_name()] = IngressReconciler | ||||
|         self.reconcilers[HTTPRouteReconciler.reconciler_name()] = HTTPRouteReconciler | ||||
|         self.reconcilers[TraefikMiddlewareReconciler.reconciler_name()] = ( | ||||
|             TraefikMiddlewareReconciler | ||||
|         ) | ||||
|         self.reconcile_order.append(IngressReconciler.reconciler_name()) | ||||
|         self.reconcile_order.append(HTTPRouteReconciler.reconciler_name()) | ||||
|         self.reconcile_order.append(TraefikMiddlewareReconciler.reconciler_name()) | ||||
|  | ||||
| @ -18,11 +18,11 @@ from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner | ||||
| from authentik.flows.stage import RedirectStage | ||||
| from authentik.lib.utils.time import timedelta_from_string | ||||
| from authentik.policies.engine import PolicyEngine | ||||
| from authentik.policies.views import BufferedPolicyAccessView | ||||
| from authentik.policies.views import PolicyAccessView | ||||
| from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider | ||||
|  | ||||
|  | ||||
| class RACStartView(BufferedPolicyAccessView): | ||||
| class RACStartView(PolicyAccessView): | ||||
|     """Start a RAC connection by checking access and creating a connection token""" | ||||
|  | ||||
|     endpoint: Endpoint | ||||
|  | ||||
| @ -15,7 +15,7 @@ from authentik.flows.models import in_memory_stage | ||||
| from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner | ||||
| from authentik.flows.views.executor import SESSION_KEY_POST | ||||
| from authentik.lib.views import bad_request_message | ||||
| from authentik.policies.views import BufferedPolicyAccessView | ||||
| from authentik.policies.views import PolicyAccessView | ||||
| from authentik.providers.saml.exceptions import CannotHandleAssertion | ||||
| from authentik.providers.saml.models import SAMLBindings, SAMLProvider | ||||
| from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser | ||||
| @ -35,7 +35,7 @@ from authentik.stages.consent.stage import ( | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class SAMLSSOView(BufferedPolicyAccessView): | ||||
| class SAMLSSOView(PolicyAccessView): | ||||
|     """SAML SSO Base View, which plans a flow and injects our final stage. | ||||
|     Calls get/post handler.""" | ||||
|  | ||||
| @ -83,7 +83,7 @@ class SAMLSSOView(BufferedPolicyAccessView): | ||||
|  | ||||
|     def post(self, request: HttpRequest, application_slug: str) -> HttpResponse: | ||||
|         """GET and POST use the same handler, but we can't | ||||
|         override .dispatch easily because BufferedPolicyAccessView's dispatch""" | ||||
|         override .dispatch easily because PolicyAccessView's dispatch""" | ||||
|         return self.get(request, application_slug) | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -99,6 +99,7 @@ class RBACPermissionViewSet(ReadOnlyModelViewSet): | ||||
|     filterset_class = PermissionFilter | ||||
|     permission_classes = [IsAuthenticated] | ||||
|     search_fields = [ | ||||
|         "name", | ||||
|         "codename", | ||||
|         "content_type__model", | ||||
|         "content_type__app_label", | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -36,6 +36,7 @@ class IdentificationStageSerializer(StageSerializer): | ||||
|             "sources", | ||||
|             "show_source_labels", | ||||
|             "pretend_user_exists", | ||||
|             "enable_remember_me", | ||||
|         ] | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -0,0 +1,21 @@ | ||||
| # Generated by Django 5.1.8 on 2025-04-16 17:14 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_stages_identification", "0015_identificationstage_captcha_stage"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="identificationstage", | ||||
|             name="enable_remember_me", | ||||
|             field=models.BooleanField( | ||||
|                 default=False, | ||||
|                 help_text="Show the user the 'Remember me on this device' toggle, allowing repeat users to skip straight to entering their password.", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -76,7 +76,13 @@ class IdentificationStage(Stage): | ||||
|             "is entered." | ||||
|         ), | ||||
|     ) | ||||
|  | ||||
|     enable_remember_me = models.BooleanField( | ||||
|         default=False, | ||||
|         help_text=_( | ||||
|             "Show the user the 'Remember me on this device' toggle, allowing repeat " | ||||
|             "users to skip straight to entering their password." | ||||
|         ), | ||||
|     ) | ||||
|     enrollment_flow = models.ForeignKey( | ||||
|         Flow, | ||||
|         on_delete=models.SET_DEFAULT, | ||||
|  | ||||
| @ -85,6 +85,7 @@ class IdentificationChallenge(Challenge): | ||||
|     primary_action = CharField() | ||||
|     sources = LoginSourceSerializer(many=True, required=False) | ||||
|     show_source_labels = BooleanField() | ||||
|     enable_remember_me = BooleanField(required=False, default=True) | ||||
|  | ||||
|     component = CharField(default="ak-stage-identification") | ||||
|  | ||||
| @ -235,6 +236,7 @@ class IdentificationStageView(ChallengeStageView): | ||||
|                 and current_stage.password_stage.allow_show_password, | ||||
|                 "show_source_labels": current_stage.show_source_labels, | ||||
|                 "flow_designation": self.executor.flow.designation, | ||||
|                 "enable_remember_me": current_stage.enable_remember_me, | ||||
|             } | ||||
|         ) | ||||
|         # If the user has been redirected to us whilst trying to access an | ||||
|  | ||||
| @ -171,7 +171,8 @@ def username_field_validator_factory() -> Callable[[PromptChallengeResponse, str | ||||
|  | ||||
|  | ||||
| def password_single_validator_factory() -> Callable[[PromptChallengeResponse, str], Any]: | ||||
|     """Return a `clean_` method for `field`. Clean method checks if username is taken already.""" | ||||
|     """Return a `clean_` method for `field`. Clean method checks if the password meets configured | ||||
|     PasswordPolicy.""" | ||||
|  | ||||
|     def password_single_clean(self: PromptChallengeResponse, value: str) -> Any: | ||||
|         """Send password validation signals for e.g. LDAP Source""" | ||||
|  | ||||
| @ -4,7 +4,13 @@ from unittest.mock import patch | ||||
|  | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik.core.models import USER_ATTRIBUTE_SOURCES, Group, Source, User, UserSourceConnection | ||||
| from authentik.core.models import ( | ||||
|     USER_ATTRIBUTE_SOURCES, | ||||
|     Group, | ||||
|     Source, | ||||
|     User, | ||||
|     UserSourceConnection, | ||||
| ) | ||||
| from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION | ||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||
| from authentik.events.models import Event, EventAction | ||||
|  | ||||
| @ -3641,6 +3641,46 @@ | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "object", | ||||
|                         "required": [ | ||||
|                             "model", | ||||
|                             "identifiers" | ||||
|                         ], | ||||
|                         "properties": { | ||||
|                             "model": { | ||||
|                                 "const": "authentik_policies_unique_password.uniquepasswordpolicy" | ||||
|                             }, | ||||
|                             "id": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "state": { | ||||
|                                 "type": "string", | ||||
|                                 "enum": [ | ||||
|                                     "absent", | ||||
|                                     "present", | ||||
|                                     "created", | ||||
|                                     "must_created" | ||||
|                                 ], | ||||
|                                 "default": "present" | ||||
|                             }, | ||||
|                             "conditions": { | ||||
|                                 "type": "array", | ||||
|                                 "items": { | ||||
|                                     "type": "boolean" | ||||
|                                 } | ||||
|                             }, | ||||
|                             "permissions": { | ||||
|                                 "$ref": "#/$defs/model_authentik_policies_unique_password.uniquepasswordpolicy_permissions" | ||||
|                             }, | ||||
|                             "attrs": { | ||||
|                                 "$ref": "#/$defs/model_authentik_policies_unique_password.uniquepasswordpolicy" | ||||
|                             }, | ||||
|                             "identifiers": { | ||||
|                                 "$ref": "#/$defs/model_authentik_policies_unique_password.uniquepasswordpolicy" | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "object", | ||||
|                         "required": [ | ||||
| @ -4822,6 +4862,7 @@ | ||||
|                         "authentik.core", | ||||
|                         "authentik.enterprise", | ||||
|                         "authentik.enterprise.audit", | ||||
|                         "authentik.enterprise.policies.unique_password", | ||||
|                         "authentik.enterprise.providers.google_workspace", | ||||
|                         "authentik.enterprise.providers.microsoft_entra", | ||||
|                         "authentik.enterprise.providers.ssf", | ||||
| @ -4929,6 +4970,7 @@ | ||||
|                         "authentik_core.applicationentitlement", | ||||
|                         "authentik_core.token", | ||||
|                         "authentik_enterprise.license", | ||||
|                         "authentik_policies_unique_password.uniquepasswordpolicy", | ||||
|                         "authentik_providers_google_workspace.googleworkspaceprovider", | ||||
|                         "authentik_providers_google_workspace.googleworkspaceprovidermapping", | ||||
|                         "authentik_providers_microsoft_entra.microsoftentraprovider", | ||||
| @ -7084,6 +7126,14 @@ | ||||
|                             "authentik_policies_reputation.delete_reputationpolicy", | ||||
|                             "authentik_policies_reputation.view_reputation", | ||||
|                             "authentik_policies_reputation.view_reputationpolicy", | ||||
|                             "authentik_policies_unique_password.add_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.add_userpasswordhistory", | ||||
|                             "authentik_policies_unique_password.change_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.change_userpasswordhistory", | ||||
|                             "authentik_policies_unique_password.delete_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.delete_userpasswordhistory", | ||||
|                             "authentik_policies_unique_password.view_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.view_userpasswordhistory", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovider", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidergroup", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidermapping", | ||||
| @ -11893,6 +11943,11 @@ | ||||
|                     "type": "boolean", | ||||
|                     "title": "Pretend user exists", | ||||
|                     "description": "When enabled, the stage will succeed and continue even when incorrect user info is entered." | ||||
|                 }, | ||||
|                 "enable_remember_me": { | ||||
|                     "type": "boolean", | ||||
|                     "title": "Enable remember me", | ||||
|                     "description": "Show the user the 'Remember me on this device' toggle, allowing repeat users to skip straight to entering their password." | ||||
|                 } | ||||
|             }, | ||||
|             "required": [] | ||||
| @ -13779,6 +13834,14 @@ | ||||
|                             "authentik_policies_reputation.delete_reputationpolicy", | ||||
|                             "authentik_policies_reputation.view_reputation", | ||||
|                             "authentik_policies_reputation.view_reputationpolicy", | ||||
|                             "authentik_policies_unique_password.add_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.add_userpasswordhistory", | ||||
|                             "authentik_policies_unique_password.change_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.change_userpasswordhistory", | ||||
|                             "authentik_policies_unique_password.delete_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.delete_userpasswordhistory", | ||||
|                             "authentik_policies_unique_password.view_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.view_userpasswordhistory", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovider", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidergroup", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidermapping", | ||||
| @ -14463,6 +14526,61 @@ | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "model_authentik_policies_unique_password.uniquepasswordpolicy": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "name": { | ||||
|                     "type": "string", | ||||
|                     "minLength": 1, | ||||
|                     "title": "Name" | ||||
|                 }, | ||||
|                 "execution_logging": { | ||||
|                     "type": "boolean", | ||||
|                     "title": "Execution logging", | ||||
|                     "description": "When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged." | ||||
|                 }, | ||||
|                 "password_field": { | ||||
|                     "type": "string", | ||||
|                     "minLength": 1, | ||||
|                     "title": "Password field", | ||||
|                     "description": "Field key to check, field keys defined in Prompt stages are available." | ||||
|                 }, | ||||
|                 "num_historical_passwords": { | ||||
|                     "type": "integer", | ||||
|                     "minimum": 0, | ||||
|                     "maximum": 2147483647, | ||||
|                     "title": "Num historical passwords", | ||||
|                     "description": "Number of passwords to check against." | ||||
|                 } | ||||
|             }, | ||||
|             "required": [] | ||||
|         }, | ||||
|         "model_authentik_policies_unique_password.uniquepasswordpolicy_permissions": { | ||||
|             "type": "array", | ||||
|             "items": { | ||||
|                 "type": "object", | ||||
|                 "required": [ | ||||
|                     "permission" | ||||
|                 ], | ||||
|                 "properties": { | ||||
|                     "permission": { | ||||
|                         "type": "string", | ||||
|                         "enum": [ | ||||
|                             "add_uniquepasswordpolicy", | ||||
|                             "change_uniquepasswordpolicy", | ||||
|                             "delete_uniquepasswordpolicy", | ||||
|                             "view_uniquepasswordpolicy" | ||||
|                         ] | ||||
|                     }, | ||||
|                     "user": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "role": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "model_authentik_providers_google_workspace.googleworkspaceprovider": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|  | ||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ require ( | ||||
| 	github.com/coreos/go-oidc/v3 v3.14.1 | ||||
| 	github.com/getsentry/sentry-go v0.32.0 | ||||
| 	github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 | ||||
| 	github.com/go-ldap/ldap/v3 v3.4.10 | ||||
| 	github.com/go-ldap/ldap/v3 v3.4.11 | ||||
| 	github.com/go-openapi/runtime v0.28.0 | ||||
| 	github.com/golang-jwt/jwt/v5 v5.2.2 | ||||
| 	github.com/google/uuid v1.6.0 | ||||
| @ -27,7 +27,7 @@ require ( | ||||
| 	github.com/spf13/cobra v1.9.1 | ||||
| 	github.com/stretchr/testify v1.10.0 | ||||
| 	github.com/wwt/guac v1.3.2 | ||||
| 	goauthentik.io/api/v3 v3.2025024.6 | ||||
| 	goauthentik.io/api/v3 v3.2025024.9 | ||||
| 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||
| 	golang.org/x/oauth2 v0.29.0 | ||||
| 	golang.org/x/sync v0.13.0 | ||||
| @ -43,7 +43,7 @@ require ( | ||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||
| 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | ||||
| 	github.com/felixge/httpsnoop v1.0.3 // indirect | ||||
| 	github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect | ||||
| 	github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect | ||||
| 	github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect | ||||
| 	github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect | ||||
| 	github.com/go-jose/go-jose/v4 v4.0.5 // indirect | ||||
|  | ||||
							
								
								
									
										82
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								go.sum
									
									
									
									
									
								
							| @ -71,8 +71,8 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd | ||||
| github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||||
| github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY= | ||||
| github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= | ||||
| github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= | ||||
| github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= | ||||
| github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= | ||||
| github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= | ||||
| github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= | ||||
| github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= | ||||
| github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | ||||
| @ -86,8 +86,8 @@ github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9 | ||||
| github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= | ||||
| github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= | ||||
| github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= | ||||
| github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= | ||||
| github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= | ||||
| github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= | ||||
| github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= | ||||
| github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | ||||
| github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= | ||||
| github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | ||||
| @ -148,7 +148,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | ||||
| github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||
| github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= | ||||
| github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= | ||||
| github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= | ||||
| @ -172,16 +171,13 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE | ||||
| github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= | ||||
| github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= | ||||
| github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= | ||||
| github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= | ||||
| github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= | ||||
| github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= | ||||
| github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= | ||||
| github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= | ||||
| github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= | ||||
| github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||
| github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= | ||||
| github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||
| github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||||
| github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= | ||||
| github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||
| @ -266,15 +262,10 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= | ||||
| github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||
| github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/wwt/guac v1.3.2 h1:sH6OFGa/1tBs7ieWBVlZe7t6F5JAOWBry/tqQL/Vup4= | ||||
| @ -282,7 +273,6 @@ github.com/wwt/guac v1.3.2/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0 | ||||
| github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||
| go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= | ||||
| go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= | ||||
| go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||
| @ -300,20 +290,14 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y | ||||
| go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= | ||||
| go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||
| goauthentik.io/api/v3 v3.2025024.6 h1:3mmZY7E0EM/RR8uMF17mxa7368ZgZEIq/FjlCLJ9+lA= | ||||
| goauthentik.io/api/v3 v3.2025024.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| goauthentik.io/api/v3 v3.2025024.9 h1:i3tbkyotE32ZpJ729BsPWTuLQUdtZ54Li4aP1amZzsM= | ||||
| goauthentik.io/api/v3 v3.2025024.9/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= | ||||
| golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= | ||||
| golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= | ||||
| golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= | ||||
| golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= | ||||
| golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= | ||||
| golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| @ -348,11 +332,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB | ||||
| golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||
| golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||
| golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||
| golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| @ -379,17 +358,8 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ | ||||
| golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||||
| golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||||
| golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= | ||||
| golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= | ||||
| golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= | ||||
| golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= | ||||
| golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= | ||||
| golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= | ||||
| golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= | ||||
| golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= | ||||
| golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= | ||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| @ -406,12 +376,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ | ||||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= | ||||
| golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||
| golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||
| golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||
| golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= | ||||
| golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| @ -440,40 +404,14 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w | ||||
| golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= | ||||
| golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||
| golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | ||||
| golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= | ||||
| golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= | ||||
| golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= | ||||
| golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= | ||||
| golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= | ||||
| golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||||
| golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | ||||
| golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | ||||
| golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= | ||||
| golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= | ||||
| golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= | ||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| @ -519,10 +457,6 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY | ||||
| golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||
| golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||
| golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||
| golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||||
| golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= | ||||
| golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= | ||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
|  | ||||
| @ -62,7 +62,8 @@ function prepare_debug { | ||||
|     export DEBIAN_FRONTEND=noninteractive | ||||
|     apt-get update | ||||
|     apt-get install -y --no-install-recommends krb5-kdc krb5-user krb5-admin-server libkrb5-dev gcc | ||||
|     VIRTUAL_ENV=/ak-root/.venv uv sync --frozen | ||||
|     source "${VENV_PATH}/bin/activate" | ||||
|     uv sync --active --frozen | ||||
|     touch /unittest.xml | ||||
|     chown authentik:authentik /unittest.xml | ||||
| } | ||||
|  | ||||
							
								
								
									
										8
									
								
								lifecycle/aws/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								lifecycle/aws/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -9,7 +9,7 @@ | ||||
|             "version": "0.0.0", | ||||
|             "license": "MIT", | ||||
|             "devDependencies": { | ||||
|                 "aws-cdk": "^2.1007.0", | ||||
|                 "aws-cdk": "^2.1012.0", | ||||
|                 "cross-env": "^7.0.3" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -17,9 +17,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/aws-cdk": { | ||||
|             "version": "2.1007.0", | ||||
|             "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1007.0.tgz", | ||||
|             "integrity": "sha512-/UOYOTGWUm+pP9qxg03tID5tL6euC+pb+xo0RBue+xhnUWwj/Bbsw6DbqbpOPMrNzTUxmM723/uMEQmM6S26dw==", | ||||
|             "version": "2.1012.0", | ||||
|             "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1012.0.tgz", | ||||
|             "integrity": "sha512-C6jSWkqP0hkY2Cs300VJHjspmTXDTMfB813kwZvRbd/OsKBfTBJBbYU16VoLAp1LVEOnQMf8otSlaSgzVF0X9A==", | ||||
|             "dev": true, | ||||
|             "license": "Apache-2.0", | ||||
|             "bin": { | ||||
|  | ||||
| @ -10,7 +10,7 @@ | ||||
|         "node": ">=20" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "aws-cdk": "^2.1007.0", | ||||
|         "aws-cdk": "^2.1012.0", | ||||
|         "cross-env": "^7.0.3" | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -3,7 +3,7 @@ from lifecycle.migrate import BaseMigration | ||||
|  | ||||
| SQL_STATEMENT = """ | ||||
| BEGIN TRANSACTION; | ||||
| ALTER TABLE authentik_tenants_tenant RENAME TO authentik_brands_brand; | ||||
| ALTER TABLE IF EXISTS authentik_tenants_tenant RENAME TO authentik_brands_brand; | ||||
| UPDATE django_migrations SET app = replace(app, 'authentik_tenants', 'authentik_brands'); | ||||
| UPDATE django_content_type SET app_label = replace(app_label, 'authentik_tenants', 'authentik_brands'); | ||||
| COMMIT; | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -8,7 +8,6 @@ | ||||
| # Jens L. <jens@goauthentik.io>, 2022 | ||||
| # Lars Lehmann <lars@lars-lehmann.net>, 2023 | ||||
| # Johannes —/—, 2023 | ||||
| # Dominic Wagner <mail@dominic-wagner.de>, 2023 | ||||
| # fde4f289d99ed356ff5cfdb762dc44aa_a8a971d, 2023 | ||||
| # Christian Foellmann <foellmann@foe-services.de>, 2023 | ||||
| # kidhab, 2023 | ||||
| @ -30,17 +29,18 @@ | ||||
| # Alexander Möbius, 2025 | ||||
| # Jonas, 2025 | ||||
| # Niklas Kroese, 2025 | ||||
| # 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025 | ||||
| # datenschmutz, 2025 | ||||
| # 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025 | ||||
| # Dominic Wagner <mail@dominic-wagner.de>, 2025 | ||||
| #  | ||||
| #, fuzzy | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-11 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: datenschmutz, 2025\n" | ||||
| "Last-Translator: Dominic Wagner <mail@dominic-wagner.de>, 2025\n" | ||||
| "Language-Team: German (https://app.transifex.com/authentik/teams/119923/de/)\n" | ||||
| "MIME-Version: 1.0\n" | ||||
| "Content-Type: text/plain; charset=UTF-8\n" | ||||
| @ -214,6 +214,7 @@ msgid "User's display name." | ||||
| msgstr "Anzeigename" | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "Benutzer" | ||||
|  | ||||
| @ -402,6 +403,18 @@ msgstr "Eigenschaft" | ||||
| msgid "Property Mappings" | ||||
| msgstr "Eigenschaften" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "Sitzung" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "Sitzungen" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "Authentifizierte Sitzung" | ||||
| @ -511,6 +524,38 @@ msgstr "Lizenzverwendung" | ||||
| msgid "License Usage Records" | ||||
| msgstr "Lizenzverwendung Aufzeichnungen" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Zu prüfender Feldschlüssel, die in den Aufforderungsstufen definierten " | ||||
| "Feldschlüssel sind verfügbar." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Passwort nicht im Kontext festgelegt" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "Enterprise ist erforderlich, um auf diese Funktion zuzugreifen." | ||||
| @ -1303,12 +1348,6 @@ msgstr "Richtlinien Cache Metriken anzeigen" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "Richtlinien Cache Metriken löschen" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Zu prüfender Feldschlüssel, die in den Aufforderungsstufen definierten " | ||||
| "Feldschlüssel sind verfügbar." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "Wie häufig der Passwort-Hash auf haveibeenpwned vertreten sein darf" | ||||
| @ -1320,10 +1359,6 @@ msgstr "" | ||||
| "Die Richtlinie wird verweigert, wenn die zxcvbn-Bewertung gleich oder " | ||||
| "kleiner diesem Wert ist." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Passwort nicht im Kontext festgelegt" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "Ungültiges Passwort." | ||||
| @ -1365,20 +1400,6 @@ msgstr "Reputationswert" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "Reputationswert" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "Erlaubnis verweigert" | ||||
| @ -2208,6 +2229,10 @@ msgstr "Rolle" | ||||
| msgid "Roles" | ||||
| msgstr "Rollen" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "Systemberechtigung" | ||||
| @ -2478,6 +2503,22 @@ msgstr "LDAP Quelle Eigenschafts-Zuordnung" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "LDAP Quelle Eigenschafts-Zuordnungen" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "" | ||||
| @ -2487,6 +2528,14 @@ msgstr "" | ||||
| msgid "No token received." | ||||
| msgstr "Kein Token empfangen." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "Token-URL anfordern" | ||||
| @ -2528,6 +2577,12 @@ msgstr "" | ||||
| msgid "Additional Scopes" | ||||
| msgstr "zusätzliche Scopes" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "Outh Quelle" | ||||
| @ -3434,6 +3489,12 @@ msgstr "" | ||||
| "Wenn aktiviert, wird die Phase auch dann erfolgreich abgeschlossen und " | ||||
| "fortgesetzt, wenn falsche Benutzerdaten eingegeben wurden." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "Optionaler Registrierungs-Flow, der unten auf der Seite verlinkt ist." | ||||
| @ -3826,6 +3887,14 @@ msgstr "" | ||||
| "Die Ereignisse werden nach dieser Dauer gelöscht (Format: " | ||||
| "Wochen=3;Tage=2;Stunden=3,Sekunden=2)." | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "" | ||||
|  | ||||
| @ -8,7 +8,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-15 00:11+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @ -451,6 +451,36 @@ msgstr "" | ||||
| msgid "License Usage Records" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "" | ||||
| @ -1175,10 +1205,6 @@ msgstr "" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "" | ||||
| @ -1188,10 +1214,6 @@ msgid "" | ||||
| "If the zxcvbn score is equal or less than this value, the policy will fail." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "" | ||||
| @ -1233,20 +1255,6 @@ msgstr "" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "" | ||||
| @ -2254,6 +2262,14 @@ msgstr "" | ||||
| msgid "No token received." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "" | ||||
| @ -2291,6 +2307,11 @@ msgstr "" | ||||
| msgid "Additional Scopes" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "" | ||||
| @ -3131,6 +3152,12 @@ msgid "" | ||||
| "info is entered." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users " | ||||
| "to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -15,7 +15,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-11 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n" | ||||
| "Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n" | ||||
| @ -190,6 +190,7 @@ msgid "User's display name." | ||||
| msgstr "Nombre para mostrar del usuario." | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "Usuario" | ||||
|  | ||||
| @ -378,6 +379,18 @@ msgstr "Asignación de Propiedades" | ||||
| msgid "Property Mappings" | ||||
| msgstr "Asignaciones de Propiedades" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "Sesión" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "Sesiones" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "Sesión autenticada" | ||||
| @ -485,6 +498,38 @@ msgstr "Uso de Licencias" | ||||
| msgid "License Usage Records" | ||||
| msgstr "Registro de Uso de Licencias" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Clave de campo a verificar, las claves de campo definidas en las etapas de " | ||||
| "Solicitud están disponibles." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "La contraseña no se ha establecido en contexto" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "Se requiere de Enterprise para acceder esta característica." | ||||
| @ -1268,12 +1313,6 @@ msgstr "Ver las métricas de caché de la Política" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "Borrar las métricas de caché de la Política" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Clave de campo a verificar, las claves de campo definidas en las etapas de " | ||||
| "Solicitud están disponibles." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "" | ||||
| @ -1287,10 +1326,6 @@ msgstr "" | ||||
| "Si la puntuación zxcvbn es igual o menor que este valor, la política " | ||||
| "fallará." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "La contraseña no se ha establecido en contexto" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "Contraseña inválida." | ||||
| @ -1332,20 +1367,6 @@ msgstr "Puntuación de Reputacion" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "Puntuaciones de Reputacion" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "Permiso denegado" | ||||
| @ -2175,6 +2196,10 @@ msgstr "Rol" | ||||
| msgid "Roles" | ||||
| msgstr "Roles" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "Permiso de sistema" | ||||
| @ -2443,6 +2468,22 @@ msgstr "Asignación de Propiedades de Fuente de LDAP" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "Asignaciones de Propiedades de Fuente de LDAP" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "La contraseña no coincide con la complejidad de Active Directory." | ||||
| @ -2451,6 +2492,14 @@ msgstr "La contraseña no coincide con la complejidad de Active Directory." | ||||
| msgid "No token received." | ||||
| msgstr "No se recibió ningún token." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "Solicitar URL de token" | ||||
| @ -2491,6 +2540,12 @@ msgstr "URL utilizada por authentik para obtener información del usuario." | ||||
| msgid "Additional Scopes" | ||||
| msgstr "Alcances Adicionales" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "Fuente de OAuth" | ||||
| @ -3407,6 +3462,12 @@ msgstr "" | ||||
| "Cuando está habilitado, la etapa tendrá éxito y continuará incluso cuando se" | ||||
| " ingrese información de usuario incorrecta." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "" | ||||
| @ -3794,6 +3855,14 @@ msgstr "" | ||||
| "Los Eventos serán eliminados después de este periodo. (Formato: " | ||||
| "weeks=3;days=2;hours=3,seconds=2)." | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -15,7 +15,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-11 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Ville Ranki, 2025\n" | ||||
| "Language-Team: Finnish (https://app.transifex.com/authentik/teams/119923/fi/)\n" | ||||
| @ -186,6 +186,7 @@ msgid "User's display name." | ||||
| msgstr "Käyttäjän näytettävä nimi" | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "Käyttäjä" | ||||
|  | ||||
| @ -371,6 +372,18 @@ msgstr "Ominaisuuskytkentä" | ||||
| msgid "Property Mappings" | ||||
| msgstr "Ominaisuuskytkennät" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "Istunto" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "Autentikoitu istunto" | ||||
| @ -478,6 +491,38 @@ msgstr "Lisenssin käyttö" | ||||
| msgid "License Usage Records" | ||||
| msgstr "Lisenssin käyttötiedot" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Kentän avain, joka tarkistetaan. Kysymysvaiheissa määritellyt kenttien " | ||||
| "avaimet ovat käytettävissä." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Salasanaa ei ole asetettu kontekstissa" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "Tämän ominaisuuden käyttöön tarvitaan Enterprise-versiota." | ||||
| @ -1251,12 +1296,6 @@ msgstr "Näytä käytäntövälimuistitilastot" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "Tyhjennä käytäntövälimuistitilastot" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Kentän avain, joka tarkistetaan. Kysymysvaiheissa määritellyt kenttien " | ||||
| "avaimet ovat käytettävissä." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "" | ||||
| @ -1269,10 +1308,6 @@ msgstr "" | ||||
| "Jos zxcvbn-pistemäärä on tämä arvo tai pienempi, käytännön suorittaminen " | ||||
| "epäonnistuu." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Salasanaa ei ole asetettu kontekstissa" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "Virheellinen salasana." | ||||
| @ -1314,20 +1349,6 @@ msgstr "Mainepistemäärä" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "Mainepistemäärät" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "Käyttö evätty" | ||||
| @ -2155,6 +2176,10 @@ msgstr "Rooli" | ||||
| msgid "Roles" | ||||
| msgstr "Roolit" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "Järjestelmän käyttöoikeus" | ||||
| @ -2420,6 +2445,22 @@ msgstr "LDAP-lähteen ominaisuuskytkentä" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "LDAP-lähteen ominaisuuskytkennät" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "Salasana ei vastaa Active Directoryn monimutkaisuusmääritystä." | ||||
| @ -2428,6 +2469,14 @@ msgstr "Salasana ei vastaa Active Directoryn monimutkaisuusmääritystä." | ||||
| msgid "No token received." | ||||
| msgstr "Tunnistetta ei saatu." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "Pyyntötunnisteen URL" | ||||
| @ -2468,6 +2517,12 @@ msgstr "URL, jota authentik käyttää käyttäjätiedon hakemiseksi." | ||||
| msgid "Additional Scopes" | ||||
| msgstr "Lisäkäyttöalueet" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "OAuth-lähde" | ||||
| @ -3377,6 +3432,12 @@ msgstr "" | ||||
| "Kun tämä on käytössä, vaihe onnistuu ja suoritus jatkuu, vaikka olisi " | ||||
| "syötetty virheelliset käyttäjätiedot." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "" | ||||
| @ -3754,6 +3815,14 @@ msgstr "" | ||||
| "Tapahtumat poistetaan tämän ajan jälkeen. (Muoto: " | ||||
| "weeks=3;days=2;hours=3;seconds=2)." | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -9,8 +9,8 @@ | ||||
| # Kyllian Delaye-Maillot, 2023 | ||||
| # Manuel Viens, 2023 | ||||
| # Mordecai, 2023 | ||||
| # Tina, 2024 | ||||
| # Charles Leclerc, 2025 | ||||
| # Tina, 2025 | ||||
| # nerdinator <florian.dupret@gmail.com>, 2025 | ||||
| # Marc Schmitt, 2025 | ||||
| #  | ||||
| @ -19,7 +19,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-15 00:11+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Marc Schmitt, 2025\n" | ||||
| "Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n" | ||||
| @ -502,6 +502,38 @@ msgstr "Utilisation de la licence" | ||||
| msgid "License Usage Records" | ||||
| msgstr "Registre d'utilisation de la licence" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Clé de champ à vérifier ; les clés de champ définies dans les étapes de " | ||||
| "d'invite sont disponibles." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "Nombre de mots de passe à vérifier." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Mot de passe non défini dans le contexte" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "Ce mot de passe a déjà été utilisé. Veuillez en choisir un autre." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "Politique d'unicité des mots de passe" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "Politiques d'unicité des mots de passe" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "Historique des mots de passe utilisateur" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "Entreprise est requis pour accéder à cette fonctionnalité." | ||||
| @ -1296,12 +1328,6 @@ msgstr "Voir les métriques de cache de la politique" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "Nettoyer les métriques de cache de la politique" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Clé de champ à vérifier ; les clés de champ définies dans les étapes de " | ||||
| "d'invite sont disponibles." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "" | ||||
| @ -1315,10 +1341,6 @@ msgstr "" | ||||
| "Si le score zxcvbn est égal ou inférieur à cette valeur, la politique " | ||||
| "échouera." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Mot de passe non défini dans le contexte" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "Mot de passe invalide." | ||||
| @ -1360,22 +1382,6 @@ msgstr "Score de Réputation" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "Scores de Réputation" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "En attente de l'authentification..." | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
| "Vous êtes déjà en cours d'authentification dans un autre onglet. Cette page " | ||||
| "se rafraîchira lorsque l'authentification sera terminée." | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "S'authentifier dans cet onglet" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "Permission refusée" | ||||
| @ -2508,6 +2514,14 @@ msgstr "Le mot de passe ne correspond pas à la complexité d'Active Directory." | ||||
| msgid "No token received." | ||||
| msgstr "Pas de jeton reçu." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "Authentification HTTP Basic" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "Inclure le client ID et secret comme paramètres de la requête" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "URL du jeton de requête" | ||||
| @ -2549,6 +2563,14 @@ msgstr "" | ||||
| msgid "Additional Scopes" | ||||
| msgstr "Portées additionnelles" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
| "Comment effectuer l'authentification lors d'une demande de jeton pour le " | ||||
| "flux authorization_code" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "Source OAuth" | ||||
| @ -3469,6 +3491,15 @@ msgstr "" | ||||
| "Lorsqu'activé, l'étape réussira et continuera même lorsque les informations " | ||||
| "utilisateurs entrées sont invalides." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
| "Afficher à l'utilisateur l'option \"Se souvenir de moi sur cet appareil\", " | ||||
| "afin de permettre aux utilisateurs réguliers de passer directement à la " | ||||
| "saisie de leur mot de passe." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "Flux d'inscription facultatif, qui sera accessible en bas de page." | ||||
|  | ||||
| @ -12,17 +12,17 @@ | ||||
| # tmassimi, 2024 | ||||
| # Marc Schmitt, 2024 | ||||
| # albanobattistella <albanobattistella@gmail.com>, 2024 | ||||
| # Matteo Piccina <altermatte@gmail.com>, 2025 | ||||
| # Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025 | ||||
| # Matteo Piccina <altermatte@gmail.com>, 2025 | ||||
| #  | ||||
| #, fuzzy | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-11 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n" | ||||
| "Last-Translator: Matteo Piccina <altermatte@gmail.com>, 2025\n" | ||||
| "Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n" | ||||
| "MIME-Version: 1.0\n" | ||||
| "Content-Type: text/plain; charset=UTF-8\n" | ||||
| @ -194,6 +194,7 @@ msgid "User's display name." | ||||
| msgstr "Nome visualizzato dell'utente." | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "Utente" | ||||
|  | ||||
| @ -380,6 +381,18 @@ msgstr "Mappatura della proprietà" | ||||
| msgid "Property Mappings" | ||||
| msgstr "Mappatura delle proprietà" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "Sessione" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "Sessioni" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "Sessione Autenticata" | ||||
| @ -487,6 +500,38 @@ msgstr "Utilizzo della licenza" | ||||
| msgid "License Usage Records" | ||||
| msgstr "Registri sull'utilizzo della licenza" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Chiave di campo da verificare, sono disponibili le chiavi di campo definite " | ||||
| "nelle fasi Richiesta." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Password non impostata nel contesto" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "Versione Enterprise richiesta per accedere a questa funzione" | ||||
| @ -1274,12 +1319,6 @@ msgstr "Visualizza le metriche della cache della Policy" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "Cancellare le metriche della cache della Policy" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Chiave di campo da verificare, sono disponibili le chiavi di campo definite " | ||||
| "nelle fasi Richiesta." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "" | ||||
| @ -1292,10 +1331,6 @@ msgstr "" | ||||
| "Se il punteggio zxcvbn è inferiore o uguale a questo valore, il criterio non" | ||||
| " verrà soddisfatto." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Password non impostata nel contesto" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "Password invalida." | ||||
| @ -1337,22 +1372,6 @@ msgstr "Punteggio di reputazione" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "Punteggi di reputazione" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "In attesa di autenticazione..." | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
| "Ti stai già autenticando in un'altra scheda. Questa pagina si aggiornerà una" | ||||
| " volta completata l'autenticazione." | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "Autenticati in questa scheda" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "Permesso negato" | ||||
| @ -2182,6 +2201,10 @@ msgstr "Ruolo" | ||||
| msgid "Roles" | ||||
| msgstr "Ruoli" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "Autorizzazione di sistema" | ||||
| @ -2452,6 +2475,22 @@ msgstr "Mappatura delle proprietà sorgente LDAP" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "Mappature delle proprietà della sorgente LDAP" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "La password non soddisfa la complessità Active Directory." | ||||
| @ -2460,6 +2499,14 @@ msgstr "La password non soddisfa la complessità Active Directory." | ||||
| msgid "No token received." | ||||
| msgstr "Nessun token ricevuto." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "URL di Richiesta Token" | ||||
| @ -2500,6 +2547,12 @@ msgstr "URL utilizzato da authentik per ottenere le informazioni dell'utente." | ||||
| msgid "Additional Scopes" | ||||
| msgstr "Ambiti aggiuntivi" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "Sorgente OAuth" | ||||
| @ -3426,6 +3479,12 @@ msgstr "" | ||||
| "Quando abilitato, la fase avrà successo e continuerà anche quando vengono " | ||||
| "inserite informazioni utente errate." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "Flusso di iscrizione opzionale, che è collegato in fondo alla pagina." | ||||
| @ -3812,6 +3871,14 @@ msgstr "" | ||||
| "Gli eventi saranno cancellati dopo questa durata. (Formato: " | ||||
| "weeks=3;days=2;hours=3,seconds=2)." | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "" | ||||
|  | ||||
| @ -12,7 +12,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-03-31 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: NavyStack, 2023\n" | ||||
| "Language-Team: Korean (https://app.transifex.com/authentik/teams/119923/ko/)\n" | ||||
| @ -176,6 +176,7 @@ msgid "User's display name." | ||||
| msgstr "사용자의 표시 이름" | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "사용자" | ||||
|  | ||||
| @ -344,6 +345,18 @@ msgstr "속성 매핑" | ||||
| msgid "Property Mappings" | ||||
| msgstr "속성 매핑" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "인증된 세션" | ||||
| @ -447,6 +460,36 @@ msgstr "라이선스 사용" | ||||
| msgid "License Usage Records" | ||||
| msgstr "라이선스 사용 기록" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "확인하려는 필드 키, 프롬프트 스테이지에서 정의된 필드 키를 사용할 수 있습니다." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "비밀번호가 컨텍스트에 설정되지 않음" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "" | ||||
| @ -1182,10 +1225,6 @@ msgstr "정책의 캐시 메트릭 보기" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "정책의 캐시 메트릭 삭제" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "확인하려는 필드 키, 프롬프트 스테이지에서 정의된 필드 키를 사용할 수 있습니다." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "비밀번호 해시가 허용되는 해시 횟수" | ||||
| @ -1195,10 +1234,6 @@ msgid "" | ||||
| "If the zxcvbn score is equal or less than this value, the policy will fail." | ||||
| msgstr "만약 zxcvbn 점수가 이 값과 같거나 이 값보다 작다면, 정책이 실패합니다." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "비밀번호가 컨텍스트에 설정되지 않음" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "" | ||||
| @ -1240,20 +1275,6 @@ msgstr "평판 점수" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "평판 점수" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "권한 거부됨" | ||||
| @ -2013,6 +2034,10 @@ msgstr "역할" | ||||
| msgid "Roles" | ||||
| msgstr "역할" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "시스템 권한" | ||||
| @ -2231,6 +2256,13 @@ msgid "" | ||||
| "enabled on a single LDAP source." | ||||
| msgstr "사용자가 비밀번호를 변경하면 LDAP로 다시 동기화합니다. 이 기능은 단일의 LDAP 소스에서만 활성화할 수 있습니다." | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "" | ||||
| "Lookup group membership based on a user attribute instead of a group " | ||||
| "attribute. This allows nested group resolution on systems like FreeIPA and " | ||||
| "Active Directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "LDAP Source" | ||||
| msgstr "LDAP 소스" | ||||
| @ -2247,6 +2279,22 @@ msgstr "" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "비밀번호가 Active Directory 복잡도와 일치하지 않습니다." | ||||
| @ -2255,6 +2303,14 @@ msgstr "비밀번호가 Active Directory 복잡도와 일치하지 않습니다. | ||||
| msgid "No token received." | ||||
| msgstr "수신된 토큰이 없습니다." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "토큰 요청 URL" | ||||
| @ -2293,6 +2349,12 @@ msgstr "사용자 정보를 가져오기 위해 authentik에서 사용하는 URL | ||||
| msgid "Additional Scopes" | ||||
| msgstr "추가 스코프" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "OAuth 소스" | ||||
| @ -3149,6 +3211,12 @@ msgid "" | ||||
| "info is entered." | ||||
| msgstr "활성화되면 잘못된 사용자 정보가 입력되더라도 단계가 성공하고 계속됩니다." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "페이지 하단에 링크된,  선택적 등록 플로우를 참조하세요." | ||||
| @ -3500,6 +3568,14 @@ msgid "" | ||||
| "weeks=3;days=2;hours=3,seconds=2)." | ||||
| msgstr "이 기간이 지나면 이벤트가 삭제됩니다. (서식: hours=-1;minutes=-2;seconds=-3)" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "" | ||||
|  | ||||
| @ -7,18 +7,18 @@ | ||||
| # Bartosz Karpiński, 2023 | ||||
| # Michał Jastrzębski, 2024 | ||||
| # Tomci 12 <drizztes@gmail.com>, 2024 | ||||
| # Darek “NeroPcStation” NeroPcStation <dareknowacki2001@gmail.com>, 2024 | ||||
| # Marc Schmitt, 2025 | ||||
| # Jens L. <jens@goauthentik.io>, 2025 | ||||
| # Darek “NeroPcStation” NeroPcStation <dareknowacki2001@gmail.com>, 2025 | ||||
| #  | ||||
| #, fuzzy | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-11 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Darek “NeroPcStation” NeroPcStation <dareknowacki2001@gmail.com>, 2025\n" | ||||
| "Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n" | ||||
| "Language-Team: Polish (https://app.transifex.com/authentik/teams/119923/pl/)\n" | ||||
| "MIME-Version: 1.0\n" | ||||
| "Content-Type: text/plain; charset=UTF-8\n" | ||||
| @ -189,6 +189,7 @@ msgid "User's display name." | ||||
| msgstr "Wyświetlana nazwa użytkownika." | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "Użytkownik" | ||||
|  | ||||
| @ -371,6 +372,18 @@ msgstr "Mapowanie właściwości" | ||||
| msgid "Property Mappings" | ||||
| msgstr "Mapowanie właściwości" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "Sesja" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "Sesje" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "Sesja uwierzytelniona" | ||||
| @ -479,6 +492,38 @@ msgstr "Wykorzystanie licencji" | ||||
| msgid "License Usage Records" | ||||
| msgstr "Rejestr wykorzystania licencji" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Klucz pola do sprawdzenia, dostępne są klucze pola zdefiniowane w etapach " | ||||
| "monitu." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Hasło nie jest ustawione w kontekście" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "Wymagane jest konto Enterprise, aby uzyskać dostęp do tej funkcji." | ||||
| @ -1257,12 +1302,6 @@ msgstr "Wyświetl metryki pamięci podręcznej Zasady" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "Wyczyść metryki pamięci podręcznej Zasady" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Klucz pola do sprawdzenia, dostępne są klucze pola zdefiniowane w etapach " | ||||
| "monitu." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "Ile razy skrót hasła może być na haveibeenpwned" | ||||
| @ -1274,10 +1313,6 @@ msgstr "" | ||||
| "Jeśli wynik zxcvbn jest równy lub mniejszy od tej wartości, zasada zakończy " | ||||
| "się niepowodzeniem." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Hasło nie jest ustawione w kontekście" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "" | ||||
| @ -1319,20 +1354,6 @@ msgstr "Punkty reputacji" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "Punkty reputacji" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "Oczekiwanie na uwierzytelnienie..." | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "Odmowa uprawnień" | ||||
| @ -2141,6 +2162,10 @@ msgstr "Rola" | ||||
| msgid "Roles" | ||||
| msgstr "Role" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "Uprawnienie systemowe" | ||||
| @ -2390,6 +2415,22 @@ msgstr "" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "Hasło nie pasuje do złożoności usługi Active Directory." | ||||
| @ -2398,6 +2439,14 @@ msgstr "Hasło nie pasuje do złożoności usługi Active Directory." | ||||
| msgid "No token received." | ||||
| msgstr "Nie otrzymano tokena." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "URL żądania tokena" | ||||
| @ -2440,6 +2489,12 @@ msgstr "URL używany przez authentik do uzyskania informacji o użytkowniku." | ||||
| msgid "Additional Scopes" | ||||
| msgstr "Dodatkowe zakresy" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "Źródło OAuth" | ||||
| @ -3344,6 +3399,12 @@ msgstr "" | ||||
| "Po włączeniu tej opcji etap zakończy się powodzeniem i będzie kontynuowany " | ||||
| "nawet po wprowadzeniu nieprawidłowych danych użytkownika." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "" | ||||
| @ -3727,6 +3788,14 @@ msgstr "" | ||||
| "Zdarzenia zostaną usunięte po upływie tego czasu. (Format: " | ||||
| "weeks=3;days=2;hours=3,seconds=2)." | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "Opcja ta konfiguruje łącza stopki na stronach wykonawców przepływu." | ||||
|  | ||||
| @ -18,7 +18,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-11 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Gil Poiares-Oliveira, 2025\n" | ||||
| "Language-Team: Portuguese (Brazil) (https://app.transifex.com/authentik/teams/119923/pt_BR/)\n" | ||||
| @ -192,6 +192,7 @@ msgid "User's display name." | ||||
| msgstr "Nome de exibição do usuário." | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "Usuário" | ||||
|  | ||||
| @ -376,6 +377,18 @@ msgstr "Mapeamento de propriedades" | ||||
| msgid "Property Mappings" | ||||
| msgstr "Mapeamentos de propriedades" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "Sessão Autenticada" | ||||
| @ -483,6 +496,38 @@ msgstr "Uso de licença" | ||||
| msgid "License Usage Records" | ||||
| msgstr "Registros de uso de licença" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Chave de campo para verificar, as chaves de campo definidas nos estágios de " | ||||
| "prompt estão disponíveis." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Senha não definida no contexto" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "Entrerprise é necessário para acessar essa funcionalidade" | ||||
| @ -1252,12 +1297,6 @@ msgstr "" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Chave de campo para verificar, as chaves de campo definidas nos estágios de " | ||||
| "prompt estão disponíveis." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "Quantas vezes o hash da senha pode estar em haveibeenpwned" | ||||
| @ -1268,10 +1307,6 @@ msgid "" | ||||
| msgstr "" | ||||
| "Se a pontuação zxcvbn for igual ou menor que esse valor, a política falhará." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Senha não definida no contexto" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "" | ||||
| @ -1313,20 +1348,6 @@ msgstr "Pontuação de reputação" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "Pontuações de reputação" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "Permissão negada" | ||||
| @ -2141,6 +2162,10 @@ msgstr "" | ||||
| msgid "Roles" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "Permissão do sistema" | ||||
| @ -2387,6 +2412,22 @@ msgstr "" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "A senha não corresponde à complexidade do Active Directory." | ||||
| @ -2395,6 +2436,14 @@ msgstr "A senha não corresponde à complexidade do Active Directory." | ||||
| msgid "No token received." | ||||
| msgstr "Nenhum token recebido." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "URL do token de solicitação" | ||||
| @ -2435,6 +2484,12 @@ msgstr "URL usado pelo authentik para obter informações do usuário." | ||||
| msgid "Additional Scopes" | ||||
| msgstr "Escopos Adicionais" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "Fonte OAuth" | ||||
| @ -3318,6 +3373,12 @@ msgid "" | ||||
| "info is entered." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| @ -3678,6 +3739,14 @@ msgstr "" | ||||
| "Os eventos serão excluídos após esta duração.(Formato: " | ||||
| "semanas=3;dias=2;horas=3,segundos=2)." | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "" | ||||
|  | ||||
| @ -18,7 +18,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-11 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Marc Schmitt, 2025\n" | ||||
| "Language-Team: Russian (https://app.transifex.com/authentik/teams/119923/ru/)\n" | ||||
| @ -191,6 +191,7 @@ msgid "User's display name." | ||||
| msgstr "Отображаемое имя пользователя." | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "Пользователь" | ||||
|  | ||||
| @ -379,6 +380,18 @@ msgstr "Сопоставление свойств" | ||||
| msgid "Property Mappings" | ||||
| msgstr "Сопоставление свойств" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "Аутентифицированная Сессия" | ||||
| @ -487,6 +500,37 @@ msgstr "Использование лицензии" | ||||
| msgid "License Usage Records" | ||||
| msgstr "Записи использования лицензии" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Ключ поля для проверки, доступны ключи поля, определенные в этапах запроса." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Пароль не задан в контексте" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "Для доступа к этой функции требуется Enterprise." | ||||
| @ -1267,11 +1311,6 @@ msgstr "Просмотр показателей кэша политики" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "Очистка показателей кэша политики" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Ключ поля для проверки, доступны ключи поля, определенные в этапах запроса." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "Как часто хэш пароля может быть представлен на haveibeenpwned" | ||||
| @ -1283,10 +1322,6 @@ msgstr "" | ||||
| "Если показатель zxcvbn равен или меньше этого значения, политика будет " | ||||
| "провалена." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Пароль не задан в контексте" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "Неправильный пароль" | ||||
| @ -1328,20 +1363,6 @@ msgstr "Оценка репутации" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "Оценка репутации" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "Доступ запрещен" | ||||
| @ -2164,6 +2185,10 @@ msgstr "Роль" | ||||
| msgid "Roles" | ||||
| msgstr "Роли" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "Системное разрешение" | ||||
| @ -2421,6 +2446,22 @@ msgstr "Сопоставление свойства LDAP источника" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "Сопоставление свойств LDAP источника" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "Пароль не соответствует сложности Active Directory." | ||||
| @ -2429,6 +2470,14 @@ msgstr "Пароль не соответствует сложности Active D | ||||
| msgid "No token received." | ||||
| msgstr "Токен не был получен." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "URL-адрес запроса токена" | ||||
| @ -2471,6 +2520,12 @@ msgstr "" | ||||
| msgid "Additional Scopes" | ||||
| msgstr "Дополнительные области" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "Источник OAuth" | ||||
| @ -3376,6 +3431,12 @@ msgstr "" | ||||
| "При включении этап будет завершаться успешно и продолжаться даже в случае " | ||||
| "ввода неправильной информации о пользователе." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "" | ||||
| @ -3767,6 +3828,14 @@ msgstr "" | ||||
| "По истечении этого времени события будут удалены. (Формат: недели=3; дни=2; " | ||||
| "часы=3, секунды=2)." | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -13,7 +13,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-03-31 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n" | ||||
| "Language-Team: Turkish (https://app.transifex.com/authentik/teams/119923/tr/)\n" | ||||
| @ -187,6 +187,7 @@ msgid "User's display name." | ||||
| msgstr "Kullanıcının görünen adı." | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "Kullanıcı" | ||||
|  | ||||
| @ -372,6 +373,18 @@ msgstr "Özellik Eşleme" | ||||
| msgid "Property Mappings" | ||||
| msgstr "Özellik Eşlemeleri" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "Oturum" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "Oturumlar" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "Kimliği Doğrulanmış Oturum" | ||||
| @ -479,6 +492,38 @@ msgstr "Lisans Kullanımı" | ||||
| msgid "License Usage Records" | ||||
| msgstr "Lisans Kullanım Kayıtları" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Alan tuşu kontrol etmek için, İstem aşamalarında tanımlanan alan tuşları " | ||||
| "mevcuttur." | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Parola bağlam içinde ayarlanmamış" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "Bu özelliğe erişmek için Kurumsal Paket gereklidir." | ||||
| @ -1253,12 +1298,6 @@ msgstr "İlke'nin önbellek ölçümlerini görüntüleme" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "İlke'nin önbellek ölçümlerini temizleyin" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "" | ||||
| "Alan tuşu kontrol etmek için, İstem aşamalarında tanımlanan alan tuşları " | ||||
| "mevcuttur." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "" | ||||
| @ -1271,10 +1310,6 @@ msgstr "" | ||||
| "Eğer zxcvbn puanı bu değere eşit veya daha az ise, politika başarısız " | ||||
| "olacaktır." | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "Parola bağlam içinde ayarlanmamış" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "" | ||||
| @ -1316,20 +1351,6 @@ msgstr "İtibar Puanı" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "İtibar Puanları" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "İzin reddedildi" | ||||
| @ -2155,6 +2176,10 @@ msgstr "Rol" | ||||
| msgid "Roles" | ||||
| msgstr "Roller" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "Sistem yetkisi" | ||||
| @ -2398,6 +2423,13 @@ msgstr "" | ||||
| "Bir kullanıcı parolasını değiştirdiğinde, parolayı LDAP ile geri eşitleyin. " | ||||
| "Bu yalnızca tek bir LDAP kaynağında etkinleştirilebilir." | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "" | ||||
| "Lookup group membership based on a user attribute instead of a group " | ||||
| "attribute. This allows nested group resolution on systems like FreeIPA and " | ||||
| "Active Directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "LDAP Source" | ||||
| msgstr "LDAP Kaynağı" | ||||
| @ -2414,6 +2446,22 @@ msgstr "LDAP Kaynak Özellik Eşlemesi" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "LDAP Kaynak Özellik Eşlemeleri" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "Parola Active Directory Karmaşıklığıyla eşleşmiyor." | ||||
| @ -2422,6 +2470,14 @@ msgstr "Parola Active Directory Karmaşıklığıyla eşleşmiyor." | ||||
| msgid "No token received." | ||||
| msgstr "Jeton alınmadı." | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "Jeton URL'si İste" | ||||
| @ -2462,6 +2518,12 @@ msgstr "Kullanıcı bilgilerini almak için authentik tarafından kullanılan UR | ||||
| msgid "Additional Scopes" | ||||
| msgstr "Ek Kapsamlar" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "OAuth Kaynağı" | ||||
| @ -3360,6 +3422,12 @@ msgstr "" | ||||
| "Etkinleştirildiğinde, yanlış kullanıcı bilgisi girilse bile aşama başarılı " | ||||
| "olur ve devam eder." | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "Sayfanın alt kısmında bağlanan isteğe bağlı kayıt akışı." | ||||
| @ -3734,6 +3802,14 @@ msgstr "" | ||||
| "Olaylar bu süreden sonra silinecektir (Format: " | ||||
| "weeks=3;days=2;hours=3,seconds=2)." | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -15,7 +15,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-15 00:11+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: deluxghost, 2025\n" | ||||
| "Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n" | ||||
| @ -461,6 +461,36 @@ msgstr "许可证使用情况" | ||||
| msgid "License Usage Records" | ||||
| msgstr "许可证使用情况记录" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "要检查的字段键,可以使用输入阶段中定义的字段键。" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "检查指定数量的密码。" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "未在上下文中设置密码" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "此密码被使用过。请选择其他密码。" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "密码唯一性策略" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "密码唯一性策略" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "用户密码历史记录" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "访问此功能需要企业版。" | ||||
| @ -1190,10 +1220,6 @@ msgstr "查看策略缓存指标" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "清除策略缓存指标" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "要检查的字段键,可以使用输入阶段中定义的字段键。" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "密码哈希允许出现在 HaveIBeenPwned 中多少次" | ||||
| @ -1203,10 +1229,6 @@ msgid "" | ||||
| "If the zxcvbn score is equal or less than this value, the policy will fail." | ||||
| msgstr "如果 zxcvbn 分数小于等于此值,则策略失败。" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "未在上下文中设置密码" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "无效密码。" | ||||
| @ -1248,20 +1270,6 @@ msgstr "信誉分数" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "信誉分数" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "正在等待身份验证…" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "您正在另一个标签页中验证身份。身份验证完成后,此页面会刷新。" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "在此标签页中验证身份" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "权限被拒绝" | ||||
| @ -2286,6 +2294,14 @@ msgstr "密码与 Active Directory 复杂度不匹配。" | ||||
| msgid "No token received." | ||||
| msgstr "未收到令牌。" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "HTTP 基本身份验证" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "包括客户端 ID 和密钥作为请求参数" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "请求令牌 URL" | ||||
| @ -2324,6 +2340,12 @@ msgstr "authentik 用来获取用户信息的 URL。" | ||||
| msgid "Additional Scopes" | ||||
| msgstr "额外的作用域" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "在 authorization_code 令牌请求流程期间,如何执行身份验证" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "OAuth 源" | ||||
| @ -3194,6 +3216,12 @@ msgid "" | ||||
| "info is entered." | ||||
| msgstr "启用时,即使输入错误的用户信息,此阶段也会成功并继续。" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "向用户显示“在此设备上记住我”开关,允许相同用户直接跳过输入密码。" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "可选注册流程,链接在页面底部。" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -14,7 +14,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-15 00:11+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: deluxghost, 2025\n" | ||||
| "Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n" | ||||
| @ -460,6 +460,36 @@ msgstr "许可证使用情况" | ||||
| msgid "License Usage Records" | ||||
| msgstr "许可证使用情况记录" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "要检查的字段键,可以使用输入阶段中定义的字段键。" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "检查指定数量的密码。" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "未在上下文中设置密码" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "此密码被使用过。请选择其他密码。" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "密码唯一性策略" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "密码唯一性策略" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "用户密码历史记录" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "访问此功能需要企业版。" | ||||
| @ -1189,10 +1219,6 @@ msgstr "查看策略缓存指标" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "清除策略缓存指标" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "要检查的字段键,可以使用输入阶段中定义的字段键。" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "密码哈希允许出现在 HaveIBeenPwned 中多少次" | ||||
| @ -1202,10 +1228,6 @@ msgid "" | ||||
| "If the zxcvbn score is equal or less than this value, the policy will fail." | ||||
| msgstr "如果 zxcvbn 分数小于等于此值,则策略失败。" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "未在上下文中设置密码" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "无效密码。" | ||||
| @ -1247,20 +1269,6 @@ msgstr "信誉分数" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "信誉分数" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "正在等待身份验证…" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "您正在另一个标签页中验证身份。身份验证完成后,此页面会刷新。" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "在此标签页中验证身份" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "权限被拒绝" | ||||
| @ -2285,6 +2293,14 @@ msgstr "密码与 Active Directory 复杂度不匹配。" | ||||
| msgid "No token received." | ||||
| msgstr "未收到令牌。" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "HTTP 基本身份验证" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "包括客户端 ID 和密钥作为请求参数" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "请求令牌 URL" | ||||
| @ -2323,6 +2339,12 @@ msgstr "authentik 用来获取用户信息的 URL。" | ||||
| msgid "Additional Scopes" | ||||
| msgstr "额外的作用域" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "在 authorization_code 令牌请求流程期间,如何执行身份验证" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "OAuth 源" | ||||
| @ -3193,6 +3215,12 @@ msgid "" | ||||
| "info is entered." | ||||
| msgstr "启用时,即使输入错误的用户信息,此阶段也会成功并继续。" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "向用户显示“在此设备上记住我”开关,允许相同用户直接跳过输入密码。" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "可选注册流程,链接在页面底部。" | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -14,7 +14,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-04-11 00:10+0000\n" | ||||
| "POT-Creation-Date: 2025-04-23 09:00+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: 刘松, 2025\n" | ||||
| "Language-Team: Chinese (Taiwan) (https://app.transifex.com/authentik/teams/119923/zh_TW/)\n" | ||||
| @ -178,6 +178,7 @@ msgid "User's display name." | ||||
| msgstr "使用者的顯示名稱。" | ||||
|  | ||||
| #: authentik/core/models.py authentik/providers/oauth2/models.py | ||||
| #: authentik/rbac/models.py | ||||
| msgid "User" | ||||
| msgstr "使用者" | ||||
|  | ||||
| @ -344,6 +345,18 @@ msgstr "屬性對應" | ||||
| msgid "Property Mappings" | ||||
| msgstr "屬性對應" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "session data" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Session" | ||||
| msgstr "会话" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Sessions" | ||||
| msgstr "会话" | ||||
|  | ||||
| #: authentik/core/models.py | ||||
| msgid "Authenticated Session" | ||||
| msgstr "已認證會談" | ||||
| @ -447,6 +460,36 @@ msgstr "授權使用情況" | ||||
| msgid "License Usage Records" | ||||
| msgstr "授權使用紀錄" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "要檢查的欄位鍵,在提示階段中有可用的已定義欄位鍵。" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Number of passwords to check against." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "未在上下文中設定密碼" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "This password has been used previously. Please choose a different one." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policy" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "Password Uniqueness Policies" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policies/unique_password/models.py | ||||
| msgid "User Password History" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/enterprise/policy.py | ||||
| msgid "Enterprise required to access this feature." | ||||
| msgstr "企業版才能存取此功能。" | ||||
| @ -1176,10 +1219,6 @@ msgstr "檢視原則的快取指標" | ||||
| msgid "Clear Policy's cache metrics" | ||||
| msgstr "清除原則的快取指標" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Field key to check, field keys defined in Prompt stages are available." | ||||
| msgstr "要檢查的欄位鍵,在提示階段中有可用的已定義欄位鍵。" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "How many times the password hash is allowed to be on haveibeenpwned" | ||||
| msgstr "密碼雜湊在 haveibeenpwned 上允許出現的次數" | ||||
| @ -1189,10 +1228,6 @@ msgid "" | ||||
| "If the zxcvbn score is equal or less than this value, the policy will fail." | ||||
| msgstr "如果 zxcvbn 分數等於或小於此值,則該政策將失敗。" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Password not set in context" | ||||
| msgstr "未在上下文中設定密碼" | ||||
|  | ||||
| #: authentik/policies/password/models.py | ||||
| msgid "Invalid password." | ||||
| msgstr "" | ||||
| @ -1234,20 +1269,6 @@ msgstr "信譽分數" | ||||
| msgid "Reputation Scores" | ||||
| msgstr "信譽分數" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Waiting for authentication..." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "" | ||||
| "You're already authenticating in another tab. This page will refresh once " | ||||
| "authentication is completed." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/buffer.html | ||||
| msgid "Authenticate in this tab" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/policies/templates/policies/denied.html | ||||
| msgid "Permission denied" | ||||
| msgstr "權限不足。" | ||||
| @ -1999,6 +2020,10 @@ msgstr "角色" | ||||
| msgid "Roles" | ||||
| msgstr "角色" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "Initial Permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/rbac/models.py | ||||
| msgid "System permission" | ||||
| msgstr "系統權限" | ||||
| @ -2240,6 +2265,22 @@ msgstr "" | ||||
| msgid "LDAP Source Property Mappings" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "User LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/models.py | ||||
| msgid "Group LDAP Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/ldap/signals.py | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "密碼不符合 Active Directory 的複雜性要求。" | ||||
| @ -2248,6 +2289,14 @@ msgstr "密碼不符合 Active Directory 的複雜性要求。" | ||||
| msgid "No token received." | ||||
| msgstr "未收到權杖。" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "HTTP Basic Authentication" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Include the client ID and secret as request parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "Request Token URL" | ||||
| msgstr "請求權杖的網址" | ||||
| @ -2286,6 +2335,12 @@ msgstr "authentik 用來擷取使用者資訊的網址。" | ||||
| msgid "Additional Scopes" | ||||
| msgstr "附加範圍" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "" | ||||
| "How to perform authentication during an authorization_code token request " | ||||
| "flow" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py | ||||
| msgid "OAuth Source" | ||||
| msgstr "OAuth 來源" | ||||
| @ -3137,6 +3192,12 @@ msgid "" | ||||
| "info is entered." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "" | ||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||
| " to skip straight to entering their password." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/identification/models.py | ||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||
| msgstr "可選的註冊流程,連結在頁面的底部。" | ||||
| @ -3481,6 +3542,14 @@ msgid "" | ||||
| "weeks=3;days=2;hours=3,seconds=2)." | ||||
| msgstr "事件將在此期間後刪除。(格式:weeks=3;days=2;hours=3,seconds=2)" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/tenants/models.py | ||||
| msgid "The option configures the footer links on the flow executor pages." | ||||
| msgstr "" | ||||
|  | ||||
| @ -18,9 +18,7 @@ | ||||
| } | ||||
|  | ||||
| .badge--support-community { | ||||
|     --ifm-badge-background-color: var( | ||||
|         --ifm-color-secondary-contrast-foreground | ||||
|     ); | ||||
|     --ifm-badge-background-color: var(--ifm-color-secondary-contrast-foreground); | ||||
|     --ifm-badge-border-color: var(--ifm-color-secondary-dark); | ||||
|     --ifm-badge-color: var(--ifm-color-secondary-contrast-background); | ||||
| } | ||||
|  | ||||
| @ -1,12 +1,12 @@ | ||||
| :root { | ||||
|     --ifm-font-family-base: | ||||
|         RedHatVF, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, | ||||
|         Noto Sans, sans-serif, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, | ||||
|         sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; | ||||
|         RedHatVF, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, | ||||
|         sans-serif, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, | ||||
|         "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; | ||||
|  | ||||
|     --ifm-font-family-monospace: | ||||
|         RedHatMonoVF, SFMono-Regular, Menlo, Monaco, Consolas, | ||||
|         "Liberation Mono", "Courier New", monospace; | ||||
|         RedHatMonoVF, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", | ||||
|         monospace; | ||||
|  | ||||
|     --ifm-heading-font-family: RedHatDisplayVF, var(--ifm-font-family-base); | ||||
|  | ||||
|  | ||||
| @ -7,11 +7,7 @@ | ||||
| } | ||||
|  | ||||
| .homepage_hero__subtitle p { | ||||
|     font-size: clamp( | ||||
|         1.125rem, | ||||
|         0.9946rem + 0.6522vi, | ||||
|         1.5rem | ||||
|     ); /* Adjust font as page scales */ | ||||
|     font-size: clamp(1.125rem, 0.9946rem + 0.6522vi, 1.5rem); /* Adjust font as page scales */ | ||||
|     max-width: 28ch; /* Apply a maximum to keep everything in the box */ | ||||
|     text-wrap: balance; /* Prevent widows, orphans, and runts. Doesn't work in Safari */ | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| :root { | ||||
|     --ifm-menu-link-padding-vertical: 1em; | ||||
|     --ifm-menu-link-padding-vertical: 0.5em; | ||||
| } | ||||
|  | ||||
| .menu__list-item { | ||||
|  | ||||
| @ -75,17 +75,14 @@ | ||||
|         --ifm-navbar-item-padding-horizontal: 1rem; | ||||
|     } | ||||
|  | ||||
|     .docs-wrapper .navbar { | ||||
|     .navbar { | ||||
|         margin: 0; | ||||
|         padding-inline-start: 0; | ||||
|     } | ||||
|  | ||||
|     .navbar__brand { | ||||
|         justify-content: center; | ||||
|     } | ||||
|  | ||||
|     .docs-wrapper .navbar__brand { | ||||
|         width: var(--doc-sidebar-width); | ||||
|         width: var(--doc-sidebar-width, 300px); | ||||
|         margin: 0; | ||||
|     } | ||||
|  | ||||
| @ -122,12 +119,8 @@ | ||||
|  | ||||
|         @media (min-width: 999px) { | ||||
|             border-inline-start: 1px solid var(--ifm-hover-overlay); | ||||
|             margin-inline-start: calc( | ||||
|                 var(--ifm-navbar-item-padding-horizontal) / 2 | ||||
|             ); | ||||
|             padding-inline-start: calc( | ||||
|                 var(--ifm-navbar-item-padding-horizontal) / 2 | ||||
|             ); | ||||
|             margin-inline-start: calc(var(--ifm-navbar-item-padding-horizontal) / 2); | ||||
|             padding-inline-start: calc(var(--ifm-navbar-item-padding-horizontal) / 2); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -151,19 +144,14 @@ | ||||
|         hsl(236.84deg 34.55% 10.78%) | ||||
|     ); | ||||
|     --docsearch-key-shadow: | ||||
|         inset 0 -2px 0 0 hsl(233.33deg 36% 24.51%), | ||||
|         inset 0 0 1px 1px hsl(232.11deg 34.86% 57.25%), | ||||
|         inset 0 -2px 0 0 hsl(233.33deg 36% 24.51%), inset 0 0 1px 1px hsl(232.11deg 34.86% 57.25%), | ||||
|         0 2px 2px 0 rgba(3, 4, 9, 0.3); | ||||
|     --docsearch-key-pressed-shadow: | ||||
|         inset 0 -2px 0 0 #282d55, | ||||
|         inset 0 0 1px 1px hsl(231.82deg 21.36% 40.39%), | ||||
|         inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px hsl(231.82deg 21.36% 40.39%), | ||||
|         0 1px 1px 0 hsl(230deg 50% 2.35% / 30.2%); | ||||
|  | ||||
|     padding: var(--ifm-navbar-item-padding-vertical) | ||||
|         var(--ifm-navbar-item-padding-horizontal) !important; | ||||
|     padding-inline-end: calc( | ||||
|         var(--ifm-navbar-item-padding-horizontal) * 1.25 | ||||
|     ) !important; | ||||
|     padding: var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal) !important; | ||||
|     padding-inline-end: calc(var(--ifm-navbar-item-padding-horizontal) * 1.25) !important; | ||||
|  | ||||
|     .DocSearch-Button-Placeholder { | ||||
|         font-family: var(--ifm-heading-font-family); | ||||
|  | ||||
| @ -13,7 +13,3 @@ | ||||
|  | ||||
|     --ifm-color-content: hsl(216 35% 3%); | ||||
| } | ||||
|  | ||||
| body { | ||||
|     overscroll-behavior-x: none; | ||||
| } | ||||
|  | ||||
| @ -4,8 +4,8 @@ | ||||
|  * @import { Config as DocusaurusConfig } from "@docusaurus/types" | ||||
|  * @import { UserThemeConfig } from "./theme.js" | ||||
|  */ | ||||
|  | ||||
| import { deepmerge } from "deepmerge-ts"; | ||||
|  | ||||
| import { createThemeConfig } from "./theme.js"; | ||||
|  | ||||
| //#region Types | ||||
|  | ||||
| @ -4,7 +4,6 @@ | ||||
|  * @import { UserThemeConfig as UserThemeConfigCommon } from "@docusaurus/theme-common"; | ||||
|  * @import { UserThemeConfig as UserThemeConfigAlgolia } from "@docusaurus/theme-search-algolia"; | ||||
|  */ | ||||
|  | ||||
| import { deepmerge } from "deepmerge-ts"; | ||||
| import { themes as prismThemes } from "prism-react-renderer"; | ||||
|  | ||||
|  | ||||
							
								
								
									
										4
									
								
								packages/docusaurus-config/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								packages/docusaurus-config/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,12 +1,12 @@ | ||||
| { | ||||
|     "name": "@goauthentik/docusaurus-config", | ||||
|     "version": "1.0.2", | ||||
|     "version": "1.0.5", | ||||
|     "lockfileVersion": 3, | ||||
|     "requires": true, | ||||
|     "packages": { | ||||
|         "": { | ||||
|             "name": "@goauthentik/docusaurus-config", | ||||
|             "version": "1.0.2", | ||||
|             "version": "1.0.5", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "deepmerge-ts": "^7.1.5", | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@goauthentik/docusaurus-config", | ||||
|     "version": "1.0.4", | ||||
|     "version": "1.0.5", | ||||
|     "description": "authentik's Docusaurus config", | ||||
|     "license": "MIT", | ||||
|     "scripts": { | ||||
|  | ||||
							
								
								
									
										423
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										423
									
								
								schema.yml
									
									
									
									
									
								
							| @ -14721,6 +14721,302 @@ paths: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /policies/unique_password/: | ||||
|     get: | ||||
|       operationId: policies_unique_password_list | ||||
|       description: Password Uniqueness Policy Viewset | ||||
|       parameters: | ||||
|       - in: query | ||||
|         name: created | ||||
|         schema: | ||||
|           type: string | ||||
|           format: date-time | ||||
|       - in: query | ||||
|         name: execution_logging | ||||
|         schema: | ||||
|           type: boolean | ||||
|       - in: query | ||||
|         name: last_updated | ||||
|         schema: | ||||
|           type: string | ||||
|           format: date-time | ||||
|       - in: query | ||||
|         name: name | ||||
|         schema: | ||||
|           type: string | ||||
|       - in: query | ||||
|         name: num_historical_passwords | ||||
|         schema: | ||||
|           type: integer | ||||
|       - name: ordering | ||||
|         required: false | ||||
|         in: query | ||||
|         description: Which field to use when ordering the results. | ||||
|         schema: | ||||
|           type: string | ||||
|       - name: page | ||||
|         required: false | ||||
|         in: query | ||||
|         description: A page number within the paginated result set. | ||||
|         schema: | ||||
|           type: integer | ||||
|       - name: page_size | ||||
|         required: false | ||||
|         in: query | ||||
|         description: Number of results to return per page. | ||||
|         schema: | ||||
|           type: integer | ||||
|       - in: query | ||||
|         name: password_field | ||||
|         schema: | ||||
|           type: string | ||||
|       - in: query | ||||
|         name: policy_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|       - name: search | ||||
|         required: false | ||||
|         in: query | ||||
|         description: A search term. | ||||
|         schema: | ||||
|           type: string | ||||
|       tags: | ||||
|       - policies | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/PaginatedUniquePasswordPolicyList' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|     post: | ||||
|       operationId: policies_unique_password_create | ||||
|       description: Password Uniqueness Policy Viewset | ||||
|       tags: | ||||
|       - policies | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/UniquePasswordPolicyRequest' | ||||
|         required: true | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '201': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/UniquePasswordPolicy' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /policies/unique_password/{policy_uuid}/: | ||||
|     get: | ||||
|       operationId: policies_unique_password_retrieve | ||||
|       description: Password Uniqueness Policy Viewset | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: policy_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Password Uniqueness Policy. | ||||
|         required: true | ||||
|       tags: | ||||
|       - policies | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/UniquePasswordPolicy' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|     put: | ||||
|       operationId: policies_unique_password_update | ||||
|       description: Password Uniqueness Policy Viewset | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: policy_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Password Uniqueness Policy. | ||||
|         required: true | ||||
|       tags: | ||||
|       - policies | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/UniquePasswordPolicyRequest' | ||||
|         required: true | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/UniquePasswordPolicy' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|     patch: | ||||
|       operationId: policies_unique_password_partial_update | ||||
|       description: Password Uniqueness Policy Viewset | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: policy_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Password Uniqueness Policy. | ||||
|         required: true | ||||
|       tags: | ||||
|       - policies | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/PatchedUniquePasswordPolicyRequest' | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/UniquePasswordPolicy' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|     delete: | ||||
|       operationId: policies_unique_password_destroy | ||||
|       description: Password Uniqueness Policy Viewset | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: policy_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Password Uniqueness Policy. | ||||
|         required: true | ||||
|       tags: | ||||
|       - policies | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '204': | ||||
|           description: No response body | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /policies/unique_password/{policy_uuid}/used_by/: | ||||
|     get: | ||||
|       operationId: policies_unique_password_used_by_list | ||||
|       description: Get a list of all objects that use this object | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: policy_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Password Uniqueness Policy. | ||||
|         required: true | ||||
|       tags: | ||||
|       - policies | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: '#/components/schemas/UsedBy' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /propertymappings/all/: | ||||
|     get: | ||||
|       operationId: propertymappings_all_list | ||||
| @ -24616,6 +24912,7 @@ paths: | ||||
|           - authentik_policies_geoip.geoippolicy | ||||
|           - authentik_policies_password.passwordpolicy | ||||
|           - authentik_policies_reputation.reputationpolicy | ||||
|           - authentik_policies_unique_password.uniquepasswordpolicy | ||||
|           - authentik_providers_google_workspace.googleworkspaceprovider | ||||
|           - authentik_providers_google_workspace.googleworkspaceprovidermapping | ||||
|           - authentik_providers_ldap.ldapprovider | ||||
| @ -24863,6 +25160,7 @@ paths: | ||||
|           - authentik_policies_geoip.geoippolicy | ||||
|           - authentik_policies_password.passwordpolicy | ||||
|           - authentik_policies_reputation.reputationpolicy | ||||
|           - authentik_policies_unique_password.uniquepasswordpolicy | ||||
|           - authentik_providers_google_workspace.googleworkspaceprovider | ||||
|           - authentik_providers_google_workspace.googleworkspaceprovidermapping | ||||
|           - authentik_providers_ldap.ldapprovider | ||||
| @ -40643,6 +40941,7 @@ components: | ||||
|       - authentik.core | ||||
|       - authentik.enterprise | ||||
|       - authentik.enterprise.audit | ||||
|       - authentik.enterprise.policies.unique_password | ||||
|       - authentik.enterprise.providers.google_workspace | ||||
|       - authentik.enterprise.providers.microsoft_entra | ||||
|       - authentik.enterprise.providers.ssf | ||||
| @ -46049,6 +46348,9 @@ components: | ||||
|             $ref: '#/components/schemas/LoginSource' | ||||
|         show_source_labels: | ||||
|           type: boolean | ||||
|         enable_remember_me: | ||||
|           type: boolean | ||||
|           default: true | ||||
|       required: | ||||
|       - flow_designation | ||||
|       - password_fields | ||||
| @ -46161,6 +46463,10 @@ components: | ||||
|           type: boolean | ||||
|           description: When enabled, the stage will succeed and continue even when | ||||
|             incorrect user info is entered. | ||||
|         enable_remember_me: | ||||
|           type: boolean | ||||
|           description: Show the user the 'Remember me on this device' toggle, allowing | ||||
|             repeat users to skip straight to entering their password. | ||||
|       required: | ||||
|       - component | ||||
|       - meta_model_name | ||||
| @ -46235,6 +46541,10 @@ components: | ||||
|           type: boolean | ||||
|           description: When enabled, the stage will succeed and continue even when | ||||
|             incorrect user info is entered. | ||||
|         enable_remember_me: | ||||
|           type: boolean | ||||
|           description: Show the user the 'Remember me on this device' toggle, allowing | ||||
|             repeat users to skip straight to entering their password. | ||||
|       required: | ||||
|       - name | ||||
|     ImpersonationRequest: | ||||
| @ -48051,6 +48361,7 @@ components: | ||||
|       - authentik_core.applicationentitlement | ||||
|       - authentik_core.token | ||||
|       - authentik_enterprise.license | ||||
|       - authentik_policies_unique_password.uniquepasswordpolicy | ||||
|       - authentik_providers_google_workspace.googleworkspaceprovider | ||||
|       - authentik_providers_google_workspace.googleworkspaceprovidermapping | ||||
|       - authentik_providers_microsoft_entra.microsoftentraprovider | ||||
| @ -50605,6 +50916,18 @@ components: | ||||
|       required: | ||||
|       - pagination | ||||
|       - results | ||||
|     PaginatedUniquePasswordPolicyList: | ||||
|       type: object | ||||
|       properties: | ||||
|         pagination: | ||||
|           $ref: '#/components/schemas/Pagination' | ||||
|         results: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/UniquePasswordPolicy' | ||||
|       required: | ||||
|       - pagination | ||||
|       - results | ||||
|     PaginatedUserAssignedObjectPermissionList: | ||||
|       type: object | ||||
|       properties: | ||||
| @ -52290,6 +52613,10 @@ components: | ||||
|           type: boolean | ||||
|           description: When enabled, the stage will succeed and continue even when | ||||
|             incorrect user info is entered. | ||||
|         enable_remember_me: | ||||
|           type: boolean | ||||
|           description: Show the user the 'Remember me on this device' toggle, allowing | ||||
|             repeat users to skip straight to entering their password. | ||||
|     PatchedInitialPermissionsRequest: | ||||
|       type: object | ||||
|       description: InitialPermissions serializer | ||||
| @ -54210,6 +54537,27 @@ components: | ||||
|           nullable: true | ||||
|         expiring: | ||||
|           type: boolean | ||||
|     PatchedUniquePasswordPolicyRequest: | ||||
|       type: object | ||||
|       description: Password Uniqueness Policy Serializer | ||||
|       properties: | ||||
|         name: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|         execution_logging: | ||||
|           type: boolean | ||||
|           description: When this option is enabled, all executions of this policy | ||||
|             will be logged. By default, only execution errors are logged. | ||||
|         password_field: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|           description: Field key to check, field keys defined in Prompt stages are | ||||
|             available. | ||||
|         num_historical_passwords: | ||||
|           type: integer | ||||
|           maximum: 2147483647 | ||||
|           minimum: 0 | ||||
|           description: Number of passwords to check against. | ||||
|     PatchedUserDeleteStageRequest: | ||||
|       type: object | ||||
|       description: UserDeleteStage Serializer | ||||
| @ -59206,6 +59554,81 @@ components: | ||||
|       - light | ||||
|       - dark | ||||
|       type: string | ||||
|     UniquePasswordPolicy: | ||||
|       type: object | ||||
|       description: Password Uniqueness Policy Serializer | ||||
|       properties: | ||||
|         pk: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           readOnly: true | ||||
|           title: Policy uuid | ||||
|         name: | ||||
|           type: string | ||||
|         execution_logging: | ||||
|           type: boolean | ||||
|           description: When this option is enabled, all executions of this policy | ||||
|             will be logged. By default, only execution errors are logged. | ||||
|         component: | ||||
|           type: string | ||||
|           description: Get object component so that we know how to edit the object | ||||
|           readOnly: true | ||||
|         verbose_name: | ||||
|           type: string | ||||
|           description: Return object's verbose_name | ||||
|           readOnly: true | ||||
|         verbose_name_plural: | ||||
|           type: string | ||||
|           description: Return object's plural verbose_name | ||||
|           readOnly: true | ||||
|         meta_model_name: | ||||
|           type: string | ||||
|           description: Return internal model name | ||||
|           readOnly: true | ||||
|         bound_to: | ||||
|           type: integer | ||||
|           description: Return objects policy is bound to | ||||
|           readOnly: true | ||||
|         password_field: | ||||
|           type: string | ||||
|           description: Field key to check, field keys defined in Prompt stages are | ||||
|             available. | ||||
|         num_historical_passwords: | ||||
|           type: integer | ||||
|           maximum: 2147483647 | ||||
|           minimum: 0 | ||||
|           description: Number of passwords to check against. | ||||
|       required: | ||||
|       - bound_to | ||||
|       - component | ||||
|       - meta_model_name | ||||
|       - name | ||||
|       - pk | ||||
|       - verbose_name | ||||
|       - verbose_name_plural | ||||
|     UniquePasswordPolicyRequest: | ||||
|       type: object | ||||
|       description: Password Uniqueness Policy Serializer | ||||
|       properties: | ||||
|         name: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|         execution_logging: | ||||
|           type: boolean | ||||
|           description: When this option is enabled, all executions of this policy | ||||
|             will be logged. By default, only execution errors are logged. | ||||
|         password_field: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|           description: Field key to check, field keys defined in Prompt stages are | ||||
|             available. | ||||
|         num_historical_passwords: | ||||
|           type: integer | ||||
|           maximum: 2147483647 | ||||
|           minimum: 0 | ||||
|           description: Number of passwords to check against. | ||||
|       required: | ||||
|       - name | ||||
|     UsedBy: | ||||
|       type: object | ||||
|       description: A list of all objects referencing the queried object | ||||
|  | ||||
| @ -410,77 +410,3 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): | ||||
|             self.driver.find_element(By.CSS_SELECTOR, "header > h1").text, | ||||
|             "Permission denied", | ||||
|         ) | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
|         "default/flow-default-authentication-flow.yaml", | ||||
|         "default/flow-default-invalidation-flow.yaml", | ||||
|     ) | ||||
|     @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml") | ||||
|     @apply_blueprint("system/providers-oauth2.yaml") | ||||
|     @reconcile_app("authentik_crypto") | ||||
|     def test_authorization_consent_implied_parallel(self): | ||||
|         """test OpenID Provider flow (default authorization flow with implied consent)""" | ||||
|         # Bootstrap all needed objects | ||||
|         authorization_flow = Flow.objects.get( | ||||
|             slug="default-provider-authorization-implicit-consent" | ||||
|         ) | ||||
|         provider = OAuth2Provider.objects.create( | ||||
|             name=generate_id(), | ||||
|             client_type=ClientTypes.CONFIDENTIAL, | ||||
|             client_id=self.client_id, | ||||
|             client_secret=self.client_secret, | ||||
|             signing_key=create_test_cert(), | ||||
|             redirect_uris=[ | ||||
|                 RedirectURI( | ||||
|                     RedirectURIMatchingMode.STRICT, "http://localhost:3000/login/generic_oauth" | ||||
|                 ) | ||||
|             ], | ||||
|             authorization_flow=authorization_flow, | ||||
|         ) | ||||
|         provider.property_mappings.set( | ||||
|             ScopeMapping.objects.filter( | ||||
|                 scope_name__in=[ | ||||
|                     SCOPE_OPENID, | ||||
|                     SCOPE_OPENID_EMAIL, | ||||
|                     SCOPE_OPENID_PROFILE, | ||||
|                     SCOPE_OFFLINE_ACCESS, | ||||
|                 ] | ||||
|             ) | ||||
|         ) | ||||
|         Application.objects.create( | ||||
|             name=generate_id(), | ||||
|             slug=self.app_slug, | ||||
|             provider=provider, | ||||
|         ) | ||||
|  | ||||
|         self.driver.get(self.live_server_url) | ||||
|         login_window = self.driver.current_window_handle | ||||
|  | ||||
|         self.driver.switch_to.new_window("tab") | ||||
|         grafana_window = self.driver.current_window_handle | ||||
|         self.driver.get("http://localhost:3000") | ||||
|         self.driver.find_element(By.CLASS_NAME, "btn-service--oauth").click() | ||||
|  | ||||
|         self.driver.switch_to.window(login_window) | ||||
|         self.login() | ||||
|  | ||||
|         self.driver.switch_to.window(grafana_window) | ||||
|         self.wait_for_url("http://localhost:3000/?orgId=1") | ||||
|         self.driver.get("http://localhost:3000/profile") | ||||
|         self.assertEqual( | ||||
|             self.driver.find_element(By.CLASS_NAME, "page-header__title").text, | ||||
|             self.user.name, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             self.driver.find_element(By.CSS_SELECTOR, "input[name=name]").get_attribute("value"), | ||||
|             self.user.name, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             self.driver.find_element(By.CSS_SELECTOR, "input[name=email]").get_attribute("value"), | ||||
|             self.user.email, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             self.driver.find_element(By.CSS_SELECTOR, "input[name=login]").get_attribute("value"), | ||||
|             self.user.email, | ||||
|         ) | ||||
|  | ||||
| @ -20,7 +20,7 @@ from tests.e2e.utils import SeleniumTestCase, retry | ||||
| class TestProviderSAML(SeleniumTestCase): | ||||
|     """test SAML Provider flow""" | ||||
|  | ||||
|     def setup_client(self, provider: SAMLProvider, force_post: bool = False, **kwargs): | ||||
|     def setup_client(self, provider: SAMLProvider, force_post: bool = False): | ||||
|         """Setup client saml-sp container which we test SAML against""" | ||||
|         metadata_url = ( | ||||
|             self.url( | ||||
| @ -40,7 +40,6 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|                 "SP_ENTITY_ID": provider.issuer, | ||||
|                 "SP_SSO_BINDING": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", | ||||
|                 "SP_METADATA_URL": metadata_url, | ||||
|                 **kwargs, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
| @ -112,74 +111,6 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|             [self.user.email], | ||||
|         ) | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
|         "default/flow-default-authentication-flow.yaml", | ||||
|         "default/flow-default-invalidation-flow.yaml", | ||||
|     ) | ||||
|     @apply_blueprint( | ||||
|         "default/flow-default-provider-authorization-implicit-consent.yaml", | ||||
|     ) | ||||
|     @apply_blueprint( | ||||
|         "system/providers-saml.yaml", | ||||
|     ) | ||||
|     @reconcile_app("authentik_crypto") | ||||
|     def test_sp_initiated_implicit_post(self): | ||||
|         """test SAML Provider flow SP-initiated flow (implicit consent)""" | ||||
|         # Bootstrap all needed objects | ||||
|         authorization_flow = Flow.objects.get( | ||||
|             slug="default-provider-authorization-implicit-consent" | ||||
|         ) | ||||
|         provider: SAMLProvider = SAMLProvider.objects.create( | ||||
|             name="saml-test", | ||||
|             acs_url="http://localhost:9009/saml/acs", | ||||
|             audience="authentik-e2e", | ||||
|             issuer="authentik-e2e", | ||||
|             sp_binding=SAMLBindings.POST, | ||||
|             authorization_flow=authorization_flow, | ||||
|             signing_kp=create_test_cert(), | ||||
|         ) | ||||
|         provider.property_mappings.set(SAMLPropertyMapping.objects.all()) | ||||
|         provider.save() | ||||
|         Application.objects.create( | ||||
|             name="SAML", | ||||
|             slug="authentik-saml", | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.setup_client(provider, True) | ||||
|         self.driver.get("http://localhost:9009") | ||||
|         self.login() | ||||
|         self.wait_for_url("http://localhost:9009/") | ||||
|  | ||||
|         body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) | ||||
|  | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], | ||||
|             [self.user.name], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"][ | ||||
|                 "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" | ||||
|             ], | ||||
|             [self.user.username], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"], | ||||
|             [self.user.username], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"], | ||||
|             [str(self.user.pk)], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], | ||||
|             [self.user.email], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"], | ||||
|             [self.user.email], | ||||
|         ) | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
|         "default/flow-default-authentication-flow.yaml", | ||||
| @ -519,81 +450,3 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|             lambda driver: driver.current_url.startswith(should_url), | ||||
|             f"URL {self.driver.current_url} doesn't match expected URL {should_url}", | ||||
|         ) | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
|         "default/flow-default-authentication-flow.yaml", | ||||
|         "default/flow-default-invalidation-flow.yaml", | ||||
|     ) | ||||
|     @apply_blueprint( | ||||
|         "default/flow-default-provider-authorization-implicit-consent.yaml", | ||||
|     ) | ||||
|     @apply_blueprint( | ||||
|         "system/providers-saml.yaml", | ||||
|     ) | ||||
|     @reconcile_app("authentik_crypto") | ||||
|     def test_sp_initiated_implicit_post_buffer(self): | ||||
|         """test SAML Provider flow SP-initiated flow (implicit consent)""" | ||||
|         # Bootstrap all needed objects | ||||
|         authorization_flow = Flow.objects.get( | ||||
|             slug="default-provider-authorization-implicit-consent" | ||||
|         ) | ||||
|         provider: SAMLProvider = SAMLProvider.objects.create( | ||||
|             name="saml-test", | ||||
|             acs_url=f"http://{self.host}:9009/saml/acs", | ||||
|             audience="authentik-e2e", | ||||
|             issuer="authentik-e2e", | ||||
|             sp_binding=SAMLBindings.POST, | ||||
|             authorization_flow=authorization_flow, | ||||
|             signing_kp=create_test_cert(), | ||||
|         ) | ||||
|         provider.property_mappings.set(SAMLPropertyMapping.objects.all()) | ||||
|         provider.save() | ||||
|         Application.objects.create( | ||||
|             name="SAML", | ||||
|             slug="authentik-saml", | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.setup_client(provider, True, SP_ROOT_URL=f"http://{self.host}:9009") | ||||
|  | ||||
|         self.driver.get(self.live_server_url) | ||||
|         login_window = self.driver.current_window_handle | ||||
|         self.driver.switch_to.new_window("tab") | ||||
|         client_window = self.driver.current_window_handle | ||||
|         # We need to access the SP on the same host as the IdP for SameSite cookies | ||||
|         self.driver.get(f"http://{self.host}:9009") | ||||
|  | ||||
|         self.driver.switch_to.window(login_window) | ||||
|         self.login() | ||||
|         self.driver.switch_to.window(client_window) | ||||
|  | ||||
|         self.wait_for_url(f"http://{self.host}:9009/") | ||||
|  | ||||
|         body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) | ||||
|  | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], | ||||
|             [self.user.name], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"][ | ||||
|                 "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" | ||||
|             ], | ||||
|             [self.user.username], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"], | ||||
|             [self.user.username], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"], | ||||
|             [str(self.user.pk)], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], | ||||
|             [self.user.email], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"], | ||||
|             [self.user.email], | ||||
|         ) | ||||
|  | ||||
							
								
								
									
										70
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										70
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @ -379,11 +379,11 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "automat" | ||||
| version = "24.8.1" | ||||
| version = "25.4.16" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/8d/2d/ede4ad7fc34ab4482389fa3369d304f2fa22e50770af706678f6a332fa82/automat-24.8.1.tar.gz", hash = "sha256:b34227cf63f6325b8ad2399ede780675083e439b20c323d376373d8ee6306d88", size = 128679 } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/e3/0f/d40bbe294bbf004d436a8bcbcfaadca8b5140d39ad0ad3d73d1a8ba15f14/automat-25.4.16.tar.gz", hash = "sha256:0017591a5477066e90d26b0e696ddc143baafd87b588cfac8100bc6be9634de0", size = 129977 } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/af/cc/55a32a2c98022d88812b5986d2a92c4ff3ee087e83b712ebc703bba452bf/Automat-24.8.1-py3-none-any.whl", hash = "sha256:bf029a7bc3da1e2c24da2343e7598affaa9f10bf0ab63ff808566ce90551e02a", size = 42585 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/02/ff/1175b0b7371e46244032d43a56862d0af455823b5280a50c63d99cc50f18/automat-25.4.16-py3-none-any.whl", hash = "sha256:04e9bce696a8d5671ee698005af6e5a9fa15354140a87f4870744604dcdd3ba1", size = 42842 }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -558,30 +558,30 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "boto3" | ||||
| version = "1.37.34" | ||||
| version = "1.37.35" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "botocore" }, | ||||
|     { name = "jmespath" }, | ||||
|     { name = "s3transfer" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/39/5d/6b1ca20ba4da350799509a69f2d295ae11d5ec08a98e82f74b5708a8180c/boto3-1.37.34.tar.gz", hash = "sha256:94ca07328474db3fa605eb99b011512caa73f7161740d365a1f00cfebfb6dd90", size = 111701 } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/48/5f/e356ecd2f236e6ddc7711eaf3f075c15b13e2d044cfdb47719d49c4ae7dd/boto3-1.37.35.tar.gz", hash = "sha256:751ed599c8fd9ca24896edcd6620e8a32b3db1b68efea3a90126312240e668a2", size = 111640 } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/cb/2e/ad43d1e87d46d11dcf4104f97b9a7f6beb38a52a0e752edfadf3eb8b6e38/boto3-1.37.34-py3-none-any.whl", hash = "sha256:586bfa72a00601c04067f9adcbb08ecaf63b05b7d731103f33cb2ce0d6950b1b", size = 139920 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/f6/e4/00958f65ac74ab0a76af33f16c8fdf5726a5c6f0d3c0d0c058ff0dd00fd7/boto3-1.37.35-py3-none-any.whl", hash = "sha256:5a90d674830adbaf86456d6b27a18f5f11378277da5286511fa860d2e7b14261", size = 139922 }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "botocore" | ||||
| version = "1.37.34" | ||||
| version = "1.37.35" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "jmespath" }, | ||||
|     { name = "python-dateutil" }, | ||||
|     { name = "urllib3" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/ca/60/9ec251a0e2d3994f3eac8bd9741576757c3aad189abbdec8fab6011f5a1a/botocore-1.37.34.tar.gz", hash = "sha256:2909b6dbf9c90347c71a6fa0364acee522d6a7664f13d6f7996c9dd1b1f46fac", size = 13817141 } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/64/0b/d281d74d53f7d4733402aed7a536275084fa344a2672f7ea4dbc8ebe1f1b/botocore-1.37.35.tar.gz", hash = "sha256:197a9bf8251c45b9d882c405ec0d0ab40c10e2d2a55ee66960185daec4beb6ec", size = 13821053 } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/e8/51/19fff717cc5000708c4ce3d081bb0e63ca117c6823975b33101d52fdd9f5/botocore-1.37.34-py3-none-any.whl", hash = "sha256:bd9af0db1097befd2028ba8525e32cacc04f26ccb9dbd5d48d6ecd05bc16c27a", size = 13483679 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/22/00/bf9c894f5af8e35b06ecf757d4a95883408e71c48642dc7f8760580584fd/botocore-1.37.35-py3-none-any.whl", hash = "sha256:50839212e90650d0b0fa6b8f7514876bf802f6164f2775f3abcd4d53c98bb73c", size = 13485892 }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -1726,16 +1726,16 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kombu" | ||||
| version = "5.5.2" | ||||
| version = "5.5.3" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "amqp" }, | ||||
|     { name = "tzdata" }, | ||||
|     { name = "vine" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/c8/12/7a340f48920f30d6febb65d0c4aca70ed01b29e116131152977df78a9a39/kombu-5.5.2.tar.gz", hash = "sha256:2dd27ec84fd843a4e0a7187424313f87514b344812cb98c25daddafbb6a7ff0e", size = 461522 } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/60/0a/128b65651ed8120460fc5af754241ad595eac74993115ec0de4f2d7bc459/kombu-5.5.3.tar.gz", hash = "sha256:021a0e11fcfcd9b0260ef1fb64088c0e92beb976eb59c1dfca7ddd4ad4562ea2", size = 461784 } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/af/ba/939f3db0fca87715c883e42cc93045347d61a9d519c270a38e54a06db6e1/kombu-5.5.2-py3-none-any.whl", hash = "sha256:40f3674ed19603b8a771b6c74de126dbf8879755a0337caac6602faa82d539cd", size = 209763 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/5d/35/1407fb0b2f5b07b50cbaf97fce09ad87d3bfefbf64f7171a8651cd8d2f68/kombu-5.5.3-py3-none-any.whl", hash = "sha256:5b0dbceb4edee50aa464f59469d34b97864be09111338cfb224a10b6a163909b", size = 209921 }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3185,15 +3185,15 @@ socks = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "uvicorn" | ||||
| version = "0.34.1" | ||||
| version = "0.34.2" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "click" }, | ||||
|     { name = "h11" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/86/37/dd92f1f9cedb5eaf74d9999044306e06abe65344ff197864175dbbd91871/uvicorn-0.34.1.tar.gz", hash = "sha256:af981725fc4b7ffc5cb3b0e9eda6258a90c4b52cb2a83ce567ae0a7ae1757afc", size = 76755 } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815 } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/5f/38/a5801450940a858c102a7ad9e6150146a25406a119851c993148d56ab041/uvicorn-0.34.1-py3-none-any.whl", hash = "sha256:984c3a8c7ca18ebaad15995ee7401179212c59521e67bfc390c07fa2b8d2e065", size = 62404 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483 }, | ||||
| ] | ||||
|  | ||||
| [package.optional-dependencies] | ||||
| @ -3382,33 +3382,33 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "yarl" | ||||
| version = "1.19.0" | ||||
| version = "1.20.0" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "idna" }, | ||||
|     { name = "multidict" }, | ||||
|     { name = "propcache" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/fc/4d/8a8f57caccce49573e567744926f88c6ab3ca0b47a257806d1cf88584c5f/yarl-1.19.0.tar.gz", hash = "sha256:01e02bb80ae0dbed44273c304095295106e1d9470460e773268a27d11e594892", size = 184396 } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/62/51/c0edba5219027f6eab262e139f73e2417b0f4efffa23bf562f6e18f76ca5/yarl-1.20.0.tar.gz", hash = "sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307", size = 185258 } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/b8/70/44ef8f69d61cb5123167a4dda87f6c739a833fbdb2ed52960b4e8409d65c/yarl-1.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b687c334da3ff8eab848c9620c47a253d005e78335e9ce0d6868ed7e8fd170b", size = 146855 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c3/94/38c14d6c8217cc818647689f2dd647b976ced8fea08d0ac84e3c8168252b/yarl-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b0fe766febcf523a2930b819c87bb92407ae1368662c1bc267234e79b20ff894", size = 97523 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/35/a5/43a613586a6255105c4655a911c307ef3420e49e540d6ae2c5829863fb25/yarl-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:742ceffd3c7beeb2b20d47cdb92c513eef83c9ef88c46829f88d5b06be6734ee", size = 95540 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/d4/60/ed26049f4a8b06ebfa6d5f3cb6a51b152fd57081aa818b6497474f65a631/yarl-1.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2af682a1e97437382ee0791eacbf540318bd487a942e068e7e0a6c571fadbbd3", size = 344386 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/49/a6/b84899cab411f49af5986cfb44b514040788d81c8084f5811e6a7c0f1ce6/yarl-1.19.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:63702f1a098d0eaaea755e9c9d63172be1acb9e2d4aeb28b187092bcc9ca2d17", size = 338889 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/cc/ce/0704f7166a781b1f81bdd45c4f49eadbae0230ebd35b9ec7cd7769d3a6ff/yarl-1.19.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3560dcba3c71ae7382975dc1e912ee76e50b4cd7c34b454ed620d55464f11876", size = 353107 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/75/e5/0ecd6f2a9cc4264c16d8dfb0d3d71ba8d03cb58f3bcd42b1df4358331189/yarl-1.19.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68972df6a0cc47c8abaf77525a76ee5c5f6ea9bbdb79b9565b3234ded3c5e675", size = 353128 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/ad/c7/cd0fd1de581f1c2e8f996e704c9fd979e00106f18eebd91b0173cf1a13c6/yarl-1.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5684e7ff93ea74e47542232bd132f608df4d449f8968fde6b05aaf9e08a140f9", size = 349107 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e6/34/ba3e5a20bd1d6a09034fc7985aaf1309976f2a7a5aefd093c9e56f6e1e0c/yarl-1.19.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8182ad422bfacdebd4759ce3adc6055c0c79d4740aea1104e05652a81cd868c6", size = 335144 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/1e/98/d9b7beb932fade015906efe0980aa7d522b8f93cf5ebf1082e74faa314b7/yarl-1.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aee5b90a5a9b71ac57400a7bdd0feaa27c51e8f961decc8d412e720a004a1791", size = 360795 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/9a/11/70b8770039cc54af5948970591517a1e1d093df3f04f328c655c9a0fefb7/yarl-1.19.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8c0b2371858d5a814b08542d5d548adb03ff2d7ab32f23160e54e92250961a72", size = 360140 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/d4/67/708e3e36fafc4d9d96b4eecc6c8b9f37c8ad50df8a16c7a1d5ba9df53050/yarl-1.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd430c2b7df4ae92498da09e9b12cad5bdbb140d22d138f9e507de1aa3edfea3", size = 364431 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c3/8b/937fbbcc895553a7e16fcd86ae4e0724c6ac9468237ad8e7c29cc3b1c9d9/yarl-1.19.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a93208282c0ccdf73065fd76c6c129bd428dba5ff65d338ae7d2ab27169861a0", size = 373832 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/f8/ca/288ddc2230c9b6647fe907504f1119adb41252ac533eb564d3fc73511215/yarl-1.19.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b8179280cdeb4c36eb18d6534a328f9d40da60d2b96ac4a295c5f93e2799e9d9", size = 378122 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/4f/5a/79e1ef31d14968fbfc0ecec70a6683b574890d9c7550c376dd6d40de7754/yarl-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eda3c2b42dc0c389b7cfda2c4df81c12eeb552019e0de28bde8f913fc3d1fcf3", size = 375178 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/95/38/9b0e56bf14026c3f550ad6425679f6d1a2f4821d70767f39d6f4c56a0820/yarl-1.19.0-cp312-cp312-win32.whl", hash = "sha256:57f3fed859af367b9ca316ecc05ce79ce327d6466342734305aa5cc380e4d8be", size = 86172 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b3/96/5c2f3987c4bb4e5cdebea3caf99a45946b13a9516f849c02222203d99860/yarl-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:5507c1f7dd3d41251b67eecba331c8b2157cfd324849879bebf74676ce76aff7", size = 92617 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/a4/06/ae25a353e8f032322df6f30d6bb1fc329773ee48e1a80a2196ccb8d1206b/yarl-1.19.0-py3-none-any.whl", hash = "sha256:a727101eb27f66727576630d02985d8a065d09cd0b5fcbe38a5793f71b2a97ef", size = 45990 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c3/e8/3efdcb83073df978bb5b1a9cc0360ce596680e6c3fac01f2a994ccbb8939/yarl-1.20.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e06b9f6cdd772f9b665e5ba8161968e11e403774114420737f7884b5bd7bdf6f", size = 147089 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/60/c3/9e776e98ea350f76f94dd80b408eaa54e5092643dbf65fd9babcffb60509/yarl-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9ae2fbe54d859b3ade40290f60fe40e7f969d83d482e84d2c31b9bff03e359e", size = 97706 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/0c/5b/45cdfb64a3b855ce074ae607b9fc40bc82e7613b94e7612b030255c93a09/yarl-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d12b8945250d80c67688602c891237994d203d42427cb14e36d1a732eda480e", size = 95719 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/2d/4e/929633b249611eeed04e2f861a14ed001acca3ef9ec2a984a757b1515889/yarl-1.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087e9731884621b162a3e06dc0d2d626e1542a617f65ba7cc7aeab279d55ad33", size = 343972 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/49/fd/047535d326c913f1a90407a3baf7ff535b10098611eaef2c527e32e81ca1/yarl-1.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69df35468b66c1a6e6556248e6443ef0ec5f11a7a4428cf1f6281f1879220f58", size = 339639 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/48/2f/11566f1176a78f4bafb0937c0072410b1b0d3640b297944a6a7a556e1d0b/yarl-1.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2992fe29002fd0d4cbaea9428b09af9b8686a9024c840b8a2b8f4ea4abc16f", size = 353745 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/26/17/07dfcf034d6ae8837b33988be66045dd52f878dfb1c4e8f80a7343f677be/yarl-1.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c903e0b42aab48abfbac668b5a9d7b6938e721a6341751331bcd7553de2dcae", size = 354178 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/15/45/212604d3142d84b4065d5f8cab6582ed3d78e4cc250568ef2a36fe1cf0a5/yarl-1.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf099e2432131093cc611623e0b0bcc399b8cddd9a91eded8bfb50402ec35018", size = 349219 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e6/e0/a10b30f294111c5f1c682461e9459935c17d467a760c21e1f7db400ff499/yarl-1.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7f62f5dc70a6c763bec9ebf922be52aa22863d9496a9a30124d65b489ea672", size = 337266 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/33/a6/6efa1d85a675d25a46a167f9f3e80104cde317dfdf7f53f112ae6b16a60a/yarl-1.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54ac15a8b60382b2bcefd9a289ee26dc0920cf59b05368c9b2b72450751c6eb8", size = 360873 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/77/67/c8ab718cb98dfa2ae9ba0f97bf3cbb7d45d37f13fe1fbad25ac92940954e/yarl-1.20.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:25b3bc0763a7aca16a0f1b5e8ef0f23829df11fb539a1b70476dcab28bd83da7", size = 360524 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/bd/e8/c3f18660cea1bc73d9f8a2b3ef423def8dadbbae6c4afabdb920b73e0ead/yarl-1.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2586e36dc070fc8fad6270f93242124df68b379c3a251af534030a4a33ef594", size = 365370 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c9/99/33f3b97b065e62ff2d52817155a89cfa030a1a9b43fee7843ef560ad9603/yarl-1.20.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:866349da9d8c5290cfefb7fcc47721e94de3f315433613e01b435473be63daa6", size = 373297 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/3d/89/7519e79e264a5f08653d2446b26d4724b01198a93a74d2e259291d538ab1/yarl-1.20.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:33bb660b390a0554d41f8ebec5cd4475502d84104b27e9b42f5321c5192bfcd1", size = 378771 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/3a/58/6c460bbb884abd2917c3eef6f663a4a873f8dc6f498561fc0ad92231c113/yarl-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737e9f171e5a07031cbee5e9180f6ce21a6c599b9d4b2c24d35df20a52fabf4b", size = 375000 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/3b/2a/dd7ed1aa23fea996834278d7ff178f215b24324ee527df53d45e34d21d28/yarl-1.20.0-cp312-cp312-win32.whl", hash = "sha256:839de4c574169b6598d47ad61534e6981979ca2c820ccb77bf70f4311dd2cc64", size = 86355 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/ca/c6/333fe0338305c0ac1c16d5aa7cc4841208d3252bbe62172e0051006b5445/yarl-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7dbbe44b443b0c4aa0971cb07dcb2c2060e4a9bf8d1301140a33a93c98e18c", size = 92904 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/ea/1f/70c57b3d7278e94ed22d85e09685d3f0a38ebdd8c5c73b65ba4c0d0fe002/yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124", size = 46124 }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
|  | ||||
| @ -1,23 +0,0 @@ | ||||
| { | ||||
|     "arrowParens": "always", | ||||
|     "bracketSpacing": true, | ||||
|     "embeddedLanguageFormatting": "auto", | ||||
|     "htmlWhitespaceSensitivity": "css", | ||||
|     "insertPragma": false, | ||||
|     "jsxSingleQuote": false, | ||||
|     "printWidth": 100, | ||||
|     "proseWrap": "preserve", | ||||
|     "quoteProps": "consistent", | ||||
|     "requirePragma": false, | ||||
|     "semi": true, | ||||
|     "singleQuote": false, | ||||
|     "tabWidth": 4, | ||||
|     "trailingComma": "all", | ||||
|     "useTabs": false, | ||||
|     "vueIndentScriptAndStyle": false, | ||||
|     "plugins": ["@trivago/prettier-plugin-sort-imports"], | ||||
|     "importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"], | ||||
|     "importOrderSeparation": true, | ||||
|     "importOrderSortSpecifiers": true, | ||||
|     "importOrderParserPlugins": ["typescript", "jsx", "classProperties", "decorators-legacy"] | ||||
| } | ||||
							
								
								
									
										114
									
								
								web/README.md
									
									
									
									
									
								
							
							
						
						
									
										114
									
								
								web/README.md
									
									
									
									
									
								
							| @ -16,16 +16,16 @@ three contexts in which to run. | ||||
|  | ||||
| The three contexts corresponds to objects in the API's `model` section, so let's use those names. | ||||
|  | ||||
| -   The root `Config`. The root configuration object of the server, containing mostly caching and | ||||
|     error reporting information. This is misleading, however; the `Config` object contains some user | ||||
|     information, specifically a list of permissions the current user (or "no user") has. | ||||
| -   The root `CurrentTenant`. This describes the `Brand` information UIs should use, such as themes, | ||||
|     logos, favicon, and specific default flows for logging in, logging out, and recovering a user | ||||
|     password. | ||||
| -   The current `SessionUser`, the person logged in: username, display name, and various states. | ||||
|     (Note: the authentik server permits administrators to "impersonate" any other user in order to | ||||
|     debug their authentikation experience. If impersonation is active, the `user` field reflects that | ||||
|     user, but it also includes a field, `original`, with the administrator's information.) | ||||
| - The root `Config`. The root configuration object of the server, containing mostly caching and | ||||
|   error reporting information. This is misleading, however; the `Config` object contains some user | ||||
|   information, specifically a list of permissions the current user (or "no user") has. | ||||
| - The root `CurrentTenant`. This describes the `Brand` information UIs should use, such as themes, | ||||
|   logos, favicon, and specific default flows for logging in, logging out, and recovering a user | ||||
|   password. | ||||
| - The current `SessionUser`, the person logged in: username, display name, and various states. | ||||
|   (Note: the authentik server permits administrators to "impersonate" any other user in order to | ||||
|   debug their authentikation experience. If impersonation is active, the `user` field reflects that | ||||
|   user, but it also includes a field, `original`, with the administrator's information.) | ||||
|  | ||||
| (There is a fourth context object, Version, but its use is limited to displaying version information | ||||
| and checking for upgrades. Just be aware that you will see it, but you will probably never interact | ||||
| @ -36,55 +36,55 @@ insides are provided by third-party libraries (Patternfly and Rapidoc, respectiv | ||||
| three are actual applications. The descriptions below are wholly from the view of the user's | ||||
| experience: | ||||
|  | ||||
| -   `Flow`: From a given URL, displays a form that requests information from the user to accomplish a | ||||
|     task. Some tasks require the user to be logged in, but many (such as logging in itself!) | ||||
|     obviously do not. | ||||
| -   `User`: Provides the user with access to the applications they can access, plus a few user | ||||
|     settings. | ||||
| -   `Admin`: Provides someone with super-user permissions access to the administrative functions of | ||||
|     the authentik server. | ||||
| - `Flow`: From a given URL, displays a form that requests information from the user to accomplish a | ||||
|   task. Some tasks require the user to be logged in, but many (such as logging in itself!) | ||||
|   obviously do not. | ||||
| - `User`: Provides the user with access to the applications they can access, plus a few user | ||||
|   settings. | ||||
| - `Admin`: Provides someone with super-user permissions access to the administrative functions of | ||||
|   the authentik server. | ||||
|  | ||||
| **Mental Model** | ||||
|  | ||||
| -   Upon initialization, _every_ authentik UI application fetches `Config` and `CurrentTenant`. `User` | ||||
|     and `Admin` will also attempt to load the `SessionUser`; if there is none, the user is kicked out | ||||
|     to the `Flow` for logging into authentik itself. | ||||
| -   `Config`, `CurrentTenant`, and `SessionUser`, are provided by the `@goauthentik/api` application, | ||||
|     not by the codebase under `./web`. (Where you are now). | ||||
| -   `Flow`, `User`, and `Admin` are all called `Interfaces` and are found in | ||||
|     `./web/src/flow/FlowInterface`, `./web/src/user/UserInterface`, `./web/src/admin/AdminInterface`, | ||||
|     respectively. | ||||
| - Upon initialization, _every_ authentik UI application fetches `Config` and `CurrentTenant`. `User` | ||||
|   and `Admin` will also attempt to load the `SessionUser`; if there is none, the user is kicked out | ||||
|   to the `Flow` for logging into authentik itself. | ||||
| - `Config`, `CurrentTenant`, and `SessionUser`, are provided by the `@goauthentik/api` application, | ||||
|   not by the codebase under `./web`. (Where you are now). | ||||
| - `Flow`, `User`, and `Admin` are all called `Interfaces` and are found in | ||||
|   `./web/src/flow/FlowInterface`, `./web/src/user/UserInterface`, `./web/src/admin/AdminInterface`, | ||||
|   respectively. | ||||
|  | ||||
| Inside each of these you will find, in a hierarchal order: | ||||
|  | ||||
| -   The context layer described above | ||||
|     -   A theme managing layer | ||||
|     -   The orchestration layer: | ||||
|         -   web socket handler for server-generated events | ||||
|         -   The router | ||||
|             -   Individual routes for each vertical slice and its relationship to other objects: | ||||
| - The context layer described above | ||||
|     - A theme managing layer | ||||
|     - The orchestration layer: | ||||
|         - web socket handler for server-generated events | ||||
|         - The router | ||||
|             - Individual routes for each vertical slice and its relationship to other objects: | ||||
|  | ||||
| Each slice corresponds to an object table on the server, and each slice _usually_ consists of the | ||||
| following: | ||||
|  | ||||
| -   A paginated collection display, usually using the `Table` foundation (found in | ||||
|     `./web/src/elements/Table`) | ||||
| -   The ability to view an individual object from the collection, which you may be able to: | ||||
|     -   Edit | ||||
|     -   Delete | ||||
| -   A form for creating a new object | ||||
| -   Tabs showing that object's relationship to other objects | ||||
|     -   Interactive elements for changing or deleting those relationships, or creating new ones. | ||||
|     -   The ability to create new objects with which to have that relationship, if they're not part of | ||||
|         the core objects (such as User->MFA authenticator apps, since the latter is not a "core" object | ||||
|         and has no tab of its own). | ||||
| - A paginated collection display, usually using the `Table` foundation (found in | ||||
|   `./web/src/elements/Table`) | ||||
| - The ability to view an individual object from the collection, which you may be able to: | ||||
|     - Edit | ||||
|     - Delete | ||||
| - A form for creating a new object | ||||
| - Tabs showing that object's relationship to other objects | ||||
|     - Interactive elements for changing or deleting those relationships, or creating new ones. | ||||
|     - The ability to create new objects with which to have that relationship, if they're not part of | ||||
|       the core objects (such as User->MFA authenticator apps, since the latter is not a "core" object | ||||
|       and has no tab of its own). | ||||
|  | ||||
| We are still a bit "all over the place" with respect to sub-units and common units; there are | ||||
| folders `common`, `elements`, and `components`, and ideally they would be: | ||||
|  | ||||
| -   `common`: non-UI related libraries all of our applications need | ||||
| -   `elements`: UI elements shared among multiple applications that do not need context | ||||
| -   `components`: UI elements shared among multiple that use one or more context | ||||
| - `common`: non-UI related libraries all of our applications need | ||||
| - `elements`: UI elements shared among multiple applications that do not need context | ||||
| - `components`: UI elements shared among multiple that use one or more context | ||||
|  | ||||
| ... but at the moment there are some context-sensitive elements, and some UI-related stuff in | ||||
| `common`. | ||||
| @ -95,18 +95,18 @@ folders `common`, `elements`, and `components`, and ideally they would be: | ||||
| reliably documented any other way. For the most part, they contain comments related to custom | ||||
| settings in JSON files, which do not support comments. | ||||
|  | ||||
| -   `tsconfig.json`: | ||||
|     -   `compilerOptions.useDefineForClassFields: false` is required to make TSC use the "classic" form | ||||
|         of field definition when compiling class definitions. Storybook does not handle the ESNext | ||||
|         proposed definition mechanism (yet). | ||||
|     -   `compilerOptions.plugins.ts-lit-plugin.rules.no-unknown-tag-name: "off"`: required to support | ||||
|         rapidoc, which exports its tag late. | ||||
|     -   `compilerOptions.plugins.ts-lit-plugin.rules.no-missing-import: "off"`: lit-analyzer currently | ||||
|         does not support path aliases very well, and cannot find the definition files associated with | ||||
|         imports using them. | ||||
|     -   `compilerOptions.plugins.ts-lit-plugin.rules.no-incompatible-type-binding: "warn"`: lit-analyzer | ||||
|         does not support generics well when parsing a subtype of `HTMLElement`. As a result, this threw | ||||
|         too many errors to be supportable. | ||||
| - `tsconfig.json`: | ||||
|     - `compilerOptions.useDefineForClassFields: false` is required to make TSC use the "classic" form | ||||
|       of field definition when compiling class definitions. Storybook does not handle the ESNext | ||||
|       proposed definition mechanism (yet). | ||||
|     - `compilerOptions.plugins.ts-lit-plugin.rules.no-unknown-tag-name: "off"`: required to support | ||||
|       rapidoc, which exports its tag late. | ||||
|     - `compilerOptions.plugins.ts-lit-plugin.rules.no-missing-import: "off"`: lit-analyzer currently | ||||
|       does not support path aliases very well, and cannot find the definition files associated with | ||||
|       imports using them. | ||||
|     - `compilerOptions.plugins.ts-lit-plugin.rules.no-incompatible-type-binding: "warn"`: lit-analyzer | ||||
|       does not support generics well when parsing a subtype of `HTMLElement`. As a result, this threw | ||||
|       too many errors to be supportable. | ||||
|  | ||||
| ### License | ||||
|  | ||||
|  | ||||
							
								
								
									
										918
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										918
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,14 +1,6 @@ | ||||
| { | ||||
|     "name": "@goauthentik/web", | ||||
|     "version": "0.0.0", | ||||
|     "overrides": { | ||||
|         "rapidoc": { | ||||
|             "@apitools/openapi-parser@": "0.0.37" | ||||
|         }, | ||||
|         "chromedriver": { | ||||
|             "axios": "^1.8.4" | ||||
|         } | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@codemirror/lang-css": "^6.3.1", | ||||
|         "@codemirror/lang-html": "^6.4.9", | ||||
| @ -20,8 +12,8 @@ | ||||
|         "@floating-ui/dom": "^1.6.11", | ||||
|         "@formatjs/intl-listformat": "^7.5.7", | ||||
|         "@fortawesome/fontawesome-free": "^6.6.0", | ||||
|         "@goauthentik/api": "^2025.2.4-1744640358", | ||||
|         "@lit-labs/ssr": "^3.2.2", | ||||
|         "@goauthentik/api": "^2025.2.4-1745519715", | ||||
|         "@lit-labs/ssr": "3.2.2", | ||||
|         "@lit/context": "^1.1.2", | ||||
|         "@lit/localize": "^0.12.2", | ||||
|         "@lit/reactive-element": "^2.0.4", | ||||
| @ -69,6 +61,9 @@ | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@eslint/js": "^9.11.1", | ||||
|         "@goauthentik/esbuild-plugin-live-reload": "^1.0.4", | ||||
|         "@goauthentik/prettier-config": "^1.0.4", | ||||
|         "@goauthentik/tsconfig": "^1.0.4", | ||||
|         "@hcaptcha/types": "^1.0.4", | ||||
|         "@lit/localize-tools": "^0.8.0", | ||||
|         "@rollup/plugin-replace": "^6.0.1", | ||||
| @ -80,7 +75,7 @@ | ||||
|         "@storybook/manager-api": "^8.3.4", | ||||
|         "@storybook/web-components": "^8.3.4", | ||||
|         "@storybook/web-components-vite": "^8.3.4", | ||||
|         "@trivago/prettier-plugin-sort-imports": "^4.3.0", | ||||
|         "@trivago/prettier-plugin-sort-imports": "^5.2.2", | ||||
|         "@types/chart.js": "^2.9.41", | ||||
|         "@types/codemirror": "^5.60.15", | ||||
|         "@types/dompurify": "^3.0.5", | ||||
| @ -103,7 +98,6 @@ | ||||
|         "eslint": "^9.11.1", | ||||
|         "eslint-plugin-lit": "^1.15.0", | ||||
|         "eslint-plugin-wc": "^2.1.1", | ||||
|         "find-free-ports": "^3.1.1", | ||||
|         "github-slugger": "^2.0.0", | ||||
|         "glob": "^11.0.0", | ||||
|         "globals": "^15.10.0", | ||||
| @ -136,6 +130,15 @@ | ||||
|         "@rollup/rollup-linux-arm64-gnu": "4.23.0", | ||||
|         "@rollup/rollup-linux-x64-gnu": "4.23.0" | ||||
|     }, | ||||
|     "overrides": { | ||||
|         "rapidoc": { | ||||
|             "@apitools/openapi-parser@": "0.0.37" | ||||
|         }, | ||||
|         "chromedriver": { | ||||
|             "axios": "^1.8.4" | ||||
|         } | ||||
|     }, | ||||
|     "prettier": "@goauthentik/prettier-config", | ||||
|     "private": true, | ||||
|     "scripts": { | ||||
|         "build": "wireit", | ||||
| @ -271,7 +274,7 @@ | ||||
|             "command": "tsc --noEmit -p ./tests" | ||||
|         }, | ||||
|         "lint:types": { | ||||
|             "command": "tsc --noEmit -p .", | ||||
|             "command": "NODE_OPTIONS=\"--max-old-space-size=3000\" tsc -b .", | ||||
|             "dependencies": [ | ||||
|                 "build-locales", | ||||
|                 "lint:types:tests" | ||||
|  | ||||
| @ -1,13 +1,18 @@ | ||||
| /// <reference types="./types.js" />
 | ||||
| 
 | ||||
| /** | ||||
|  * @file | ||||
|  * Client-side observer for ESBuild events. | ||||
|  * @file Client-side observer for ESBuild events. | ||||
|  * | ||||
|  * @import { Message as ESBuildMessage } from "esbuild"; | ||||
|  */ | ||||
| import type { Message as ESBuildMessage } from "esbuild"; | ||||
| 
 | ||||
| const logPrefix = "👷 [ESBuild]"; | ||||
| const log = console.debug.bind(console, logPrefix); | ||||
| 
 | ||||
| type BuildEventListener<Data = unknown> = (event: MessageEvent<Data>) => void; | ||||
| /** | ||||
|  * @template {unknown} [Data=unknown] | ||||
|  * @typedef {(event: MessageEvent) => void} BuildEventListener | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * A client-side watcher for ESBuild. | ||||
| @ -16,14 +21,13 @@ type BuildEventListener<Data = unknown> = (event: MessageEvent<Data>) => void; | ||||
|  * ESBuild may tree-shake it out of production builds. | ||||
|  * | ||||
|  * ```ts
 | ||||
|  * if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) { | ||||
|  *   const { ESBuildObserver } = await import("@goauthentik/common/client"); | ||||
|  * | ||||
|  *   new ESBuildObserver(process.env.WATCHER_URL); | ||||
|  * if (process.env.NODE_ENV === "development") { | ||||
|  *   await import("@goauthentik/esbuild-plugin-live-reload/client") | ||||
|  *     .catch(() => console.warn("Failed to import watcher")) | ||||
|  * } | ||||
|  * ``` | ||||
| } | ||||
| 
 | ||||
|  * | ||||
|  * @implements {Disposable} | ||||
|  */ | ||||
| export class ESBuildObserver extends EventSource { | ||||
|     /** | ||||
| @ -58,15 +62,19 @@ export class ESBuildObserver extends EventSource { | ||||
| 
 | ||||
|     /** | ||||
|      * The interval for the keep-alive check. | ||||
|      * @type {ReturnType<typeof setInterval> | undefined} | ||||
|      */ | ||||
|     #keepAliveInterval: ReturnType<typeof setInterval> | undefined; | ||||
|     #keepAliveInterval; | ||||
| 
 | ||||
|     #trackActivity = () => { | ||||
|         this.lastUpdatedAt = Date.now(); | ||||
|         this.alive = true; | ||||
|     }; | ||||
| 
 | ||||
|     #startListener: BuildEventListener = () => { | ||||
|     /** | ||||
|      * @type {BuildEventListener} | ||||
|      */ | ||||
|     #startListener = () => { | ||||
|         this.#trackActivity(); | ||||
|         log("⏰  Build started..."); | ||||
|     }; | ||||
| @ -82,13 +90,18 @@ export class ESBuildObserver extends EventSource { | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     #errorListener: BuildEventListener<string> = (event) => { | ||||
|     /** | ||||
|      * @type {BuildEventListener<string>} | ||||
|      */ | ||||
|     #errorListener = (event) => { | ||||
|         this.#trackActivity(); | ||||
| 
 | ||||
|         // eslint-disable-next-line no-console
 | ||||
|         console.group(logPrefix, "⛔️⛔️⛔️  Build error..."); | ||||
| 
 | ||||
|         const esbuildErrorMessages: ESBuildMessage[] = JSON.parse(event.data); | ||||
|         /** | ||||
|          * @type {ESBuildMessage[]} | ||||
|          */ | ||||
|         const esbuildErrorMessages = JSON.parse(event.data); | ||||
| 
 | ||||
|         for (const error of esbuildErrorMessages) { | ||||
|             console.warn(error.text); | ||||
| @ -101,11 +114,13 @@ export class ESBuildObserver extends EventSource { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // eslint-disable-next-line no-console
 | ||||
|         console.groupEnd(); | ||||
|     }; | ||||
| 
 | ||||
|     #endListener: BuildEventListener = () => { | ||||
|     /** | ||||
|      * @type {BuildEventListener} | ||||
|      */ | ||||
|     #endListener = () => { | ||||
|         cancelAnimationFrame(this.#reloadFrameID); | ||||
| 
 | ||||
|         this.#trackActivity(); | ||||
| @ -126,12 +141,36 @@ export class ESBuildObserver extends EventSource { | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     #keepAliveListener: BuildEventListener = () => { | ||||
|     /** | ||||
|      * @type {BuildEventListener} | ||||
|      */ | ||||
|     #keepAliveListener = () => { | ||||
|         this.#trackActivity(); | ||||
|         log("🏓 Keep-alive"); | ||||
|     }; | ||||
| 
 | ||||
|     constructor(url: string | URL) { | ||||
|     /** | ||||
|      * Initialize the ESBuild observer. | ||||
|      * This should be called once in your application. | ||||
|      * | ||||
|      * @param {string | URL} [url] | ||||
|      * @returns {ESBuildObserver} | ||||
|      */ | ||||
|     static initialize = (url) => { | ||||
|         const esbuildObserver = new ESBuildObserver(url); | ||||
| 
 | ||||
|         return esbuildObserver; | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @param {string | URL} [url] | ||||
|      */ | ||||
|     constructor(url) { | ||||
|         if (!url) { | ||||
|             throw new TypeError("ESBuildObserver: Cannot construct without a URL"); | ||||
|         } | ||||
| 
 | ||||
|         super(url); | ||||
| 
 | ||||
|         this.addEventListener("esbuild:start", this.#startListener); | ||||
| @ -167,4 +206,14 @@ export class ESBuildObserver extends EventSource { | ||||
|             log("👋  Waiting for build to start..."); | ||||
|         }, 15_000); | ||||
|     } | ||||
| 
 | ||||
|     [Symbol.dispose]() { | ||||
|         return this.close(); | ||||
|     } | ||||
| 
 | ||||
|     dispose() { | ||||
|         return this[Symbol.dispose](); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default ESBuildObserver; | ||||
							
								
								
									
										13
									
								
								web/packages/esbuild-plugin-live-reload/client/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/packages/esbuild-plugin-live-reload/client/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| /// <reference types="./types.js" /> | ||||
| /** | ||||
|  * @file Entry point for the ESBuild client-side observer. | ||||
|  */ | ||||
| import { ESBuildObserver } from "./ESBuildObserver.js"; | ||||
|  | ||||
| if (import.meta.env?.ESBUILD_WATCHER_URL) { | ||||
|     const buildObserver = new ESBuildObserver(import.meta.env.ESBUILD_WATCHER_URL); | ||||
|  | ||||
|     window.addEventListener("beforeunload", () => { | ||||
|         buildObserver.dispose(); | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										18
									
								
								web/packages/esbuild-plugin-live-reload/client/types.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/packages/esbuild-plugin-live-reload/client/types.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| /** | ||||
|  * @file Import meta environment variables available via ESBuild. | ||||
|  */ | ||||
|  | ||||
| export {}; | ||||
| declare global { | ||||
|     interface ImportMeta { | ||||
|         readonly env: { | ||||
|             /** | ||||
|              * The injected watcher URL for ESBuild. | ||||
|              * This is used for live reloading in development mode. | ||||
|              * | ||||
|              * @format url | ||||
|              */ | ||||
|             ESBUILD_WATCHER_URL: string; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										2
									
								
								web/packages/esbuild-plugin-live-reload/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								web/packages/esbuild-plugin-live-reload/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| export * from "./client/index.js"; | ||||
| export * from "./plugin/index.js"; | ||||
							
								
								
									
										53
									
								
								web/packages/esbuild-plugin-live-reload/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								web/packages/esbuild-plugin-live-reload/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| { | ||||
|     "name": "@goauthentik/esbuild-plugin-live-reload", | ||||
|     "description": "ESBuild plugin to watch for file changes and trigger client-side reloads.", | ||||
|     "version": "1.0.4", | ||||
|     "dependencies": { | ||||
|         "find-free-ports": "^3.1.1" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@goauthentik/prettier-config": "^1.0.4", | ||||
|         "@goauthentik/tsconfig": "^1.0.4", | ||||
|         "@trivago/prettier-plugin-sort-imports": "^5.2.2", | ||||
|         "@types/node": "^22.14.1", | ||||
|         "esbuild": "^0.25.0", | ||||
|         "prettier": "^3.3.3", | ||||
|         "typescript": "^5.6.2" | ||||
|     }, | ||||
|     "engines": { | ||||
|         "node": ">=20.11" | ||||
|     }, | ||||
|     "exports": { | ||||
|         "./package.json": "./package.json", | ||||
|         ".": { | ||||
|             "types": "./out/index.d.ts", | ||||
|             "import": "./index.js" | ||||
|         }, | ||||
|         "./client": { | ||||
|             "types": "./out/client/index.d.ts", | ||||
|             "import": "./client/index.js" | ||||
|         }, | ||||
|         "./plugin": { | ||||
|             "types": "./out/plugin/index.d.ts", | ||||
|             "import": "./plugin/index.js" | ||||
|         } | ||||
|     }, | ||||
|     "files": [ | ||||
|         "./index.js", | ||||
|         "client/**/*", | ||||
|         "plugin/**/*", | ||||
|         "out/**/*" | ||||
|     ], | ||||
|     "license": "MIT", | ||||
|     "main": "index.js", | ||||
|     "peerDependencies": { | ||||
|         "esbuild": "^0.25.0" | ||||
|     }, | ||||
|     "prettier": "@goauthentik/prettier-config", | ||||
|     "private": true, | ||||
|     "publishConfig": { | ||||
|         "access": "public" | ||||
|     }, | ||||
|     "type": "module", | ||||
|     "types": "./out/index.d.ts" | ||||
| } | ||||
							
								
								
									
										243
									
								
								web/packages/esbuild-plugin-live-reload/plugin/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								web/packages/esbuild-plugin-live-reload/plugin/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,243 @@ | ||||
| /** | ||||
|  * @file Live reload plugin for ESBuild. | ||||
|  * | ||||
|  * @import { ListenOptions } from "node:net"; | ||||
|  * @import {Server as HTTPServer} from "node:http"; | ||||
|  * @import {Server as HTTPSServer} from "node:https"; | ||||
|  */ | ||||
| import { findFreePorts } from "find-free-ports"; | ||||
| import * as http from "node:http"; | ||||
| import * as path from "node:path"; | ||||
|  | ||||
| /** | ||||
|  * Serializes a custom event to a text stream. | ||||
|  * @param {Event} event | ||||
|  * @returns {string} | ||||
|  */ | ||||
| export function serializeCustomEventToStream(event) { | ||||
|     // @ts-expect-error - TS doesn't know about the detail property | ||||
|     const data = event.detail ?? {}; | ||||
|  | ||||
|     const eventContent = [`event: ${event.type}`, `data: ${JSON.stringify(data)}`]; | ||||
|  | ||||
|     return eventContent.join("\n") + "\n\n"; | ||||
| } | ||||
|  | ||||
| const MIN_PORT = 1025; | ||||
| const MAX_PORT = 65535; | ||||
|  | ||||
| /** | ||||
|  * Find a random port that is not in use, sufficiently far from the default port. | ||||
|  * @returns {Promise<number>} | ||||
|  */ | ||||
| async function findDisparatePort() { | ||||
|     const startPort = Math.floor(Math.random() * (MAX_PORT - MIN_PORT + 1)) + MIN_PORT; | ||||
|  | ||||
|     const wathcherPorts = await findFreePorts(1, { | ||||
|         startPort, | ||||
|     }); | ||||
|  | ||||
|     const [port] = wathcherPorts; | ||||
|  | ||||
|     if (!port) { | ||||
|         throw new Error("No free ports available"); | ||||
|     } | ||||
|  | ||||
|     return port; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Event server initialization options. | ||||
|  * | ||||
|  * @typedef {Object} EventServerInit | ||||
|  * | ||||
|  * @property {string} pathname | ||||
|  * @property {EventTarget} dispatcher | ||||
|  * @property {string} [logPrefix] | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @typedef {(req: http.IncomingMessage, res: http.ServerResponse) => void} RequestHandler | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Create an event request handler. | ||||
|  * @param {EventServerInit} options | ||||
|  * @returns {RequestHandler} | ||||
|  * @category ESBuild | ||||
|  */ | ||||
| export function createRequestHandler({ pathname, dispatcher, logPrefix = "Build Observer" }) { | ||||
|     // eslint-disable-next-line no-console | ||||
|     const log = console.log.bind(console, `[${logPrefix}]`); | ||||
|  | ||||
|     /** | ||||
|      * @type {RequestHandler} | ||||
|      */ | ||||
|     const requestHandler = (req, res) => { | ||||
|         res.setHeader("Access-Control-Allow-Origin", "*"); | ||||
|         res.setHeader("Access-Control-Allow-Methods", "GET"); | ||||
|         res.setHeader("Access-Control-Allow-Headers", "Content-Type"); | ||||
|  | ||||
|         if (req.url !== pathname) { | ||||
|             log(`🚫 Invalid request to ${req.url}`); | ||||
|             res.writeHead(404); | ||||
|             res.end(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         log("🔌 Client connected"); | ||||
|  | ||||
|         res.writeHead(200, { | ||||
|             "Content-Type": "text/event-stream", | ||||
|             "Cache-Control": "no-cache", | ||||
|             "Connection": "keep-alive", | ||||
|         }); | ||||
|  | ||||
|         /** | ||||
|          * @param {Event} event | ||||
|          */ | ||||
|         const listener = (event) => { | ||||
|             const body = serializeCustomEventToStream(event); | ||||
|  | ||||
|             res.write(body); | ||||
|         }; | ||||
|  | ||||
|         dispatcher.addEventListener("esbuild:start", listener); | ||||
|         dispatcher.addEventListener("esbuild:error", listener); | ||||
|         dispatcher.addEventListener("esbuild:end", listener); | ||||
|  | ||||
|         req.on("close", () => { | ||||
|             log("🔌 Client disconnected"); | ||||
|  | ||||
|             clearInterval(keepAliveInterval); | ||||
|  | ||||
|             dispatcher.removeEventListener("esbuild:start", listener); | ||||
|             dispatcher.removeEventListener("esbuild:error", listener); | ||||
|             dispatcher.removeEventListener("esbuild:end", listener); | ||||
|         }); | ||||
|  | ||||
|         const keepAliveInterval = setInterval(() => { | ||||
|             console.timeStamp("🏓 Keep-alive"); | ||||
|  | ||||
|             res.write("event: keep-alive\n\n"); | ||||
|             res.write(serializeCustomEventToStream(new CustomEvent("esbuild:keep-alive"))); | ||||
|         }, 15_000); | ||||
|     }; | ||||
|  | ||||
|     return requestHandler; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Options for the build observer plugin. | ||||
|  * | ||||
|  * @typedef {object} BuildObserverOptions | ||||
|  * | ||||
|  * @property {HTTPServer | HTTPSServer} [server] | ||||
|  * @property {ListenOptions} [listenOptions] | ||||
|  * @property {string | URL} [publicURL] | ||||
|  * @property {string} [logPrefix] | ||||
|  * @property {string} [relativeRoot] | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Creates a plugin that listens for build events and sends them to a server-sent event stream. | ||||
|  * | ||||
|  * @param {BuildObserverOptions} [options] | ||||
|  * @returns {import('esbuild').Plugin} | ||||
|  */ | ||||
| export function liveReloadPlugin(options = {}) { | ||||
|     return { | ||||
|         name: "build-watcher", | ||||
|         setup: async (build) => { | ||||
|             const logPrefix = options.logPrefix || "Build Observer"; | ||||
|  | ||||
|             const timerLabel = `[${logPrefix}] 🏁`; | ||||
|             const relativeRoot = options.relativeRoot || process.cwd(); | ||||
|  | ||||
|             const dispatcher = new EventTarget(); | ||||
|  | ||||
|             /** | ||||
|              * @type {URL} | ||||
|              */ | ||||
|             let publicURL; | ||||
|  | ||||
|             if (!options.publicURL) { | ||||
|                 const port = await findDisparatePort(); | ||||
|  | ||||
|                 publicURL = new URL(`http://localhost:${port}/events`); | ||||
|             } else { | ||||
|                 publicURL = | ||||
|                     typeof options.publicURL === "string" | ||||
|                         ? new URL(options.publicURL) | ||||
|                         : options.publicURL; | ||||
|             } | ||||
|  | ||||
|             build.initialOptions.define = { | ||||
|                 ...build.initialOptions.define, | ||||
|                 "import.meta.env.ESBUILD_WATCHER_URL": JSON.stringify(publicURL.href), | ||||
|             }; | ||||
|  | ||||
|             const requestHandler = createRequestHandler({ | ||||
|                 pathname: publicURL.pathname, | ||||
|                 dispatcher, | ||||
|                 logPrefix, | ||||
|             }); | ||||
|  | ||||
|             const server = options.server || http.createServer(requestHandler); | ||||
|  | ||||
|             const listenOptions = options.listenOptions || { | ||||
|                 port: parseInt(publicURL.port, 10), | ||||
|                 host: publicURL.hostname, | ||||
|             }; | ||||
|  | ||||
|             server.listen(listenOptions, () => { | ||||
|                 // eslint-disable-next-line no-console | ||||
|                 console.log(`[${logPrefix}] Listening`); | ||||
|             }); | ||||
|  | ||||
|             build.onDispose(() => { | ||||
|                 server?.close(); | ||||
|             }); | ||||
|  | ||||
|             build.onStart(() => { | ||||
|                 console.time(timerLabel); | ||||
|  | ||||
|                 dispatcher.dispatchEvent( | ||||
|                     new CustomEvent("esbuild:start", { | ||||
|                         detail: new Date().toISOString(), | ||||
|                     }), | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             build.onEnd((buildResult) => { | ||||
|                 console.timeEnd(timerLabel); | ||||
|  | ||||
|                 if (!buildResult.errors.length) { | ||||
|                     dispatcher.dispatchEvent( | ||||
|                         new CustomEvent("esbuild:end", { | ||||
|                             detail: new Date().toISOString(), | ||||
|                         }), | ||||
|                     ); | ||||
|  | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 console.warn(`Build ended with ${buildResult.errors.length} errors`); | ||||
|  | ||||
|                 dispatcher.dispatchEvent( | ||||
|                     new CustomEvent("esbuild:error", { | ||||
|                         detail: buildResult.errors.map((error) => ({ | ||||
|                             ...error, | ||||
|                             location: error.location | ||||
|                                 ? { | ||||
|                                       ...error.location, | ||||
|                                       file: path.resolve(relativeRoot, error.location.file), | ||||
|                                   } | ||||
|                                 : null, | ||||
|                         })), | ||||
|                     }), | ||||
|                 ); | ||||
|             }); | ||||
|         }, | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										10
									
								
								web/packages/esbuild-plugin-live-reload/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								web/packages/esbuild-plugin-live-reload/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| { | ||||
|     "extends": "@goauthentik/tsconfig", | ||||
|     "compilerOptions": { | ||||
|         "lib": ["ESNext", "DOM", "DOM.Iterable"], | ||||
|         "resolveJsonModule": true, | ||||
|         "baseUrl": ".", | ||||
|         "checkJs": true, | ||||
|         "emitDeclarationOnly": true | ||||
|     } | ||||
| } | ||||
| @ -9,11 +9,11 @@ It exists primarily to support late versions of Microsoft Office365 and Microsof | ||||
| software that still uses the MSEdge-18 and IE-11 _Trident_ web engine for web-based log-ins. It has | ||||
| limited support for the full language, supporting only the following stages: | ||||
|  | ||||
| -   identification | ||||
| -   password | ||||
| -   redirect | ||||
| -   autosubmit | ||||
| -   authenticator validation (both code and WebAuthn) | ||||
| - identification | ||||
| - password | ||||
| - redirect | ||||
| - autosubmit | ||||
| - authenticator validation (both code and WebAuthn) | ||||
|  | ||||
| ### License | ||||
|  | ||||
|  | ||||
| @ -1,8 +1,13 @@ | ||||
| /** | ||||
|  * @file ESBuild script for building the authentik web UI. | ||||
|  * | ||||
|  * @import { BuildOptions } from "esbuild"; | ||||
|  */ | ||||
| import { liveReloadPlugin } from "@goauthentik/esbuild-plugin-live-reload/plugin"; | ||||
| import { execFileSync } from "child_process"; | ||||
| import { deepmerge } from "deepmerge-ts"; | ||||
| import esbuild from "esbuild"; | ||||
| import { polyfillNode } from "esbuild-plugin-polyfill-node"; | ||||
| import findFreePorts from "find-free-ports"; | ||||
| import { copyFileSync, mkdirSync, readFileSync, statSync } from "fs"; | ||||
| import { globSync } from "glob"; | ||||
| import * as path from "path"; | ||||
| @ -11,7 +16,6 @@ import process from "process"; | ||||
| import { fileURLToPath } from "url"; | ||||
|  | ||||
| import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs"; | ||||
| import { buildObserverPlugin } from "./esbuild/build-observer-plugin.mjs"; | ||||
|  | ||||
| const __dirname = fileURLToPath(new URL(".", import.meta.url)); | ||||
| let authentikProjectRoot = path.join(__dirname, "..", ".."); | ||||
| @ -120,7 +124,7 @@ const BASE_ESBUILD_OPTIONS = { | ||||
|     splitting: true, | ||||
|     treeShaking: true, | ||||
|     external: ["*.woff", "*.woff2"], | ||||
|     tsconfig: "./tsconfig.json", | ||||
|     tsconfig: path.resolve(__dirname, "..", "tsconfig.build.json"), | ||||
|     loader: { | ||||
|         ".css": "text", | ||||
|     }, | ||||
| @ -220,26 +224,17 @@ function doHelp() { | ||||
| async function doWatch() { | ||||
|     console.log("Watching all entry points..."); | ||||
|  | ||||
|     const wathcherPorts = await findFreePorts(entryPoints.length); | ||||
|  | ||||
|     const buildContexts = await Promise.all( | ||||
|         entryPoints.map((entryPoint, i) => { | ||||
|             const port = wathcherPorts[i]; | ||||
|             const serverURL = new URL(`http://localhost:${port}/events`); | ||||
|  | ||||
|         entryPoints.map((entryPoint) => { | ||||
|             return esbuild.context( | ||||
|                 createEntryPointOptions(entryPoint, { | ||||
|                     define: definitions, | ||||
|                     plugins: [ | ||||
|                         buildObserverPlugin({ | ||||
|                             serverURL, | ||||
|                             logPrefix: entryPoint[1], | ||||
|                         liveReloadPlugin({ | ||||
|                             logPrefix: `Build Observer (${entryPoint[1]})`, | ||||
|                             relativeRoot: path.join(__dirname, ".."), | ||||
|                         }), | ||||
|                     ], | ||||
|                     define: { | ||||
|                         ...definitions, | ||||
|                         "process.env.WATCHER_URL": JSON.stringify(serverURL.toString()), | ||||
|                     }, | ||||
|                 }), | ||||
|             ); | ||||
|         }), | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user