Compare commits
	
		
			6 Commits
		
	
	
		
			providers/
			...
			files-in-d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d410083cfc | |||
| 6045f96a05 | |||
| c50df0f843 | |||
| c8ebd9f74b | |||
| b3f441f2cd | |||
| 647f054be3 | 
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -94,7 +94,7 @@ gen-build:  ## Extract the schema from the database | |||||||
| 	AUTHENTIK_DEBUG=true \ | 	AUTHENTIK_DEBUG=true \ | ||||||
| 		AUTHENTIK_TENANTS__ENABLED=true \ | 		AUTHENTIK_TENANTS__ENABLED=true \ | ||||||
| 		AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ | 		AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ | ||||||
| 		uv run ak make_blueprint_schema --file blueprints/schema.json | 		uv run ak make_blueprint_schema > blueprints/schema.json | ||||||
| 	AUTHENTIK_DEBUG=true \ | 	AUTHENTIK_DEBUG=true \ | ||||||
| 		AUTHENTIK_TENANTS__ENABLED=true \ | 		AUTHENTIK_TENANTS__ENABLED=true \ | ||||||
| 		AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ | 		AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ | ||||||
|  | |||||||
| @ -72,33 +72,20 @@ class Command(BaseCommand): | |||||||
|                     "additionalProperties": True, |                     "additionalProperties": True, | ||||||
|                 }, |                 }, | ||||||
|                 "entries": { |                 "entries": { | ||||||
|                     "anyOf": [ |  | ||||||
|                         { |  | ||||||
|                     "type": "array", |                     "type": "array", | ||||||
|                             "items": {"$ref": "#/$defs/blueprint_entry"}, |                     "items": { | ||||||
|                         }, |                         "oneOf": [], | ||||||
|                         { |  | ||||||
|                             "type": "object", |  | ||||||
|                             "additionalProperties": { |  | ||||||
|                                 "type": "array", |  | ||||||
|                                 "items": {"$ref": "#/$defs/blueprint_entry"}, |  | ||||||
|                     }, |                     }, | ||||||
|                 }, |                 }, | ||||||
|                     ], |  | ||||||
|             }, |             }, | ||||||
|             }, |             "$defs": {}, | ||||||
|             "$defs": {"blueprint_entry": {"oneOf": []}}, |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     def add_arguments(self, parser): |  | ||||||
|         parser.add_argument("--file", type=str) |  | ||||||
|  |  | ||||||
|     @no_translations |     @no_translations | ||||||
|     def handle(self, *args, file: str, **options): |     def handle(self, *args, **options): | ||||||
|         """Generate JSON Schema for blueprints""" |         """Generate JSON Schema for blueprints""" | ||||||
|         self.build() |         self.build() | ||||||
|         with open(file, "w") as _schema: |         self.stdout.write(dumps(self.schema, indent=4, default=Command.json_default)) | ||||||
|             _schema.write(dumps(self.schema, indent=4, default=Command.json_default)) |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def json_default(value: Any) -> Any: |     def json_default(value: Any) -> Any: | ||||||
| @ -125,7 +112,7 @@ class Command(BaseCommand): | |||||||
|                 } |                 } | ||||||
|             ) |             ) | ||||||
|             model_path = f"{model._meta.app_label}.{model._meta.model_name}" |             model_path = f"{model._meta.app_label}.{model._meta.model_name}" | ||||||
|             self.schema["$defs"]["blueprint_entry"]["oneOf"].append( |             self.schema["properties"]["entries"]["items"]["oneOf"].append( | ||||||
|                 self.template_entry(model_path, model, serializer) |                 self.template_entry(model_path, model, serializer) | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| version: 1 | version: 1 | ||||||
| entries: | entries: | ||||||
|   foo: |  | ||||||
|     - identifiers: |     - identifiers: | ||||||
|           name: "%(id)s" |           name: "%(id)s" | ||||||
|           slug: "%(id)s" |           slug: "%(id)s" | ||||||
|  | |||||||
| @ -191,18 +191,11 @@ class Blueprint: | |||||||
|     """Dataclass used for a full export""" |     """Dataclass used for a full export""" | ||||||
|  |  | ||||||
|     version: int = field(default=1) |     version: int = field(default=1) | ||||||
|     entries: list[BlueprintEntry] | dict[str, list[BlueprintEntry]] = field(default_factory=list) |     entries: list[BlueprintEntry] = field(default_factory=list) | ||||||
|     context: dict = field(default_factory=dict) |     context: dict = field(default_factory=dict) | ||||||
|  |  | ||||||
|     metadata: BlueprintMetadata | None = field(default=None) |     metadata: BlueprintMetadata | None = field(default=None) | ||||||
|  |  | ||||||
|     def iter_entries(self) -> Iterable[BlueprintEntry]: |  | ||||||
|         if isinstance(self.entries, dict): |  | ||||||
|             for _section, entries in self.entries.items(): |  | ||||||
|                 yield from entries |  | ||||||
|         else: |  | ||||||
|             yield from self.entries |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class YAMLTag: | class YAMLTag: | ||||||
|     """Base class for all YAML Tags""" |     """Base class for all YAML Tags""" | ||||||
| @ -233,7 +226,7 @@ class KeyOf(YAMLTag): | |||||||
|         self.id_from = node.value |         self.id_from = node.value | ||||||
|  |  | ||||||
|     def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: |     def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: | ||||||
|         for _entry in blueprint.iter_entries(): |         for _entry in blueprint.entries: | ||||||
|             if _entry.id == self.id_from and _entry._state.instance: |             if _entry.id == self.id_from and _entry._state.instance: | ||||||
|                 # Special handling for PolicyBindingModels, as they'll have a different PK |                 # Special handling for PolicyBindingModels, as they'll have a different PK | ||||||
|                 # which is used when creating policy bindings |                 # which is used when creating policy bindings | ||||||
|  | |||||||
| @ -384,7 +384,7 @@ class Importer: | |||||||
|     def _apply_models(self, raise_errors=False) -> bool: |     def _apply_models(self, raise_errors=False) -> bool: | ||||||
|         """Apply (create/update) models yaml""" |         """Apply (create/update) models yaml""" | ||||||
|         self.__pk_map = {} |         self.__pk_map = {} | ||||||
|         for entry in self._import.iter_entries(): |         for entry in self._import.entries: | ||||||
|             model_app_label, model_name = entry.get_model(self._import).split(".") |             model_app_label, model_name = entry.get_model(self._import).split(".") | ||||||
|             try: |             try: | ||||||
|                 model: type[SerializerModel] = registry.get_model(model_app_label, model_name) |                 model: type[SerializerModel] = registry.get_model(model_app_label, model_name) | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								authentik/core/api/files.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								authentik/core/api/files.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | from rest_framework.viewsets import ModelViewSet | ||||||
|  | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
|  | from authentik.core.api.used_by import UsedByMixin | ||||||
|  | from authentik.core.api.utils import ModelSerializer | ||||||
|  | from authentik.core.models import File | ||||||
|  |  | ||||||
|  | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FileSerializer(ModelSerializer): | ||||||
|  |     class Meta: | ||||||
|  |         model = File | ||||||
|  |         fields = ( | ||||||
|  |             "pk", | ||||||
|  |             "name", | ||||||
|  |             "content", | ||||||
|  |             "location", | ||||||
|  |             "private", | ||||||
|  |             "url", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FileViewSet(UsedByMixin, ModelViewSet): | ||||||
|  |     queryset = File.objects.all() | ||||||
|  |     serializer_class = FileSerializer | ||||||
|  |     filterset_fields = ("private",) | ||||||
|  |     ordering = ("name",) | ||||||
|  |     search_fields = ( | ||||||
|  |         "name", | ||||||
|  |         "location", | ||||||
|  |     ) | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | # Generated by Django 5.1.11 on 2025-06-13 15:12 | ||||||
|  |  | ||||||
|  | import uuid | ||||||
|  | import django.db.models.deletion | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_core", "0048_delete_oldauthenticatedsession_content_type"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name="File", | ||||||
|  |             fields=[ | ||||||
|  |                 ( | ||||||
|  |                     "id", | ||||||
|  |                     models.UUIDField( | ||||||
|  |                         default=uuid.uuid4, editable=False, primary_key=True, serialize=False | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("name", models.TextField()), | ||||||
|  |                 ("content", models.BinaryField()), | ||||||
|  |                 ("public", models.BooleanField(default=False)), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 "verbose_name": "Files", | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.RenameField( | ||||||
|  |             model_name="application", | ||||||
|  |             old_name="meta_icon", | ||||||
|  |             new_name="meta_old_icon", | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="application", | ||||||
|  |             name="meta_icon", | ||||||
|  |             field=models.ForeignKey( | ||||||
|  |                 null=True, on_delete=django.db.models.deletion.SET_NULL, to="authentik_core.file" | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -0,0 +1,32 @@ | |||||||
|  | # Generated by Django 5.1.11 on 2025-06-13 15:29 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_core", "0049_file_rename_meta_icon_application_meta_old_icon"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="file", | ||||||
|  |             name="location", | ||||||
|  |             field=models.TextField(null=True), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name="file", | ||||||
|  |             name="content", | ||||||
|  |             field=models.BinaryField(null=True), | ||||||
|  |         ), | ||||||
|  |         migrations.AddConstraint( | ||||||
|  |             model_name="file", | ||||||
|  |             constraint=models.CheckConstraint( | ||||||
|  |                 condition=models.Q( | ||||||
|  |                     ("content__isnull", False), ("location__isnull", False), _connector="OR" | ||||||
|  |                 ), | ||||||
|  |                 name="one_of_content_location_is_defined", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -29,6 +29,7 @@ from authentik.blueprints.models import ManagedModel | |||||||
| from authentik.core.expression.exceptions import PropertyMappingExpressionException | from authentik.core.expression.exceptions import PropertyMappingExpressionException | ||||||
| from authentik.core.types import UILoginButton, UserSettingSerializer | from authentik.core.types import UILoginButton, UserSettingSerializer | ||||||
| from authentik.lib.avatars import get_avatar | from authentik.lib.avatars import get_avatar | ||||||
|  | from authentik.lib.config import CONFIG | ||||||
| from authentik.lib.expression.exceptions import ControlFlowException | from authentik.lib.expression.exceptions import ControlFlowException | ||||||
| from authentik.lib.generators import generate_id | from authentik.lib.generators import generate_id | ||||||
| from authentik.lib.merge import MERGE_LIST_UNIQUE | from authentik.lib.merge import MERGE_LIST_UNIQUE | ||||||
| @ -533,12 +534,13 @@ class Application(SerializerModel, PolicyBindingModel): | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     # For template applications, this can be set to /static/authentik/applications/* |     # For template applications, this can be set to /static/authentik/applications/* | ||||||
|     meta_icon = models.FileField( |     meta_old_icon = models.FileField( | ||||||
|         upload_to="application-icons/", |         upload_to="application-icons/", | ||||||
|         default=None, |         default=None, | ||||||
|         null=True, |         null=True, | ||||||
|         max_length=500, |         max_length=500, | ||||||
|     ) |     ) | ||||||
|  |     meta_icon = models.ForeignKey("File", null=True, on_delete=models.SET_NULL) | ||||||
|     meta_description = models.TextField(default="", blank=True) |     meta_description = models.TextField(default="", blank=True) | ||||||
|     meta_publisher = models.TextField(default="", blank=True) |     meta_publisher = models.TextField(default="", blank=True) | ||||||
|  |  | ||||||
| @ -1100,3 +1102,44 @@ class AuthenticatedSession(SerializerModel): | |||||||
|             session=Session.objects.filter(session_key=request.session.session_key).first(), |             session=Session.objects.filter(session_key=request.session.session_key).first(), | ||||||
|             user=user, |             user=user, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class File(SerializerModel): | ||||||
|  |     id = models.UUIDField(primary_key=True, editable=False, default=uuid4) | ||||||
|  |  | ||||||
|  |     name = models.TextField() | ||||||
|  |     content = models.BinaryField(null=True) | ||||||
|  |     location = models.TextField(null=True) | ||||||
|  |     public = models.BooleanField(default=False) | ||||||
|  |     delete_on_delete = models.BooleanField(default=False) | ||||||
|  |     expiry = models.DateTimeField() | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         verbose_name = _("File") | ||||||
|  |         verbose_name = _("Files") | ||||||
|  |         constraints = ( | ||||||
|  |             models.CheckConstraint( | ||||||
|  |                 condition=Q(content__isnull=False) | Q(location__isnull=False), | ||||||
|  |                 name="one_of_content_location_is_defined", | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def __str__(self) -> str: | ||||||
|  |         return self.name | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def serializer(self) -> type[Serializer]: | ||||||
|  |         from authentik.core.api.files import FileSerializer | ||||||
|  |  | ||||||
|  |         return FileSerializer | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def url(self) -> str: | ||||||
|  |         if self.content: | ||||||
|  |             return ( | ||||||
|  |                 CONFIG.get("web.path", "/")[:-1] | ||||||
|  |                 + f"/files/{'public' if self.public else 'private'}/{self.pk}" | ||||||
|  |             ) | ||||||
|  |         elif self.location.startswith("/static"): | ||||||
|  |             return CONFIG.get("web.path", "/")[:-1] + self.location | ||||||
|  |         return self.location | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ from authentik.core.api.application_entitlements import ApplicationEntitlementVi | |||||||
| from authentik.core.api.applications import ApplicationViewSet | from authentik.core.api.applications import ApplicationViewSet | ||||||
| from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet | from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet | ||||||
| from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet | from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet | ||||||
|  | from authentik.core.api.files import FileViewSet | ||||||
| from authentik.core.api.groups import GroupViewSet | from authentik.core.api.groups import GroupViewSet | ||||||
| from authentik.core.api.property_mappings import PropertyMappingViewSet | from authentik.core.api.property_mappings import PropertyMappingViewSet | ||||||
| from authentik.core.api.providers import ProviderViewSet | from authentik.core.api.providers import ProviderViewSet | ||||||
| @ -78,6 +79,7 @@ api_urlpatterns = [ | |||||||
|         TransactionalApplicationView.as_view(), |         TransactionalApplicationView.as_view(), | ||||||
|         name="core-transactional-application", |         name="core-transactional-application", | ||||||
|     ), |     ), | ||||||
|  |     ("core/files", FileViewSet), | ||||||
|     ("core/groups", GroupViewSet), |     ("core/groups", GroupViewSet), | ||||||
|     ("core/users", UserViewSet), |     ("core/users", UserViewSet), | ||||||
|     ("core/tokens", TokenViewSet), |     ("core/tokens", TokenViewSet), | ||||||
|  | |||||||
| @ -15,7 +15,6 @@ class OAuth2Error(SentryIgnoredException): | |||||||
|  |  | ||||||
|     error: str |     error: str | ||||||
|     description: str |     description: str | ||||||
|     cause: str | None = None |  | ||||||
|  |  | ||||||
|     def create_dict(self): |     def create_dict(self): | ||||||
|         """Return error as dict for JSON Rendering""" |         """Return error as dict for JSON Rendering""" | ||||||
| @ -35,10 +34,6 @@ class OAuth2Error(SentryIgnoredException): | |||||||
|             **kwargs, |             **kwargs, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def with_cause(self, cause: str): |  | ||||||
|         self.cause = cause |  | ||||||
|         return self |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RedirectUriError(OAuth2Error): | class RedirectUriError(OAuth2Error): | ||||||
|     """The request fails due to a missing, invalid, or mismatching |     """The request fails due to a missing, invalid, or mismatching | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ from authentik.core.tests.utils import create_test_admin_user, create_test_flow | |||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
| from authentik.lib.generators import generate_id | from authentik.lib.generators import generate_id | ||||||
| from authentik.lib.utils.time import timedelta_from_string | from authentik.lib.utils.time import timedelta_from_string | ||||||
| from authentik.providers.oauth2.constants import SCOPE_OFFLINE_ACCESS, SCOPE_OPENID, TOKEN_TYPE | from authentik.providers.oauth2.constants import TOKEN_TYPE | ||||||
| from authentik.providers.oauth2.errors import AuthorizeError, ClientIdError, RedirectUriError | from authentik.providers.oauth2.errors import AuthorizeError, ClientIdError, RedirectUriError | ||||||
| from authentik.providers.oauth2.models import ( | from authentik.providers.oauth2.models import ( | ||||||
|     AccessToken, |     AccessToken, | ||||||
| @ -43,7 +43,7 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid/Foo")], |             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid/Foo")], | ||||||
|         ) |         ) | ||||||
|         with self.assertRaises(AuthorizeError) as cm: |         with self.assertRaises(AuthorizeError): | ||||||
|             request = self.factory.get( |             request = self.factory.get( | ||||||
|                 "/", |                 "/", | ||||||
|                 data={ |                 data={ | ||||||
| @ -53,7 +53,6 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|             OAuthAuthorizationParams.from_request(request) |             OAuthAuthorizationParams.from_request(request) | ||||||
|         self.assertEqual(cm.exception.error, "unsupported_response_type") |  | ||||||
|  |  | ||||||
|     def test_invalid_client_id(self): |     def test_invalid_client_id(self): | ||||||
|         """Test invalid client ID""" |         """Test invalid client ID""" | ||||||
| @ -69,7 +68,7 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid/Foo")], |             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid/Foo")], | ||||||
|         ) |         ) | ||||||
|         with self.assertRaises(AuthorizeError) as cm: |         with self.assertRaises(AuthorizeError): | ||||||
|             request = self.factory.get( |             request = self.factory.get( | ||||||
|                 "/", |                 "/", | ||||||
|                 data={ |                 data={ | ||||||
| @ -80,30 +79,19 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|             OAuthAuthorizationParams.from_request(request) |             OAuthAuthorizationParams.from_request(request) | ||||||
|         self.assertEqual(cm.exception.error, "request_not_supported") |  | ||||||
|  |  | ||||||
|     def test_invalid_redirect_uri_missing(self): |  | ||||||
|         """test missing redirect URI""" |  | ||||||
|         OAuth2Provider.objects.create( |  | ||||||
|             name=generate_id(), |  | ||||||
|             client_id="test", |  | ||||||
|             authorization_flow=create_test_flow(), |  | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid")], |  | ||||||
|         ) |  | ||||||
|         with self.assertRaises(RedirectUriError) as cm: |  | ||||||
|             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) |  | ||||||
|             OAuthAuthorizationParams.from_request(request) |  | ||||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_missing") |  | ||||||
|  |  | ||||||
|     def test_invalid_redirect_uri(self): |     def test_invalid_redirect_uri(self): | ||||||
|         """test invalid redirect URI""" |         """test missing/invalid redirect URI""" | ||||||
|         OAuth2Provider.objects.create( |         OAuth2Provider.objects.create( | ||||||
|             name=generate_id(), |             name=generate_id(), | ||||||
|             client_id="test", |             client_id="test", | ||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid")], |             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid")], | ||||||
|         ) |         ) | ||||||
|         with self.assertRaises(RedirectUriError) as cm: |         with self.assertRaises(RedirectUriError): | ||||||
|  |             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||||
|  |             OAuthAuthorizationParams.from_request(request) | ||||||
|  |         with self.assertRaises(RedirectUriError): | ||||||
|             request = self.factory.get( |             request = self.factory.get( | ||||||
|                 "/", |                 "/", | ||||||
|                 data={ |                 data={ | ||||||
| @ -113,7 +101,6 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|             OAuthAuthorizationParams.from_request(request) |             OAuthAuthorizationParams.from_request(request) | ||||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_no_match") |  | ||||||
|  |  | ||||||
|     def test_blocked_redirect_uri(self): |     def test_blocked_redirect_uri(self): | ||||||
|         """test missing/invalid redirect URI""" |         """test missing/invalid redirect URI""" | ||||||
| @ -121,9 +108,9 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             name=generate_id(), |             name=generate_id(), | ||||||
|             client_id="test", |             client_id="test", | ||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "data:localhost")], |             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "data:local.invalid")], | ||||||
|         ) |         ) | ||||||
|         with self.assertRaises(RedirectUriError) as cm: |         with self.assertRaises(RedirectUriError): | ||||||
|             request = self.factory.get( |             request = self.factory.get( | ||||||
|                 "/", |                 "/", | ||||||
|                 data={ |                 data={ | ||||||
| @ -133,7 +120,6 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|             OAuthAuthorizationParams.from_request(request) |             OAuthAuthorizationParams.from_request(request) | ||||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_forbidden_scheme") |  | ||||||
|  |  | ||||||
|     def test_invalid_redirect_uri_empty(self): |     def test_invalid_redirect_uri_empty(self): | ||||||
|         """test missing/invalid redirect URI""" |         """test missing/invalid redirect URI""" | ||||||
| @ -143,6 +129,9 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris=[], |             redirect_uris=[], | ||||||
|         ) |         ) | ||||||
|  |         with self.assertRaises(RedirectUriError): | ||||||
|  |             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||||
|  |             OAuthAuthorizationParams.from_request(request) | ||||||
|         request = self.factory.get( |         request = self.factory.get( | ||||||
|             "/", |             "/", | ||||||
|             data={ |             data={ | ||||||
| @ -161,9 +150,12 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             name=generate_id(), |             name=generate_id(), | ||||||
|             client_id="test", |             client_id="test", | ||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.REGEX, "http://local.invalid?")], |             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid?")], | ||||||
|         ) |         ) | ||||||
|         with self.assertRaises(RedirectUriError) as cm: |         with self.assertRaises(RedirectUriError): | ||||||
|  |             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||||
|  |             OAuthAuthorizationParams.from_request(request) | ||||||
|  |         with self.assertRaises(RedirectUriError): | ||||||
|             request = self.factory.get( |             request = self.factory.get( | ||||||
|                 "/", |                 "/", | ||||||
|                 data={ |                 data={ | ||||||
| @ -173,7 +165,6 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|             OAuthAuthorizationParams.from_request(request) |             OAuthAuthorizationParams.from_request(request) | ||||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_no_match") |  | ||||||
|  |  | ||||||
|     def test_redirect_uri_invalid_regex(self): |     def test_redirect_uri_invalid_regex(self): | ||||||
|         """test missing/invalid redirect URI (invalid regex)""" |         """test missing/invalid redirect URI (invalid regex)""" | ||||||
| @ -181,9 +172,12 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             name=generate_id(), |             name=generate_id(), | ||||||
|             client_id="test", |             client_id="test", | ||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.REGEX, "+")], |             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "+")], | ||||||
|         ) |         ) | ||||||
|         with self.assertRaises(RedirectUriError) as cm: |         with self.assertRaises(RedirectUriError): | ||||||
|  |             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||||
|  |             OAuthAuthorizationParams.from_request(request) | ||||||
|  |         with self.assertRaises(RedirectUriError): | ||||||
|             request = self.factory.get( |             request = self.factory.get( | ||||||
|                 "/", |                 "/", | ||||||
|                 data={ |                 data={ | ||||||
| @ -193,22 +187,23 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|             OAuthAuthorizationParams.from_request(request) |             OAuthAuthorizationParams.from_request(request) | ||||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_no_match") |  | ||||||
|  |  | ||||||
|     def test_redirect_uri_regex(self): |     def test_empty_redirect_uri(self): | ||||||
|         """test valid redirect URI (regex)""" |         """test empty redirect URI (configure in provider)""" | ||||||
|         OAuth2Provider.objects.create( |         OAuth2Provider.objects.create( | ||||||
|             name=generate_id(), |             name=generate_id(), | ||||||
|             client_id="test", |             client_id="test", | ||||||
|             authorization_flow=create_test_flow(), |             authorization_flow=create_test_flow(), | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.REGEX, ".+")], |  | ||||||
|         ) |         ) | ||||||
|  |         with self.assertRaises(RedirectUriError): | ||||||
|  |             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||||
|  |             OAuthAuthorizationParams.from_request(request) | ||||||
|         request = self.factory.get( |         request = self.factory.get( | ||||||
|             "/", |             "/", | ||||||
|             data={ |             data={ | ||||||
|                 "response_type": "code", |                 "response_type": "code", | ||||||
|                 "client_id": "test", |                 "client_id": "test", | ||||||
|                 "redirect_uri": "http://foo.bar.baz", |                 "redirect_uri": "http://localhost", | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|         OAuthAuthorizationParams.from_request(request) |         OAuthAuthorizationParams.from_request(request) | ||||||
| @ -263,7 +258,7 @@ class TestAuthorize(OAuthTestCase): | |||||||
|             GrantTypes.IMPLICIT, |             GrantTypes.IMPLICIT, | ||||||
|         ) |         ) | ||||||
|         # Implicit without openid scope |         # Implicit without openid scope | ||||||
|         with self.assertRaises(AuthorizeError) as cm: |         with self.assertRaises(AuthorizeError): | ||||||
|             request = self.factory.get( |             request = self.factory.get( | ||||||
|                 "/", |                 "/", | ||||||
|                 data={ |                 data={ | ||||||
| @ -290,7 +285,7 @@ class TestAuthorize(OAuthTestCase): | |||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             OAuthAuthorizationParams.from_request(request).grant_type, GrantTypes.HYBRID |             OAuthAuthorizationParams.from_request(request).grant_type, GrantTypes.HYBRID | ||||||
|         ) |         ) | ||||||
|         with self.assertRaises(AuthorizeError) as cm: |         with self.assertRaises(AuthorizeError): | ||||||
|             request = self.factory.get( |             request = self.factory.get( | ||||||
|                 "/", |                 "/", | ||||||
|                 data={ |                 data={ | ||||||
| @ -300,7 +295,6 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|             OAuthAuthorizationParams.from_request(request) |             OAuthAuthorizationParams.from_request(request) | ||||||
|         self.assertEqual(cm.exception.error, "unsupported_response_type") |  | ||||||
|  |  | ||||||
|     def test_full_code(self): |     def test_full_code(self): | ||||||
|         """Test full authorization""" |         """Test full authorization""" | ||||||
| @ -621,54 +615,3 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 }, |                 }, | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def test_openid_missing_invalid(self): |  | ||||||
|         """test request requiring an OpenID scope to be set""" |  | ||||||
|         OAuth2Provider.objects.create( |  | ||||||
|             name=generate_id(), |  | ||||||
|             client_id="test", |  | ||||||
|             authorization_flow=create_test_flow(), |  | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://localhost")], |  | ||||||
|         ) |  | ||||||
|         request = self.factory.get( |  | ||||||
|             "/", |  | ||||||
|             data={ |  | ||||||
|                 "response_type": "id_token", |  | ||||||
|                 "client_id": "test", |  | ||||||
|                 "redirect_uri": "http://localhost", |  | ||||||
|                 "scope": "", |  | ||||||
|             }, |  | ||||||
|         ) |  | ||||||
|         with self.assertRaises(AuthorizeError) as cm: |  | ||||||
|             OAuthAuthorizationParams.from_request(request) |  | ||||||
|         self.assertEqual(cm.exception.cause, "scope_openid_missing") |  | ||||||
|  |  | ||||||
|     @apply_blueprint("system/providers-oauth2.yaml") |  | ||||||
|     def test_offline_access_invalid(self): |  | ||||||
|         """test request for offline_access with invalid response type""" |  | ||||||
|         provider = OAuth2Provider.objects.create( |  | ||||||
|             name=generate_id(), |  | ||||||
|             client_id="test", |  | ||||||
|             authorization_flow=create_test_flow(), |  | ||||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://localhost")], |  | ||||||
|         ) |  | ||||||
|         provider.property_mappings.set( |  | ||||||
|             ScopeMapping.objects.filter( |  | ||||||
|                 managed__in=[ |  | ||||||
|                     "goauthentik.io/providers/oauth2/scope-openid", |  | ||||||
|                     "goauthentik.io/providers/oauth2/scope-offline_access", |  | ||||||
|                 ] |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|         request = self.factory.get( |  | ||||||
|             "/", |  | ||||||
|             data={ |  | ||||||
|                 "response_type": "id_token", |  | ||||||
|                 "client_id": "test", |  | ||||||
|                 "redirect_uri": "http://localhost", |  | ||||||
|                 "scope": f"{SCOPE_OPENID} {SCOPE_OFFLINE_ACCESS}", |  | ||||||
|                 "nonce": generate_id(), |  | ||||||
|             }, |  | ||||||
|         ) |  | ||||||
|         parsed = OAuthAuthorizationParams.from_request(request) |  | ||||||
|         self.assertNotIn(SCOPE_OFFLINE_ACCESS, parsed.scope) |  | ||||||
|  | |||||||
| @ -190,7 +190,7 @@ class OAuthAuthorizationParams: | |||||||
|         allowed_redirect_urls = self.provider.redirect_uris |         allowed_redirect_urls = self.provider.redirect_uris | ||||||
|         if not self.redirect_uri: |         if not self.redirect_uri: | ||||||
|             LOGGER.warning("Missing redirect uri.") |             LOGGER.warning("Missing redirect uri.") | ||||||
|             raise RedirectUriError("", allowed_redirect_urls).with_cause("redirect_uri_missing") |             raise RedirectUriError("", allowed_redirect_urls) | ||||||
|  |  | ||||||
|         if len(allowed_redirect_urls) < 1: |         if len(allowed_redirect_urls) < 1: | ||||||
|             LOGGER.info("Setting redirect for blank redirect_uris", redirect=self.redirect_uri) |             LOGGER.info("Setting redirect for blank redirect_uris", redirect=self.redirect_uri) | ||||||
| @ -219,14 +219,10 @@ class OAuthAuthorizationParams: | |||||||
|                         provider=self.provider, |                         provider=self.provider, | ||||||
|                     ) |                     ) | ||||||
|         if not match_found: |         if not match_found: | ||||||
|             raise RedirectUriError(self.redirect_uri, allowed_redirect_urls).with_cause( |             raise RedirectUriError(self.redirect_uri, allowed_redirect_urls) | ||||||
|                 "redirect_uri_no_match" |  | ||||||
|             ) |  | ||||||
|         # Check against forbidden schemes |         # Check against forbidden schemes | ||||||
|         if urlparse(self.redirect_uri).scheme in FORBIDDEN_URI_SCHEMES: |         if urlparse(self.redirect_uri).scheme in FORBIDDEN_URI_SCHEMES: | ||||||
|             raise RedirectUriError(self.redirect_uri, allowed_redirect_urls).with_cause( |             raise RedirectUriError(self.redirect_uri, allowed_redirect_urls) | ||||||
|                 "redirect_uri_forbidden_scheme" |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|     def check_scope(self, github_compat=False): |     def check_scope(self, github_compat=False): | ||||||
|         """Ensure openid scope is set in Hybrid flows, or when requesting an id_token""" |         """Ensure openid scope is set in Hybrid flows, or when requesting an id_token""" | ||||||
| @ -255,9 +251,7 @@ class OAuthAuthorizationParams: | |||||||
|             or self.response_type in [ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN] |             or self.response_type in [ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN] | ||||||
|         ): |         ): | ||||||
|             LOGGER.warning("Missing 'openid' scope.") |             LOGGER.warning("Missing 'openid' scope.") | ||||||
|             raise AuthorizeError( |             raise AuthorizeError(self.redirect_uri, "invalid_scope", self.grant_type, self.state) | ||||||
|                 self.redirect_uri, "invalid_scope", self.grant_type, self.state |  | ||||||
|             ).with_cause("scope_openid_missing") |  | ||||||
|         if SCOPE_OFFLINE_ACCESS in self.scope: |         if SCOPE_OFFLINE_ACCESS in self.scope: | ||||||
|             # https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess |             # https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess | ||||||
|             # Don't explicitly request consent with offline_access, as the spec allows for |             # Don't explicitly request consent with offline_access, as the spec allows for | ||||||
| @ -292,9 +286,7 @@ class OAuthAuthorizationParams: | |||||||
|             return |             return | ||||||
|         if not self.nonce: |         if not self.nonce: | ||||||
|             LOGGER.warning("Missing nonce for OpenID Request") |             LOGGER.warning("Missing nonce for OpenID Request") | ||||||
|             raise AuthorizeError( |             raise AuthorizeError(self.redirect_uri, "invalid_request", self.grant_type, self.state) | ||||||
|                 self.redirect_uri, "invalid_request", self.grant_type, self.state |  | ||||||
|             ).with_cause("none_missing") |  | ||||||
|  |  | ||||||
|     def check_code_challenge(self): |     def check_code_challenge(self): | ||||||
|         """PKCE validation of the transformation method.""" |         """PKCE validation of the transformation method.""" | ||||||
| @ -353,10 +345,10 @@ class AuthorizationFlowInitView(PolicyAccessView): | |||||||
|                 self.request, github_compat=self.github_compat |                 self.request, github_compat=self.github_compat | ||||||
|             ) |             ) | ||||||
|         except AuthorizeError as error: |         except AuthorizeError as error: | ||||||
|             LOGGER.warning(error.description, redirect_uri=error.redirect_uri, cause=error.cause) |             LOGGER.warning(error.description, redirect_uri=error.redirect_uri) | ||||||
|             raise RequestValidationError(error.get_response(self.request)) from None |             raise RequestValidationError(error.get_response(self.request)) from None | ||||||
|         except OAuth2Error as error: |         except OAuth2Error as error: | ||||||
|             LOGGER.warning(error.description, cause=error.cause) |             LOGGER.warning(error.description) | ||||||
|             raise RequestValidationError( |             raise RequestValidationError( | ||||||
|                 bad_request_message(self.request, error.description, title=error.error) |                 bad_request_message(self.request, error.description, title=error.error) | ||||||
|             ) from None |             ) from None | ||||||
|  | |||||||
| @ -20,9 +20,6 @@ from authentik.lib.utils.time import timedelta_from_string | |||||||
| from authentik.policies.engine import PolicyEngine | from authentik.policies.engine import PolicyEngine | ||||||
| from authentik.policies.views import PolicyAccessView | from authentik.policies.views import PolicyAccessView | ||||||
| from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider | from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider | ||||||
| from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT |  | ||||||
|  |  | ||||||
| PLAN_CONNECTION_SETTINGS = "connection_settings" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RACStartView(PolicyAccessView): | class RACStartView(PolicyAccessView): | ||||||
| @ -112,15 +109,10 @@ class RACFinalStage(RedirectStage): | |||||||
|         return super().dispatch(request, *args, **kwargs) |         return super().dispatch(request, *args, **kwargs) | ||||||
|  |  | ||||||
|     def get_challenge(self, *args, **kwargs) -> RedirectChallenge: |     def get_challenge(self, *args, **kwargs) -> RedirectChallenge: | ||||||
|         settings = self.executor.plan.context.get(PLAN_CONNECTION_SETTINGS) |  | ||||||
|         if not settings: |  | ||||||
|             settings = self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}).get( |  | ||||||
|                 PLAN_CONNECTION_SETTINGS |  | ||||||
|             ) |  | ||||||
|         token = ConnectionToken.objects.create( |         token = ConnectionToken.objects.create( | ||||||
|             provider=self.provider, |             provider=self.provider, | ||||||
|             endpoint=self.endpoint, |             endpoint=self.endpoint, | ||||||
|             settings=settings or {}, |             settings=self.executor.plan.context.get("connection_settings", {}), | ||||||
|             session=self.request.session["authenticatedsession"], |             session=self.request.session["authenticatedsession"], | ||||||
|             expires=now() + timedelta_from_string(self.provider.connection_expiry), |             expires=now() + timedelta_from_string(self.provider.connection_expiry), | ||||||
|             expiring=True, |             expiring=True, | ||||||
|  | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -38,27 +38,8 @@ | |||||||
|             "additionalProperties": true |             "additionalProperties": true | ||||||
|         }, |         }, | ||||||
|         "entries": { |         "entries": { | ||||||
|             "anyOf": [ |  | ||||||
|                 { |  | ||||||
|             "type": "array", |             "type": "array", | ||||||
|             "items": { |             "items": { | ||||||
|                         "$ref": "#/$defs/blueprint_entry" |  | ||||||
|                     } |  | ||||||
|                 }, |  | ||||||
|                 { |  | ||||||
|                     "type": "object", |  | ||||||
|                     "additionalProperties": { |  | ||||||
|                         "type": "array", |  | ||||||
|                         "items": { |  | ||||||
|                             "$ref": "#/$defs/blueprint_entry" |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     "$defs": { |  | ||||||
|         "blueprint_entry": { |  | ||||||
|                 "oneOf": [ |                 "oneOf": [ | ||||||
|                     { |                     { | ||||||
|                         "type": "object", |                         "type": "object", | ||||||
| @ -4257,7 +4238,10 @@ | |||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 ] |                 ] | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     }, |     }, | ||||||
|  |     "$defs": { | ||||||
|         "model_authentik_blueprints.blueprintinstance": { |         "model_authentik_blueprints.blueprintinstance": { | ||||||
|             "type": "object", |             "type": "object", | ||||||
|             "properties": { |             "properties": { | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								go.mod
									
									
									
									
									
								
							| @ -62,6 +62,12 @@ require ( | |||||||
| 	github.com/go-openapi/validate v0.24.0 // indirect | 	github.com/go-openapi/validate v0.24.0 // indirect | ||||||
| 	github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect | 	github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect | ||||||
| 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||||||
|  | 	github.com/jackc/pgpassfile v1.0.0 // indirect | ||||||
|  | 	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect | ||||||
|  | 	github.com/jackc/pgx/v5 v5.7.5 // indirect | ||||||
|  | 	github.com/jackc/puddle/v2 v2.2.2 // indirect | ||||||
|  | 	github.com/jinzhu/inflection v1.0.0 // indirect | ||||||
|  | 	github.com/jinzhu/now v1.1.5 // indirect | ||||||
| 	github.com/josharian/intern v1.0.0 // indirect | 	github.com/josharian/intern v1.0.0 // indirect | ||||||
| 	github.com/klauspost/compress v1.18.0 // indirect | 	github.com/klauspost/compress v1.18.0 // indirect | ||||||
| 	github.com/mailru/easyjson v0.7.7 // indirect | 	github.com/mailru/easyjson v0.7.7 // indirect | ||||||
| @ -77,9 +83,11 @@ require ( | |||||||
| 	go.opentelemetry.io/otel v1.24.0 // indirect | 	go.opentelemetry.io/otel v1.24.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/metric v1.24.0 // indirect | 	go.opentelemetry.io/otel/metric v1.24.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/trace v1.24.0 // indirect | 	go.opentelemetry.io/otel/trace v1.24.0 // indirect | ||||||
| 	golang.org/x/crypto v0.36.0 // indirect | 	golang.org/x/crypto v0.39.0 // indirect | ||||||
| 	golang.org/x/sys v0.31.0 // indirect | 	golang.org/x/sys v0.33.0 // indirect | ||||||
| 	golang.org/x/text v0.24.0 // indirect | 	golang.org/x/text v0.26.0 // indirect | ||||||
| 	google.golang.org/protobuf v1.36.5 // indirect | 	google.golang.org/protobuf v1.36.5 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
|  | 	gorm.io/driver/postgres v1.6.0 // indirect | ||||||
|  | 	gorm.io/gorm v1.30.0 // indirect | ||||||
| ) | ) | ||||||
|  | |||||||
							
								
								
									
										22
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								go.sum
									
									
									
									
									
								
							| @ -191,6 +191,14 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ | |||||||
| github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||||
| github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | ||||||
| github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||||||
|  | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= | ||||||
|  | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= | ||||||
|  | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= | ||||||
|  | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= | ||||||
|  | github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= | ||||||
|  | github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= | ||||||
|  | github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= | ||||||
|  | github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= | ||||||
| github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= | github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= | ||||||
| github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= | ||||||
| github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= | github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= | ||||||
| @ -205,6 +213,10 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ | |||||||
| github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= | github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= | ||||||
| github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= | github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= | ||||||
| github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= | github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= | ||||||
|  | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= | ||||||
|  | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= | ||||||
|  | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= | ||||||
|  | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= | ||||||
| github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= | ||||||
| github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= | ||||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||||
| @ -308,6 +320,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh | |||||||
| golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
| golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= | ||||||
| golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= | ||||||
|  | golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= | ||||||
|  | golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= | ||||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||||
| golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||||
| golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | ||||||
| @ -415,6 +429,8 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w | |||||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= | ||||||
| golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||||
|  | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= | ||||||
|  | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| @ -422,6 +438,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | |||||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= | ||||||
| golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= | ||||||
|  | golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= | ||||||
|  | golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= | ||||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| @ -555,6 +573,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | |||||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= | ||||||
|  | gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= | ||||||
|  | gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= | ||||||
|  | gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= | ||||||
| honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
| honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
| honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ type Config struct { | |||||||
| 	Storage        StorageConfig        `yaml:"storage"` | 	Storage        StorageConfig        `yaml:"storage"` | ||||||
| 	LogLevel       string               `yaml:"log_level" env:"AUTHENTIK_LOG_LEVEL, overwrite"` | 	LogLevel       string               `yaml:"log_level" env:"AUTHENTIK_LOG_LEVEL, overwrite"` | ||||||
| 	ErrorReporting ErrorReportingConfig `yaml:"error_reporting" env:", prefix=AUTHENTIK_ERROR_REPORTING__"` | 	ErrorReporting ErrorReportingConfig `yaml:"error_reporting" env:", prefix=AUTHENTIK_ERROR_REPORTING__"` | ||||||
|  | 	Postgresql     PostgresqlConfig     `yaml:"postgresql" env:", prefix=AUTHENTIK_POSTGRESQL__"` | ||||||
| 	Redis          RedisConfig          `yaml:"redis" env:", prefix=AUTHENTIK_REDIS__"` | 	Redis          RedisConfig          `yaml:"redis" env:", prefix=AUTHENTIK_REDIS__"` | ||||||
| 	Outposts       OutpostConfig        `yaml:"outposts" env:", prefix=AUTHENTIK_OUTPOSTS__"` | 	Outposts       OutpostConfig        `yaml:"outposts" env:", prefix=AUTHENTIK_OUTPOSTS__"` | ||||||
|  |  | ||||||
| @ -25,6 +26,16 @@ type Config struct { | |||||||
| 	AuthentikInsecure    bool   `env:"AUTHENTIK_INSECURE"` | 	AuthentikInsecure    bool   `env:"AUTHENTIK_INSECURE"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TODO: SSL | ||||||
|  | type PostgresqlConfig struct { | ||||||
|  | 	Host          string `yaml:"host" env:"HOST, overwrite"` | ||||||
|  | 	Port          string `yaml:"port" env:"PORT, overwrite"` | ||||||
|  | 	User          string `yaml:"user" env:"USER, overwrite"` | ||||||
|  | 	Password      string `yaml:"password" env:"PASSWORD, overwrite"` | ||||||
|  | 	Name          string `yaml:"name" env:"NAME, overwrite"` | ||||||
|  | 	DefaultSchema string `yaml:"default_schema" env:"DEFAULT_SCHEMA, overwrite"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type RedisConfig struct { | type RedisConfig struct { | ||||||
| 	Host      string `yaml:"host" env:"HOST, overwrite"` | 	Host      string `yaml:"host" env:"HOST, overwrite"` | ||||||
| 	Port      int    `yaml:"port" env:"PORT, overwrite"` | 	Port      int    `yaml:"port" env:"PORT, overwrite"` | ||||||
|  | |||||||
							
								
								
									
										62
									
								
								internal/web/files.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								internal/web/files.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | |||||||
|  | package web | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"github.com/go-http-utils/etag" | ||||||
|  | 	"github.com/gorilla/mux" | ||||||
|  |  | ||||||
|  | 	"goauthentik.io/internal/config" | ||||||
|  | 	"goauthentik.io/internal/constants" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type File struct { | ||||||
|  | 	ID string `gorm:"primaryKey"` | ||||||
|  |  | ||||||
|  | 	Name     string | ||||||
|  | 	Content  []byte | ||||||
|  | 	Location string | ||||||
|  | 	Public   bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ws *WebServer) configureFiles() { | ||||||
|  | 	// Setup routers | ||||||
|  | 	filesRouter := ws.loggingRouter.NewRoute().Subrouter() | ||||||
|  | 	filesRouter.Use(ws.filesHeaderMiddleware) | ||||||
|  |  | ||||||
|  | 	filesRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/files/public/{pk}").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||||
|  | 		vars := mux.Vars(r) | ||||||
|  |  | ||||||
|  | 		pk := vars["pk"] | ||||||
|  |  | ||||||
|  | 		var file File | ||||||
|  | 		ws.postgresClient.First(&file, "id = ? AND public = true AND content <> NULL", pk) | ||||||
|  |  | ||||||
|  | 		// TODO: get from DB | ||||||
|  | 		rw.Write(file.Content) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	filesRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/files/private/{pk}").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||||
|  | 		vars := mux.Vars(r) | ||||||
|  |  | ||||||
|  | 		// TODO: check session | ||||||
|  |  | ||||||
|  | 		pk := vars["pk"] | ||||||
|  |  | ||||||
|  | 		var file File | ||||||
|  | 		ws.postgresClient.First(&file, "id = ? AND content <> NULL", pk) | ||||||
|  |  | ||||||
|  | 		rw.Write([]byte(file.Content)) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TODO: anything else? | ||||||
|  | func (ws *WebServer) filesHeaderMiddleware(h http.Handler) http.Handler { | ||||||
|  | 	etagHandler := etag.Handler(h, false) | ||||||
|  | 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		w.Header().Set("Cache-Control", "public, no-transform") | ||||||
|  | 		w.Header().Set("X-authentik-version", constants.VERSION) | ||||||
|  | 		w.Header().Set("Vary", "X-authentik-version, Etag") | ||||||
|  | 		etagHandler.ServeHTTP(w, r) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @ -17,6 +17,8 @@ import ( | |||||||
| 	"github.com/gorilla/securecookie" | 	"github.com/gorilla/securecookie" | ||||||
| 	"github.com/pires/go-proxyproto" | 	"github.com/pires/go-proxyproto" | ||||||
| 	log "github.com/sirupsen/logrus" | 	log "github.com/sirupsen/logrus" | ||||||
|  | 	"gorm.io/driver/postgres" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  |  | ||||||
| 	"goauthentik.io/api/v3" | 	"goauthentik.io/api/v3" | ||||||
| 	"goauthentik.io/internal/config" | 	"goauthentik.io/internal/config" | ||||||
| @ -49,6 +51,7 @@ type WebServer struct { | |||||||
| 	mainRouter     *mux.Router | 	mainRouter     *mux.Router | ||||||
| 	loggingRouter  *mux.Router | 	loggingRouter  *mux.Router | ||||||
| 	log            *log.Entry | 	log            *log.Entry | ||||||
|  | 	postgresClient *gorm.DB | ||||||
| 	upstreamClient *http.Client | 	upstreamClient *http.Client | ||||||
| 	upstreamURL    *url.URL | 	upstreamURL    *url.URL | ||||||
|  |  | ||||||
| @ -64,6 +67,21 @@ func NewWebServer() *WebServer { | |||||||
| 	loggingHandler := mainHandler.NewRoute().Subrouter() | 	loggingHandler := mainHandler.NewRoute().Subrouter() | ||||||
| 	loggingHandler.Use(web.NewLoggingHandler(l, nil)) | 	loggingHandler.Use(web.NewLoggingHandler(l, nil)) | ||||||
|  |  | ||||||
|  | 	// TODO: ssl | ||||||
|  | 	postgresDsn := fmt.Sprintf( | ||||||
|  | 		"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", | ||||||
|  | 		config.Get().Postgresql.Host, | ||||||
|  | 		config.Get().Postgresql.Port, | ||||||
|  | 		config.Get().Postgresql.User, | ||||||
|  | 		config.Get().Postgresql.Password, | ||||||
|  | 		config.Get().Postgresql.Name, | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	db, err := gorm.Open(postgres.Open(postgresDsn), &gorm.Config{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	tmp := os.TempDir() | 	tmp := os.TempDir() | ||||||
| 	socketPath := path.Join(tmp, UnixSocketName) | 	socketPath := path.Join(tmp, UnixSocketName) | ||||||
|  |  | ||||||
| @ -88,6 +106,7 @@ func NewWebServer() *WebServer { | |||||||
| 		mainRouter:     mainHandler, | 		mainRouter:     mainHandler, | ||||||
| 		loggingRouter:  loggingHandler, | 		loggingRouter:  loggingHandler, | ||||||
| 		log:            l, | 		log:            l, | ||||||
|  | 		postgresClient: db, | ||||||
| 		gunicornReady:  false, | 		gunicornReady:  false, | ||||||
| 		upstreamClient: upstreamClient, | 		upstreamClient: upstreamClient, | ||||||
| 		upstreamURL:    u, | 		upstreamURL:    u, | ||||||
|  | |||||||
| @ -6,18 +6,18 @@ | |||||||
| # Translators: | # Translators: | ||||||
| # jcamat, 2022 | # jcamat, 2022 | ||||||
| # Angel, 2024 | # Angel, 2024 | ||||||
|  | # Iamanaws, 2024 | ||||||
| # Marcelo Elizeche Landó, 2025 | # Marcelo Elizeche Landó, 2025 | ||||||
| # Jens L. <jens@goauthentik.io>, 2025 | # Jens L. <jens@goauthentik.io>, 2025 | ||||||
| # Iamanaws, 2025 |  | ||||||
| #  | #  | ||||||
| #, fuzzy | #, fuzzy | ||||||
| msgid "" | msgid "" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: PACKAGE VERSION\n" | "Project-Id-Version: PACKAGE VERSION\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2025-06-04 00:12+0000\n" | "POT-Creation-Date: 2025-05-28 11:25+0000\n" | ||||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||||
| "Last-Translator: Iamanaws, 2025\n" | "Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n" | ||||||
| "Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n" | "Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n" | ||||||
| "MIME-Version: 1.0\n" | "MIME-Version: 1.0\n" | ||||||
| "Content-Type: text/plain; charset=UTF-8\n" | "Content-Type: text/plain; charset=UTF-8\n" | ||||||
| @ -111,7 +111,7 @@ msgstr "Certificado Web usado por el servidor web Core de authentik" | |||||||
|  |  | ||||||
| #: authentik/brands/models.py | #: authentik/brands/models.py | ||||||
| msgid "Certificates used for client authentication." | msgid "Certificates used for client authentication." | ||||||
| msgstr "Certificados utilizados para la autenticación del cliente." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/brands/models.py | #: authentik/brands/models.py | ||||||
| msgid "Brand" | msgid "Brand" | ||||||
| @ -131,7 +131,7 @@ msgstr "Descripción adicional no disponible." | |||||||
|  |  | ||||||
| #: authentik/core/api/groups.py | #: authentik/core/api/groups.py | ||||||
| msgid "Cannot set group as parent of itself." | msgid "Cannot set group as parent of itself." | ||||||
| msgstr "No se puede establecer un grupo como su propio padre." | msgstr "No se puede establecer el grupo como padre de sí mismo." | ||||||
|  |  | ||||||
| #: authentik/core/api/providers.py | #: authentik/core/api/providers.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -183,11 +183,11 @@ msgstr "Remueve usuario del grupo" | |||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "Enable superuser status" | msgid "Enable superuser status" | ||||||
| msgstr "Habilitar el estado de superusuario" | msgstr "Habiliar estado de \"superusuario\"" | ||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "Disable superuser status" | msgid "Disable superuser status" | ||||||
| msgstr "Deshabilitar el estado de superusuario" | msgstr "Deshabiliar estado de \"superusuario\"" | ||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "User's display name." | msgid "User's display name." | ||||||
| @ -241,7 +241,7 @@ msgstr "Flujo utilizado al autorizar a este proveedor." | |||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "Flow used ending the session from a provider." | msgid "Flow used ending the session from a provider." | ||||||
| msgstr "Flujo utilizado para finalizar la sesión desde un proveedor." | msgstr "Flujo usado para terminar la sesión de un proveedor." | ||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -273,11 +273,11 @@ msgstr "Aplicaciones" | |||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "Application Entitlement" | msgid "Application Entitlement" | ||||||
| msgstr "Derecho de Aplicación" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "Application Entitlements" | msgid "Application Entitlements" | ||||||
| msgstr "Derechos de Aplicación" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "Use the source-specific identifier" | msgid "Use the source-specific identifier" | ||||||
| @ -288,9 +288,9 @@ msgid "" | |||||||
| "Link to a user with identical email address. Can have security implications " | "Link to a user with identical email address. Can have security implications " | ||||||
| "when a source doesn't validate email addresses." | "when a source doesn't validate email addresses." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Enlace a un usuario con la misma dirección de correo electrónico. Puede " | "Apunta a un usuario con una dirección de correo electrónico idéntica. Puede " | ||||||
| "tener implicaciones de seguridad cuando una fuente no valida las direcciones" | "tener implicaciones de seguridad cuando una fuente no valida la dirección de" | ||||||
| " de correo electrónico." | " correo electrónico." | ||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -305,8 +305,8 @@ msgid "" | |||||||
| "Link to a user with identical username. Can have security implications when " | "Link to a user with identical username. Can have security implications when " | ||||||
| "a username is used with another source." | "a username is used with another source." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Enlace a un usuario con el mismo nombre de usuario. Puede tener " | "Enlace a un usuario con un nombre de usuario idéntico. Puede tener " | ||||||
| "implicaciones de seguridad cuando un nombre de usuario se utiliza con otra " | "implicaciones de seguridad cuando se usa un nombre de usuario con otra " | ||||||
| "fuente." | "fuente." | ||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| @ -322,8 +322,8 @@ msgid "" | |||||||
| "Link to a group with identical name. Can have security implications when a " | "Link to a group with identical name. Can have security implications when a " | ||||||
| "group name is used with another source." | "group name is used with another source." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Enlace a un grupo con el mismo nombre. Puede tener implicaciones de " | "Enlace a un grupo con un nombre idéntico. Puede tener implicaciones de " | ||||||
| "seguridad cuando un nombre de grupo se utiliza con otra fuente." | "seguridad cuando se utiliza un nombre de grupo con otra fuente." | ||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "Use the group name, but deny enrollment when the name already exists." | msgid "Use the group name, but deny enrollment when the name already exists." | ||||||
| @ -385,7 +385,7 @@ msgstr "Asignaciones de Propiedades" | |||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "session data" | msgid "session data" | ||||||
| msgstr "datos de sesión" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/core/models.py | #: authentik/core/models.py | ||||||
| msgid "Session" | msgid "Session" | ||||||
| @ -424,7 +424,7 @@ msgstr "¡Autenticado exitosamente con {source}!" | |||||||
| #: authentik/core/sources/flow_manager.py | #: authentik/core/sources/flow_manager.py | ||||||
| #, python-brace-format | #, python-brace-format | ||||||
| msgid "Successfully linked {source}!" | msgid "Successfully linked {source}!" | ||||||
| msgstr "¡{source} enlazado correctamente!" | msgstr "¡{source} vinculado exitosamente!" | ||||||
|  |  | ||||||
| #: authentik/core/sources/flow_manager.py | #: authentik/core/sources/flow_manager.py | ||||||
| msgid "Source is not configured for enrollment." | msgid "Source is not configured for enrollment." | ||||||
| @ -476,11 +476,11 @@ msgstr "" | |||||||
|  |  | ||||||
| #: authentik/crypto/models.py | #: authentik/crypto/models.py | ||||||
| msgid "Certificate-Key Pair" | msgid "Certificate-Key Pair" | ||||||
| msgstr "Par Certificado-Clave" | msgstr "Par de claves de certificado" | ||||||
|  |  | ||||||
| #: authentik/crypto/models.py | #: authentik/crypto/models.py | ||||||
| msgid "Certificate-Key Pairs" | msgid "Certificate-Key Pairs" | ||||||
| msgstr "Pares Certificado-Clave" | msgstr "Pares de claves de certificado" | ||||||
|  |  | ||||||
| #: authentik/enterprise/api.py | #: authentik/enterprise/api.py | ||||||
| msgid "Enterprise is required to create/update this object." | msgid "Enterprise is required to create/update this object." | ||||||
| @ -511,7 +511,7 @@ msgstr "" | |||||||
|  |  | ||||||
| #: authentik/enterprise/policies/unique_password/models.py | #: authentik/enterprise/policies/unique_password/models.py | ||||||
| msgid "Number of passwords to check against." | msgid "Number of passwords to check against." | ||||||
| msgstr "Número de contraseñas contra las que verificar." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/policies/unique_password/models.py | #: authentik/enterprise/policies/unique_password/models.py | ||||||
| #: authentik/policies/password/models.py | #: authentik/policies/password/models.py | ||||||
| @ -521,20 +521,18 @@ msgstr "La contraseña no se ha establecido en contexto" | |||||||
| #: authentik/enterprise/policies/unique_password/models.py | #: authentik/enterprise/policies/unique_password/models.py | ||||||
| msgid "This password has been used previously. Please choose a different one." | msgid "This password has been used previously. Please choose a different one." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Esta contraseña se ha utilizado anteriormente. Por favor, elija una " |  | ||||||
| "diferente." |  | ||||||
|  |  | ||||||
| #: authentik/enterprise/policies/unique_password/models.py | #: authentik/enterprise/policies/unique_password/models.py | ||||||
| msgid "Password Uniqueness Policy" | msgid "Password Uniqueness Policy" | ||||||
| msgstr "Política de Unicidad de Contraseñas" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/policies/unique_password/models.py | #: authentik/enterprise/policies/unique_password/models.py | ||||||
| msgid "Password Uniqueness Policies" | msgid "Password Uniqueness Policies" | ||||||
| msgstr "Políticas de Unicidad de Contraseñas" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/policies/unique_password/models.py | #: authentik/enterprise/policies/unique_password/models.py | ||||||
| msgid "User Password History" | msgid "User Password History" | ||||||
| msgstr "Historial de Contraseñas del Usuario" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/policy.py | #: authentik/enterprise/policy.py | ||||||
| msgid "Enterprise required to access this feature." | msgid "Enterprise required to access this feature." | ||||||
| @ -619,39 +617,39 @@ msgstr "Clave de firma" | |||||||
|  |  | ||||||
| #: authentik/enterprise/providers/ssf/models.py | #: authentik/enterprise/providers/ssf/models.py | ||||||
| msgid "Key used to sign the SSF Events." | msgid "Key used to sign the SSF Events." | ||||||
| msgstr "Clave utilizada para firmar los eventos SSF." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/providers/ssf/models.py | #: authentik/enterprise/providers/ssf/models.py | ||||||
| msgid "Shared Signals Framework Provider" | msgid "Shared Signals Framework Provider" | ||||||
| msgstr "Proveedor del Marco de Señales Compartidas" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/providers/ssf/models.py | #: authentik/enterprise/providers/ssf/models.py | ||||||
| msgid "Shared Signals Framework Providers" | msgid "Shared Signals Framework Providers" | ||||||
| msgstr "Proveedores del Marco de Señales Compartidas" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/providers/ssf/models.py | #: authentik/enterprise/providers/ssf/models.py | ||||||
| msgid "Add stream to SSF provider" | msgid "Add stream to SSF provider" | ||||||
| msgstr "Agregar flujo de datos al proveedor SSF" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/providers/ssf/models.py | #: authentik/enterprise/providers/ssf/models.py | ||||||
| msgid "SSF Stream" | msgid "SSF Stream" | ||||||
| msgstr "Flujo de Datos SSF" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/providers/ssf/models.py | #: authentik/enterprise/providers/ssf/models.py | ||||||
| msgid "SSF Streams" | msgid "SSF Streams" | ||||||
| msgstr "Flujos de Datos SSF" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/providers/ssf/models.py | #: authentik/enterprise/providers/ssf/models.py | ||||||
| msgid "SSF Stream Event" | msgid "SSF Stream Event" | ||||||
| msgstr "Evento de Flujo de Datos SSF" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/providers/ssf/models.py | #: authentik/enterprise/providers/ssf/models.py | ||||||
| msgid "SSF Stream Events" | msgid "SSF Stream Events" | ||||||
| msgstr "Eventos de Flujos de Datos SSF" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/providers/ssf/tasks.py | #: authentik/enterprise/providers/ssf/tasks.py | ||||||
| msgid "Failed to send request" | msgid "Failed to send request" | ||||||
| msgstr "Error al enviar la solicitud" | msgstr "Falló envio de petición" | ||||||
|  |  | ||||||
| #: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py | #: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py | ||||||
| msgid "Endpoint Authenticator Google Device Trust Connector Stage" | msgid "Endpoint Authenticator Google Device Trust Connector Stage" | ||||||
| @ -683,29 +681,26 @@ msgid "" | |||||||
| "option has a higher priority than the `client_certificate` option on " | "option has a higher priority than the `client_certificate` option on " | ||||||
| "`Brand`." | "`Brand`." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Configura las autoridades certificadoras para validar el certificado. Esta " |  | ||||||
| "opción tiene una prioridad mayor que la opción `client_certificate` en " |  | ||||||
| "`Brand`." |  | ||||||
|  |  | ||||||
| #: authentik/enterprise/stages/mtls/models.py | #: authentik/enterprise/stages/mtls/models.py | ||||||
| msgid "Mutual TLS Stage" | msgid "Mutual TLS Stage" | ||||||
| msgstr "Etapa de TLS mutuo" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/stages/mtls/models.py | #: authentik/enterprise/stages/mtls/models.py | ||||||
| msgid "Mutual TLS Stages" | msgid "Mutual TLS Stages" | ||||||
| msgstr "Etapas de TLS mutuo" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/stages/mtls/models.py | #: authentik/enterprise/stages/mtls/models.py | ||||||
| msgid "Permissions to pass Certificates for outposts." | msgid "Permissions to pass Certificates for outposts." | ||||||
| msgstr "Permisos para pasar Certificados a los puestos avanzados." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/stages/mtls/stage.py | #: authentik/enterprise/stages/mtls/stage.py | ||||||
| msgid "Certificate required but no certificate was given." | msgid "Certificate required but no certificate was given." | ||||||
| msgstr "Se requiere certificado, pero no se proporcionó ninguno." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/stages/mtls/stage.py | #: authentik/enterprise/stages/mtls/stage.py | ||||||
| msgid "No user found for certificate." | msgid "No user found for certificate." | ||||||
| msgstr "No se encontró usuario para el certificado." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/enterprise/stages/source/models.py | #: authentik/enterprise/stages/source/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -758,16 +753,12 @@ msgid "" | |||||||
| "Customize the body of the request. Mapping should return data that is JSON-" | "Customize the body of the request. Mapping should return data that is JSON-" | ||||||
| "serializable." | "serializable." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Personaliza el cuerpo de la solicitud. El mapeo debe devolver datos que sean" |  | ||||||
| " serializables en JSON." |  | ||||||
|  |  | ||||||
| #: authentik/events/models.py | #: authentik/events/models.py | ||||||
| msgid "" | msgid "" | ||||||
| "Configure additional headers to be sent. Mapping should return a dictionary " | "Configure additional headers to be sent. Mapping should return a dictionary " | ||||||
| "of key-value pairs" | "of key-value pairs" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Configura encabezados adicionales para enviar. El mapeo debe devolver un " |  | ||||||
| "diccionario de pares clave-valor" |  | ||||||
|  |  | ||||||
| #: authentik/events/models.py | #: authentik/events/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -795,7 +786,7 @@ msgstr "Transporte de notificaciones" | |||||||
|  |  | ||||||
| #: authentik/events/models.py | #: authentik/events/models.py | ||||||
| msgid "Notification Transports" | msgid "Notification Transports" | ||||||
| msgstr "Medios de Notificación" | msgstr "Transportes de notificación" | ||||||
|  |  | ||||||
| #: authentik/events/models.py | #: authentik/events/models.py | ||||||
| msgid "Notice" | msgid "Notice" | ||||||
| @ -822,9 +813,9 @@ msgid "" | |||||||
| "Select which transports should be used to notify the user. If none are " | "Select which transports should be used to notify the user. If none are " | ||||||
| "selected, the notification will only be shown in the authentik UI." | "selected, the notification will only be shown in the authentik UI." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Selecciona qué medios se deben usar para notificar al usuario. Si no se " | "Seleccione qué transportes se deben usar para notificar al usuario. Si no se" | ||||||
| " selecciona ninguno, la notificación solo se mostrará en la interfaz de " | " selecciona ninguno, la notificación solo se mostrará en la interfaz de " | ||||||
| "authentik." | "usuario de authentik." | ||||||
|  |  | ||||||
| #: authentik/events/models.py | #: authentik/events/models.py | ||||||
| msgid "Controls which severity level the created notifications will have." | msgid "Controls which severity level the created notifications will have." | ||||||
| @ -996,7 +987,7 @@ msgstr "Evalúa políticas durante el proceso de planeación del Flujo." | |||||||
|  |  | ||||||
| #: authentik/flows/models.py | #: authentik/flows/models.py | ||||||
| msgid "Evaluate policies when the Stage is presented to the user." | msgid "Evaluate policies when the Stage is presented to the user." | ||||||
| msgstr "Evaluar las políticas cuando la Etapa se presenta al usuario." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/flows/models.py | #: authentik/flows/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -1043,8 +1034,6 @@ msgid "" | |||||||
| "When enabled, provider will not modify or create objects in the remote " | "When enabled, provider will not modify or create objects in the remote " | ||||||
| "system." | "system." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Cuando está habilitado, el proveedor no modificará ni creará objetos en el " |  | ||||||
| "sistema remoto." |  | ||||||
|  |  | ||||||
| #: authentik/lib/sync/outgoing/tasks.py | #: authentik/lib/sync/outgoing/tasks.py | ||||||
| msgid "Starting full provider sync" | msgid "Starting full provider sync" | ||||||
| @ -1052,21 +1041,20 @@ msgstr "Iniciando sincronización completa de proveedor" | |||||||
|  |  | ||||||
| #: authentik/lib/sync/outgoing/tasks.py | #: authentik/lib/sync/outgoing/tasks.py | ||||||
| msgid "Syncing users" | msgid "Syncing users" | ||||||
| msgstr "Sincronizando usuarios" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/lib/sync/outgoing/tasks.py | #: authentik/lib/sync/outgoing/tasks.py | ||||||
| msgid "Syncing groups" | msgid "Syncing groups" | ||||||
| msgstr "Sincronizando grupos" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/lib/sync/outgoing/tasks.py | #: authentik/lib/sync/outgoing/tasks.py | ||||||
| #, python-brace-format | #, python-brace-format | ||||||
| msgid "Syncing page {page} of {object_type}" | msgid "Syncing page {page} of groups" | ||||||
| msgstr "Sincronizando página {page} de {object_type}" | msgstr "Sincronizando página {page} de grupos" | ||||||
|  |  | ||||||
| #: authentik/lib/sync/outgoing/tasks.py | #: authentik/lib/sync/outgoing/tasks.py | ||||||
| msgid "Dropping mutating request due to dry run" | msgid "Dropping mutating request due to dry run" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Descartando solicitud de mutación debido a ejecución en modo de simulación" |  | ||||||
|  |  | ||||||
| #: authentik/lib/sync/outgoing/tasks.py | #: authentik/lib/sync/outgoing/tasks.py | ||||||
| #, python-brace-format | #, python-brace-format | ||||||
| @ -1245,7 +1233,7 @@ msgstr "" | |||||||
|  |  | ||||||
| #: authentik/policies/expiry/models.py | #: authentik/policies/expiry/models.py | ||||||
| msgid "Password has expired." | msgid "Password has expired." | ||||||
| msgstr "La contraseña ha expirado." | msgstr "La contraseña ha caducado." | ||||||
|  |  | ||||||
| #: authentik/policies/expiry/models.py | #: authentik/policies/expiry/models.py | ||||||
| msgid "Password Expiry Policy" | msgid "Password Expiry Policy" | ||||||
| @ -1283,7 +1271,7 @@ msgstr "La IP del cliente no está en un país permitido." | |||||||
|  |  | ||||||
| #: authentik/policies/geoip/models.py | #: authentik/policies/geoip/models.py | ||||||
| msgid "Distance from previous authentication is larger than threshold." | msgid "Distance from previous authentication is larger than threshold." | ||||||
| msgstr "La distancia desde la autenticación anterior es mayor que el umbral." | msgstr "La distancia desde la autenticación previa es mayor que el límite." | ||||||
|  |  | ||||||
| #: authentik/policies/geoip/models.py | #: authentik/policies/geoip/models.py | ||||||
| msgid "Distance is further than possible." | msgid "Distance is further than possible." | ||||||
| @ -1332,7 +1320,7 @@ msgstr "Vinculación de Políticas" | |||||||
|  |  | ||||||
| #: authentik/policies/models.py | #: authentik/policies/models.py | ||||||
| msgid "Policy Bindings" | msgid "Policy Bindings" | ||||||
| msgstr "Vinculaciones de Políticas" | msgstr "Vinculaciones de políticas" | ||||||
|  |  | ||||||
| #: authentik/policies/models.py | #: authentik/policies/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -1606,11 +1594,11 @@ msgstr "ES256 (Encriptación Asimétrica)" | |||||||
|  |  | ||||||
| #: authentik/providers/oauth2/models.py | #: authentik/providers/oauth2/models.py | ||||||
| msgid "ES384 (Asymmetric Encryption)" | msgid "ES384 (Asymmetric Encryption)" | ||||||
| msgstr "ES384 (Encriptación Asimétrica)" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/providers/oauth2/models.py | #: authentik/providers/oauth2/models.py | ||||||
| msgid "ES512 (Asymmetric Encryption)" | msgid "ES512 (Asymmetric Encryption)" | ||||||
| msgstr "ES512 (Encriptación Asimétrica)" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/providers/oauth2/models.py | #: authentik/providers/oauth2/models.py | ||||||
| msgid "Scope used by the client" | msgid "Scope used by the client" | ||||||
| @ -1825,7 +1813,7 @@ msgstr "Valida Certificados SSL de servidores de origen" | |||||||
|  |  | ||||||
| #: authentik/providers/proxy/models.py | #: authentik/providers/proxy/models.py | ||||||
| msgid "Internal host SSL Validation" | msgid "Internal host SSL Validation" | ||||||
| msgstr "Validación SSL del host interno" | msgstr "Validación SSL de host interno" | ||||||
|  |  | ||||||
| #: authentik/providers/proxy/models.py | #: authentik/providers/proxy/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -2039,7 +2027,7 @@ msgstr "" | |||||||
|  |  | ||||||
| #: authentik/providers/saml/models.py | #: authentik/providers/saml/models.py | ||||||
| msgid "AuthnContextClassRef Property Mapping" | msgid "AuthnContextClassRef Property Mapping" | ||||||
| msgstr "Asignación de Propiedades de AuthnContextClassRef" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/providers/saml/models.py | #: authentik/providers/saml/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -2047,9 +2035,6 @@ msgid "" | |||||||
| "empty, the AuthnContextClassRef will be set based on which authentication " | "empty, the AuthnContextClassRef will be set based on which authentication " | ||||||
| "methods the user used to authenticate." | "methods the user used to authenticate." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Configura cómo se creará el valor de AuthnContextClassRef. Si se deja vacío," |  | ||||||
| " el AuthnContextClassRef se establecerá según los métodos de autenticación " |  | ||||||
| "que el usuario haya utilizado para autenticarse." |  | ||||||
|  |  | ||||||
| #: authentik/providers/saml/models.py | #: authentik/providers/saml/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -2199,11 +2184,11 @@ msgstr "Predeterminado" | |||||||
|  |  | ||||||
| #: authentik/providers/scim/models.py | #: authentik/providers/scim/models.py | ||||||
| msgid "AWS" | msgid "AWS" | ||||||
| msgstr "AWS" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/providers/scim/models.py | #: authentik/providers/scim/models.py | ||||||
| msgid "Slack" | msgid "Slack" | ||||||
| msgstr "Slack" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/providers/scim/models.py | #: authentik/providers/scim/models.py | ||||||
| msgid "Base URL to SCIM requests, usually ends in /v2" | msgid "Base URL to SCIM requests, usually ends in /v2" | ||||||
| @ -2215,13 +2200,11 @@ msgstr "Token de Autenticación" | |||||||
|  |  | ||||||
| #: authentik/providers/scim/models.py | #: authentik/providers/scim/models.py | ||||||
| msgid "SCIM Compatibility Mode" | msgid "SCIM Compatibility Mode" | ||||||
| msgstr "Modo de Compatibilidad SCIM" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/providers/scim/models.py | #: authentik/providers/scim/models.py | ||||||
| msgid "Alter authentik behavior for vendor-specific SCIM implementations." | msgid "Alter authentik behavior for vendor-specific SCIM implementations." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Modificar el comportamiento de authentik para implementaciones SCIM " |  | ||||||
| "específicas de proveedores." |  | ||||||
|  |  | ||||||
| #: authentik/providers/scim/models.py | #: authentik/providers/scim/models.py | ||||||
| msgid "SCIM Provider" | msgid "SCIM Provider" | ||||||
| @ -2249,7 +2232,7 @@ msgstr "Roles" | |||||||
|  |  | ||||||
| #: authentik/rbac/models.py | #: authentik/rbac/models.py | ||||||
| msgid "Initial Permissions" | msgid "Initial Permissions" | ||||||
| msgstr "Permisos Iniciales" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/rbac/models.py | #: authentik/rbac/models.py | ||||||
| msgid "System permission" | msgid "System permission" | ||||||
| @ -2287,7 +2270,7 @@ msgstr "" | |||||||
|  |  | ||||||
| #: authentik/recovery/views.py | #: authentik/recovery/views.py | ||||||
| msgid "Used recovery-link to authenticate." | msgid "Used recovery-link to authenticate." | ||||||
| msgstr "Se utilizó un enlace de recuperación para autenticarse." | msgstr "Se usó el enlace de recuperación para autenticarse." | ||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "Kerberos realm" | msgid "Kerberos realm" | ||||||
| @ -2299,7 +2282,7 @@ msgstr "krb5.conf personalizado a usar. Usa el del sistema por defecto." | |||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "KAdmin server type" | msgid "KAdmin server type" | ||||||
| msgstr "Tipo de servidor KAdmin" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "Sync users from Kerberos into authentik" | msgid "Sync users from Kerberos into authentik" | ||||||
| @ -2307,24 +2290,23 @@ msgstr "Sincronizar usuarios desde Kerberos hacia Authentik" | |||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "When a user changes their password, sync it back to Kerberos" | msgid "When a user changes their password, sync it back to Kerberos" | ||||||
| msgstr "" | msgstr "Cuando un usuario cambia su contraseña, sincronizarlo hacia Kerberos" | ||||||
| "Cuando un usuario cambie su contraseña, sincronizarla de vuelta a Kerberos." |  | ||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "Principal to authenticate to kadmin for sync." | msgid "Principal to authenticate to kadmin for sync." | ||||||
| msgstr "Principal para autenticarse en kadmin para la sincronización." | msgstr "Principal para autenticarse como kadmin para la sincronización." | ||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "Password to authenticate to kadmin for sync" | msgid "Password to authenticate to kadmin for sync" | ||||||
| msgstr "Contraseña para autenticarse en kadmin para la sincronización" | msgstr "Contraseña para autenticarse como kadmin para la sincronización" | ||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "" | msgid "" | ||||||
| "Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the " | "Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the " | ||||||
| "form TYPE:residual" | "form TYPE:residual" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Keytab para autenticarse en kadmin para la sincronización. Debe estar " | "Keytab para autenticarse como kadmin para la sincronización. Debe estar " | ||||||
| "codificado en base64 o en el formato TIPO:residuo" | "codificado en base64 o en el formato TIPO:residual" | ||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "" | msgid "" | ||||||
| @ -2340,7 +2322,7 @@ msgid "" | |||||||
| "HTTP@hostname" | "HTTP@hostname" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Forzar el uso de un nombre de servidor específico para SPNEGO. Debe estar en" | "Forzar el uso de un nombre de servidor específico para SPNEGO. Debe estar en" | ||||||
| " el formato HTTP@nombre_de_host" | " el formato HTTP@nombredelservidor" | ||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path" | msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path" | ||||||
| @ -2357,8 +2339,8 @@ msgid "" | |||||||
| "If enabled, the authentik-stored password will be updated upon login with " | "If enabled, the authentik-stored password will be updated upon login with " | ||||||
| "the Kerberos password backend" | "the Kerberos password backend" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Si está habilitado, la contraseña almacenada en authentik se actualizará al " | "Si está habilitado, la contraseña almacenada por authentik será actualizada " | ||||||
| "iniciar sesión con el backend de contraseñas de Kerberos." | "al iniciar sesión con el backend de contraseñas Kerberos" | ||||||
|  |  | ||||||
| #: authentik/sources/kerberos/models.py | #: authentik/sources/kerberos/models.py | ||||||
| msgid "Kerberos Source" | msgid "Kerberos Source" | ||||||
| @ -2406,7 +2388,7 @@ msgid "" | |||||||
| msgstr "" | msgstr "" | ||||||
| "\n" | "\n" | ||||||
| "                    Asegúrate de que tienes entradas válidas\n" | "                    Asegúrate de que tienes entradas válidas\n" | ||||||
| "                    (obtenibles mediante kinit) \n" | "                    (se obtienen a través de kinit) \n" | ||||||
| "                    y de haber configurado correctamente el navegador.\n" | "                    y de haber configurado correctamente el navegador.\n" | ||||||
| "                    Por favor, contacta a tu administrador.\n" | "                    Por favor, contacta a tu administrador.\n" | ||||||
| "                " | "                " | ||||||
| @ -2471,10 +2453,6 @@ msgstr "DN de grupo de adición" | |||||||
| msgid "Consider Objects matching this filter to be Users." | msgid "Consider Objects matching this filter to be Users." | ||||||
| msgstr "Considere que los objetos que coinciden con este filtro son usuarios." | msgstr "Considere que los objetos que coinciden con este filtro son usuarios." | ||||||
|  |  | ||||||
| #: authentik/sources/ldap/models.py |  | ||||||
| msgid "Attribute which matches the value of `group_membership_field`." |  | ||||||
| msgstr "Atributo que coincide con el valor de `group_membership_field`." |  | ||||||
|  |  | ||||||
| #: authentik/sources/ldap/models.py | #: authentik/sources/ldap/models.py | ||||||
| msgid "Field which contains members of a group." | msgid "Field which contains members of a group." | ||||||
| msgstr "Campo que contiene los miembros de un grupo." | msgstr "Campo que contiene los miembros de un grupo." | ||||||
| @ -2507,17 +2485,12 @@ msgid "" | |||||||
| "attribute. This allows nested group resolution on systems like FreeIPA and " | "attribute. This allows nested group resolution on systems like FreeIPA and " | ||||||
| "Active Directory" | "Active Directory" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Buscar la pertenencia a grupos basándose en un atributo del usuario en lugar" |  | ||||||
| " de un atributo del grupo. Esto permite la resolución de grupos anidados en " |  | ||||||
| "sistemas como FreeIPA y Active Directory" |  | ||||||
|  |  | ||||||
| #: authentik/sources/ldap/models.py | #: authentik/sources/ldap/models.py | ||||||
| msgid "" | msgid "" | ||||||
| "Delete authentik users and groups which were previously supplied by this " | "Delete authentik users and groups which were previously supplied by this " | ||||||
| "source, but are now missing from it." | "source, but are now missing from it." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Eliminar usuarios y grupos de authentik que fueron proporcionados " |  | ||||||
| "previamente por esta fuente, pero que ahora están ausentes." |  | ||||||
|  |  | ||||||
| #: authentik/sources/ldap/models.py | #: authentik/sources/ldap/models.py | ||||||
| msgid "LDAP Source" | msgid "LDAP Source" | ||||||
| @ -2539,24 +2512,22 @@ msgstr "Asignaciones de Propiedades de Fuente de LDAP" | |||||||
| msgid "" | msgid "" | ||||||
| "Unique ID used while checking if this object still exists in the directory." | "Unique ID used while checking if this object still exists in the directory." | ||||||
| msgstr "" | msgstr "" | ||||||
| "ID único utilizado para verificar si este objeto aún existe en el " |  | ||||||
| "directorio." |  | ||||||
|  |  | ||||||
| #: authentik/sources/ldap/models.py | #: authentik/sources/ldap/models.py | ||||||
| msgid "User LDAP Source Connection" | msgid "User LDAP Source Connection" | ||||||
| msgstr "Conexión de Fuente LDAP de Usuario" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/sources/ldap/models.py | #: authentik/sources/ldap/models.py | ||||||
| msgid "User LDAP Source Connections" | msgid "User LDAP Source Connections" | ||||||
| msgstr "Conexiones de Fuente LDAP de Usuario" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/sources/ldap/models.py | #: authentik/sources/ldap/models.py | ||||||
| msgid "Group LDAP Source Connection" | msgid "Group LDAP Source Connection" | ||||||
| msgstr "Conexión de Fuente LDAP de Grupo" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/sources/ldap/models.py | #: authentik/sources/ldap/models.py | ||||||
| msgid "Group LDAP Source Connections" | msgid "Group LDAP Source Connections" | ||||||
| msgstr "Conexiones de Fuente LDAP de Grupo" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/sources/ldap/signals.py | #: authentik/sources/ldap/signals.py | ||||||
| msgid "Password does not match Active Directory Complexity." | msgid "Password does not match Active Directory Complexity." | ||||||
| @ -2568,11 +2539,11 @@ msgstr "No se recibió ningún token." | |||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "HTTP Basic Authentication" | msgid "HTTP Basic Authentication" | ||||||
| msgstr "Autenticación Básica HTTP" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "Include the client ID and secret as request parameters" | msgid "Include the client ID and secret as request parameters" | ||||||
| msgstr "Incluir el ID de cliente y el secreto como parámetros de la solicitud" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "Request Token URL" | msgid "Request Token URL" | ||||||
| @ -2619,8 +2590,6 @@ msgid "" | |||||||
| "How to perform authentication during an authorization_code token request " | "How to perform authentication during an authorization_code token request " | ||||||
| "flow" | "flow" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Cómo realizar la autenticación durante un flujo de solicitud de token con " |  | ||||||
| "authorization_code" |  | ||||||
|  |  | ||||||
| #: authentik/sources/oauth/models.py | #: authentik/sources/oauth/models.py | ||||||
| msgid "OAuth Source" | msgid "OAuth Source" | ||||||
| @ -2938,7 +2907,7 @@ msgstr "Conexiones de Fuente de SAML de Grupo" | |||||||
| #: authentik/sources/saml/views.py | #: authentik/sources/saml/views.py | ||||||
| #, python-brace-format | #, python-brace-format | ||||||
| msgid "Continue to {source_name}" | msgid "Continue to {source_name}" | ||||||
| msgstr "Continuar a {source_name}" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/sources/scim/models.py | #: authentik/sources/scim/models.py | ||||||
| msgid "SCIM Source" | msgid "SCIM Source" | ||||||
| @ -2974,7 +2943,7 @@ msgstr "Dispositivos Duo" | |||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/models.py | #: authentik/stages/authenticator_email/models.py | ||||||
| msgid "Email OTP" | msgid "Email OTP" | ||||||
| msgstr "OTP por Correo Electrónico" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/models.py | #: authentik/stages/authenticator_email/models.py | ||||||
| #: authentik/stages/email/models.py | #: authentik/stages/email/models.py | ||||||
| @ -2995,11 +2964,11 @@ msgstr "" | |||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/models.py | #: authentik/stages/authenticator_email/models.py | ||||||
| msgid "Email Authenticator Setup Stage" | msgid "Email Authenticator Setup Stage" | ||||||
| msgstr "Etapa de Configuración del Autenticador de Correo Electrónico" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/models.py | #: authentik/stages/authenticator_email/models.py | ||||||
| msgid "Email Authenticator Setup Stages" | msgid "Email Authenticator Setup Stages" | ||||||
| msgstr "Etapas de Configuración del Autenticador de Correo Electrónico" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/models.py | #: authentik/stages/authenticator_email/models.py | ||||||
| #: authentik/stages/authenticator_email/stage.py | #: authentik/stages/authenticator_email/stage.py | ||||||
| @ -3010,11 +2979,11 @@ msgstr "" | |||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/models.py | #: authentik/stages/authenticator_email/models.py | ||||||
| msgid "Email Device" | msgid "Email Device" | ||||||
| msgstr "Dispositivo de correo electrónico" | msgstr "Dispositivo de Email" | ||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/models.py | #: authentik/stages/authenticator_email/models.py | ||||||
| msgid "Email Devices" | msgid "Email Devices" | ||||||
| msgstr "Dispositivos de correo electrónico" | msgstr "Dispositivos de Email" | ||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/stage.py | #: authentik/stages/authenticator_email/stage.py | ||||||
| #: authentik/stages/authenticator_sms/stage.py | #: authentik/stages/authenticator_sms/stage.py | ||||||
| @ -3024,7 +2993,7 @@ msgstr "El código no coincide" | |||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/stage.py | #: authentik/stages/authenticator_email/stage.py | ||||||
| msgid "Invalid email" | msgid "Invalid email" | ||||||
| msgstr "Correo electrónico inválido" | msgstr "Email Inválido" | ||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/templates/email/email_otp.html | #: authentik/stages/authenticator_email/templates/email/email_otp.html | ||||||
| #: authentik/stages/email/templates/email/password_reset.html | #: authentik/stages/email/templates/email/password_reset.html | ||||||
| @ -3044,9 +3013,6 @@ msgid "" | |||||||
| "          Email MFA code.\n" | "          Email MFA code.\n" | ||||||
| "          " | "          " | ||||||
| msgstr "" | msgstr "" | ||||||
| "\n" |  | ||||||
| "          Código MFA por correo electrónico.\n" |  | ||||||
| "          " |  | ||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/templates/email/email_otp.html | #: authentik/stages/authenticator_email/templates/email/email_otp.html | ||||||
| #, python-format | #, python-format | ||||||
| @ -3056,8 +3022,7 @@ msgid "" | |||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
| "\n" | "\n" | ||||||
| "    Si no solicitaste este código, por favor ignora este correo. El código anterior es válido por %(expires)s.\n" | "Si no solicitaste este código, por favor ignora este correo. El código anterior es válido por %(expires)s." | ||||||
| "    " |  | ||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/templates/email/email_otp.txt | #: authentik/stages/authenticator_email/templates/email/email_otp.txt | ||||||
| #: authentik/stages/email/templates/email/password_reset.txt | #: authentik/stages/email/templates/email/password_reset.txt | ||||||
| @ -3070,8 +3035,6 @@ msgid "" | |||||||
| "\n" | "\n" | ||||||
| "Email MFA code\n" | "Email MFA code\n" | ||||||
| msgstr "" | msgstr "" | ||||||
| "\n" |  | ||||||
| "Código MFA por correo electrónico\n" |  | ||||||
|  |  | ||||||
| #: authentik/stages/authenticator_email/templates/email/email_otp.txt | #: authentik/stages/authenticator_email/templates/email/email_otp.txt | ||||||
| #, python-format | #, python-format | ||||||
| @ -3313,8 +3276,8 @@ msgstr "No se pudo validar el token" | |||||||
| msgid "" | msgid "" | ||||||
| "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)." | "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Desfase después del cual expira el consentimiento. (Formato: " | "Compensación después de la cual caduca el consentimiento. (Formato: horas = " | ||||||
| "hours=1;minutes=2;seconds=3)." | "1; minutos = 2; segundos = 3)." | ||||||
|  |  | ||||||
| #: authentik/stages/consent/models.py | #: authentik/stages/consent/models.py | ||||||
| msgid "Consent Stage" | msgid "Consent Stage" | ||||||
| @ -3334,7 +3297,7 @@ msgstr "Consentimientos del usuario" | |||||||
|  |  | ||||||
| #: authentik/stages/consent/stage.py | #: authentik/stages/consent/stage.py | ||||||
| msgid "Invalid consent token, re-showing prompt" | msgid "Invalid consent token, re-showing prompt" | ||||||
| msgstr "Token de consentimiento inválido, mostrando el aviso nuevamente" | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/stages/deny/models.py | #: authentik/stages/deny/models.py | ||||||
| msgid "Deny Stage" | msgid "Deny Stage" | ||||||
| @ -3354,11 +3317,11 @@ msgstr "Etapas ficticias" | |||||||
|  |  | ||||||
| #: authentik/stages/email/flow.py | #: authentik/stages/email/flow.py | ||||||
| msgid "Continue to confirm this email address." | msgid "Continue to confirm this email address." | ||||||
| msgstr "Continúa para confirmar esta dirección de correo electrónico." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/stages/email/flow.py | #: authentik/stages/email/flow.py | ||||||
| msgid "Link was already used, please request a new link." | msgid "Link was already used, please request a new link." | ||||||
| msgstr "El enlace ya fue utilizado, por favor, solícita uno nuevo." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/stages/email/models.py | #: authentik/stages/email/models.py | ||||||
| msgid "Password Reset" | msgid "Password Reset" | ||||||
| @ -3482,8 +3445,7 @@ msgid "" | |||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
| "\n" | "\n" | ||||||
| "    Si no solicitaste un cambio de contraseña, por favor ignora este correo. El enlace anterior es válido por %(expires)s.\n" | "Si no solicitaste un cambio de contraseña, por favor ignora este correo. El enlace anterior es válido por %(expires)s." | ||||||
| "    " |  | ||||||
|  |  | ||||||
| #: authentik/stages/email/templates/email/password_reset.txt | #: authentik/stages/email/templates/email/password_reset.txt | ||||||
| msgid "" | msgid "" | ||||||
| @ -3567,26 +3529,24 @@ msgid "" | |||||||
| "Show the user the 'Remember me on this device' toggle, allowing repeat users" | "Show the user the 'Remember me on this device' toggle, allowing repeat users" | ||||||
| " to skip straight to entering their password." | " to skip straight to entering their password." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Mostrar al usuario la opción \"Recordarme en este dispositivo\", permitiendo" |  | ||||||
| " que los usuarios recurrentes pasen directamente a ingresar su contraseña." |  | ||||||
|  |  | ||||||
| #: authentik/stages/identification/models.py | #: authentik/stages/identification/models.py | ||||||
| msgid "Optional enrollment flow, which is linked at the bottom of the page." | msgid "Optional enrollment flow, which is linked at the bottom of the page." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Flujo de inscripción opcional, que se enlaza en la parte inferior de la " | "Flujo de inscripción opcional, que está vinculado en la parte inferior de la" | ||||||
| " página." | " página." | ||||||
|  |  | ||||||
| #: authentik/stages/identification/models.py | #: authentik/stages/identification/models.py | ||||||
| msgid "Optional recovery flow, which is linked at the bottom of the page." | msgid "Optional recovery flow, which is linked at the bottom of the page." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Flujo de recuperación opcional, que se enlaza en la parte inferior de la " | "Flujo de recuperación opcional, que está vinculado en la parte inferior de " | ||||||
| "página." | "la página." | ||||||
|  |  | ||||||
| #: authentik/stages/identification/models.py | #: authentik/stages/identification/models.py | ||||||
| msgid "Optional passwordless flow, which is linked at the bottom of the page." | msgid "Optional passwordless flow, which is linked at the bottom of the page." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Flujo opcional sin contraseña, que se enlaza en la parte inferior de la " | "Flujo sin contraseña opcional, el cual está vinculado en la parte inferior " | ||||||
| "página." | "de la página." | ||||||
|  |  | ||||||
| #: authentik/stages/identification/models.py | #: authentik/stages/identification/models.py | ||||||
| msgid "Specify which sources should be shown." | msgid "Specify which sources should be shown." | ||||||
| @ -3820,11 +3780,11 @@ msgstr "Las contraseñas no coinciden." | |||||||
|  |  | ||||||
| #: authentik/stages/redirect/api.py | #: authentik/stages/redirect/api.py | ||||||
| msgid "Target URL should be present when mode is Static." | msgid "Target URL should be present when mode is Static." | ||||||
| msgstr "La URL de destino debe estar presente cuando el modo es Estático." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/stages/redirect/api.py | #: authentik/stages/redirect/api.py | ||||||
| msgid "Target Flow should be present when mode is Flow." | msgid "Target Flow should be present when mode is Flow." | ||||||
| msgstr "El Flujo de Destino debe estar presente cuando el modo es Flujo." | msgstr "" | ||||||
|  |  | ||||||
| #: authentik/stages/redirect/models.py | #: authentik/stages/redirect/models.py | ||||||
| msgid "Redirect Stage" | msgid "Redirect Stage" | ||||||
| @ -3881,6 +3841,10 @@ msgstr "Etapas de inicio de" | |||||||
| msgid "No Pending user to login." | msgid "No Pending user to login." | ||||||
| msgstr "Ningún usuario pendiente para iniciar sesión." | msgstr "Ningún usuario pendiente para iniciar sesión." | ||||||
|  |  | ||||||
|  | #: authentik/stages/user_login/stage.py | ||||||
|  | msgid "Successfully logged in!" | ||||||
|  | msgstr "¡Se ha iniciado sesión correctamente!" | ||||||
|  |  | ||||||
| #: authentik/stages/user_logout/models.py | #: authentik/stages/user_logout/models.py | ||||||
| msgid "User Logout Stage" | msgid "User Logout Stage" | ||||||
| msgstr "Etapa de cierre de sesión del usuario" | msgstr "Etapa de cierre de sesión del usuario" | ||||||
| @ -3956,12 +3920,10 @@ msgstr "" | |||||||
| #: authentik/tenants/models.py | #: authentik/tenants/models.py | ||||||
| msgid "Reputation cannot decrease lower than this value. Zero or negative." | msgid "Reputation cannot decrease lower than this value. Zero or negative." | ||||||
| msgstr "" | msgstr "" | ||||||
| "La reputación no puede disminuir por debajo de este valor. Cero o negativo." |  | ||||||
|  |  | ||||||
| #: authentik/tenants/models.py | #: authentik/tenants/models.py | ||||||
| msgid "Reputation cannot increase higher than this value. Zero or positive." | msgid "Reputation cannot increase higher than this value. Zero or positive." | ||||||
| msgstr "" | msgstr "" | ||||||
| "La reputación no puede aumentar por encima de este valor. Cero o positivo." |  | ||||||
|  |  | ||||||
| #: authentik/tenants/models.py | #: authentik/tenants/models.py | ||||||
| msgid "The option configures the footer links on the flow executor pages." | msgid "The option configures the footer links on the flow executor pages." | ||||||
| @ -3984,8 +3946,8 @@ msgstr "Personificación habilitada/deshabilitada globalmente." | |||||||
| #: authentik/tenants/models.py | #: authentik/tenants/models.py | ||||||
| msgid "Require administrators to provide a reason for impersonating a user." | msgid "Require administrators to provide a reason for impersonating a user." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Requerir que los administradores proporcionen una razón para personificar a " | "Requerir a los administradores proporcionar una razón para suplantar un " | ||||||
| "un usuario." | "usuario." | ||||||
|  |  | ||||||
| #: authentik/tenants/models.py | #: authentik/tenants/models.py | ||||||
| msgid "Default token duration" | msgid "Default token duration" | ||||||
| @ -3997,7 +3959,7 @@ msgstr "Longitud predeterminada del token" | |||||||
|  |  | ||||||
| #: authentik/tenants/models.py | #: authentik/tenants/models.py | ||||||
| msgid "Tenant" | msgid "Tenant" | ||||||
| msgstr "Inquilino" | msgstr "inquilino" | ||||||
|  |  | ||||||
| #: authentik/tenants/models.py | #: authentik/tenants/models.py | ||||||
| msgid "Tenants" | msgid "Tenants" | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/components/ak-secret-textarea-input.js"; | import "@goauthentik/components/ak-private-textarea-input.js"; | ||||||
| import "@goauthentik/elements/CodeMirror"; | import "@goauthentik/elements/CodeMirror"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; | import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; | ||||||
| @ -46,7 +46,7 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string | |||||||
|                     required |                     required | ||||||
|                 /> |                 /> | ||||||
|             </ak-form-element-horizontal> |             </ak-form-element-horizontal> | ||||||
|             <ak-secret-textarea-input |             <ak-private-textarea-input | ||||||
|                 label=${msg("Certificate")} |                 label=${msg("Certificate")} | ||||||
|                 name="certificateData" |                 name="certificateData" | ||||||
|                 input-hint="code" |                 input-hint="code" | ||||||
| @ -54,8 +54,8 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string | |||||||
|                 required |                 required | ||||||
|                 ?revealed=${this.instance === undefined} |                 ?revealed=${this.instance === undefined} | ||||||
|                 help=${msg("PEM-encoded Certificate data.")} |                 help=${msg("PEM-encoded Certificate data.")} | ||||||
|             ></ak-secret-textarea-input> |             ></ak-private-textarea-input> | ||||||
|             <ak-secret-textarea-input |             <ak-private-textarea-input | ||||||
|                 label=${msg("Private Key")} |                 label=${msg("Private Key")} | ||||||
|                 name="keyData" |                 name="keyData" | ||||||
|                 input-hint="code" |                 input-hint="code" | ||||||
| @ -63,7 +63,7 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string | |||||||
|                 help=${msg( |                 help=${msg( | ||||||
|                     "Optional Private Key. If this is set, you can use this keypair for encryption.", |                     "Optional Private Key. If this is set, you can use this keypair for encryption.", | ||||||
|                 )} |                 )} | ||||||
|             ></ak-secret-textarea-input>`; |             ></ak-private-textarea-input>`; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants"; | import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants"; | ||||||
| import "@goauthentik/components/ak-secret-textarea-input.js"; | import "@goauthentik/components/ak-private-textarea-input.js"; | ||||||
| import "@goauthentik/elements/CodeMirror"; | import "@goauthentik/elements/CodeMirror"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; | import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; | ||||||
| @ -62,13 +62,13 @@ export class EnterpriseLicenseForm extends ModelForm<License, string> { | |||||||
|                     value="${ifDefined(this.installID)}" |                     value="${ifDefined(this.installID)}" | ||||||
|                 /> |                 /> | ||||||
|             </ak-form-element-horizontal> |             </ak-form-element-horizontal> | ||||||
|             <ak-secret-textarea-input |             <ak-private-textarea-input | ||||||
|                 name="key" |                 name="key" | ||||||
|                 ?revealed=${this.instance === undefined} |                 ?revealed=${this.instance === undefined} | ||||||
|                 label=${msg("License key")} |                 label=${msg("License key")} | ||||||
|                 input-hint="code" |                 input-hint="code" | ||||||
|             > |             > | ||||||
|             </ak-secret-textarea-input>`; |             </ak-private-textarea-input>`; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -4,7 +4,6 @@ import { | |||||||
|     propertyMappingsSelector, |     propertyMappingsSelector, | ||||||
| } from "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderFormHelpers.js"; | } from "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderFormHelpers.js"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/components/ak-hidden-text-input"; |  | ||||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js"; | import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -69,15 +68,21 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn | |||||||
|                             ${msg("Client ID for the app registration.")} |                             ${msg("Client ID for the app registration.")} | ||||||
|                         </p> |                         </p> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|                     <ak-hidden-text-input |                     <ak-form-element-horizontal | ||||||
|                         name="clientSecret" |  | ||||||
|                         label=${msg("Client Secret")} |                         label=${msg("Client Secret")} | ||||||
|                         value="${this.instance?.clientSecret ?? ""}" |  | ||||||
|                         input-hint="code" |  | ||||||
|                         required |                         required | ||||||
|                         .help=${msg("Client secret for the app registration.")} |                         name="clientSecret" | ||||||
|                     > |                     > | ||||||
|                     </ak-hidden-text-input> |                         <input | ||||||
|  |                             type="text" | ||||||
|  |                             value="${this.instance?.clientSecret ?? ""}" | ||||||
|  |                             class="pf-c-form-control pf-m-monospace" | ||||||
|  |                             required | ||||||
|  |                         /> | ||||||
|  |                         <p class="pf-c-form__helper-text"> | ||||||
|  |                             ${msg("Client secret for the app registration.")} | ||||||
|  |                         </p> | ||||||
|  |                     </ak-form-element-horizontal> | ||||||
|                     <ak-form-element-horizontal label=${msg("Tenant ID")} required name="tenantId"> |                     <ak-form-element-horizontal label=${msg("Tenant ID")} required name="tenantId"> | ||||||
|                         <input |                         <input | ||||||
|                             type="text" |                             type="text" | ||||||
|  | |||||||
| @ -5,7 +5,6 @@ import { | |||||||
|     akOAuthRedirectURIInput, |     akOAuthRedirectURIInput, | ||||||
| } from "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI"; | } from "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI"; | ||||||
| import { ascii_letters, digits, randomString } from "@goauthentik/common/utils"; | import { ascii_letters, digits, randomString } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/components/ak-hidden-text-input"; |  | ||||||
| import "@goauthentik/components/ak-radio-input"; | import "@goauthentik/components/ak-radio-input"; | ||||||
| import "@goauthentik/components/ak-text-input"; | import "@goauthentik/components/ak-text-input"; | ||||||
| import "@goauthentik/components/ak-textarea-input"; | import "@goauthentik/components/ak-textarea-input"; | ||||||
| @ -167,16 +166,17 @@ export function renderForm( | |||||||
|                     input-hint="code" |                     input-hint="code" | ||||||
|                 > |                 > | ||||||
|                 </ak-text-input> |                 </ak-text-input> | ||||||
|                 <ak-hidden-text-input |                 <ak-text-input | ||||||
|                     name="clientSecret" |                     name="clientSecret" | ||||||
|                     label=${msg("Client Secret")} |                     label=${msg("Client Secret")} | ||||||
|                     value="${provider?.clientSecret ?? randomString(128, ascii_letters + digits)}" |                     value="${provider?.clientSecret ?? randomString(128, ascii_letters + digits)}" | ||||||
|                     input-hint="code" |                     input-hint="code" | ||||||
|                     ?hidden=${!showClientSecret} |                     ?hidden=${!showClientSecret} | ||||||
|                 > |                 > | ||||||
|                 </ak-hidden-text-input> |                 </ak-text-input> | ||||||
|                 <ak-form-element-horizontal |                 <ak-form-element-horizontal | ||||||
|                     label=${msg("Redirect URIs/Origins (RegEx)")} |                     label=${msg("Redirect URIs/Origins (RegEx)")} | ||||||
|  |                     required | ||||||
|                     name="redirectUris" |                     name="redirectUris" | ||||||
|                 > |                 > | ||||||
|                     <ak-array-input |                     <ak-array-input | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; | import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; | ||||||
| import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; | import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; | ||||||
| import { ascii_letters, digits, randomString } from "@goauthentik/common/utils"; | import { ascii_letters, digits, randomString } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/components/ak-hidden-text-input"; |  | ||||||
| import "@goauthentik/components/ak-text-input"; |  | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import "@goauthentik/elements/forms/SearchSelect"; | import "@goauthentik/elements/forms/SearchSelect"; | ||||||
| @ -76,14 +74,14 @@ export function renderForm( | |||||||
|         <ak-form-group expanded> |         <ak-form-group expanded> | ||||||
|             <span slot="header"> ${msg("Protocol settings")} </span> |             <span slot="header"> ${msg("Protocol settings")} </span> | ||||||
|             <div slot="body" class="pf-c-form"> |             <div slot="body" class="pf-c-form"> | ||||||
|                 <ak-hidden-text-input |                 <ak-text-input | ||||||
|                     name="sharedSecret" |                     name="sharedSecret" | ||||||
|                     label=${msg("Shared secret")} |                     label=${msg("Shared secret")} | ||||||
|                     .errorMessages=${errors?.sharedSecret ?? []} |                     .errorMessages=${errors?.sharedSecret ?? []} | ||||||
|                     value=${provider?.sharedSecret ?? randomString(128, ascii_letters + digits)} |                     value=${provider?.sharedSecret ?? randomString(128, ascii_letters + digits)} | ||||||
|                     required |                     required | ||||||
|                     input-hint="code" |                     input-hint="code" | ||||||
|                 ></ak-hidden-text-input> |                 ></ak-text-input> | ||||||
|                 <ak-text-input |                 <ak-text-input | ||||||
|                     name="clientNetworks" |                     name="clientNetworks" | ||||||
|                     label=${msg("Client Networks")} |                     label=${msg("Client Networks")} | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/components/ak-hidden-text-input"; |  | ||||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| @ -51,7 +50,7 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE | |||||||
|                 > |                 > | ||||||
|                 </ak-switch-input> |                 </ak-switch-input> | ||||||
|  |  | ||||||
|                 <ak-hidden-text-input |                 <ak-text-input | ||||||
|                     name="token" |                     name="token" | ||||||
|                     label=${msg("Token")} |                     label=${msg("Token")} | ||||||
|                     value="${provider?.token ?? ""}" |                     value="${provider?.token ?? ""}" | ||||||
| @ -61,7 +60,7 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE | |||||||
|                         "Token to authenticate with. Currently only bearer authentication is supported.", |                         "Token to authenticate with. Currently only bearer authentication is supported.", | ||||||
|                     )} |                     )} | ||||||
|                     input-hint="code" |                     input-hint="code" | ||||||
|                 ></ak-hidden-text-input> |                 ></ak-text-input> | ||||||
|                 <ak-radio-input |                 <ak-radio-input | ||||||
|                     name="compatibilityMode" |                     name="compatibilityMode" | ||||||
|                     label=${msg("Compatibility Mode")} |                     label=${msg("Compatibility Mode")} | ||||||
|  | |||||||
| @ -7,8 +7,8 @@ import { | |||||||
|     UserMatchingModeToLabel, |     UserMatchingModeToLabel, | ||||||
| } from "@goauthentik/admin/sources/oauth/utils"; | } from "@goauthentik/admin/sources/oauth/utils"; | ||||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/components/ak-secret-text-input.js"; | import "@goauthentik/components/ak-private-text-input.js"; | ||||||
| import "@goauthentik/components/ak-secret-textarea-input.js"; | import "@goauthentik/components/ak-private-textarea-input.js"; | ||||||
| import "@goauthentik/components/ak-switch-input"; | import "@goauthentik/components/ak-switch-input"; | ||||||
| import "@goauthentik/components/ak-text-input"; | import "@goauthentik/components/ak-text-input"; | ||||||
| import "@goauthentik/components/ak-textarea-input"; | import "@goauthentik/components/ak-textarea-input"; | ||||||
| @ -248,22 +248,22 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke | |||||||
|                         value=${ifDefined(this.instance?.syncPrincipal)} |                         value=${ifDefined(this.instance?.syncPrincipal)} | ||||||
|                         help=${msg("Principal used to authenticate to the KDC for syncing.")} |                         help=${msg("Principal used to authenticate to the KDC for syncing.")} | ||||||
|                     ></ak-text-input> |                     ></ak-text-input> | ||||||
|                     <ak-secret-text-input |                     <ak-private-text-input | ||||||
|                         name="syncPassword" |                         name="syncPassword" | ||||||
|                         label=${msg("Sync password")} |                         label=${msg("Sync password")} | ||||||
|                         ?revealed=${this.instance === undefined} |                         ?revealed=${this.instance === undefined} | ||||||
|                         help=${msg( |                         help=${msg( | ||||||
|                             "Password used to authenticate to the KDC for syncing. Optional if Sync keytab or Sync credentials cache is provided.", |                             "Password used to authenticate to the KDC for syncing. Optional if Sync keytab or Sync credentials cache is provided.", | ||||||
|                         )} |                         )} | ||||||
|                     ></ak-secret-text-input> |                     ></ak-private-text-input> | ||||||
|                     <ak-secret-textarea-input |                     <ak-private-textarea-input | ||||||
|                         name="syncKeytab" |                         name="syncKeytab" | ||||||
|                         label=${msg("Sync keytab")} |                         label=${msg("Sync keytab")} | ||||||
|                         ?revealed=${this.instance === undefined} |                         ?revealed=${this.instance === undefined} | ||||||
|                         help=${msg( |                         help=${msg( | ||||||
|                             "Keytab used to authenticate to the KDC for syncing. Optional if Sync password or Sync credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.", |                             "Keytab used to authenticate to the KDC for syncing. Optional if Sync password or Sync credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.", | ||||||
|                         )} |                         )} | ||||||
|                     ></ak-secret-textarea-input> |                     ></ak-private-textarea-input> | ||||||
|                     <ak-text-input |                     <ak-text-input | ||||||
|                         name="syncCcache" |                         name="syncCcache" | ||||||
|                         label=${msg("Sync credentials cache")} |                         label=${msg("Sync credentials cache")} | ||||||
| @ -285,14 +285,14 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke | |||||||
|                             "Force the use of a specific server name for SPNEGO. Must be in the form HTTP@domain", |                             "Force the use of a specific server name for SPNEGO. Must be in the form HTTP@domain", | ||||||
|                         )} |                         )} | ||||||
|                     ></ak-text-input> |                     ></ak-text-input> | ||||||
|                     <ak-secret-textarea-input |                     <ak-private-textarea-input | ||||||
|                         name="spnegoKeytab" |                         name="spnegoKeytab" | ||||||
|                         label=${msg("SPNEGO keytab")} |                         label=${msg("SPNEGO keytab")} | ||||||
|                         ?revealed=${this.instance === undefined} |                         ?revealed=${this.instance === undefined} | ||||||
|                         help=${msg( |                         help=${msg( | ||||||
|                             "Keytab used for SPNEGO. Optional if SPNEGO credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.", |                             "Keytab used for SPNEGO. Optional if SPNEGO credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.", | ||||||
|                         )} |                         )} | ||||||
|                     ></ak-secret-textarea-input> |                     ></ak-private-textarea-input> | ||||||
|                     <ak-text-input |                     <ak-text-input | ||||||
|                         name="spnegoCcache" |                         name="spnegoCcache" | ||||||
|                         label=${msg("SPNEGO credentials cache")} |                         label=${msg("SPNEGO credentials cache")} | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search"; | |||||||
| import { placeholderHelperText } from "@goauthentik/admin/helperText"; | import { placeholderHelperText } from "@goauthentik/admin/helperText"; | ||||||
| import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; | import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/components/ak-secret-text-input.js"; | import "@goauthentik/components/ak-private-text-input.js"; | ||||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| @ -260,11 +260,11 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> { | |||||||
|                             class="pf-c-form-control" |                             class="pf-c-form-control" | ||||||
|                         /> |                         /> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|                     <ak-secret-text-input |                     <ak-private-text-input | ||||||
|                         label=${msg("Bind Password")} |                         label=${msg("Bind Password")} | ||||||
|                         name="bindPassword" |                         name="bindPassword" | ||||||
|                         ?revealed=${this.instance === undefined} |                         ?revealed=${this.instance === undefined} | ||||||
|                     ></ak-secret-text-input> |                     ></ak-private-text-input> | ||||||
|                     <ak-form-element-horizontal label=${msg("Base DN")} required name="baseDn"> |                     <ak-form-element-horizontal label=${msg("Base DN")} required name="baseDn"> | ||||||
|                         <input |                         <input | ||||||
|                             type="text" |                             type="text" | ||||||
|  | |||||||
| @ -8,8 +8,8 @@ import { | |||||||
|     UserMatchingModeToLabel, |     UserMatchingModeToLabel, | ||||||
| } from "@goauthentik/admin/sources/oauth/utils"; | } from "@goauthentik/admin/sources/oauth/utils"; | ||||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||||
|  | import "@goauthentik/components/ak-private-textarea-input.js"; | ||||||
| import "@goauthentik/components/ak-radio-input"; | import "@goauthentik/components/ak-radio-input"; | ||||||
| import "@goauthentik/components/ak-secret-textarea-input.js"; |  | ||||||
| import "@goauthentik/elements/CodeMirror"; | import "@goauthentik/elements/CodeMirror"; | ||||||
| import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; | import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; | ||||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||||
| @ -441,14 +441,14 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth | |||||||
|                         /> |                         /> | ||||||
|                         <p class="pf-c-form__helper-text">${msg("Also known as Client ID.")}</p> |                         <p class="pf-c-form__helper-text">${msg("Also known as Client ID.")}</p> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|                     <ak-secret-textarea-input |                     <ak-private-textarea-input | ||||||
|                         label=${msg("Consumer secret")} |                         label=${msg("Consumer secret")} | ||||||
|                         name="consumerSecret" |                         name="consumerSecret" | ||||||
|                         input-hint="code" |                         input-hint="code" | ||||||
|                         help=${msg("Also known as Client Secret.")} |                         help=${msg("Also known as Client Secret.")} | ||||||
|                         required |                         required | ||||||
|                         ?revealed=${this.instance === undefined} |                         ?revealed=${this.instance === undefined} | ||||||
|                     ></ak-secret-textarea-input> |                     ></ak-private-textarea-input> | ||||||
|                     <ak-form-element-horizontal label=${msg("Scopes")} name="additionalScopes"> |                     <ak-form-element-horizontal label=${msg("Scopes")} name="additionalScopes"> | ||||||
|                         <input |                         <input | ||||||
|                             type="text" |                             type="text" | ||||||
|  | |||||||
| @ -128,7 +128,7 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo | |||||||
|                     this.doAuth(); |                     this.doAuth(); | ||||||
|                 }} |                 }} | ||||||
|             > |             > | ||||||
|                 ${msg("Re-authenticate with Plex")} |                 ${msg("Re-authenticate with plex")} | ||||||
|             </button> |             </button> | ||||||
|             <ak-form-element-horizontal name="allowFriends"> |             <ak-form-element-horizontal name="allowFriends"> | ||||||
|                 <label class="pf-c-switch"> |                 <label class="pf-c-switch"> | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; | import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/components/ak-secret-text-input.js"; | import "@goauthentik/components/ak-private-text-input.js"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import "@goauthentik/elements/forms/SearchSelect"; | import "@goauthentik/elements/forms/SearchSelect"; | ||||||
| @ -95,13 +95,13 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta | |||||||
|                             required |                             required | ||||||
|                         /> |                         /> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|                     <ak-secret-text-input |                     <ak-private-text-input | ||||||
|                         name="clientSecret" |                         name="clientSecret" | ||||||
|                         label=${msg("Secret key")} |                         label=${msg("Secret key")} | ||||||
|                         input-hint="code" |                         input-hint="code" | ||||||
|                         required |                         required | ||||||
|                         ?revealed=${this.instance === undefined} |                         ?revealed=${this.instance === undefined} | ||||||
|                     ></ak-secret-text-input> |                     ></ak-private-text-input> | ||||||
|                 </div> |                 </div> | ||||||
|             </ak-form-group> |             </ak-form-group> | ||||||
|             <ak-form-group> |             <ak-form-group> | ||||||
| @ -125,12 +125,12 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta | |||||||
|                             spellcheck="false" |                             spellcheck="false" | ||||||
|                         /> |                         /> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|                     <ak-secret-text-input |                     <ak-private-text-input | ||||||
|                         name="adminSecretKey" |                         name="adminSecretKey" | ||||||
|                         label=${msg("Secret key")} |                         label=${msg("Secret key")} | ||||||
|                         input-hint="code" |                         input-hint="code" | ||||||
|                         ?revealed=${this.instance === undefined} |                         ?revealed=${this.instance === undefined} | ||||||
|                     ></ak-secret-text-input> |                     ></ak-private-text-input> | ||||||
|                 </div> |                 </div> | ||||||
|             </ak-form-group> |             </ak-form-group> | ||||||
|             <ak-form-group expanded> |             <ak-form-group expanded> | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; | import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/components/ak-secret-text-input.js"; | import "@goauthentik/components/ak-private-text-input.js"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import "@goauthentik/elements/forms/Radio"; | import "@goauthentik/elements/forms/Radio"; | ||||||
| @ -77,11 +77,11 @@ export class AuthenticatorEmailStageForm extends BaseStageForm<AuthenticatorEmai | |||||||
|                     /> |                     /> | ||||||
|                 </ak-form-element-horizontal> |                 </ak-form-element-horizontal> | ||||||
|  |  | ||||||
|                 <ak-secret-text-input |                 <ak-private-text-input | ||||||
|                     name="password" |                     name="password" | ||||||
|                     label=${msg("SMTP Password")} |                     label=${msg("SMTP Password")} | ||||||
|                     ?revealed=${this.instance === undefined} |                     ?revealed=${this.instance === undefined} | ||||||
|                 ></ak-secret-text-input> |                 ></ak-private-text-input> | ||||||
|  |  | ||||||
|                 <ak-form-element-horizontal name="useTls"> |                 <ak-form-element-horizontal name="useTls"> | ||||||
|                     <label class="pf-c-switch"> |                     <label class="pf-c-switch"> | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; | import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/components/ak-number-input"; | import "@goauthentik/components/ak-number-input"; | ||||||
| import "@goauthentik/components/ak-secret-text-input.js"; | import "@goauthentik/components/ak-private-text-input.js"; | ||||||
| import "@goauthentik/components/ak-switch-input"; | import "@goauthentik/components/ak-switch-input"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| @ -70,7 +70,7 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> { | |||||||
|                         </p> |                         </p> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|  |  | ||||||
|                     <ak-secret-text-input |                     <ak-private-text-input | ||||||
|                         name="privateKey" |                         name="privateKey" | ||||||
|                         label=${msg("Private Key")} |                         label=${msg("Private Key")} | ||||||
|                         input-hint="code" |                         input-hint="code" | ||||||
| @ -79,7 +79,7 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> { | |||||||
|                         help=${msg( |                         help=${msg( | ||||||
|                             "Private key, acquired from https://www.google.com/recaptcha/intro/v3.html.", |                             "Private key, acquired from https://www.google.com/recaptcha/intro/v3.html.", | ||||||
|                         )} |                         )} | ||||||
|                     ></ak-secret-text-input> |                     ></ak-private-text-input> | ||||||
|  |  | ||||||
|                     <ak-switch-input |                     <ak-switch-input | ||||||
|                         name="interactive" |                         name="interactive" | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; | import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/components/ak-secret-text-input.js"; | import "@goauthentik/components/ak-private-text-input.js"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import "@goauthentik/elements/utils/TimeDeltaHelp"; | import "@goauthentik/elements/utils/TimeDeltaHelp"; | ||||||
| @ -73,11 +73,11 @@ export class EmailStageForm extends BaseStageForm<EmailStage> { | |||||||
|                         class="pf-c-form-control" |                         class="pf-c-form-control" | ||||||
|                     /> |                     /> | ||||||
|                 </ak-form-element-horizontal> |                 </ak-form-element-horizontal> | ||||||
|                 <ak-secret-text-input |                 <ak-private-text-input | ||||||
|                     label=${msg("SMTP Password")} |                     label=${msg("SMTP Password")} | ||||||
|                     name="password" |                     name="password" | ||||||
|                     ?revealed=${this.instance === undefined} |                     ?revealed=${this.instance === undefined} | ||||||
|                 ></ak-secret-text-input> |                 ></ak-private-text-input> | ||||||
|                 <ak-form-element-horizontal name="useTls"> |                 <ak-form-element-horizontal name="useTls"> | ||||||
|                     <label class="pf-c-switch"> |                     <label class="pf-c-switch"> | ||||||
|                         <input |                         <input | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { dateTimeLocal } from "@goauthentik/common/temporal"; | import { dateTimeLocal } from "@goauthentik/common/temporal"; | ||||||
| import "@goauthentik/components/ak-hidden-text-input"; |  | ||||||
| import { Form } from "@goauthentik/elements/forms/Form"; | import { Form } from "@goauthentik/elements/forms/Form"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import { ModalForm } from "@goauthentik/elements/forms/ModalForm"; | import { ModalForm } from "@goauthentik/elements/forms/ModalForm"; | ||||||
| @ -125,14 +124,19 @@ export class ServiceAccountForm extends Form<UserServiceAccountRequest> { | |||||||
|                         class="pf-c-form-control" |                         class="pf-c-form-control" | ||||||
|                     /> |                     /> | ||||||
|                 </ak-form-element-horizontal> |                 </ak-form-element-horizontal> | ||||||
|                 <ak-hidden-text-input |                 <ak-form-element-horizontal label=${msg("Password")}> | ||||||
|                     label=${msg("Password")} |                     <input | ||||||
|                     value="${this.result?.token ?? ""}" |                         type="text" | ||||||
|                     .help=${msg( |                         readonly | ||||||
|  |                         value=${ifDefined(this.result?.token)} | ||||||
|  |                         class="pf-c-form-control" | ||||||
|  |                     /> | ||||||
|  |                     <p class="pf-c-form__helper-text"> | ||||||
|  |                         ${msg( | ||||||
|                             "Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List.", |                             "Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List.", | ||||||
|                         )} |                         )} | ||||||
|                 > |                     </p> | ||||||
|                 </ak-hidden-text-input> |                 </ak-form-element-horizontal> | ||||||
|             </form>`; |             </form>`; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { AKElement, type AKElementProps } from "@goauthentik/elements/Base"; | import { AKElement } from "@goauthentik/elements/Base"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement.js"; | import "@goauthentik/elements/forms/HorizontalFormElement.js"; | ||||||
|  |  | ||||||
| import { TemplateResult, html, nothing } from "lit"; | import { TemplateResult, html, nothing } from "lit"; | ||||||
| @ -6,19 +6,6 @@ import { property } from "lit/decorators.js"; | |||||||
|  |  | ||||||
| type HelpType = TemplateResult | typeof nothing; | type HelpType = TemplateResult | typeof nothing; | ||||||
|  |  | ||||||
| export interface HorizontalLightComponentProps<T> extends AKElementProps { |  | ||||||
|     name: string; |  | ||||||
|     label?: string; |  | ||||||
|     required?: boolean; |  | ||||||
|     help?: string; |  | ||||||
|     bighelp?: TemplateResult | TemplateResult[]; |  | ||||||
|     hidden?: boolean; |  | ||||||
|     invalid?: boolean; |  | ||||||
|     errorMessages?: string[]; |  | ||||||
|     value?: T; |  | ||||||
|     inputHint?: string; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export class HorizontalLightComponent<T> extends AKElement { | export class HorizontalLightComponent<T> extends AKElement { | ||||||
|     // Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but |     // Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but | ||||||
|     // we're not actually using that and, for the meantime, we need the form handlers to be able to |     // we're not actually using that and, for the meantime, we need the form handlers to be able to | ||||||
| @ -31,81 +18,37 @@ export class HorizontalLightComponent<T> extends AKElement { | |||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The name attribute for the form element |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, reflect: true }) |     @property({ type: String, reflect: true }) | ||||||
|     name!: string; |     name!: string; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The label for the input control |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, reflect: true }) |     @property({ type: String, reflect: true }) | ||||||
|     label = ""; |     label = ""; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: Boolean, reflect: true }) |     @property({ type: Boolean, reflect: true }) | ||||||
|     required = false; |     required = false; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Help text to display below the form element. Optional |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, reflect: true }) |     @property({ type: String, reflect: true }) | ||||||
|     help = ""; |     help = ""; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Extended help content. Optional. Expects to be a TemplateResult |  | ||||||
|      * @property |  | ||||||
|      */ |  | ||||||
|     @property({ type: Object }) |     @property({ type: Object }) | ||||||
|     bighelp?: TemplateResult | TemplateResult[]; |     bighelp?: TemplateResult | TemplateResult[]; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: Boolean, reflect: true }) |     @property({ type: Boolean, reflect: true }) | ||||||
|     hidden = false; |     hidden = false; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: Boolean, reflect: true }) |     @property({ type: Boolean, reflect: true }) | ||||||
|     invalid = false; |     invalid = false; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      */ |  | ||||||
|     @property({ attribute: false }) |     @property({ attribute: false }) | ||||||
|     errorMessages: string[] = []; |     errorMessages: string[] = []; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @attribute |  | ||||||
|      * @property |  | ||||||
|      */ |  | ||||||
|     @property({ attribute: false }) |     @property({ attribute: false }) | ||||||
|     value?: T; |     value?: T; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Input hint. |  | ||||||
|      *   - `code`: uses a monospace font and disables spellcheck & autocomplete |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, attribute: "input-hint" }) |     @property({ type: String, attribute: "input-hint" }) | ||||||
|     inputHint = ""; |     inputHint = ""; | ||||||
|  |  | ||||||
|     protected renderControl() { |     renderControl() { | ||||||
|         throw new Error("Must be implemented in a subclass"); |         throw new Error("Must be implemented in a subclass"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,159 +0,0 @@ | |||||||
| import { bound } from "#elements/decorators/bound"; |  | ||||||
|  |  | ||||||
| import { msg } from "@lit/localize"; |  | ||||||
| import { css, html } from "lit"; |  | ||||||
| import { customElement, property, query } from "lit/decorators.js"; |  | ||||||
| import { classMap } from "lit/directives/class-map.js"; |  | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; |  | ||||||
|  |  | ||||||
| import { |  | ||||||
|     HorizontalLightComponent, |  | ||||||
|     HorizontalLightComponentProps, |  | ||||||
| } from "./HorizontalLightComponent"; |  | ||||||
| import "./ak-visibility-toggle.js"; |  | ||||||
| import type { VisibilityToggleProps } from "./ak-visibility-toggle.js"; |  | ||||||
|  |  | ||||||
| type BaseProps = HorizontalLightComponentProps<string> & |  | ||||||
|     Pick<VisibilityToggleProps, "showMessage" | "hideMessage">; |  | ||||||
|  |  | ||||||
| export interface AkHiddenTextInputProps extends BaseProps { |  | ||||||
|     revealed: boolean; |  | ||||||
|     placeholder?: string; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export type InputLike = HTMLTextAreaElement | HTMLInputElement; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @element ak-hidden-text-input |  | ||||||
|  * @class AkHiddenTextInput |  | ||||||
|  * |  | ||||||
|  * A text-input field with a visibility control, so you can show/hide sensitive fields. |  | ||||||
|  * |  | ||||||
|  * ## CSS Parts |  | ||||||
|  * @csspart container - The main container div |  | ||||||
|  * @csspart input - The input element |  | ||||||
|  * @csspart toggle - The visibility toggle button |  | ||||||
|  * |  | ||||||
|  */ |  | ||||||
| @customElement("ak-hidden-text-input") |  | ||||||
| export class AkHiddenTextInput<T extends InputLike = HTMLInputElement> |  | ||||||
|     extends HorizontalLightComponent<string> |  | ||||||
|     implements AkHiddenTextInputProps |  | ||||||
| { |  | ||||||
|     public static get styles() { |  | ||||||
|         return [ |  | ||||||
|             css` |  | ||||||
|                 main { |  | ||||||
|                     display: flex; |  | ||||||
|                 } |  | ||||||
|             `, |  | ||||||
|         ]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, reflect: true }) |  | ||||||
|     public value = ""; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: Boolean, reflect: true }) |  | ||||||
|     public revealed = false; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Text for when the input has no set value |  | ||||||
|      * |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String }) |  | ||||||
|     public placeholder?: string; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Specify kind of help the browser should try to provide |  | ||||||
|      * |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String }) |  | ||||||
|     public autocomplete?: "none" | AutoFill; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, attribute: "show-message" }) |  | ||||||
|     public showMessage = msg("Show field content"); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, attribute: "hide-message" }) |  | ||||||
|     public hideMessage = msg("Hide field content"); |  | ||||||
|  |  | ||||||
|     @query("#main > input") |  | ||||||
|     protected inputField!: T; |  | ||||||
|  |  | ||||||
|     @bound |  | ||||||
|     private handleToggleVisibility() { |  | ||||||
|         this.revealed = !this.revealed; |  | ||||||
|  |  | ||||||
|         // Maintain focus on input after toggle |  | ||||||
|         this.updateComplete.then(() => { |  | ||||||
|             if (this.inputField && document.activeElement === this) { |  | ||||||
|                 this.inputField.focus(); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // TODO: Because of the peculiarities of how HorizontalLightComponent works, keeping its content |  | ||||||
|     // in the LightDom so the inner components actually inherit styling, the normal `css` options |  | ||||||
|     // aren't available. Embedding styles is bad styling, and we'll fix it in the next style |  | ||||||
|     // refresh. |  | ||||||
|     protected renderInputField(setValue: (ev: InputEvent) => void, code: boolean) { |  | ||||||
|         return html` <input |  | ||||||
|             style="flex: 1 1 auto; min-width: 0;" |  | ||||||
|             part="input" |  | ||||||
|             type=${this.revealed ? "text" : "password"} |  | ||||||
|             @input=${setValue} |  | ||||||
|             value=${ifDefined(this.value)} |  | ||||||
|             placeholder=${ifDefined(this.placeholder)} |  | ||||||
|             class="${classMap({ |  | ||||||
|                 "pf-c-form-control": true, |  | ||||||
|                 "pf-m-monospace": code, |  | ||||||
|             })}" |  | ||||||
|             spellcheck=${code ? "false" : "true"} |  | ||||||
|             ?required=${this.required} |  | ||||||
|         />`; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected override renderControl() { |  | ||||||
|         const code = this.inputHint === "code"; |  | ||||||
|         const setValue = (ev: InputEvent) => { |  | ||||||
|             this.value = (ev.target as T).value; |  | ||||||
|         }; |  | ||||||
|         return html` <div style="display: flex; gap: 0.25rem"> |  | ||||||
|             ${this.renderInputField(setValue, code)} |  | ||||||
|             <!-- --> |  | ||||||
|             <ak-visibility-toggle |  | ||||||
|                 part="toggle" |  | ||||||
|                 style="flex: 0 0 auto; align-self: flex-start" |  | ||||||
|                 ?open=${this.revealed} |  | ||||||
|                 show-message=${this.showMessage} |  | ||||||
|                 hide-message=${this.hideMessage} |  | ||||||
|                 @click=${() => (this.revealed = !this.revealed)} |  | ||||||
|             ></ak-visibility-toggle> |  | ||||||
|         </div>`; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| declare global { |  | ||||||
|     interface HTMLElementTagNameMap { |  | ||||||
|         "ak-hidden-text-input": AkHiddenTextInput; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,128 +0,0 @@ | |||||||
| import { css, html } from "lit"; |  | ||||||
| import { customElement, property, query } from "lit/decorators.js"; |  | ||||||
| import { classMap } from "lit/directives/class-map.js"; |  | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; |  | ||||||
|  |  | ||||||
| import { AkHiddenTextInput, type AkHiddenTextInputProps } from "./ak-hidden-text-input.js"; |  | ||||||
|  |  | ||||||
| export interface AkHiddenTextAreaInputProps extends AkHiddenTextInputProps { |  | ||||||
|     /** |  | ||||||
|      * Number of visible text lines (rows) |  | ||||||
|      */ |  | ||||||
|     rows?: number; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Number of visible character width (cols) |  | ||||||
|      */ |  | ||||||
|     cols?: number; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * How the textarea can be resized |  | ||||||
|      */ |  | ||||||
|     resize?: "none" | "both" | "horizontal" | "vertical"; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Whether text should wrap |  | ||||||
|      */ |  | ||||||
|     wrap?: "soft" | "hard" | "off"; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @element ak-hidden-text-input |  | ||||||
|  * @class AkHiddenTextInput |  | ||||||
|  * |  | ||||||
|  * A text-input field with a visibility control, so you can show/hide sensitive fields. |  | ||||||
|  * |  | ||||||
|  * ## CSS Parts |  | ||||||
|  * @csspart container - The main container div |  | ||||||
|  * @csspart input - The input element |  | ||||||
|  * @csspart toggle - The visibility toggle button |  | ||||||
|  * |  | ||||||
|  */ |  | ||||||
| @customElement("ak-hidden-textarea-input") |  | ||||||
| export class AkHiddenTextAreaInput |  | ||||||
|     extends AkHiddenTextInput<HTMLTextAreaElement> |  | ||||||
|     implements AkHiddenTextAreaInputProps |  | ||||||
| { |  | ||||||
|     /* These are mostly just forwarded to the textarea component. */ |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: Number }) |  | ||||||
|     rows?: number = 4; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: Number }) |  | ||||||
|     cols?: number; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      * |  | ||||||
|      * You want `resize=true` so that the resize value is visible in the component tag, activating |  | ||||||
|      * the CSS associated with these values. |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, reflect: true }) |  | ||||||
|     resize?: "none" | "both" | "horizontal" | "vertical" = "vertical"; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String }) |  | ||||||
|     wrap?: "soft" | "hard" | "off" = "soft"; |  | ||||||
|  |  | ||||||
|     @query("#main > textarea") |  | ||||||
|     protected inputField!: HTMLTextAreaElement; |  | ||||||
|  |  | ||||||
|     get displayValue() { |  | ||||||
|         const value = this.value ?? ""; |  | ||||||
|         if (this.revealed) { |  | ||||||
|             return value; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return value |  | ||||||
|             .split("\n") |  | ||||||
|             .reduce((acc: string[], line: string) => [...acc, "*".repeat(line.length)], []) |  | ||||||
|             .join("\n"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // TODO: Because of the peculiarities of how HorizontalLightComponent works, keeping its content |  | ||||||
|     // in the LightDom so the inner components actually inherit styling, the normal `css` options |  | ||||||
|     // aren't available. Embedding styles is bad styling, and we'll fix it in the next style |  | ||||||
|     // refresh. |  | ||||||
|     protected override renderInputField(setValue: (ev: InputEvent) => void, code: boolean) { |  | ||||||
|         const wrap = this.revealed ? this.wrap : "soft"; |  | ||||||
|  |  | ||||||
|         return html` |  | ||||||
|             <textarea |  | ||||||
|                 style="flex: 1 1 auto; min-width: 0;" |  | ||||||
|                 part="textarea" |  | ||||||
|                 @input=${setValue} |  | ||||||
|                 placeholder=${ifDefined(this.placeholder)} |  | ||||||
|                 rows=${ifDefined(this.rows)} |  | ||||||
|                 cols=${ifDefined(this.cols)} |  | ||||||
|                 wrap=${ifDefined(wrap)} |  | ||||||
|                 class=${classMap({ |  | ||||||
|                     "pf-c-form-control": true, |  | ||||||
|                     "pf-m-monospace": code, |  | ||||||
|                 })} |  | ||||||
|                 spellcheck=${code ? "false" : "true"} |  | ||||||
|                 ?required=${this.required} |  | ||||||
|             > |  | ||||||
| ${this.displayValue}</textarea |  | ||||||
|             > |  | ||||||
|         `; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| declare global { |  | ||||||
|     interface HTMLElementTagNameMap { |  | ||||||
|         "ak-hidden-textarea-input": AkHiddenTextAreaInput; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -8,8 +8,8 @@ import { ifDefined } from "lit/directives/if-defined.js"; | |||||||
| 
 | 
 | ||||||
| import { HorizontalLightComponent } from "./HorizontalLightComponent"; | import { HorizontalLightComponent } from "./HorizontalLightComponent"; | ||||||
| 
 | 
 | ||||||
| @customElement("ak-secret-text-input") | @customElement("ak-private-text-input") | ||||||
| export class AkSecretTextInput extends HorizontalLightComponent<string> { | export class AkPrivateTextInput extends HorizontalLightComponent<string> { | ||||||
|     @property({ type: String, reflect: true }) |     @property({ type: String, reflect: true }) | ||||||
|     public value = ""; |     public value = ""; | ||||||
| 
 | 
 | ||||||
| @ -23,7 +23,7 @@ export class AkSecretTextInput extends HorizontalLightComponent<string> { | |||||||
|         this.revealed = true; |         this.revealed = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #renderSecretInput() { |     #renderPrivateInput() { | ||||||
|         return html`<div class="pf-c-form__horizontal-group" @click=${() => this.#onReveal()}>
 |         return html`<div class="pf-c-form__horizontal-group" @click=${() => this.#onReveal()}>
 | ||||||
|             <input |             <input | ||||||
|                 class="pf-c-form-control" |                 class="pf-c-form-control" | ||||||
| @ -60,14 +60,14 @@ export class AkSecretTextInput extends HorizontalLightComponent<string> { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public override renderControl() { |     public override renderControl() { | ||||||
|         return this.revealed ? this.renderVisibleInput() : this.#renderSecretInput(); |         return this.revealed ? this.renderVisibleInput() : this.#renderPrivateInput(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default AkSecretTextInput; | export default AkPrivateTextInput; | ||||||
| 
 | 
 | ||||||
| declare global { | declare global { | ||||||
|     interface HTMLElementTagNameMap { |     interface HTMLElementTagNameMap { | ||||||
|         "ak-secret-text-input": AkSecretTextInput; |         "ak-private-text-input": AkPrivateTextInput; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -5,10 +5,10 @@ import { customElement, property } from "lit/decorators.js"; | |||||||
| import { classMap } from "lit/directives/class-map.js"; | import { classMap } from "lit/directives/class-map.js"; | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; | import { ifDefined } from "lit/directives/if-defined.js"; | ||||||
| 
 | 
 | ||||||
| import { AkSecretTextInput } from "./ak-secret-text-input.js"; | import { AkPrivateTextInput } from "./ak-private-text-input.js"; | ||||||
| 
 | 
 | ||||||
| @customElement("ak-secret-textarea-input") | @customElement("ak-private-textarea-input") | ||||||
| export class AkSecretTextAreaInput extends AkSecretTextInput { | export class AkPrivateTextAreaInput extends AkPrivateTextInput { | ||||||
|     protected override renderVisibleInput() { |     protected override renderVisibleInput() { | ||||||
|         const code = this.inputHint === "code"; |         const code = this.inputHint === "code"; | ||||||
|         const setValue = (ev: InputEvent) => { |         const setValue = (ev: InputEvent) => { | ||||||
| @ -34,10 +34,10 @@ export class AkSecretTextAreaInput extends AkSecretTextInput { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default AkSecretTextAreaInput; | export default AkPrivateTextAreaInput; | ||||||
| 
 | 
 | ||||||
| declare global { | declare global { | ||||||
|     interface HTMLElementTagNameMap { |     interface HTMLElementTagNameMap { | ||||||
|         "ak-secret-textarea-input": AkSecretTextAreaInput; |         "ak-private-textarea-input": AkPrivateTextAreaInput; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -1,89 +0,0 @@ | |||||||
| import { AKElement } from "@goauthentik/elements/Base.js"; |  | ||||||
|  |  | ||||||
| import { msg } from "@lit/localize"; |  | ||||||
| import { html } from "lit"; |  | ||||||
| import { customElement, property } from "lit/decorators.js"; |  | ||||||
|  |  | ||||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; |  | ||||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; |  | ||||||
|  |  | ||||||
| export interface VisibilityToggleProps { |  | ||||||
|     open: boolean; |  | ||||||
|     disabled: boolean; |  | ||||||
|     showMessage: string; |  | ||||||
|     hideMessage: string; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @component ak-visibility-toggle |  | ||||||
|  * @class VisibilityToggle |  | ||||||
|  * |  | ||||||
|  * A straightforward two-state iconic button we use in a few places as way of telling users to hide |  | ||||||
|  * or show something secret, such as a password or private key. Expects the client to manage its |  | ||||||
|  * state. |  | ||||||
|  * |  | ||||||
|  * @events |  | ||||||
|  * - click: when the toggle is clicked. |  | ||||||
|  */ |  | ||||||
| @customElement("ak-visibility-toggle") |  | ||||||
| export class VisibilityToggle extends AKElement implements VisibilityToggleProps { |  | ||||||
|     static get styles() { |  | ||||||
|         return [PFBase, PFButton]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: Boolean, reflect: true }) |  | ||||||
|     open = false; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: Boolean, reflect: true }) |  | ||||||
|     disabled = false; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, attribute: "show-message" }) |  | ||||||
|     showMessage = msg("Show field content"); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @property |  | ||||||
|      * @attribute |  | ||||||
|      */ |  | ||||||
|     @property({ type: String, attribute: "hide-message" }) |  | ||||||
|     hideMessage = msg("Hide field content"); |  | ||||||
|  |  | ||||||
|     render() { |  | ||||||
|         const [label, icon] = this.open |  | ||||||
|             ? [this.hideMessage, "fa-eye"] |  | ||||||
|             : [this.showMessage, "fa-eye-slash"]; |  | ||||||
|  |  | ||||||
|         const onClick = (ev: PointerEvent) => { |  | ||||||
|             ev.stopPropagation(); |  | ||||||
|             this.dispatchEvent(new PointerEvent(ev.type, ev)); |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         return html`<button |  | ||||||
|             aria-label=${label} |  | ||||||
|             title=${label} |  | ||||||
|             @click=${onClick} |  | ||||||
|             ?disabled=${this.disabled} |  | ||||||
|             class="pf-c-button pf-m-control" |  | ||||||
|             type="button" |  | ||||||
|         > |  | ||||||
|             <i class="fas ${icon}" aria-hidden="true"></i> |  | ||||||
|         </button>`; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| declare global { |  | ||||||
|     interface HTMLElementTagNameMap { |  | ||||||
|         "ak-visibility-toggle": VisibilityToggle; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,93 +0,0 @@ | |||||||
| import type { Meta, StoryObj } from "@storybook/web-components"; |  | ||||||
|  |  | ||||||
| import { html, nothing } from "lit"; |  | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; |  | ||||||
|  |  | ||||||
| import "../ak-hidden-text-input"; |  | ||||||
| import { type AkHiddenTextInput, type AkHiddenTextInputProps } from "../ak-hidden-text-input.js"; |  | ||||||
|  |  | ||||||
| const metadata: Meta<AkHiddenTextInputProps> = { |  | ||||||
|     title: "Components / <ak-hidden-text-input>", |  | ||||||
|     component: "ak-hidden-text-input", |  | ||||||
|     tags: ["autodocs"], |  | ||||||
|     parameters: { |  | ||||||
|         docs: { |  | ||||||
|             description: { |  | ||||||
|                 component: ` |  | ||||||
| # Hidden Text Input Component |  | ||||||
|  |  | ||||||
| A text-input field with a visibility control, so you can show/hide sensitive fields. |  | ||||||
| `, |  | ||||||
|             }, |  | ||||||
|         }, |  | ||||||
|         layout: "padded", |  | ||||||
|     }, |  | ||||||
|     argTypes: { |  | ||||||
|         label: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Label text for the input field", |  | ||||||
|         }, |  | ||||||
|         value: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Current value of the input", |  | ||||||
|         }, |  | ||||||
|         revealed: { |  | ||||||
|             control: "boolean", |  | ||||||
|             description: "Whether the text is currently visible", |  | ||||||
|         }, |  | ||||||
|         placeholder: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Placeholder text for the input", |  | ||||||
|         }, |  | ||||||
|         required: { |  | ||||||
|             control: "boolean", |  | ||||||
|             description: "Whether the input is required", |  | ||||||
|         }, |  | ||||||
|         inputHint: { |  | ||||||
|             control: "select", |  | ||||||
|             options: ["text", "code"], |  | ||||||
|             description: "Input type hint for styling and behavior", |  | ||||||
|         }, |  | ||||||
|         showMessage: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Custom message for show action", |  | ||||||
|         }, |  | ||||||
|         hideMessage: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Custom message for hide action", |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default metadata; |  | ||||||
|  |  | ||||||
| type Story = StoryObj<AkHiddenTextInput>; |  | ||||||
|  |  | ||||||
| const Template: Story = { |  | ||||||
|     args: { |  | ||||||
|         label: "Hidden Text Input", |  | ||||||
|         value: "", |  | ||||||
|         revealed: false, |  | ||||||
|     }, |  | ||||||
|     render: (args) => html` |  | ||||||
|         <ak-hidden-text-input |  | ||||||
|             label=${ifDefined(args.label)} |  | ||||||
|             value=${ifDefined(args.value)} |  | ||||||
|             ?revealed=${args.revealed} |  | ||||||
|             placeholder=${ifDefined(args.placeholder)} |  | ||||||
|             ?required=${args.required} |  | ||||||
|             input-hint=${ifDefined(args.inputHint)} |  | ||||||
|             show-message=${ifDefined(args.showMessage)} |  | ||||||
|             hide-message=${ifDefined(args.hideMessage)} |  | ||||||
|         ></ak-hidden-text-input> |  | ||||||
|     `, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const Password: Story = { |  | ||||||
|     ...Template, |  | ||||||
|     args: { |  | ||||||
|         label: "Password", |  | ||||||
|         placeholder: "Enter your password", |  | ||||||
|         required: true, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| @ -1,140 +0,0 @@ | |||||||
| import type { Meta, StoryObj } from "@storybook/web-components"; |  | ||||||
|  |  | ||||||
| import { html, nothing } from "lit"; |  | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; |  | ||||||
|  |  | ||||||
| import "../ak-hidden-textarea-input"; |  | ||||||
| import { |  | ||||||
|     type AkHiddenTextAreaInput, |  | ||||||
|     type AkHiddenTextAreaInputProps, |  | ||||||
| } from "../ak-hidden-textarea-input.js"; |  | ||||||
|  |  | ||||||
| const metadata: Meta<AkHiddenTextAreaInputProps> = { |  | ||||||
|     title: "Components / <ak-hidden-textarea-input>", |  | ||||||
|     component: "ak-hidden-textarea-input", |  | ||||||
|     tags: ["autodocs"], |  | ||||||
|     parameters: { |  | ||||||
|         docs: { |  | ||||||
|             description: { |  | ||||||
|                 component: ` |  | ||||||
| # Hidden Textarea Input Component |  | ||||||
|  |  | ||||||
| A textarea input field with a visibility control, so you can show/hide sensitive fields. |  | ||||||
| `, |  | ||||||
|             }, |  | ||||||
|         }, |  | ||||||
|         layout: "padded", |  | ||||||
|     }, |  | ||||||
|     argTypes: { |  | ||||||
|         label: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Label text for the input field", |  | ||||||
|         }, |  | ||||||
|         value: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Current value of the input", |  | ||||||
|         }, |  | ||||||
|         revealed: { |  | ||||||
|             control: "boolean", |  | ||||||
|             description: "Whether the text is currently visible", |  | ||||||
|         }, |  | ||||||
|         placeholder: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Placeholder text for the input", |  | ||||||
|         }, |  | ||||||
|         required: { |  | ||||||
|             control: "boolean", |  | ||||||
|             description: "Whether the input is required", |  | ||||||
|         }, |  | ||||||
|         inputHint: { |  | ||||||
|             control: "select", |  | ||||||
|             options: ["text", "code"], |  | ||||||
|             description: "Input type hint for styling and behavior", |  | ||||||
|         }, |  | ||||||
|         showMessage: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Custom message for show action", |  | ||||||
|         }, |  | ||||||
|         hideMessage: { |  | ||||||
|             control: "text", |  | ||||||
|             description: "Custom message for hide action", |  | ||||||
|         }, |  | ||||||
|         rows: { |  | ||||||
|             control: { type: "number", min: 1, max: 50 }, |  | ||||||
|             description: "Number of visible text lines", |  | ||||||
|         }, |  | ||||||
|         cols: { |  | ||||||
|             control: { type: "number", min: 10, max: 200 }, |  | ||||||
|             description: "Number of visible character width", |  | ||||||
|         }, |  | ||||||
|         resize: { |  | ||||||
|             control: "select", |  | ||||||
|             options: ["none", "both", "horizontal", "vertical"], |  | ||||||
|             description: "How the textarea can be resized", |  | ||||||
|         }, |  | ||||||
|         wrap: { |  | ||||||
|             control: "select", |  | ||||||
|             options: ["soft", "hard", "off"], |  | ||||||
|             description: "Text wrapping behavior", |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default metadata; |  | ||||||
|  |  | ||||||
| type Story = StoryObj<AkHiddenTextAreaInput>; |  | ||||||
|  |  | ||||||
| const Template: Story = { |  | ||||||
|     args: { |  | ||||||
|         label: "Hidden Textarea Input", |  | ||||||
|         value: "", |  | ||||||
|         revealed: false, |  | ||||||
|         rows: 4, |  | ||||||
|     }, |  | ||||||
|     render: (args) => html` |  | ||||||
|         <ak-hidden-textarea-input |  | ||||||
|             label=${ifDefined(args.label)} |  | ||||||
|             value=${ifDefined(args.value)} |  | ||||||
|             ?revealed=${args.revealed} |  | ||||||
|             placeholder=${ifDefined(args.placeholder)} |  | ||||||
|             rows=${ifDefined(args.rows)} |  | ||||||
|             cols=${ifDefined(args.cols)} |  | ||||||
|             resize=${ifDefined(args.resize)} |  | ||||||
|             wrap=${ifDefined(args.wrap)} |  | ||||||
|             ?required=${args.required} |  | ||||||
|             input-hint=${ifDefined(args.inputHint)} |  | ||||||
|             show-message=${ifDefined(args.showMessage)} |  | ||||||
|             hide-message=${ifDefined(args.hideMessage)} |  | ||||||
|         ></ak-hidden-textarea-input> |  | ||||||
|     `, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const SslCertificate: Story = { |  | ||||||
|     ...Template, |  | ||||||
|     args: { |  | ||||||
|         label: "SSL Certificate", |  | ||||||
|         value: `-----BEGIN CERTIFICATE----- |  | ||||||
| MIIDXTCCAkWgAwIBAgIJAKoK/heBjcOuMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV |  | ||||||
| BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX |  | ||||||
| aWRnaXRzIFB0eSBMdGQwHhcNMTcwNTEwMTk0MDA2WhcNMTgwNTEwMTk0MDA2WjBF |  | ||||||
| MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 |  | ||||||
| ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE3MDUxMDE5NDAwNloXDTE4MDUxMDE5 |  | ||||||
| NDAwNlowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNV |  | ||||||
| BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD |  | ||||||
| ggEPADCCAQoCggEBALdUlNS31SzxwoFShahGfjHj6GgpcVbzL1Siq0Pqnf82T6M2 |  | ||||||
| EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggE |  | ||||||
| BAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqn |  | ||||||
| f82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgM |  | ||||||
| BAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeM |  | ||||||
| Hyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDu |  | ||||||
| neMLzAgMBAAECggEBAJkPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAECggEBAJ |  | ||||||
| kPFn6jeMHyiq0Pqnf82T6M2EDuneMLzAgMBAAE= |  | ||||||
| -----END CERTIFICATE-----`, |  | ||||||
|         inputHint: "code", |  | ||||||
|         rows: 15, |  | ||||||
|         resize: "vertical", |  | ||||||
|         showMessage: "Show certificate content", |  | ||||||
|         hideMessage: "Hide certificate content", |  | ||||||
|         autocomplete: "off", |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| @ -1,121 +0,0 @@ | |||||||
| import type { Meta, StoryObj } from "@storybook/web-components"; |  | ||||||
|  |  | ||||||
| import { html, nothing } from "lit"; |  | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; |  | ||||||
|  |  | ||||||
| import "../ak-visibility-toggle"; |  | ||||||
| import { type VisibilityToggle, type VisibilityToggleProps } from "../ak-visibility-toggle.js"; |  | ||||||
|  |  | ||||||
| const metadata: Meta<VisibilityToggleProps> = { |  | ||||||
|     title: "Elements/<ak-visibility-toggle>", |  | ||||||
|     component: "ak-visibility-toggle", |  | ||||||
|     tags: ["autodocs"], |  | ||||||
|     parameters: { |  | ||||||
|         docs: { |  | ||||||
|             description: { |  | ||||||
|                 component: ` |  | ||||||
| # Visibility Toggle Component |  | ||||||
|  |  | ||||||
| A straightforward two-state iconic button for toggling the visibility of sensitive content such as passwords, private keys, or other secret information. |  | ||||||
|                  |  | ||||||
| - Use for sensitive content that users might want to temporarily reveal |  | ||||||
| - There are default hide/show messages for screen readers, but they can be overridden |  | ||||||
| - Clients always handle the state |  | ||||||
| - The \`open\` state is false by default; we assume you want sensitive content hidden at start |  | ||||||
| `, |  | ||||||
|             }, |  | ||||||
|         }, |  | ||||||
|         layout: "padded", |  | ||||||
|     }, |  | ||||||
|     argTypes: { |  | ||||||
|         open: { |  | ||||||
|             control: "boolean", |  | ||||||
|             description: "Whether the toggle is in the 'show' state (true) or 'hide' state (false)", |  | ||||||
|         }, |  | ||||||
|         showMessage: { |  | ||||||
|             control: "text", |  | ||||||
|             description: |  | ||||||
|                 'Message for screen readers when in hide state (default: "Show field content")', |  | ||||||
|         }, |  | ||||||
|         hideMessage: { |  | ||||||
|             control: "text", |  | ||||||
|             description: |  | ||||||
|                 'Message for screen readers when in show state (default: "Hide field content")', |  | ||||||
|         }, |  | ||||||
|         disabled: { |  | ||||||
|             control: "boolean", |  | ||||||
|             description: "Whether the button should be disabled (for demo purposes)", |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default metadata; |  | ||||||
|  |  | ||||||
| type Story = StoryObj<VisibilityToggle>; |  | ||||||
|  |  | ||||||
| const Template: Story = { |  | ||||||
|     args: { |  | ||||||
|         open: false, |  | ||||||
|         showMessage: "Show field content", |  | ||||||
|         hideMessage: "Hide field content", |  | ||||||
|     }, |  | ||||||
|     render: (args) => html` |  | ||||||
|         <ak-visibility-toggle |  | ||||||
|             ?open=${args.open} |  | ||||||
|             show-message=${ifDefined(args.showMessage)} |  | ||||||
|             hide-message=${ifDefined(args.hideMessage)} |  | ||||||
|             @click=${(e: Event) => { |  | ||||||
|                 const target = e.target as VisibilityToggle; |  | ||||||
|                 target.open = !target.open; |  | ||||||
|                 // In a real application, you would also toggle the visibility |  | ||||||
|                 // of the associated content here |  | ||||||
|             }} |  | ||||||
|         ></ak-visibility-toggle> |  | ||||||
|     `, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // Password field integration example |  | ||||||
| export const PasswordFieldExample: Story = { |  | ||||||
|     args: { |  | ||||||
|         showMessage: "Reveal password", |  | ||||||
|         hideMessage: "Conceal password", |  | ||||||
|     }, |  | ||||||
|     render: () => { |  | ||||||
|         let isVisible = false; |  | ||||||
|  |  | ||||||
|         const toggleVisibility = (e: Event) => { |  | ||||||
|             isVisible = !isVisible; |  | ||||||
|             const toggle = e.target as VisibilityToggle; |  | ||||||
|             const passwordField = document.querySelector("#demo-password") as HTMLInputElement; |  | ||||||
|  |  | ||||||
|             toggle.open = isVisible; |  | ||||||
|             if (passwordField) { |  | ||||||
|                 passwordField.type = isVisible ? "text" : "password"; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         return html` |  | ||||||
|             <div style="display: flex; flex-direction: column; gap: 1rem; max-width: 300px;"> |  | ||||||
|                 <label for="demo-password" style="font-weight: bold;">Password:</label> |  | ||||||
|                 <div style="display: flex; align-items: center; gap: 0.5rem;"> |  | ||||||
|                     <input |  | ||||||
|                         id="demo-password" |  | ||||||
|                         type="password" |  | ||||||
|                         value="supersecretpassword123" |  | ||||||
|                         style="flex: 1; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px;" |  | ||||||
|                         readonly |  | ||||||
|                     /> |  | ||||||
|                     <ak-visibility-toggle |  | ||||||
|                         ?open=${isVisible} |  | ||||||
|                         show-message="Show password" |  | ||||||
|                         hide-message="Hide password" |  | ||||||
|                         @click=${toggleVisibility} |  | ||||||
|                     ></ak-visibility-toggle> |  | ||||||
|                 </div> |  | ||||||
|                 <p style="font-size: 0.875rem; color: #666;"> |  | ||||||
|                     Click the eye icon to toggle password visibility |  | ||||||
|                 </p> |  | ||||||
|             </div> |  | ||||||
|         `; |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| @ -16,12 +16,8 @@ import { property } from "lit/decorators.js"; | |||||||
|  |  | ||||||
| import { UiThemeEnum } from "@goauthentik/api"; | import { UiThemeEnum } from "@goauthentik/api"; | ||||||
|  |  | ||||||
| export interface AKElementProps { |  | ||||||
|     activeTheme: ResolvedUITheme; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @localized() | @localized() | ||||||
| export class AKElement extends LitElement implements AKElementProps { | export class AKElement extends LitElement { | ||||||
|     //#region Static Properties |     //#region Static Properties | ||||||
|  |  | ||||||
|     public static styles?: Array<CSSResult | CSSModule>; |     public static styles?: Array<CSSResult | CSSModule>; | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ import { | |||||||
|  |  | ||||||
| function localeComparator(a: DualSelectPair, b: DualSelectPair) { | function localeComparator(a: DualSelectPair, b: DualSelectPair) { | ||||||
|     const aSortBy = a[2] || a[0]; |     const aSortBy = a[2] || a[0]; | ||||||
|     const bSortBy = b[2] || b[0]; |     const bSortBy = b[2] || a[0]; | ||||||
|  |  | ||||||
|     return aSortBy.localeCompare(bSortBy); |     return aSortBy.localeCompare(bSortBy); | ||||||
| } | } | ||||||
|  | |||||||
| @ -3001,6 +3001,11 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>Server laden</target> |         <target>Server laden</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Mit Plex erneut authentifizieren</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9236,15 +9241,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -2414,6 +2414,10 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>Load servers</target> |         <target>Load servers</target> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Re-authenticate with plex</target> | ||||||
|  |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
|         <target>Allow friends to authenticate via Plex, even if you don't share any servers</target> |         <target>Allow friends to authenticate via Plex, even if you don't share any servers</target> | ||||||
| @ -7744,15 +7748,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -2982,6 +2982,11 @@ no se aprueba cuando una o ambas de las opciones seleccionadas son iguales o sup | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>Servidores de carga</target> |         <target>Servidores de carga</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Vuelva a autenticarse con plex</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9296,15 +9301,6 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -3010,6 +3010,11 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>Charger les serveurs</target> |         <target>Charger les serveurs</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Se ré-authentifier avec Plex</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9865,15 +9870,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -3011,6 +3011,11 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>Carico server</target> |         <target>Carico server</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Riautenticarsi con plex</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9848,15 +9853,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -2973,6 +2973,11 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>서버 로드</target> |         <target>서버 로드</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Plex로 다시 인증하기</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9204,15 +9209,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -2987,6 +2987,11 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>Servers laden</target> |         <target>Servers laden</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Opnieuw authenticeren met Plex</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9108,15 +9113,6 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -3012,6 +3012,11 @@ nie przechodzi, gdy jedna lub obie wybrane opcje są równe lub wyższe od progu | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>Załaduj serwery</target> |         <target>Załaduj serwery</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Ponowne uwierzytelnienie za pomocą plex</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9531,15 +9536,6 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -2990,6 +2990,11 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|   <target>Ĺōàď śēŕvēŕś</target> |   <target>Ĺōàď śēŕvēŕś</target> | ||||||
|  |  | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |   <target>Ŕē-àũţĥēńţĩćàţē ŵĩţĥ ƥĺēx</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9540,13 +9545,4 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> |  | ||||||
| </body></file></xliff> | </body></file></xliff> | ||||||
|  | |||||||
| @ -3011,6 +3011,11 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>Загрузить серверы</target> |         <target>Загрузить серверы</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Повторная аутентификация с помощью plex</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9623,15 +9628,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -2990,6 +2990,11 @@ Belirlenen seçeneklerden biri veya her ikisi de eşiğe eşit veya eşiğin üz | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>Sunucuları yükle</target> |         <target>Sunucuları yükle</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>Plex ile yeniden kimlik doğrulama</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9595,15 +9600,6 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -2128,6 +2128,9 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
| <trans-unit id="s91f389c796720a81"> | <trans-unit id="s91f389c796720a81"> | ||||||
|   <source>Load servers</source> |   <source>Load servers</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|  | <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |   <source>Re-authenticate with plex</source> | ||||||
|  | </trans-unit> | ||||||
| <trans-unit id="sc297b2e13c28ecf9"> | <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|   <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |   <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| @ -6359,15 +6362,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> |  | ||||||
| </body> | </body> | ||||||
| </file> | </file> | ||||||
| </xliff> | </xliff> | ||||||
|  | |||||||
| @ -3011,6 +3011,11 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>加载服务器</target> |         <target>加载服务器</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>使用 Plex 重新验证身份</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9875,15 +9880,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
|   <target>生成新的证书密钥对</target> |   <target>生成新的证书密钥对</target> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -2290,6 +2290,10 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>加载服务器</target> |         <target>加载服务器</target> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>使用 plex 重新进行身份验证</target> | ||||||
|  |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
|         <target>允许好友通过Plex进行身份验证,即使您不共享任何服务器</target> |         <target>允许好友通过Plex进行身份验证,即使您不共享任何服务器</target> | ||||||
| @ -7444,15 +7448,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -2972,6 +2972,11 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|         <source>Load servers</source> |         <source>Load servers</source> | ||||||
|         <target>載入伺服器</target> |         <target>載入伺服器</target> | ||||||
|          |          | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s24f405197ede5ebb"> | ||||||
|  |         <source>Re-authenticate with plex</source> | ||||||
|  |         <target>使用 plex 重新身分認證</target> | ||||||
|  |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc297b2e13c28ecf9"> |       <trans-unit id="sc297b2e13c28ecf9"> | ||||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> |         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||||
| @ -9183,15 +9188,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb3d5c0a0501669df"> | <trans-unit id="sb3d5c0a0501669df"> | ||||||
|   <source>Generate New Certificate-Key Pair</source> |   <source>Generate New Certificate-Key Pair</source> | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sf9686d31d28fcf7d"> |  | ||||||
|   <source>Show field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="sb1b05a7573ab618c"> |  | ||||||
|   <source>Hide field content</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s4f820625804ed29b"> |  | ||||||
|   <source>Re-authenticate with Plex</source> |  | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	