Merge branch 'main' into web/update-provider-forms-for-invalidation
* main: (22 commits) lifecycle: fix missing krb5 deps for full testing in image (#11815) translate: Updates for file web/xliff/en.xlf in zh-Hans (#11810) translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#11809) translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#11808) web: bump API Client version (#11807) core: bump goauthentik.io/api/v3 from 3.2024083.12 to 3.2024083.13 (#11806) core: bump ruff from 0.7.0 to 0.7.1 (#11805) core: bump twilio from 9.3.4 to 9.3.5 (#11804) core, web: update translations (#11803) providers/scim: handle no members in group in consistency check (#11801) stages/identification: add captcha to identification stage (#11711) website/docs: improve root page and redirect (#11798) providers/scim: clamp batch size for patch requests (#11797) web/admin: fix missing div in wizard forms (#11794) providers/proxy: fix handling of AUTHENTIK_HOST_BROWSER (#11722) core, web: update translations (#11789) core: bump goauthentik.io/api/v3 from 3.2024083.11 to 3.2024083.12 (#11790) core: bump gssapi from 1.8.3 to 1.9.0 (#11791) web: bump API Client version (#11792) stages/authenticator_validate: autoselect last used 2fa device (#11087) ...
This commit is contained in:
@ -46,6 +46,7 @@ class TestFlowInspector(APITestCase):
|
|||||||
res.content,
|
res.content,
|
||||||
{
|
{
|
||||||
"allow_show_password": False,
|
"allow_show_password": False,
|
||||||
|
"captcha_stage": None,
|
||||||
"component": "ak-stage-identification",
|
"component": "ak-stage-identification",
|
||||||
"flow_info": {
|
"flow_info": {
|
||||||
"background": flow.background_url,
|
"background": flow.background_url,
|
||||||
|
|||||||
@ -197,6 +197,8 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
|||||||
chunk_size = self._config.bulk.maxOperations
|
chunk_size = self._config.bulk.maxOperations
|
||||||
if chunk_size < 1:
|
if chunk_size < 1:
|
||||||
chunk_size = len(ops)
|
chunk_size = len(ops)
|
||||||
|
if len(ops) < 1:
|
||||||
|
return
|
||||||
for chunk in batched(ops, chunk_size):
|
for chunk in batched(ops, chunk_size):
|
||||||
req = PatchRequest(Operations=list(chunk))
|
req = PatchRequest(Operations=list(chunk))
|
||||||
self._request(
|
self._request(
|
||||||
@ -237,13 +239,16 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
|||||||
users_to_add = []
|
users_to_add = []
|
||||||
users_to_remove = []
|
users_to_remove = []
|
||||||
# Check users currently in group and if they shouldn't be in the group and remove them
|
# Check users currently in group and if they shouldn't be in the group and remove them
|
||||||
for user in current_group.members:
|
for user in current_group.members or []:
|
||||||
if user.value not in users_should:
|
if user.value not in users_should:
|
||||||
users_to_remove.append(user.value)
|
users_to_remove.append(user.value)
|
||||||
# Check users that should be in the group and add them
|
# Check users that should be in the group and add them
|
||||||
for user in users_should:
|
for user in users_should:
|
||||||
if len([x for x in current_group.members if x.value == user]) < 1:
|
if len([x for x in current_group.members if x.value == user]) < 1:
|
||||||
users_to_add.append(user)
|
users_to_add.append(user)
|
||||||
|
# Only send request if we need to make changes
|
||||||
|
if len(users_to_add) < 1 and len(users_to_remove) < 1:
|
||||||
|
return
|
||||||
return self._patch_chunked(
|
return self._patch_chunked(
|
||||||
scim_group.scim_id,
|
scim_group.scim_id,
|
||||||
*[
|
*[
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from django.http.response import Http404
|
|||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import gettext as __
|
from django.utils.translation import gettext as __
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField, DateTimeField
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
from webauthn import options_to_json
|
from webauthn import options_to_json
|
||||||
@ -45,6 +45,7 @@ class DeviceChallenge(PassiveSerializer):
|
|||||||
device_class = CharField()
|
device_class = CharField()
|
||||||
device_uid = CharField()
|
device_uid = CharField()
|
||||||
challenge = JSONDictField()
|
challenge = JSONDictField()
|
||||||
|
last_used = DateTimeField(allow_null=True)
|
||||||
|
|
||||||
|
|
||||||
def get_challenge_for_device(
|
def get_challenge_for_device(
|
||||||
|
|||||||
@ -217,6 +217,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
"device_class": device_class,
|
"device_class": device_class,
|
||||||
"device_uid": device.pk,
|
"device_uid": device.pk,
|
||||||
"challenge": get_challenge_for_device(self.request, stage, device),
|
"challenge": get_challenge_for_device(self.request, stage, device),
|
||||||
|
"last_used": device.last_used,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
challenge.is_valid()
|
challenge.is_valid()
|
||||||
@ -237,6 +238,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
self.request,
|
self.request,
|
||||||
self.executor.current_stage,
|
self.executor.current_stage,
|
||||||
),
|
),
|
||||||
|
"last_used": None,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
challenge.is_valid()
|
challenge.is_valid()
|
||||||
|
|||||||
@ -107,6 +107,7 @@ class AuthenticatorValidateStageSMSTests(FlowTestCase):
|
|||||||
"device_class": "sms",
|
"device_class": "sms",
|
||||||
"device_uid": str(device.pk),
|
"device_uid": str(device.pk),
|
||||||
"challenge": {},
|
"challenge": {},
|
||||||
|
"last_used": None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -169,6 +169,7 @@ class AuthenticatorValidateStageTests(FlowTestCase):
|
|||||||
"device_class": "baz",
|
"device_class": "baz",
|
||||||
"device_uid": "quox",
|
"device_uid": "quox",
|
||||||
"challenge": {},
|
"challenge": {},
|
||||||
|
"last_used": None,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -188,6 +189,7 @@ class AuthenticatorValidateStageTests(FlowTestCase):
|
|||||||
"device_class": "static",
|
"device_class": "static",
|
||||||
"device_uid": "1",
|
"device_uid": "1",
|
||||||
"challenge": {},
|
"challenge": {},
|
||||||
|
"last_used": None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -274,6 +274,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
"device_class": device.__class__.__name__.lower().replace("device", ""),
|
"device_class": device.__class__.__name__.lower().replace("device", ""),
|
||||||
"device_uid": device.pk,
|
"device_uid": device.pk,
|
||||||
"challenge": {},
|
"challenge": {},
|
||||||
|
"last_used": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
session[SESSION_KEY_PLAN] = plan
|
session[SESSION_KEY_PLAN] = plan
|
||||||
@ -352,6 +353,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
"device_class": device.__class__.__name__.lower().replace("device", ""),
|
"device_class": device.__class__.__name__.lower().replace("device", ""),
|
||||||
"device_uid": device.pk,
|
"device_uid": device.pk,
|
||||||
"challenge": {},
|
"challenge": {},
|
||||||
|
"last_used": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
session[SESSION_KEY_PLAN] = plan
|
session[SESSION_KEY_PLAN] = plan
|
||||||
@ -432,6 +434,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
"device_class": device.__class__.__name__.lower().replace("device", ""),
|
"device_class": device.__class__.__name__.lower().replace("device", ""),
|
||||||
"device_uid": device.pk,
|
"device_uid": device.pk,
|
||||||
"challenge": {},
|
"challenge": {},
|
||||||
|
"last_used": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
session[SESSION_KEY_PLAN] = plan
|
session[SESSION_KEY_PLAN] = plan
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
"""authentik captcha stage"""
|
"""authentik captcha stage"""
|
||||||
|
|
||||||
from django.http.response import HttpResponse
|
from django.http.response import HttpResponse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
from requests import RequestException
|
from requests import RequestException
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
@ -16,6 +17,7 @@ from authentik.lib.utils.http import get_http_session
|
|||||||
from authentik.root.middleware import ClientIPMiddleware
|
from authentik.root.middleware import ClientIPMiddleware
|
||||||
from authentik.stages.captcha.models import CaptchaStage
|
from authentik.stages.captcha.models import CaptchaStage
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
PLAN_CONTEXT_CAPTCHA = "captcha"
|
PLAN_CONTEXT_CAPTCHA = "captcha"
|
||||||
|
|
||||||
|
|
||||||
@ -27,15 +29,8 @@ class CaptchaChallenge(WithUserInfoChallenge):
|
|||||||
component = CharField(default="ak-stage-captcha")
|
component = CharField(default="ak-stage-captcha")
|
||||||
|
|
||||||
|
|
||||||
class CaptchaChallengeResponse(ChallengeResponse):
|
def verify_captcha_token(stage: CaptchaStage, token: str, remote_ip: str):
|
||||||
"""Validate captcha token"""
|
"""Validate captcha token"""
|
||||||
|
|
||||||
token = CharField()
|
|
||||||
component = CharField(default="ak-stage-captcha")
|
|
||||||
|
|
||||||
def validate_token(self, token: str) -> str:
|
|
||||||
"""Validate captcha token"""
|
|
||||||
stage: CaptchaStage = self.stage.executor.current_stage
|
|
||||||
try:
|
try:
|
||||||
response = get_http_session().post(
|
response = get_http_session().post(
|
||||||
stage.api_url,
|
stage.api_url,
|
||||||
@ -45,20 +40,33 @@ class CaptchaChallengeResponse(ChallengeResponse):
|
|||||||
data={
|
data={
|
||||||
"secret": stage.private_key,
|
"secret": stage.private_key,
|
||||||
"response": token,
|
"response": token,
|
||||||
"remoteip": ClientIPMiddleware.get_client_ip(self.stage.request),
|
"remoteip": remote_ip,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if stage.error_on_invalid_score:
|
if stage.error_on_invalid_score:
|
||||||
if not data.get("success", False):
|
if not data.get("success", False):
|
||||||
raise ValidationError(
|
error_codes = data.get("error-codes", ["unknown-error"])
|
||||||
_(
|
LOGGER.warning("Failed to verify captcha token", error_codes=error_codes)
|
||||||
"Failed to validate token: {error}".format(
|
|
||||||
error=data.get("error-codes", _("Unknown error"))
|
# These cases can usually be fixed by simply requesting a new token and retrying.
|
||||||
)
|
# [reCAPTCHA](https://developers.google.com/recaptcha/docs/verify#error_code_reference)
|
||||||
)
|
# [hCaptcha](https://docs.hcaptcha.com/#siteverify-error-codes-table)
|
||||||
)
|
# [Turnstile](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/#error-codes)
|
||||||
|
retriable_error_codes = [
|
||||||
|
"missing-input-response",
|
||||||
|
"invalid-input-response",
|
||||||
|
"timeout-or-duplicate",
|
||||||
|
"expired-input-response",
|
||||||
|
"already-seen-response",
|
||||||
|
]
|
||||||
|
|
||||||
|
if set(error_codes).issubset(set(retriable_error_codes)):
|
||||||
|
error_message = _("Invalid captcha response. Retrying may solve this issue.")
|
||||||
|
else:
|
||||||
|
error_message = _("Invalid captcha response")
|
||||||
|
raise ValidationError(error_message)
|
||||||
if "score" in data:
|
if "score" in data:
|
||||||
score = float(data.get("score"))
|
score = float(data.get("score"))
|
||||||
if stage.score_max_threshold > -1 and score > stage.score_max_threshold:
|
if stage.score_max_threshold > -1 and score > stage.score_max_threshold:
|
||||||
@ -67,9 +75,24 @@ class CaptchaChallengeResponse(ChallengeResponse):
|
|||||||
raise ValidationError(_("Invalid captcha response"))
|
raise ValidationError(_("Invalid captcha response"))
|
||||||
except (RequestException, TypeError) as exc:
|
except (RequestException, TypeError) as exc:
|
||||||
raise ValidationError(_("Failed to validate token")) from exc
|
raise ValidationError(_("Failed to validate token")) from exc
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class CaptchaChallengeResponse(ChallengeResponse):
|
||||||
|
"""Validate captcha token"""
|
||||||
|
|
||||||
|
token = CharField()
|
||||||
|
component = CharField(default="ak-stage-captcha")
|
||||||
|
|
||||||
|
def validate_token(self, token: str) -> str:
|
||||||
|
"""Validate captcha token"""
|
||||||
|
stage: CaptchaStage = self.stage.executor.current_stage
|
||||||
|
client_ip = ClientIPMiddleware.get_client_ip(self.stage.request)
|
||||||
|
|
||||||
|
return verify_captcha_token(stage, token, client_ip)
|
||||||
|
|
||||||
|
|
||||||
class CaptchaStageView(ChallengeStageView):
|
class CaptchaStageView(ChallengeStageView):
|
||||||
"""Simple captcha checker, logic is handled in django-captcha module"""
|
"""Simple captcha checker, logic is handled in django-captcha module"""
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ class IdentificationStageSerializer(StageSerializer):
|
|||||||
fields = StageSerializer.Meta.fields + [
|
fields = StageSerializer.Meta.fields + [
|
||||||
"user_fields",
|
"user_fields",
|
||||||
"password_stage",
|
"password_stage",
|
||||||
|
"captcha_stage",
|
||||||
"case_insensitive_matching",
|
"case_insensitive_matching",
|
||||||
"show_matched_user",
|
"show_matched_user",
|
||||||
"enrollment_flow",
|
"enrollment_flow",
|
||||||
@ -46,6 +47,7 @@ class IdentificationStageViewSet(UsedByMixin, ModelViewSet):
|
|||||||
filterset_fields = [
|
filterset_fields = [
|
||||||
"name",
|
"name",
|
||||||
"password_stage",
|
"password_stage",
|
||||||
|
"captcha_stage",
|
||||||
"case_insensitive_matching",
|
"case_insensitive_matching",
|
||||||
"show_matched_user",
|
"show_matched_user",
|
||||||
"enrollment_flow",
|
"enrollment_flow",
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 5.0.8 on 2024-08-29 11:31
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_stages_captcha", "0003_captchastage_error_on_invalid_score_and_more"),
|
||||||
|
("authentik_stages_identification", "0014_identificationstage_pretend"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="identificationstage",
|
||||||
|
name="captcha_stage",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
default=None,
|
||||||
|
help_text="When set, adds functionality exactly like a Captcha stage, but baked into the Identification stage.",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="authentik_stages_captcha.captchastage",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -8,6 +8,7 @@ from rest_framework.serializers import BaseSerializer
|
|||||||
|
|
||||||
from authentik.core.models import Source
|
from authentik.core.models import Source
|
||||||
from authentik.flows.models import Flow, Stage
|
from authentik.flows.models import Flow, Stage
|
||||||
|
from authentik.stages.captcha.models import CaptchaStage
|
||||||
from authentik.stages.password.models import PasswordStage
|
from authentik.stages.password.models import PasswordStage
|
||||||
|
|
||||||
|
|
||||||
@ -43,6 +44,19 @@ class IdentificationStage(Stage):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
captcha_stage = models.ForeignKey(
|
||||||
|
CaptchaStage,
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
help_text=_(
|
||||||
|
(
|
||||||
|
"When set, adds functionality exactly like a Captcha stage, but baked into the "
|
||||||
|
"Identification stage."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
case_insensitive_matching = models.BooleanField(
|
case_insensitive_matching = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
help_text=_("When enabled, user fields are matched regardless of their casing."),
|
help_text=_("When enabled, user fields are matched regardless of their casing."),
|
||||||
|
|||||||
@ -29,6 +29,7 @@ from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_
|
|||||||
from authentik.lib.utils.reflection import all_subclasses
|
from authentik.lib.utils.reflection import all_subclasses
|
||||||
from authentik.lib.utils.urls import reverse_with_qs
|
from authentik.lib.utils.urls import reverse_with_qs
|
||||||
from authentik.root.middleware import ClientIPMiddleware
|
from authentik.root.middleware import ClientIPMiddleware
|
||||||
|
from authentik.stages.captcha.stage import CaptchaChallenge, verify_captcha_token
|
||||||
from authentik.stages.identification.models import IdentificationStage
|
from authentik.stages.identification.models import IdentificationStage
|
||||||
from authentik.stages.identification.signals import identification_failed
|
from authentik.stages.identification.signals import identification_failed
|
||||||
from authentik.stages.password.stage import authenticate
|
from authentik.stages.password.stage import authenticate
|
||||||
@ -75,6 +76,7 @@ class IdentificationChallenge(Challenge):
|
|||||||
allow_show_password = BooleanField(default=False)
|
allow_show_password = BooleanField(default=False)
|
||||||
application_pre = CharField(required=False)
|
application_pre = CharField(required=False)
|
||||||
flow_designation = ChoiceField(FlowDesignation.choices)
|
flow_designation = ChoiceField(FlowDesignation.choices)
|
||||||
|
captcha_stage = CaptchaChallenge(required=False)
|
||||||
|
|
||||||
enroll_url = CharField(required=False)
|
enroll_url = CharField(required=False)
|
||||||
recovery_url = CharField(required=False)
|
recovery_url = CharField(required=False)
|
||||||
@ -91,14 +93,16 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
|||||||
|
|
||||||
uid_field = CharField()
|
uid_field = CharField()
|
||||||
password = CharField(required=False, allow_blank=True, allow_null=True)
|
password = CharField(required=False, allow_blank=True, allow_null=True)
|
||||||
|
captcha_token = CharField(required=False, allow_blank=True, allow_null=True)
|
||||||
component = CharField(default="ak-stage-identification")
|
component = CharField(default="ak-stage-identification")
|
||||||
|
|
||||||
pre_user: User | None = None
|
pre_user: User | None = None
|
||||||
|
|
||||||
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Validate that user exists, and optionally their password"""
|
"""Validate that user exists, and optionally their password and captcha token"""
|
||||||
uid_field = attrs["uid_field"]
|
uid_field = attrs["uid_field"]
|
||||||
current_stage: IdentificationStage = self.stage.executor.current_stage
|
current_stage: IdentificationStage = self.stage.executor.current_stage
|
||||||
|
client_ip = ClientIPMiddleware.get_client_ip(self.stage.request)
|
||||||
|
|
||||||
pre_user = self.stage.get_user(uid_field)
|
pre_user = self.stage.get_user(uid_field)
|
||||||
if not pre_user:
|
if not pre_user:
|
||||||
@ -113,7 +117,7 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
|||||||
self.stage.logger.info(
|
self.stage.logger.info(
|
||||||
"invalid_login",
|
"invalid_login",
|
||||||
identifier=uid_field,
|
identifier=uid_field,
|
||||||
client_ip=ClientIPMiddleware.get_client_ip(self.stage.request),
|
client_ip=client_ip,
|
||||||
action="invalid_identifier",
|
action="invalid_identifier",
|
||||||
context={
|
context={
|
||||||
"stage": sanitize_item(self.stage),
|
"stage": sanitize_item(self.stage),
|
||||||
@ -136,6 +140,15 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
|||||||
return attrs
|
return attrs
|
||||||
raise ValidationError("Failed to authenticate.")
|
raise ValidationError("Failed to authenticate.")
|
||||||
self.pre_user = pre_user
|
self.pre_user = pre_user
|
||||||
|
|
||||||
|
# Captcha check
|
||||||
|
if captcha_stage := current_stage.captcha_stage:
|
||||||
|
captcha_token = attrs.get("captcha_token", None)
|
||||||
|
if not captcha_token:
|
||||||
|
self.stage.logger.warning("Token not set for captcha attempt")
|
||||||
|
verify_captcha_token(captcha_stage, captcha_token, client_ip)
|
||||||
|
|
||||||
|
# Password check
|
||||||
if not current_stage.password_stage:
|
if not current_stage.password_stage:
|
||||||
# No password stage select, don't validate the password
|
# No password stage select, don't validate the password
|
||||||
return attrs
|
return attrs
|
||||||
@ -206,6 +219,14 @@ class IdentificationStageView(ChallengeStageView):
|
|||||||
"primary_action": self.get_primary_action(),
|
"primary_action": self.get_primary_action(),
|
||||||
"user_fields": current_stage.user_fields,
|
"user_fields": current_stage.user_fields,
|
||||||
"password_fields": bool(current_stage.password_stage),
|
"password_fields": bool(current_stage.password_stage),
|
||||||
|
"captcha_stage": (
|
||||||
|
{
|
||||||
|
"js_url": current_stage.captcha_stage.js_url,
|
||||||
|
"site_key": current_stage.captcha_stage.public_key,
|
||||||
|
}
|
||||||
|
if current_stage.captcha_stage
|
||||||
|
else None
|
||||||
|
),
|
||||||
"allow_show_password": bool(current_stage.password_stage)
|
"allow_show_password": bool(current_stage.password_stage)
|
||||||
and current_stage.password_stage.allow_show_password,
|
and current_stage.password_stage.allow_show_password,
|
||||||
"show_source_labels": current_stage.show_source_labels,
|
"show_source_labels": current_stage.show_source_labels,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"""identification tests"""
|
"""identification tests"""
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from requests_mock import Mocker
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||||
@ -8,6 +9,8 @@ from authentik.flows.models import FlowDesignation, FlowStageBinding
|
|||||||
from authentik.flows.tests import FlowTestCase
|
from authentik.flows.tests import FlowTestCase
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.sources.oauth.models import OAuthSource
|
from authentik.sources.oauth.models import OAuthSource
|
||||||
|
from authentik.stages.captcha.models import CaptchaStage
|
||||||
|
from authentik.stages.captcha.tests import RECAPTCHA_PRIVATE_KEY, RECAPTCHA_PUBLIC_KEY
|
||||||
from authentik.stages.identification.api import IdentificationStageSerializer
|
from authentik.stages.identification.api import IdentificationStageSerializer
|
||||||
from authentik.stages.identification.models import IdentificationStage, UserFields
|
from authentik.stages.identification.models import IdentificationStage, UserFields
|
||||||
from authentik.stages.password import BACKEND_INBUILT
|
from authentik.stages.password import BACKEND_INBUILT
|
||||||
@ -133,6 +136,135 @@ class TestIdentificationStage(FlowTestCase):
|
|||||||
user_fields=["email"],
|
user_fields=["email"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Mocker()
|
||||||
|
def test_valid_with_captcha(self, mock: Mocker):
|
||||||
|
"""Test with valid email and captcha token in single step"""
|
||||||
|
mock.post(
|
||||||
|
"https://www.recaptcha.net/recaptcha/api/siteverify",
|
||||||
|
json={
|
||||||
|
"success": True,
|
||||||
|
"score": 0.5,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
captcha_stage = CaptchaStage.objects.create(
|
||||||
|
name="captcha",
|
||||||
|
public_key=RECAPTCHA_PUBLIC_KEY,
|
||||||
|
private_key=RECAPTCHA_PRIVATE_KEY,
|
||||||
|
)
|
||||||
|
self.stage.captcha_stage = captcha_stage
|
||||||
|
self.stage.save()
|
||||||
|
|
||||||
|
form_data = {"uid_field": self.user.email, "captcha_token": "PASSED"}
|
||||||
|
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||||
|
response = self.client.post(url, form_data)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
||||||
|
|
||||||
|
@Mocker()
|
||||||
|
def test_invalid_with_captcha(self, mock: Mocker):
|
||||||
|
"""Test with valid email and invalid captcha token in single step"""
|
||||||
|
mock.post(
|
||||||
|
"https://www.recaptcha.net/recaptcha/api/siteverify",
|
||||||
|
json={
|
||||||
|
"success": False,
|
||||||
|
"score": 0.5,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
captcha_stage = CaptchaStage.objects.create(
|
||||||
|
name="captcha",
|
||||||
|
public_key=RECAPTCHA_PUBLIC_KEY,
|
||||||
|
private_key=RECAPTCHA_PRIVATE_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stage.captcha_stage = captcha_stage
|
||||||
|
self.stage.save()
|
||||||
|
|
||||||
|
form_data = {
|
||||||
|
"uid_field": self.user.email,
|
||||||
|
"captcha_token": "FAILED",
|
||||||
|
}
|
||||||
|
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||||
|
response = self.client.post(url, form_data)
|
||||||
|
self.assertStageResponse(
|
||||||
|
response,
|
||||||
|
self.flow,
|
||||||
|
component="ak-stage-identification",
|
||||||
|
password_fields=False,
|
||||||
|
primary_action="Log in",
|
||||||
|
response_errors={
|
||||||
|
"non_field_errors": [{"code": "invalid", "string": "Invalid captcha response"}]
|
||||||
|
},
|
||||||
|
sources=[
|
||||||
|
{
|
||||||
|
"challenge": {
|
||||||
|
"component": "xak-flow-redirect",
|
||||||
|
"to": "/source/oauth/login/test/",
|
||||||
|
},
|
||||||
|
"icon_url": "/static/authentik/sources/default.svg",
|
||||||
|
"name": "test",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
show_source_labels=False,
|
||||||
|
user_fields=["email"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@Mocker()
|
||||||
|
def test_invalid_with_captcha_retriable(self, mock: Mocker):
|
||||||
|
"""Test with valid email and invalid captcha token in single step"""
|
||||||
|
mock.post(
|
||||||
|
"https://www.recaptcha.net/recaptcha/api/siteverify",
|
||||||
|
json={
|
||||||
|
"success": False,
|
||||||
|
"score": 0.5,
|
||||||
|
"error-codes": ["timeout-or-duplicate"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
captcha_stage = CaptchaStage.objects.create(
|
||||||
|
name="captcha",
|
||||||
|
public_key=RECAPTCHA_PUBLIC_KEY,
|
||||||
|
private_key=RECAPTCHA_PRIVATE_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stage.captcha_stage = captcha_stage
|
||||||
|
self.stage.save()
|
||||||
|
|
||||||
|
form_data = {
|
||||||
|
"uid_field": self.user.email,
|
||||||
|
"captcha_token": "FAILED",
|
||||||
|
}
|
||||||
|
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||||
|
response = self.client.post(url, form_data)
|
||||||
|
self.assertStageResponse(
|
||||||
|
response,
|
||||||
|
self.flow,
|
||||||
|
component="ak-stage-identification",
|
||||||
|
password_fields=False,
|
||||||
|
primary_action="Log in",
|
||||||
|
response_errors={
|
||||||
|
"non_field_errors": [
|
||||||
|
{
|
||||||
|
"code": "invalid",
|
||||||
|
"string": "Invalid captcha response. Retrying may solve this issue.",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sources=[
|
||||||
|
{
|
||||||
|
"challenge": {
|
||||||
|
"component": "xak-flow-redirect",
|
||||||
|
"to": "/source/oauth/login/test/",
|
||||||
|
},
|
||||||
|
"icon_url": "/static/authentik/sources/default.svg",
|
||||||
|
"name": "test",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
show_source_labels=False,
|
||||||
|
user_fields=["email"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_invalid_with_username(self):
|
def test_invalid_with_username(self):
|
||||||
"""Test invalid with username (user exists but stage only allows email)"""
|
"""Test invalid with username (user exists but stage only allows email)"""
|
||||||
form_data = {"uid_field": self.user.username}
|
form_data = {"uid_field": self.user.username}
|
||||||
|
|||||||
@ -6974,7 +6974,7 @@
|
|||||||
"spnego_server_name": {
|
"spnego_server_name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Spnego server name",
|
"title": "Spnego server name",
|
||||||
"description": "Force the use of a specific server name for SPNEGO"
|
"description": "Force the use of a specific server name for SPNEGO. Must be in the form HTTP@hostname"
|
||||||
},
|
},
|
||||||
"spnego_keytab": {
|
"spnego_keytab": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -10679,6 +10679,11 @@
|
|||||||
"title": "Password stage",
|
"title": "Password stage",
|
||||||
"description": "When set, shows a password field, instead of showing the password field as separate step."
|
"description": "When set, shows a password field, instead of showing the password field as separate step."
|
||||||
},
|
},
|
||||||
|
"captcha_stage": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Captcha stage",
|
||||||
|
"description": "When set, adds functionality exactly like a Captcha stage, but baked into the Identification stage."
|
||||||
|
},
|
||||||
"case_insensitive_matching": {
|
"case_insensitive_matching": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"title": "Case insensitive matching",
|
"title": "Case insensitive matching",
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -29,7 +29,7 @@ require (
|
|||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/wwt/guac v1.3.2
|
github.com/wwt/guac v1.3.2
|
||||||
goauthentik.io/api/v3 v3.2024083.11
|
goauthentik.io/api/v3 v3.2024083.13
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.23.0
|
golang.org/x/oauth2 v0.23.0
|
||||||
golang.org/x/sync v0.8.0
|
golang.org/x/sync v0.8.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
goauthentik.io/api/v3 v3.2024083.11 h1:kF5WAnS0dB2cq9Uldqel8e8PDepJg/824JC3YFsQVHU=
|
goauthentik.io/api/v3 v3.2024083.13 h1:xKh3feJYUeLw583zZ5ifgV0qjD37ZCOzgXPfbHQSbHM=
|
||||||
goauthentik.io/api/v3 v3.2024083.11/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2024083.13/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
|||||||
@ -82,6 +82,9 @@ func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bo
|
|||||||
if embedded {
|
if embedded {
|
||||||
ep.Issuer = updateURL(ep.Issuer, newHost.Scheme, newHost.Host)
|
ep.Issuer = updateURL(ep.Issuer, newHost.Scheme, newHost.Host)
|
||||||
ep.JwksUri = updateURL(jwksUri, newHost.Scheme, newHost.Host)
|
ep.JwksUri = updateURL(jwksUri, newHost.Scheme, newHost.Host)
|
||||||
|
} else {
|
||||||
|
// Fixes: https://github.com/goauthentik/authentik/issues/9622 / ep.Issuer must be the HostBrowser URL
|
||||||
|
ep.Issuer = updateURL(ep.Issuer, newBrowserHost.Scheme, newBrowserHost.Host)
|
||||||
}
|
}
|
||||||
return ep
|
return ep
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ func TestEndpointAuthentikHostBrowser(t *testing.T) {
|
|||||||
assert.Equal(t, "https://browser.test.goauthentik.io/application/o/authorize/", ep.AuthURL)
|
assert.Equal(t, "https://browser.test.goauthentik.io/application/o/authorize/", ep.AuthURL)
|
||||||
assert.Equal(t, "https://browser.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint)
|
assert.Equal(t, "https://browser.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint)
|
||||||
assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL)
|
assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL)
|
||||||
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/", ep.Issuer)
|
assert.Equal(t, "https://browser.test.goauthentik.io/application/o/test-app/", ep.Issuer)
|
||||||
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri)
|
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri)
|
||||||
assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection)
|
assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,6 +54,8 @@ function cleanup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function prepare_debug {
|
function prepare_debug {
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends krb5-kdc krb5-user krb5-admin-server
|
apt-get install -y --no-install-recommends krb5-kdc krb5-user krb5-admin-server
|
||||||
VIRTUAL_ENV=/ak-root/venv poetry install --no-ansi --no-interaction
|
VIRTUAL_ENV=/ak-root/venv poetry install --no-ansi --no-interaction
|
||||||
touch /unittest.xml
|
touch /unittest.xml
|
||||||
|
|||||||
@ -19,7 +19,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-18 00:09+0000\n"
|
"POT-Creation-Date: 2024-10-23 16:39+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Marc Schmitt, 2024\n"
|
"Last-Translator: Marc Schmitt, 2024\n"
|
||||||
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
||||||
@ -587,6 +587,30 @@ msgstr "Limite maximum de connection atteinte."
|
|||||||
msgid "(You are already connected in another tab/window)"
|
msgid "(You are already connected in another tab/window)"
|
||||||
msgstr "(Vous êtes déjà connecté dans un autre onglet/une autre fenêtre)"
|
msgstr "(Vous êtes déjà connecté dans un autre onglet/une autre fenêtre)"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||||
|
msgstr ""
|
||||||
|
"Étape d'authentificateur d'appareil du connecteur de confiance des appareils"
|
||||||
|
" Google"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Authenticator Google Device Trust Connector Stages"
|
||||||
|
msgstr ""
|
||||||
|
"Étapes d'authentificateur d'appareil du connecteur de confiance des "
|
||||||
|
"appareils Google"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Device"
|
||||||
|
msgstr "Appareil point de terminaison"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Devices"
|
||||||
|
msgstr "Appareils point de terminaison"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py
|
||||||
|
msgid "Verifying your browser..."
|
||||||
|
msgstr "Vérification de votre navigateur..."
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -2029,6 +2053,125 @@ msgstr ""
|
|||||||
msgid "Used recovery-link to authenticate."
|
msgid "Used recovery-link to authenticate."
|
||||||
msgstr "Utiliser un lien de récupération pour se connecter."
|
msgstr "Utiliser un lien de récupération pour se connecter."
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos realm"
|
||||||
|
msgstr "Realm Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Custom krb5.conf to use. Uses the system one by default"
|
||||||
|
msgstr ""
|
||||||
|
"krb5.conf personnalisé à utiliser. Utilise celui du système par défault"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Sync users from Kerberos into authentik"
|
||||||
|
msgstr "Synchroniser les utilisateurs Kerberos dans authentik"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "When a user changes their password, sync it back to Kerberos"
|
||||||
|
msgstr ""
|
||||||
|
"Lorsqu'un utilisateur change son mot de passe, le synchroniser à nouveau "
|
||||||
|
"vers Kerberos."
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Principal to authenticate to kadmin for sync."
|
||||||
|
msgstr "Principal pour s'authentifier à kadmin pour la synchronisation."
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Password to authenticate to kadmin for sync"
|
||||||
|
msgstr "Mot de passe pour s'authentifier à kadmin pour la synchronisation."
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the "
|
||||||
|
"form TYPE:residual"
|
||||||
|
msgstr ""
|
||||||
|
"Keytab pour s'authentifier à kadmin pour la synchronisation. Doit être "
|
||||||
|
"encodé en base64 ou de la forme TYPE:residual"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"Credentials cache to authenticate to kadmin for sync. Must be in the form "
|
||||||
|
"TYPE:residual"
|
||||||
|
msgstr ""
|
||||||
|
"Credentials cache pour s'authentifier à kadmin pour la synchronisation. Doit"
|
||||||
|
" être de la forme TYPE:residual"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"Force the use of a specific server name for SPNEGO. Must be in the form "
|
||||||
|
"HTTP@hostname"
|
||||||
|
msgstr ""
|
||||||
|
"Force l'utilisation d'un nom de serveur spécifique pour SPNEGO. Doit être de"
|
||||||
|
" la forme HTTP@hostname"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path"
|
||||||
|
msgstr ""
|
||||||
|
"Keytab SPNEGO encodée en base64 ou chemin vers la keytab de la forme "
|
||||||
|
"FILE:path"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Credential cache to use for SPNEGO in form type:residual"
|
||||||
|
msgstr "Credentials cache pour SPNEGO de la forme TYPE:residual"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"If enabled, the authentik-stored password will be updated upon login with "
|
||||||
|
"the Kerberos password backend"
|
||||||
|
msgstr ""
|
||||||
|
"Si activé, le mot de passe stocké par authentik sera mis à jour à la "
|
||||||
|
"connexion avec le backend de mot de passe Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Source"
|
||||||
|
msgstr "Source Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Sources"
|
||||||
|
msgstr "Sources Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Source Property Mapping"
|
||||||
|
msgstr "Mappage de propriété source Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Source Property Mappings"
|
||||||
|
msgstr "Mappages de propriété source Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "User Kerberos Source Connection"
|
||||||
|
msgstr "Connexion de l'utilisateur à la source Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "User Kerberos Source Connections"
|
||||||
|
msgstr "Connexions de l'utilisateur à la source Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Group Kerberos Source Connection"
|
||||||
|
msgstr "Connexion du groupe à la source Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Group Kerberos Source Connections"
|
||||||
|
msgstr "Connexions du groupe à la source Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/views.py
|
||||||
|
msgid "SPNEGO authentication required"
|
||||||
|
msgstr "Authentification SPNEGO requise"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/views.py
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Make sure you have valid tickets (obtainable via kinit)\n"
|
||||||
|
" and configured the browser correctly.\n"
|
||||||
|
" Please contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
" Vérifiez que vous avez des tickets valides (qu'on peut obtenir via kinit)\n"
|
||||||
|
" et que le navigateur est configuré correctement.\n"
|
||||||
|
" Veuillez contacter votre administrateur.\n"
|
||||||
|
" "
|
||||||
|
|
||||||
#: authentik/sources/ldap/api.py
|
#: authentik/sources/ldap/api.py
|
||||||
msgid "Only a single LDAP Source with password synchronization is allowed"
|
msgid "Only a single LDAP Source with password synchronization is allowed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3121,6 +3264,10 @@ msgstr "Base de données utilisateurs + mots de passes applicatifs"
|
|||||||
msgid "User database + LDAP password"
|
msgid "User database + LDAP password"
|
||||||
msgstr "Base de données utilisateurs + mot de passe LDAP"
|
msgstr "Base de données utilisateurs + mot de passe LDAP"
|
||||||
|
|
||||||
|
#: authentik/stages/password/models.py
|
||||||
|
msgid "User database + Kerberos password"
|
||||||
|
msgstr "Base de données utilisateurs + mot de passe Kerberos"
|
||||||
|
|
||||||
#: authentik/stages/password/models.py
|
#: authentik/stages/password/models.py
|
||||||
msgid "Selection of backends to test the password against."
|
msgid "Selection of backends to test the password against."
|
||||||
msgstr "Sélection de backends pour tester le mot de passe."
|
msgstr "Sélection de backends pour tester le mot de passe."
|
||||||
|
|||||||
@ -15,7 +15,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-18 00:09+0000\n"
|
"POT-Creation-Date: 2024-10-23 16:39+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: deluxghost, 2024\n"
|
"Last-Translator: deluxghost, 2024\n"
|
||||||
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
|
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
|
||||||
@ -540,6 +540,26 @@ msgstr "已达到最大连接数。"
|
|||||||
msgid "(You are already connected in another tab/window)"
|
msgid "(You are already connected in another tab/window)"
|
||||||
msgstr "(您已经在另一个标签页/窗口连接了)"
|
msgstr "(您已经在另一个标签页/窗口连接了)"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||||
|
msgstr "端点身份验证器 Google 设备信任连接器阶段"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Authenticator Google Device Trust Connector Stages"
|
||||||
|
msgstr "端点身份验证器 Google 设备信任连接器阶段"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Device"
|
||||||
|
msgstr "端点设备"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Devices"
|
||||||
|
msgstr "端点设备"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py
|
||||||
|
msgid "Verifying your browser..."
|
||||||
|
msgstr "正在验证您的浏览器…"
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -1848,6 +1868,112 @@ msgstr "创建一个密钥,可用于恢复对 authentik 的访问权限。"
|
|||||||
msgid "Used recovery-link to authenticate."
|
msgid "Used recovery-link to authenticate."
|
||||||
msgstr "已使用恢复链接进行身份验证。"
|
msgstr "已使用恢复链接进行身份验证。"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos realm"
|
||||||
|
msgstr "Kerberos 领域"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Custom krb5.conf to use. Uses the system one by default"
|
||||||
|
msgstr "要使用的自定义 krb5.conf。默认使用系统自带"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Sync users from Kerberos into authentik"
|
||||||
|
msgstr "从 Kerberos 同步用户到 authentik"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "When a user changes their password, sync it back to Kerberos"
|
||||||
|
msgstr "当用户修改密码时,将其同步回 Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Principal to authenticate to kadmin for sync."
|
||||||
|
msgstr "向 kadmin 进行身份验证以进行同步的主体。"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Password to authenticate to kadmin for sync"
|
||||||
|
msgstr "向 kadmin 进行身份验证以进行同步的密码"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the "
|
||||||
|
"form TYPE:residual"
|
||||||
|
msgstr "向 kadmin 进行身份验证以进行同步的 Keytab。必须以 Base64 编码,或者形式为 TYPE:residual"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"Credentials cache to authenticate to kadmin for sync. Must be in the form "
|
||||||
|
"TYPE:residual"
|
||||||
|
msgstr "向 kadmin 进行身份验证以进行同步的凭据缓存。形式必须为 TYPE:residual"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"Force the use of a specific server name for SPNEGO. Must be in the form "
|
||||||
|
"HTTP@hostname"
|
||||||
|
msgstr "强制为 SPNEGO 使用特定服务器名称。形式必须为 HTTP@主机名"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path"
|
||||||
|
msgstr "以 Base64 编码的 SPNEGO Keytab 或 FILE:path 形式的 Keytab 路径"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Credential cache to use for SPNEGO in form type:residual"
|
||||||
|
msgstr "SPNEGO 使用的凭据缓存,形式为 type:residual"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"If enabled, the authentik-stored password will be updated upon login with "
|
||||||
|
"the Kerberos password backend"
|
||||||
|
msgstr "启用时,authentik 存储的密码将会在使用 Kerberos 密码后端登录时更新"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Source"
|
||||||
|
msgstr "Kerberos 源"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Sources"
|
||||||
|
msgstr "Kerberos 源"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Source Property Mapping"
|
||||||
|
msgstr "Kerberos 源属性映射"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Source Property Mappings"
|
||||||
|
msgstr "Kerberos 源属性映射"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "User Kerberos Source Connection"
|
||||||
|
msgstr "用户 Kerberos 源连接"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "User Kerberos Source Connections"
|
||||||
|
msgstr "用户 Kerberos 源连接"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Group Kerberos Source Connection"
|
||||||
|
msgstr "组 Kerberos 源连接"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Group Kerberos Source Connections"
|
||||||
|
msgstr "组 Kerberos 源连接"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/views.py
|
||||||
|
msgid "SPNEGO authentication required"
|
||||||
|
msgstr "需要 SPNEGO 身份验证"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/views.py
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Make sure you have valid tickets (obtainable via kinit)\n"
|
||||||
|
" and configured the browser correctly.\n"
|
||||||
|
" Please contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
" 请确认您拥有有效票据(通过 kinit 获得)\n"
|
||||||
|
" 并且已正确配置浏览器。\n"
|
||||||
|
" 请联系您的管理员。\n"
|
||||||
|
" "
|
||||||
|
|
||||||
#: authentik/sources/ldap/api.py
|
#: authentik/sources/ldap/api.py
|
||||||
msgid "Only a single LDAP Source with password synchronization is allowed"
|
msgid "Only a single LDAP Source with password synchronization is allowed"
|
||||||
msgstr "仅允许使用密码同步的单个 LDAP 源"
|
msgstr "仅允许使用密码同步的单个 LDAP 源"
|
||||||
@ -2876,6 +3002,10 @@ msgstr "用户数据库 + 应用程序密码"
|
|||||||
msgid "User database + LDAP password"
|
msgid "User database + LDAP password"
|
||||||
msgstr "用户数据库 + LDAP 密码"
|
msgstr "用户数据库 + LDAP 密码"
|
||||||
|
|
||||||
|
#: authentik/stages/password/models.py
|
||||||
|
msgid "User database + Kerberos password"
|
||||||
|
msgstr "用户数据库 + Kerberos 密码"
|
||||||
|
|
||||||
#: authentik/stages/password/models.py
|
#: authentik/stages/password/models.py
|
||||||
msgid "Selection of backends to test the password against."
|
msgid "Selection of backends to test the password against."
|
||||||
msgstr "选择用于测试密码的后端。"
|
msgstr "选择用于测试密码的后端。"
|
||||||
|
|||||||
@ -14,7 +14,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-18 00:09+0000\n"
|
"POT-Creation-Date: 2024-10-23 16:39+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: deluxghost, 2024\n"
|
"Last-Translator: deluxghost, 2024\n"
|
||||||
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
||||||
@ -539,6 +539,26 @@ msgstr "已达到最大连接数。"
|
|||||||
msgid "(You are already connected in another tab/window)"
|
msgid "(You are already connected in another tab/window)"
|
||||||
msgstr "(您已经在另一个标签页/窗口连接了)"
|
msgstr "(您已经在另一个标签页/窗口连接了)"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||||
|
msgstr "端点身份验证器 Google 设备信任连接器阶段"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Authenticator Google Device Trust Connector Stages"
|
||||||
|
msgstr "端点身份验证器 Google 设备信任连接器阶段"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Device"
|
||||||
|
msgstr "端点设备"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
|
msgid "Endpoint Devices"
|
||||||
|
msgstr "端点设备"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py
|
||||||
|
msgid "Verifying your browser..."
|
||||||
|
msgstr "正在验证您的浏览器…"
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -1847,6 +1867,112 @@ msgstr "创建一个密钥,可用于恢复对 authentik 的访问权限。"
|
|||||||
msgid "Used recovery-link to authenticate."
|
msgid "Used recovery-link to authenticate."
|
||||||
msgstr "已使用恢复链接进行身份验证。"
|
msgstr "已使用恢复链接进行身份验证。"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos realm"
|
||||||
|
msgstr "Kerberos 领域"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Custom krb5.conf to use. Uses the system one by default"
|
||||||
|
msgstr "要使用的自定义 krb5.conf。默认使用系统自带"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Sync users from Kerberos into authentik"
|
||||||
|
msgstr "从 Kerberos 同步用户到 authentik"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "When a user changes their password, sync it back to Kerberos"
|
||||||
|
msgstr "当用户修改密码时,将其同步回 Kerberos"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Principal to authenticate to kadmin for sync."
|
||||||
|
msgstr "向 kadmin 进行身份验证以进行同步的主体。"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Password to authenticate to kadmin for sync"
|
||||||
|
msgstr "向 kadmin 进行身份验证以进行同步的密码"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the "
|
||||||
|
"form TYPE:residual"
|
||||||
|
msgstr "向 kadmin 进行身份验证以进行同步的 Keytab。必须以 Base64 编码,或者形式为 TYPE:residual"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"Credentials cache to authenticate to kadmin for sync. Must be in the form "
|
||||||
|
"TYPE:residual"
|
||||||
|
msgstr "向 kadmin 进行身份验证以进行同步的凭据缓存。形式必须为 TYPE:residual"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"Force the use of a specific server name for SPNEGO. Must be in the form "
|
||||||
|
"HTTP@hostname"
|
||||||
|
msgstr "强制为 SPNEGO 使用特定服务器名称。形式必须为 HTTP@主机名"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path"
|
||||||
|
msgstr "以 Base64 编码的 SPNEGO Keytab 或 FILE:path 形式的 Keytab 路径"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Credential cache to use for SPNEGO in form type:residual"
|
||||||
|
msgstr "SPNEGO 使用的凭据缓存,形式为 type:residual"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid ""
|
||||||
|
"If enabled, the authentik-stored password will be updated upon login with "
|
||||||
|
"the Kerberos password backend"
|
||||||
|
msgstr "启用时,authentik 存储的密码将会在使用 Kerberos 密码后端登录时更新"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Source"
|
||||||
|
msgstr "Kerberos 源"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Sources"
|
||||||
|
msgstr "Kerberos 源"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Source Property Mapping"
|
||||||
|
msgstr "Kerberos 源属性映射"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Kerberos Source Property Mappings"
|
||||||
|
msgstr "Kerberos 源属性映射"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "User Kerberos Source Connection"
|
||||||
|
msgstr "用户 Kerberos 源连接"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "User Kerberos Source Connections"
|
||||||
|
msgstr "用户 Kerberos 源连接"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Group Kerberos Source Connection"
|
||||||
|
msgstr "组 Kerberos 源连接"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/models.py
|
||||||
|
msgid "Group Kerberos Source Connections"
|
||||||
|
msgstr "组 Kerberos 源连接"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/views.py
|
||||||
|
msgid "SPNEGO authentication required"
|
||||||
|
msgstr "需要 SPNEGO 身份验证"
|
||||||
|
|
||||||
|
#: authentik/sources/kerberos/views.py
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Make sure you have valid tickets (obtainable via kinit)\n"
|
||||||
|
" and configured the browser correctly.\n"
|
||||||
|
" Please contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
" 请确认您拥有有效票据(通过 kinit 获得)\n"
|
||||||
|
" 并且已正确配置浏览器。\n"
|
||||||
|
" 请联系您的管理员。\n"
|
||||||
|
" "
|
||||||
|
|
||||||
#: authentik/sources/ldap/api.py
|
#: authentik/sources/ldap/api.py
|
||||||
msgid "Only a single LDAP Source with password synchronization is allowed"
|
msgid "Only a single LDAP Source with password synchronization is allowed"
|
||||||
msgstr "仅允许使用密码同步的单个 LDAP 源"
|
msgstr "仅允许使用密码同步的单个 LDAP 源"
|
||||||
@ -2875,6 +3001,10 @@ msgstr "用户数据库 + 应用程序密码"
|
|||||||
msgid "User database + LDAP password"
|
msgid "User database + LDAP password"
|
||||||
msgstr "用户数据库 + LDAP 密码"
|
msgstr "用户数据库 + LDAP 密码"
|
||||||
|
|
||||||
|
#: authentik/stages/password/models.py
|
||||||
|
msgid "User database + Kerberos password"
|
||||||
|
msgstr "用户数据库 + Kerberos 密码"
|
||||||
|
|
||||||
#: authentik/stages/password/models.py
|
#: authentik/stages/password/models.py
|
||||||
msgid "Selection of backends to test the password against."
|
msgid "Selection of backends to test the password against."
|
||||||
msgstr "选择用于测试密码的后端。"
|
msgstr "选择用于测试密码的后端。"
|
||||||
|
|||||||
97
poetry.lock
generated
97
poetry.lock
generated
@ -1849,35 +1849,36 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gssapi"
|
name = "gssapi"
|
||||||
version = "1.8.3"
|
version = "1.9.0"
|
||||||
description = "Python GSSAPI Wrapper"
|
description = "Python GSSAPI Wrapper"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "gssapi-1.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4e4a83e9b275fe69b5d40be6d5479889866b80333a12c51a9243f2712d4f0554"},
|
{file = "gssapi-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:261e00ac426d840055ddb2199f4989db7e3ce70fa18b1538f53e392b4823e8f1"},
|
||||||
{file = "gssapi-1.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d57d67547e18f4e44a688bfb20abbf176d1b8df547da2b31c3f2df03cfdc269"},
|
{file = "gssapi-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:14a1ae12fdf1e4c8889206195ba1843de09fe82587fa113112887cd5894587c6"},
|
||||||
{file = "gssapi-1.8.3-cp310-cp310-win32.whl", hash = "sha256:3a3f63105f39c4af29ffc8f7b6542053d87fe9d63010c689dd9a9f5571facb8e"},
|
{file = "gssapi-1.9.0-cp310-cp310-win32.whl", hash = "sha256:2a9c745255e3a810c3e8072e267b7b302de0705f8e9a0f2c5abc92fe12b9475e"},
|
||||||
{file = "gssapi-1.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:b031c0f186ab4275186da385b2c7470dd47c9b27522cb3b753757c9ac4bebf11"},
|
{file = "gssapi-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:dfc1b4c0bfe9f539537601c9f187edc320daf488f694e50d02d0c1eb37416962"},
|
||||||
{file = "gssapi-1.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b03d6b30f1fcd66d9a688b45a97e302e4dd3f1386d5c333442731aec73cdb409"},
|
{file = "gssapi-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:67d9be5e34403e47fb5749d5a1ad4e5a85b568e6a9add1695edb4a5b879f7560"},
|
||||||
{file = "gssapi-1.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ca6ceb17fc15eda2a69f2e8c6cf10d11e2edb32832255e5d4c65b21b6db4680a"},
|
{file = "gssapi-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11e9b92cef11da547fc8c210fa720528fd854038504103c1b15ae2a89dce5fcd"},
|
||||||
{file = "gssapi-1.8.3-cp311-cp311-win32.whl", hash = "sha256:edc8ef3a9e397dbe18bb6016f8e2209969677b534316d20bb139da2865a38efe"},
|
{file = "gssapi-1.9.0-cp311-cp311-win32.whl", hash = "sha256:6c5f8a549abd187687440ec0b72e5b679d043d620442b3637d31aa2766b27cbe"},
|
||||||
{file = "gssapi-1.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:8fdb1ff130cee49bc865ec1624dee8cf445cd6c6e93b04bffef2c6f363a60cb9"},
|
{file = "gssapi-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:59e1a1a9a6c5dc430dc6edfcf497f5ca00cf417015f781c9fac2e85652cd738f"},
|
||||||
{file = "gssapi-1.8.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:19c373b3ba63ce19cd3163aa1495635e3d01b0de6cc4ff1126095eded1df6e01"},
|
{file = "gssapi-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b66a98827fbd2864bf8993677a039d7ba4a127ca0d2d9ed73e0ef4f1baa7fd7f"},
|
||||||
{file = "gssapi-1.8.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f1a8046d695f2c9b8d640a6e385780d3945c0741571ed6fee6f94c31e431dc"},
|
{file = "gssapi-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2bddd1cc0c9859c5e0fd96d4d88eb67bd498fdbba45b14cdccfe10bfd329479f"},
|
||||||
{file = "gssapi-1.8.3-cp312-cp312-win32.whl", hash = "sha256:338db18612e3e6ed64e92b6d849242a535fdc98b365f21122992fb8cae737617"},
|
{file = "gssapi-1.9.0-cp312-cp312-win32.whl", hash = "sha256:10134db0cf01bd7d162acb445762dbcc58b5c772a613e17c46cf8ad956c4dfec"},
|
||||||
{file = "gssapi-1.8.3-cp312-cp312-win_amd64.whl", hash = "sha256:5731c5b40ecc3116cfe7fb7e1d1e128583ec8b3df1e68bf8cd12073160793acd"},
|
{file = "gssapi-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:e28c7d45da68b7e36ed3fb3326744bfe39649f16e8eecd7b003b082206039c76"},
|
||||||
{file = "gssapi-1.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e556878da197ad115a566d36e46a8082d0079731d9c24d1ace795132d725ff2a"},
|
{file = "gssapi-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cea344246935b5337e6f8a69bb6cc45619ab3a8d74a29fcb0a39fd1e5843c89c"},
|
||||||
{file = "gssapi-1.8.3-cp37-cp37m-win32.whl", hash = "sha256:e2bb081f2db2111377effe7d40ba23f9a87359b9d2f4881552b731e9da88b36b"},
|
{file = "gssapi-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a5786bd9fcf435bd0c87dc95ae99ad68cefcc2bcc80c71fef4cb0ccdfb40f1e"},
|
||||||
{file = "gssapi-1.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4d9ed83f2064cda60aad90e6840ae282096801b2c814b8cbd390bf0df4635aab"},
|
{file = "gssapi-1.9.0-cp313-cp313-win32.whl", hash = "sha256:c99959a9dd62358e370482f1691e936cb09adf9a69e3e10d4f6a097240e9fd28"},
|
||||||
{file = "gssapi-1.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7d91fe6e2a5c89b32102ea8e374b8ae13b9031d43d7b55f3abc1f194ddce820d"},
|
{file = "gssapi-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:a2e43f50450e81fe855888c53df70cdd385ada979db79463b38031710a12acd9"},
|
||||||
{file = "gssapi-1.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d5b28237afc0668046934792756dd4b6b7e957b0d95a608d02f296734a2819ad"},
|
{file = "gssapi-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c0e378d62b2fc352ca0046030cda5911d808a965200f612fdd1d74501b83e98f"},
|
||||||
{file = "gssapi-1.8.3-cp38-cp38-win32.whl", hash = "sha256:791e44f7bea602b8e3da1ec56fbdb383b8ee3326fdeb736f904c2aa9af13a67d"},
|
{file = "gssapi-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b74031c70864d04864b7406c818f41be0c1637906fb9654b06823bcc79f151dc"},
|
||||||
{file = "gssapi-1.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:5b4bf84d0a6d7779a4bf11dacfd3db57ae02dd53562e2aeadac4219a68eaee07"},
|
{file = "gssapi-1.9.0-cp38-cp38-win32.whl", hash = "sha256:f2f3a46784d8127cc7ef10d3367dedcbe82899ea296710378ccc9b7cefe96f4c"},
|
||||||
{file = "gssapi-1.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e40efc88ccefefd6142f8c47b8af498731938958b808bad49990442a91f45160"},
|
{file = "gssapi-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:a81f30cde21031e7b1f8194a3eea7285e39e551265e7744edafd06eadc1c95bc"},
|
||||||
{file = "gssapi-1.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee74b9211c977b9181ff4652d886d7712c9a221560752a35393b58e5ea07887a"},
|
{file = "gssapi-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbc93fdadd5aab9bae594538b2128044b8c5cdd1424fe015a465d8a8a587411a"},
|
||||||
{file = "gssapi-1.8.3-cp39-cp39-win32.whl", hash = "sha256:465c6788f2ac6ef7c738394ba8fde1ede6004e5721766f386add63891d8c90af"},
|
{file = "gssapi-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b2a3c0a9beb895942d4b8e31f515e52c17026e55aeaa81ee0df9bbfdac76098"},
|
||||||
{file = "gssapi-1.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:8fb8ee70458f47b51ed881a6881f30b187c987c02af16cc0fff0079255d4d465"},
|
{file = "gssapi-1.9.0-cp39-cp39-win32.whl", hash = "sha256:060b58b455d29ab8aca74770e667dca746264bee660ac5b6a7a17476edc2c0b8"},
|
||||||
{file = "gssapi-1.8.3.tar.gz", hash = "sha256:aa3c8d0b1526f52559552bb2c9d2d6be013d76a8e5db00b39a1db5727e93b0b0"},
|
{file = "gssapi-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:11c9fe066edb0fa0785697eb0cecf2719c7ad1d9f2bf27be57b647a617bcfaa5"},
|
||||||
|
{file = "gssapi-1.9.0.tar.gz", hash = "sha256:f468fac8f3f5fca8f4d1ca19e3cd4d2e10bd91074e7285464b22715d13548afe"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -4292,29 +4293,29 @@ pyasn1 = ">=0.1.3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628"},
|
{file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"},
|
||||||
{file = "ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737"},
|
{file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"},
|
||||||
{file = "ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06"},
|
{file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"},
|
||||||
{file = "ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be"},
|
{file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"},
|
||||||
{file = "ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa"},
|
{file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"},
|
||||||
{file = "ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495"},
|
{file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"},
|
||||||
{file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598"},
|
{file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"},
|
||||||
{file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e"},
|
{file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"},
|
||||||
{file = "ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914"},
|
{file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"},
|
||||||
{file = "ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9"},
|
{file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"},
|
||||||
{file = "ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4"},
|
{file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"},
|
||||||
{file = "ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9"},
|
{file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"},
|
||||||
{file = "ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d"},
|
{file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"},
|
||||||
{file = "ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11"},
|
{file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"},
|
||||||
{file = "ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec"},
|
{file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"},
|
||||||
{file = "ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2"},
|
{file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"},
|
||||||
{file = "ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e"},
|
{file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"},
|
||||||
{file = "ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b"},
|
{file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4750,13 +4751,13 @@ wsproto = ">=0.14"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "twilio"
|
name = "twilio"
|
||||||
version = "9.3.4"
|
version = "9.3.5"
|
||||||
description = "Twilio API client and TwiML generator"
|
description = "Twilio API client and TwiML generator"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7.0"
|
python-versions = ">=3.7.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "twilio-9.3.4-py2.py3-none-any.whl", hash = "sha256:2cae99f0f7aecbd9da02fa59ad8f11b360db4a9281fc3fb3237ad50be21d8a9b"},
|
{file = "twilio-9.3.5-py2.py3-none-any.whl", hash = "sha256:d6a97a77b98cc176a61c960f11894af385bc1c11b93e2e8b79fdfb9601788fb0"},
|
||||||
{file = "twilio-9.3.4.tar.gz", hash = "sha256:38a6ab04752f44313dcf736eae45236a901528d3f53dfc21d3afd33539243c7f"},
|
{file = "twilio-9.3.5.tar.gz", hash = "sha256:608d78a903d403465aac1840c58a6546a090b7e222d2bf539a93c3831072880c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|||||||
47
schema.yml
47
schema.yml
@ -33862,6 +33862,11 @@ paths:
|
|||||||
operationId: stages_identification_list
|
operationId: stages_identification_list
|
||||||
description: IdentificationStage Viewset
|
description: IdentificationStage Viewset
|
||||||
parameters:
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: captcha_stage
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
- in: query
|
- in: query
|
||||||
name: case_insensitive_matching
|
name: case_insensitive_matching
|
||||||
schema:
|
schema:
|
||||||
@ -40204,10 +40209,15 @@ components:
|
|||||||
challenge:
|
challenge:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
|
last_used:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
nullable: true
|
||||||
required:
|
required:
|
||||||
- challenge
|
- challenge
|
||||||
- device_class
|
- device_class
|
||||||
- device_uid
|
- device_uid
|
||||||
|
- last_used
|
||||||
DeviceChallengeRequest:
|
DeviceChallengeRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Single device challenge
|
description: Single device challenge
|
||||||
@ -40221,10 +40231,15 @@ components:
|
|||||||
challenge:
|
challenge:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
|
last_used:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
nullable: true
|
||||||
required:
|
required:
|
||||||
- challenge
|
- challenge
|
||||||
- device_class
|
- device_class
|
||||||
- device_uid
|
- device_uid
|
||||||
|
- last_used
|
||||||
DeviceClassesEnum:
|
DeviceClassesEnum:
|
||||||
enum:
|
enum:
|
||||||
- static
|
- static
|
||||||
@ -42494,6 +42509,8 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
flow_designation:
|
flow_designation:
|
||||||
$ref: '#/components/schemas/FlowDesignationEnum'
|
$ref: '#/components/schemas/FlowDesignationEnum'
|
||||||
|
captcha_stage:
|
||||||
|
$ref: '#/components/schemas/CaptchaChallenge'
|
||||||
enroll_url:
|
enroll_url:
|
||||||
type: string
|
type: string
|
||||||
recovery_url:
|
recovery_url:
|
||||||
@ -42528,6 +42545,9 @@ components:
|
|||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
|
captcha_token:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
required:
|
required:
|
||||||
- uid_field
|
- uid_field
|
||||||
IdentificationStage:
|
IdentificationStage:
|
||||||
@ -42573,6 +42593,12 @@ components:
|
|||||||
nullable: true
|
nullable: true
|
||||||
description: When set, shows a password field, instead of showing the password
|
description: When set, shows a password field, instead of showing the password
|
||||||
field as separate step.
|
field as separate step.
|
||||||
|
captcha_stage:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
description: When set, adds functionality exactly like a Captcha stage,
|
||||||
|
but baked into the Identification stage.
|
||||||
case_insensitive_matching:
|
case_insensitive_matching:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: When enabled, user fields are matched regardless of their casing.
|
description: When enabled, user fields are matched regardless of their casing.
|
||||||
@ -42641,6 +42667,12 @@ components:
|
|||||||
nullable: true
|
nullable: true
|
||||||
description: When set, shows a password field, instead of showing the password
|
description: When set, shows a password field, instead of showing the password
|
||||||
field as separate step.
|
field as separate step.
|
||||||
|
captcha_stage:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
description: When set, adds functionality exactly like a Captcha stage,
|
||||||
|
but baked into the Identification stage.
|
||||||
case_insensitive_matching:
|
case_insensitive_matching:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: When enabled, user fields are matched regardless of their casing.
|
description: When enabled, user fields are matched regardless of their casing.
|
||||||
@ -42943,7 +42975,8 @@ components:
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
spnego_server_name:
|
spnego_server_name:
|
||||||
type: string
|
type: string
|
||||||
description: Force the use of a specific server name for SPNEGO
|
description: Force the use of a specific server name for SPNEGO. Must be
|
||||||
|
in the form HTTP@hostname
|
||||||
spnego_ccache:
|
spnego_ccache:
|
||||||
type: string
|
type: string
|
||||||
description: Credential cache to use for SPNEGO in form type:residual
|
description: Credential cache to use for SPNEGO in form type:residual
|
||||||
@ -43112,7 +43145,8 @@ components:
|
|||||||
be in the form TYPE:residual
|
be in the form TYPE:residual
|
||||||
spnego_server_name:
|
spnego_server_name:
|
||||||
type: string
|
type: string
|
||||||
description: Force the use of a specific server name for SPNEGO
|
description: Force the use of a specific server name for SPNEGO. Must be
|
||||||
|
in the form HTTP@hostname
|
||||||
spnego_keytab:
|
spnego_keytab:
|
||||||
type: string
|
type: string
|
||||||
writeOnly: true
|
writeOnly: true
|
||||||
@ -48231,6 +48265,12 @@ components:
|
|||||||
nullable: true
|
nullable: true
|
||||||
description: When set, shows a password field, instead of showing the password
|
description: When set, shows a password field, instead of showing the password
|
||||||
field as separate step.
|
field as separate step.
|
||||||
|
captcha_stage:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
description: When set, adds functionality exactly like a Captcha stage,
|
||||||
|
but baked into the Identification stage.
|
||||||
case_insensitive_matching:
|
case_insensitive_matching:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: When enabled, user fields are matched regardless of their casing.
|
description: When enabled, user fields are matched regardless of their casing.
|
||||||
@ -48410,7 +48450,8 @@ components:
|
|||||||
be in the form TYPE:residual
|
be in the form TYPE:residual
|
||||||
spnego_server_name:
|
spnego_server_name:
|
||||||
type: string
|
type: string
|
||||||
description: Force the use of a specific server name for SPNEGO
|
description: Force the use of a specific server name for SPNEGO. Must be
|
||||||
|
in the form HTTP@hostname
|
||||||
spnego_keytab:
|
spnego_keytab:
|
||||||
type: string
|
type: string
|
||||||
writeOnly: true
|
writeOnly: true
|
||||||
|
|||||||
8
web/package-lock.json
generated
8
web/package-lock.json
generated
@ -23,7 +23,7 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.5.7",
|
"@formatjs/intl-listformat": "^7.5.7",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
"@goauthentik/api": "^2024.8.3-1729699127",
|
"@goauthentik/api": "^2024.8.3-1729836831",
|
||||||
"@lit-labs/ssr": "^3.2.2",
|
"@lit-labs/ssr": "^3.2.2",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
@ -1775,9 +1775,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@goauthentik/api": {
|
"node_modules/@goauthentik/api": {
|
||||||
"version": "2024.8.3-1729699127",
|
"version": "2024.8.3-1729836831",
|
||||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.8.3-1729699127.tgz",
|
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.8.3-1729836831.tgz",
|
||||||
"integrity": "sha512-luo0SAASR6BTTtLszDgfdwofBejv4F3hCHgPxeSoTSFgE8/A2+zJD8EtWPZaa1udDkwPa9lbIeJSSmbgFke3jA=="
|
"integrity": "sha512-nOgvjYQiK+HhWuiZ635h/aSsq7Mfj5cDrIyBJt+IJRQuJFtnnHx8nscRXKK/8sBl9obH2zMCoZgeqytK8145bg=="
|
||||||
},
|
},
|
||||||
"node_modules/@goauthentik/web": {
|
"node_modules/@goauthentik/web": {
|
||||||
"resolved": "",
|
"resolved": "",
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.5.7",
|
"@formatjs/intl-listformat": "^7.5.7",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
"@goauthentik/api": "^2024.8.3-1729699127",
|
"@goauthentik/api": "^2024.8.3-1729836831",
|
||||||
"@lit-labs/ssr": "^3.2.2",
|
"@lit-labs/ssr": "^3.2.2",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export class ApplicationWizardPageBase
|
|||||||
return AwadStyles;
|
return AwadStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@consume({ context: applicationWizardContext })
|
@consume({ context: applicationWizardContext, subscribe: true })
|
||||||
public wizard!: ApplicationWizardState;
|
public wizard!: ApplicationWizardState;
|
||||||
|
|
||||||
@query("form")
|
@query("form")
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import { createContext } from "@lit/context";
|
import { createContext } from "@lit/context";
|
||||||
|
|
||||||
|
import { LocalTypeCreate } from "./auth-method-choice/ak-application-wizard-authentication-method-choice.choices.js";
|
||||||
import { ApplicationWizardState } from "./types";
|
import { ApplicationWizardState } from "./types";
|
||||||
|
|
||||||
export const applicationWizardContext = createContext<ApplicationWizardState>(
|
export const applicationWizardContext = createContext<ApplicationWizardState>(
|
||||||
Symbol("ak-application-wizard-state-context"),
|
Symbol("ak-application-wizard-state-context"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const applicationWizardProvidersContext = createContext<LocalTypeCreate[]>(
|
||||||
|
Symbol("ak-application-wizard-providers-context"),
|
||||||
|
);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard";
|
import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard";
|
||||||
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
|
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||||
|
|
||||||
@ -5,7 +6,10 @@ import { ContextProvider } from "@lit/context";
|
|||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
|
|
||||||
import { applicationWizardContext } from "./ContextIdentity";
|
import { ProvidersApi, ProxyMode } from "@goauthentik/api";
|
||||||
|
|
||||||
|
import { applicationWizardContext, applicationWizardProvidersContext } from "./ContextIdentity";
|
||||||
|
import { providerTypeRenderers } from "./auth-method-choice/ak-application-wizard-authentication-method-choice.choices.js";
|
||||||
import { newSteps } from "./steps";
|
import { newSteps } from "./steps";
|
||||||
import {
|
import {
|
||||||
ApplicationStep,
|
ApplicationStep,
|
||||||
@ -19,6 +23,7 @@ const freshWizardState = (): ApplicationWizardState => ({
|
|||||||
app: {},
|
app: {},
|
||||||
provider: {},
|
provider: {},
|
||||||
errors: {},
|
errors: {},
|
||||||
|
proxyMode: ProxyMode.Proxy,
|
||||||
});
|
});
|
||||||
|
|
||||||
@customElement("ak-application-wizard")
|
@customElement("ak-application-wizard")
|
||||||
@ -46,6 +51,11 @@ export class ApplicationWizard extends CustomListenerElement(
|
|||||||
initialValue: this.wizardState,
|
initialValue: this.wizardState,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
wizardProviderProvider = new ContextProvider(this, {
|
||||||
|
context: applicationWizardProvidersContext,
|
||||||
|
initialValue: [],
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One of our steps has multiple display variants, one for each type of service provider. We
|
* One of our steps has multiple display variants, one for each type of service provider. We
|
||||||
* want to *preserve* a customer's decisions about different providers; never make someone "go
|
* want to *preserve* a customer's decisions about different providers; never make someone "go
|
||||||
@ -56,6 +66,21 @@ export class ApplicationWizard extends CustomListenerElement(
|
|||||||
*/
|
*/
|
||||||
providerCache: Map<string, OneOfProvider> = new Map();
|
providerCache: Map<string, OneOfProvider> = new Map();
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((providerTypes) => {
|
||||||
|
const wizardReadyProviders = Object.keys(providerTypeRenderers);
|
||||||
|
this.wizardProviderProvider.setValue(
|
||||||
|
providerTypes
|
||||||
|
.filter((providerType) => wizardReadyProviders.includes(providerType.modelName))
|
||||||
|
.map((providerType) => ({
|
||||||
|
...providerType,
|
||||||
|
renderer: providerTypeRenderers[providerType.modelName],
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// And this is where all the special cases go...
|
// And this is where all the special cases go...
|
||||||
handleUpdate(detail: ApplicationWizardStateUpdate) {
|
handleUpdate(detail: ApplicationWizardStateUpdate) {
|
||||||
if (detail.status === "submitted") {
|
if (detail.status === "submitted") {
|
||||||
|
|||||||
@ -1,176 +1,28 @@
|
|||||||
import "@goauthentik/admin/common/ak-license-notice";
|
import "@goauthentik/admin/common/ak-license-notice";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
|
|
||||||
import type { ProviderModelEnum as ProviderModelEnumType, TypeCreate } from "@goauthentik/api";
|
import type { TypeCreate } from "@goauthentik/api";
|
||||||
import { ProviderModelEnum, ProxyMode } from "@goauthentik/api";
|
|
||||||
import type {
|
|
||||||
LDAPProviderRequest,
|
|
||||||
ModelRequest,
|
|
||||||
OAuth2ProviderRequest,
|
|
||||||
ProxyProviderRequest,
|
|
||||||
RACProviderRequest,
|
|
||||||
RadiusProviderRequest,
|
|
||||||
SAMLProviderRequest,
|
|
||||||
SCIMProviderRequest,
|
|
||||||
} from "@goauthentik/api";
|
|
||||||
|
|
||||||
import { OneOfProvider } from "../types";
|
|
||||||
|
|
||||||
type ProviderRenderer = () => TemplateResult;
|
type ProviderRenderer = () => TemplateResult;
|
||||||
|
|
||||||
type ModelConverter = (provider: OneOfProvider) => ModelRequest;
|
|
||||||
|
|
||||||
type ProviderNoteProvider = () => TemplateResult | undefined;
|
|
||||||
type ProviderNote = ProviderNoteProvider | undefined;
|
|
||||||
|
|
||||||
export type LocalTypeCreate = TypeCreate & {
|
export type LocalTypeCreate = TypeCreate & {
|
||||||
formName: string;
|
|
||||||
modelName: ProviderModelEnumType;
|
|
||||||
converter: ModelConverter;
|
|
||||||
note?: ProviderNote;
|
|
||||||
renderer: ProviderRenderer;
|
renderer: ProviderRenderer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const providerModelsList: LocalTypeCreate[] = [
|
export const providerTypeRenderers = {
|
||||||
{
|
oauth2provider: () =>
|
||||||
formName: "oauth2provider",
|
|
||||||
name: msg("OAuth2/OpenID Provider"),
|
|
||||||
description: msg("Modern applications, APIs and Single-page applications."),
|
|
||||||
renderer: () =>
|
|
||||||
html`<ak-application-wizard-authentication-by-oauth></ak-application-wizard-authentication-by-oauth>`,
|
html`<ak-application-wizard-authentication-by-oauth></ak-application-wizard-authentication-by-oauth>`,
|
||||||
modelName: ProviderModelEnum.Oauth2Oauth2provider,
|
ldapprovider: () =>
|
||||||
converter: (provider: OneOfProvider) => ({
|
|
||||||
providerModel: ProviderModelEnum.Oauth2Oauth2provider,
|
|
||||||
...(provider as OAuth2ProviderRequest),
|
|
||||||
}),
|
|
||||||
component: "",
|
|
||||||
iconUrl: "/static/authentik/sources/openidconnect.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
formName: "ldapprovider",
|
|
||||||
name: msg("LDAP Provider"),
|
|
||||||
description: msg(
|
|
||||||
"Provide an LDAP interface for applications and users to authenticate against.",
|
|
||||||
),
|
|
||||||
renderer: () =>
|
|
||||||
html`<ak-application-wizard-authentication-by-ldap></ak-application-wizard-authentication-by-ldap>`,
|
html`<ak-application-wizard-authentication-by-ldap></ak-application-wizard-authentication-by-ldap>`,
|
||||||
modelName: ProviderModelEnum.LdapLdapprovider,
|
proxyprovider: () =>
|
||||||
converter: (provider: OneOfProvider) => ({
|
|
||||||
providerModel: ProviderModelEnum.LdapLdapprovider,
|
|
||||||
...(provider as LDAPProviderRequest),
|
|
||||||
}),
|
|
||||||
component: "",
|
|
||||||
iconUrl: "/static/authentik/sources/ldap.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
formName: "proxyprovider-proxy",
|
|
||||||
name: msg("Transparent Reverse Proxy"),
|
|
||||||
description: msg("For transparent reverse proxies with required authentication"),
|
|
||||||
renderer: () =>
|
|
||||||
html`<ak-application-wizard-authentication-for-reverse-proxy></ak-application-wizard-authentication-for-reverse-proxy>`,
|
html`<ak-application-wizard-authentication-for-reverse-proxy></ak-application-wizard-authentication-for-reverse-proxy>`,
|
||||||
modelName: ProviderModelEnum.ProxyProxyprovider,
|
racprovider: () =>
|
||||||
converter: (provider: OneOfProvider) => ({
|
|
||||||
providerModel: ProviderModelEnum.ProxyProxyprovider,
|
|
||||||
...(provider as ProxyProviderRequest),
|
|
||||||
mode: ProxyMode.Proxy,
|
|
||||||
}),
|
|
||||||
component: "",
|
|
||||||
iconUrl: "/static/authentik/sources/proxy.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
formName: "proxyprovider-forwardsingle",
|
|
||||||
name: msg("Forward Auth (Single Application)"),
|
|
||||||
description: msg("For nginx's auth_request or traefik's forwardAuth"),
|
|
||||||
renderer: () =>
|
|
||||||
html`<ak-application-wizard-authentication-for-single-forward-proxy></ak-application-wizard-authentication-for-single-forward-proxy>`,
|
|
||||||
modelName: ProviderModelEnum.ProxyProxyprovider,
|
|
||||||
converter: (provider: OneOfProvider) => ({
|
|
||||||
providerModel: ProviderModelEnum.ProxyProxyprovider,
|
|
||||||
...(provider as ProxyProviderRequest),
|
|
||||||
mode: ProxyMode.ForwardSingle,
|
|
||||||
}),
|
|
||||||
component: "",
|
|
||||||
iconUrl: "/static/authentik/sources/proxy.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
formName: "proxyprovider-forwarddomain",
|
|
||||||
name: msg("Forward Auth (Domain Level)"),
|
|
||||||
description: msg("For nginx's auth_request or traefik's forwardAuth per root domain"),
|
|
||||||
renderer: () =>
|
|
||||||
html`<ak-application-wizard-authentication-for-forward-proxy-domain></ak-application-wizard-authentication-for-forward-proxy-domain>`,
|
|
||||||
modelName: ProviderModelEnum.ProxyProxyprovider,
|
|
||||||
converter: (provider: OneOfProvider) => ({
|
|
||||||
providerModel: ProviderModelEnum.ProxyProxyprovider,
|
|
||||||
...(provider as ProxyProviderRequest),
|
|
||||||
mode: ProxyMode.ForwardDomain,
|
|
||||||
}),
|
|
||||||
component: "",
|
|
||||||
iconUrl: "/static/authentik/sources/proxy.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
formName: "racprovider",
|
|
||||||
name: msg("Remote Access Provider"),
|
|
||||||
description: msg("Remotely access computers/servers via RDP/SSH/VNC"),
|
|
||||||
renderer: () =>
|
|
||||||
html`<ak-application-wizard-authentication-for-rac></ak-application-wizard-authentication-for-rac>`,
|
html`<ak-application-wizard-authentication-for-rac></ak-application-wizard-authentication-for-rac>`,
|
||||||
modelName: ProviderModelEnum.RacRacprovider,
|
samlprovider: () =>
|
||||||
converter: (provider: OneOfProvider) => ({
|
|
||||||
providerModel: ProviderModelEnum.RacRacprovider,
|
|
||||||
...(provider as RACProviderRequest),
|
|
||||||
}),
|
|
||||||
note: () => html`<ak-license-notice></ak-license-notice>`,
|
|
||||||
requiresEnterprise: true,
|
|
||||||
component: "",
|
|
||||||
iconUrl: "/static/authentik/sources/rac.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
formName: "samlprovider",
|
|
||||||
name: msg("SAML Provider"),
|
|
||||||
description: msg("Configure SAML provider manually"),
|
|
||||||
renderer: () =>
|
|
||||||
html`<ak-application-wizard-authentication-by-saml-configuration></ak-application-wizard-authentication-by-saml-configuration>`,
|
html`<ak-application-wizard-authentication-by-saml-configuration></ak-application-wizard-authentication-by-saml-configuration>`,
|
||||||
modelName: ProviderModelEnum.SamlSamlprovider,
|
radiusprovider: () =>
|
||||||
converter: (provider: OneOfProvider) => ({
|
|
||||||
providerModel: ProviderModelEnum.SamlSamlprovider,
|
|
||||||
...(provider as SAMLProviderRequest),
|
|
||||||
}),
|
|
||||||
component: "",
|
|
||||||
iconUrl: "/static/authentik/sources/saml.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
formName: "radiusprovider",
|
|
||||||
name: msg("Radius Provider"),
|
|
||||||
description: msg("Configure RADIUS provider manually"),
|
|
||||||
renderer: () =>
|
|
||||||
html`<ak-application-wizard-authentication-by-radius></ak-application-wizard-authentication-by-radius>`,
|
html`<ak-application-wizard-authentication-by-radius></ak-application-wizard-authentication-by-radius>`,
|
||||||
modelName: ProviderModelEnum.RadiusRadiusprovider,
|
scimprovider: () =>
|
||||||
converter: (provider: OneOfProvider) => ({
|
|
||||||
providerModel: ProviderModelEnum.RadiusRadiusprovider,
|
|
||||||
...(provider as RadiusProviderRequest),
|
|
||||||
}),
|
|
||||||
component: "",
|
|
||||||
iconUrl: "/static/authentik/sources/radius.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
formName: "scimprovider",
|
|
||||||
name: msg("SCIM Provider"),
|
|
||||||
description: msg("Configure SCIM provider manually"),
|
|
||||||
renderer: () =>
|
|
||||||
html`<ak-application-wizard-authentication-by-scim></ak-application-wizard-authentication-by-scim>`,
|
html`<ak-application-wizard-authentication-by-scim></ak-application-wizard-authentication-by-scim>`,
|
||||||
modelName: ProviderModelEnum.ScimScimprovider,
|
};
|
||||||
converter: (provider: OneOfProvider) => ({
|
|
||||||
providerModel: ProviderModelEnum.ScimScimprovider,
|
|
||||||
...(provider as SCIMProviderRequest),
|
|
||||||
}),
|
|
||||||
component: "",
|
|
||||||
iconUrl: "/static/authentik/sources/scim.png",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const providerRendererList = new Map<string, ProviderRenderer>(
|
|
||||||
providerModelsList.map((tc) => [tc.formName, tc.renderer]),
|
|
||||||
);
|
|
||||||
|
|
||||||
export default providerModelsList;
|
|
||||||
|
|||||||
@ -7,34 +7,29 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
|
|||||||
import "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
import "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||||
import { TypeCreateWizardPageLayouts } from "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
import { TypeCreateWizardPageLayouts } from "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||||
|
|
||||||
|
import { consume } from "@lit/context";
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
|
|
||||||
import BasePanel from "../BasePanel";
|
import BasePanel from "../BasePanel";
|
||||||
|
import { applicationWizardProvidersContext } from "../ContextIdentity";
|
||||||
import type { LocalTypeCreate } from "./ak-application-wizard-authentication-method-choice.choices";
|
import type { LocalTypeCreate } from "./ak-application-wizard-authentication-method-choice.choices";
|
||||||
import providerModelsList from "./ak-application-wizard-authentication-method-choice.choices";
|
|
||||||
|
|
||||||
@customElement("ak-application-wizard-authentication-method-choice")
|
@customElement("ak-application-wizard-authentication-method-choice")
|
||||||
export class ApplicationWizardAuthenticationMethodChoice extends WithLicenseSummary(BasePanel) {
|
export class ApplicationWizardAuthenticationMethodChoice extends WithLicenseSummary(BasePanel) {
|
||||||
|
@consume({ context: applicationWizardProvidersContext })
|
||||||
|
public providerModelsList: LocalTypeCreate[];
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const selectedTypes = providerModelsList.filter(
|
const selectedTypes = this.providerModelsList.filter(
|
||||||
(t) => t.formName === this.wizard.providerModel,
|
(t) => t.modelName === this.wizard.providerModel,
|
||||||
);
|
);
|
||||||
|
|
||||||
// As a hack, the Application wizard has separate provider paths for our three types of
|
return this.providerModelsList.length > 0
|
||||||
// proxy providers. This patch swaps the form we want to be directed to on page 3 from the
|
|
||||||
// modelName to the formName, so we get the right one. This information isn't modified
|
|
||||||
// or forwarded, so the proxy-plus-subtype is correctly mapped on submission.
|
|
||||||
const typesForWizard = providerModelsList.map((provider) => ({
|
|
||||||
...provider,
|
|
||||||
modelName: provider.formName,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return providerModelsList.length > 0
|
|
||||||
? html`<form class="pf-c-form pf-m-horizontal">
|
? html`<form class="pf-c-form pf-m-horizontal">
|
||||||
<ak-wizard-page-type-create
|
<ak-wizard-page-type-create
|
||||||
.types=${typesForWizard}
|
.types=${this.providerModelsList}
|
||||||
name="selectProviderType"
|
name="selectProviderType"
|
||||||
layout=${TypeCreateWizardPageLayouts.grid}
|
layout=${TypeCreateWizardPageLayouts.grid}
|
||||||
.selectedType=${selectedTypes.length > 0 ? selectedTypes[0] : undefined}
|
.selectedType=${selectedTypes.length > 0 ? selectedTypes[0] : undefined}
|
||||||
@ -42,7 +37,7 @@ export class ApplicationWizardAuthenticationMethodChoice extends WithLicenseSumm
|
|||||||
this.dispatchWizardUpdate({
|
this.dispatchWizardUpdate({
|
||||||
update: {
|
update: {
|
||||||
...this.wizard,
|
...this.wizard,
|
||||||
providerModel: ev.detail.formName,
|
providerModel: ev.detail.modelName,
|
||||||
errors: {},
|
errors: {},
|
||||||
},
|
},
|
||||||
status: this.valid ? "valid" : "invalid",
|
status: this.valid ? "valid" : "invalid",
|
||||||
|
|||||||
@ -21,14 +21,14 @@ import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
|
|||||||
import {
|
import {
|
||||||
type ApplicationRequest,
|
type ApplicationRequest,
|
||||||
CoreApi,
|
CoreApi,
|
||||||
type ModelRequest,
|
ProviderModelEnum,
|
||||||
|
ProxyMode,
|
||||||
type TransactionApplicationRequest,
|
type TransactionApplicationRequest,
|
||||||
type TransactionApplicationResponse,
|
type TransactionApplicationResponse,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
import BasePanel from "../BasePanel";
|
import BasePanel from "../BasePanel";
|
||||||
import providerModelsList from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices";
|
|
||||||
|
|
||||||
function cleanApplication(app: Partial<ApplicationRequest>): ApplicationRequest {
|
function cleanApplication(app: Partial<ApplicationRequest>): ApplicationRequest {
|
||||||
return {
|
return {
|
||||||
@ -38,14 +38,19 @@ function cleanApplication(app: Partial<ApplicationRequest>): ApplicationRequest
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderModelType = Exclude<ModelRequest["providerModel"], "11184809">;
|
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
state: "idle" | "running" | "error" | "success";
|
state: "idle" | "running" | "error" | "success";
|
||||||
label: string | TemplateResult;
|
label: string | TemplateResult;
|
||||||
icon: string[];
|
icon: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const providerMap: Map<string, string> = Object.values(ProviderModelEnum)
|
||||||
|
.filter((value) => /^authentik_providers_/.test(value) && /provider$/.test(value))
|
||||||
|
.reduce((acc: Map<string, string>, value) => {
|
||||||
|
acc.set(value.split(".")[1], value);
|
||||||
|
return acc;
|
||||||
|
}, new Map());
|
||||||
|
|
||||||
const idleState: State = {
|
const idleState: State = {
|
||||||
state: "idle",
|
state: "idle",
|
||||||
label: "",
|
label: "",
|
||||||
@ -98,19 +103,25 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
|||||||
if (this.commitState === idleState) {
|
if (this.commitState === idleState) {
|
||||||
this.response = undefined;
|
this.response = undefined;
|
||||||
this.commitState = runningState;
|
this.commitState = runningState;
|
||||||
const providerModel = providerModelsList.find(
|
|
||||||
({ formName }) => formName === this.wizard.providerModel,
|
// Stringly-based API. Not the best, but it works. Just be aware that it is
|
||||||
);
|
// stringly-based.
|
||||||
if (!providerModel) {
|
const providerModel = providerMap.get(this.wizard.providerModel);
|
||||||
throw new Error(
|
const provider = this.wizard.provider;
|
||||||
`Could not determine provider model from user request: ${JSON.stringify(this.wizard, null, 2)}`,
|
provider.providerModel = providerModel;
|
||||||
);
|
|
||||||
|
// Special case for providers.
|
||||||
|
if (this.wizard.providerModel === "proxyprovider") {
|
||||||
|
provider.mode = this.wizard.proxyMode;
|
||||||
|
if (provider.model !== ProxyMode.ForwardDomain) {
|
||||||
|
provider.cookieDomain = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const request: TransactionApplicationRequest = {
|
const request: TransactionApplicationRequest = {
|
||||||
providerModel: providerModel.modelName as ProviderModelType,
|
|
||||||
app: cleanApplication(this.wizard.app),
|
app: cleanApplication(this.wizard.app),
|
||||||
provider: providerModel.converter(this.wizard.provider),
|
providerModel,
|
||||||
|
provider,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.send(request);
|
this.send(request);
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
|
import { consume } from "@lit/context";
|
||||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||||
|
|
||||||
import BasePanel from "../BasePanel";
|
import BasePanel from "../BasePanel";
|
||||||
import { providerRendererList } from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices";
|
import { applicationWizardProvidersContext } from "../ContextIdentity";
|
||||||
|
import type { LocalTypeCreate } from "./ak-application-wizard-authentication-method-choice.choices";
|
||||||
import "./ldap/ak-application-wizard-authentication-by-ldap";
|
import "./ldap/ak-application-wizard-authentication-by-ldap";
|
||||||
import "./oauth/ak-application-wizard-authentication-by-oauth";
|
import "./oauth/ak-application-wizard-authentication-by-oauth";
|
||||||
import "./proxy/ak-application-wizard-authentication-for-forward-domain-proxy";
|
|
||||||
import "./proxy/ak-application-wizard-authentication-for-reverse-proxy";
|
import "./proxy/ak-application-wizard-authentication-for-reverse-proxy";
|
||||||
import "./proxy/ak-application-wizard-authentication-for-single-forward-proxy";
|
|
||||||
import "./rac/ak-application-wizard-authentication-for-rac";
|
import "./rac/ak-application-wizard-authentication-for-rac";
|
||||||
import "./radius/ak-application-wizard-authentication-by-radius";
|
import "./radius/ak-application-wizard-authentication-by-radius";
|
||||||
import "./saml/ak-application-wizard-authentication-by-saml-configuration";
|
import "./saml/ak-application-wizard-authentication-by-saml-configuration";
|
||||||
@ -14,14 +14,19 @@ import "./scim/ak-application-wizard-authentication-by-scim";
|
|||||||
|
|
||||||
@customElement("ak-application-wizard-authentication-method")
|
@customElement("ak-application-wizard-authentication-method")
|
||||||
export class ApplicationWizardApplicationDetails extends BasePanel {
|
export class ApplicationWizardApplicationDetails extends BasePanel {
|
||||||
|
@consume({ context: applicationWizardProvidersContext })
|
||||||
|
public providerModelsList: LocalTypeCreate[];
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const handler = providerRendererList.get(this.wizard.providerModel);
|
const handler: LocalTypeCreate | undefined = this.providerModelsList.find(
|
||||||
|
({ modelName }) => modelName === this.wizard.providerModel,
|
||||||
|
);
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Unrecognized authentication method in ak-application-wizard-authentication-method",
|
"Unrecognized authentication method in ak-application-wizard-authentication-method",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return handler();
|
return handler.renderer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,268 +0,0 @@
|
|||||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
|
||||||
import {
|
|
||||||
makeSourceSelector,
|
|
||||||
oauth2SourcesProvider,
|
|
||||||
} from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js";
|
|
||||||
import {
|
|
||||||
makeProxyPropertyMappingsSelector,
|
|
||||||
proxyPropertyMappingsProvider,
|
|
||||||
} from "@goauthentik/admin/providers/proxy/ProxyProviderPropertyMappings.js";
|
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
|
||||||
import { first } from "@goauthentik/common/utils";
|
|
||||||
import "@goauthentik/components/ak-switch-input";
|
|
||||||
import "@goauthentik/components/ak-text-input";
|
|
||||||
import "@goauthentik/components/ak-textarea-input";
|
|
||||||
import "@goauthentik/components/ak-toggle-group";
|
|
||||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
|
||||||
import { state } from "@lit/reactive-element/decorators.js";
|
|
||||||
import { TemplateResult, html, nothing } from "lit";
|
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
|
||||||
|
|
||||||
import {
|
|
||||||
FlowsInstancesListDesignationEnum,
|
|
||||||
PaginatedOAuthSourceList,
|
|
||||||
PaginatedScopeMappingList,
|
|
||||||
ProxyMode,
|
|
||||||
ProxyProvider,
|
|
||||||
SourcesApi,
|
|
||||||
} from "@goauthentik/api";
|
|
||||||
|
|
||||||
import BaseProviderPanel from "../BaseProviderPanel";
|
|
||||||
|
|
||||||
type MaybeTemplateResult = TemplateResult | typeof nothing;
|
|
||||||
|
|
||||||
export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
new SourcesApi(DEFAULT_CONFIG)
|
|
||||||
.sourcesOauthList({
|
|
||||||
ordering: "name",
|
|
||||||
hasJwks: true,
|
|
||||||
})
|
|
||||||
.then((oauthSources: PaginatedOAuthSourceList) => {
|
|
||||||
this.oauthSources = oauthSources;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
propertyMappings?: PaginatedScopeMappingList;
|
|
||||||
oauthSources?: PaginatedOAuthSourceList;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
showHttpBasic = true;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
mode: ProxyMode = ProxyMode.Proxy;
|
|
||||||
|
|
||||||
get instance(): ProxyProvider | undefined {
|
|
||||||
return this.wizard.provider as ProxyProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderModeDescription(): MaybeTemplateResult {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderProxyMode(): TemplateResult {
|
|
||||||
throw new Error("Must be implemented in a child class.");
|
|
||||||
}
|
|
||||||
|
|
||||||
renderHttpBasic() {
|
|
||||||
return html`<ak-text-input
|
|
||||||
name="basicAuthUserAttribute"
|
|
||||||
label=${msg("HTTP-Basic Username Key")}
|
|
||||||
value="${ifDefined(this.instance?.basicAuthUserAttribute)}"
|
|
||||||
help=${msg(
|
|
||||||
"User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used.",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ak-text-input>
|
|
||||||
|
|
||||||
<ak-text-input
|
|
||||||
name="basicAuthPasswordAttribute"
|
|
||||||
label=${msg("HTTP-Basic Password Key")}
|
|
||||||
value="${ifDefined(this.instance?.basicAuthPasswordAttribute)}"
|
|
||||||
help=${msg(
|
|
||||||
"User/Group Attribute used for the password part of the HTTP-Basic Header.",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ak-text-input>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const errors = this.wizard.errors.provider;
|
|
||||||
|
|
||||||
return html` <ak-wizard-title>${msg("Configure Proxy Provider")}</ak-wizard-title>
|
|
||||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
|
||||||
${this.renderModeDescription()}
|
|
||||||
<ak-text-input
|
|
||||||
name="name"
|
|
||||||
value=${ifDefined(this.instance?.name)}
|
|
||||||
required
|
|
||||||
.errorMessages=${errors?.name ?? []}
|
|
||||||
label=${msg("Name")}
|
|
||||||
></ak-text-input>
|
|
||||||
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Authorization flow")}
|
|
||||||
required
|
|
||||||
name="authorizationFlow"
|
|
||||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
|
||||||
>
|
|
||||||
<ak-flow-search
|
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
|
||||||
.currentFlow=${this.instance?.authorizationFlow}
|
|
||||||
required
|
|
||||||
></ak-flow-search>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Flow used when authorizing this provider.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
|
|
||||||
${this.renderProxyMode()}
|
|
||||||
|
|
||||||
<ak-text-input
|
|
||||||
name="accessTokenValidity"
|
|
||||||
value=${first(this.instance?.accessTokenValidity, "hours=24")}
|
|
||||||
label=${msg("Token validity")}
|
|
||||||
help=${msg("Configure how long tokens are valid for.")}
|
|
||||||
.errorMessages=${errors?.accessTokenValidity ?? []}
|
|
||||||
></ak-text-input>
|
|
||||||
|
|
||||||
<ak-form-group>
|
|
||||||
<span slot="header">${msg("Advanced protocol settings")}</span>
|
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Certificate")}
|
|
||||||
name="certificate"
|
|
||||||
.errorMessages=${errors?.certificate ?? []}
|
|
||||||
>
|
|
||||||
<ak-crypto-certificate-search
|
|
||||||
certificate=${ifDefined(this.instance?.certificate ?? undefined)}
|
|
||||||
></ak-crypto-certificate-search>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Additional scopes")}
|
|
||||||
name="propertyMappings"
|
|
||||||
>
|
|
||||||
<ak-dual-select-dynamic-selected
|
|
||||||
.provider=${proxyPropertyMappingsProvider}
|
|
||||||
.selector=${makeProxyPropertyMappingsSelector(
|
|
||||||
this.instance?.propertyMappings,
|
|
||||||
)}
|
|
||||||
available-label="${msg("Available Scopes")}"
|
|
||||||
selected-label="${msg("Selected Scopes")}"
|
|
||||||
></ak-dual-select-dynamic-selected>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Additional scope mappings, which are passed to the proxy.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
|
|
||||||
<ak-textarea-input
|
|
||||||
name="skipPathRegex"
|
|
||||||
label=${
|
|
||||||
this.mode === ProxyMode.ForwardDomain
|
|
||||||
? msg("Unauthenticated URLs")
|
|
||||||
: msg("Unauthenticated Paths")
|
|
||||||
}
|
|
||||||
value=${ifDefined(this.instance?.skipPathRegex)}
|
|
||||||
.errorMessages=${errors?.skipPathRegex ?? []}
|
|
||||||
.bighelp=${html` <p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.",
|
|
||||||
)}
|
|
||||||
</p>`}
|
|
||||||
>
|
|
||||||
</ak-textarea-input>
|
|
||||||
</div>
|
|
||||||
</ak-form-group>
|
|
||||||
<ak-form-group>
|
|
||||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
name="authenticationFlow"
|
|
||||||
label=${msg("Authentication flow")}
|
|
||||||
>
|
|
||||||
<ak-flow-search
|
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
|
||||||
.currentFlow=${this.instance?.authenticationFlow}
|
|
||||||
></ak-flow-search>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"Flow used when a user access this provider and is not authenticated.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Invalidation flow")}
|
|
||||||
name="invalidationFlow"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<ak-flow-search
|
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
|
||||||
.currentFlow=${this.instance?.invalidationFlow}
|
|
||||||
defaultFlowSlug="default-provider-invalidation-flow"
|
|
||||||
required
|
|
||||||
></ak-flow-search>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Flow used when logging out of this provider.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
</div>
|
|
||||||
</ak-form-group>
|
|
||||||
<ak-form-group>
|
|
||||||
<span slot="header">${msg("Authentication settings")}</span>
|
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-switch-input
|
|
||||||
name="interceptHeaderAuth"
|
|
||||||
?checked=${first(this.instance?.interceptHeaderAuth, true)}
|
|
||||||
label=${msg("Intercept header authentication")}
|
|
||||||
help=${msg(
|
|
||||||
"When enabled, authentik will intercept the Authorization header to authenticate the request.",
|
|
||||||
)}
|
|
||||||
></ak-switch-input>
|
|
||||||
|
|
||||||
<ak-switch-input
|
|
||||||
name="basicAuthEnabled"
|
|
||||||
?checked=${first(this.instance?.basicAuthEnabled, false)}
|
|
||||||
@change=${(ev: Event) => {
|
|
||||||
const el = ev.target as HTMLInputElement;
|
|
||||||
this.showHttpBasic = el.checked;
|
|
||||||
}}
|
|
||||||
label=${msg("Send HTTP-Basic Authentication")}
|
|
||||||
help=${msg(
|
|
||||||
"Send a custom HTTP-Basic Authentication header based on values from authentik.",
|
|
||||||
)}
|
|
||||||
></ak-switch-input>
|
|
||||||
|
|
||||||
${this.showHttpBasic ? this.renderHttpBasic() : html``}
|
|
||||||
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Trusted OIDC Sources")}
|
|
||||||
name="jwksSources"
|
|
||||||
.errorMessages=${errors?.jwksSources ?? []}
|
|
||||||
>
|
|
||||||
<ak-dual-select-dynamic-selected
|
|
||||||
.provider=${oauth2SourcesProvider}
|
|
||||||
.selector=${makeSourceSelector(this.instance?.jwksSources)}
|
|
||||||
available-label=${msg("Available Sources")}
|
|
||||||
selected-label=${msg("Selected Sources")}
|
|
||||||
></ak-dual-select-dynamic-selected>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
</div>
|
|
||||||
</ak-form-group>
|
|
||||||
</form>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AkTypeProxyApplicationWizardPage;
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import "@goauthentik/components/ak-text-input";
|
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
|
||||||
import { customElement } from "@lit/reactive-element/decorators.js";
|
|
||||||
import { html } from "lit";
|
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
|
||||||
|
|
||||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
|
||||||
|
|
||||||
import { ProxyProvider } from "@goauthentik/api";
|
|
||||||
|
|
||||||
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
|
|
||||||
|
|
||||||
@customElement("ak-application-wizard-authentication-for-forward-proxy-domain")
|
|
||||||
export class AkForwardDomainProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
|
|
||||||
static get styles() {
|
|
||||||
return super.styles.concat(PFList);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderModeDescription() {
|
|
||||||
return html`<p>
|
|
||||||
${msg(
|
|
||||||
"Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div>
|
|
||||||
${msg("An example setup can look like this:")}
|
|
||||||
<ul class="pf-c-list">
|
|
||||||
<li>${msg("authentik running on auth.example.com")}</li>
|
|
||||||
<li>${msg("app1 running on app1.example.com")}</li>
|
|
||||||
</ul>
|
|
||||||
${msg(
|
|
||||||
"In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com.",
|
|
||||||
)}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderProxyMode() {
|
|
||||||
const provider = this.wizard.provider as ProxyProvider | undefined;
|
|
||||||
const errors = this.wizard.errors.provider;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ak-text-input
|
|
||||||
name="externalHost"
|
|
||||||
label=${msg("External host")}
|
|
||||||
value=${ifDefined(provider?.externalHost)}
|
|
||||||
.errorMessages=${errors?.externalHost ?? []}
|
|
||||||
required
|
|
||||||
help=${msg(
|
|
||||||
"The external URL you'll authenticate at. The authentik core server should be reachable under this URL.",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ak-text-input>
|
|
||||||
<ak-text-input
|
|
||||||
name="cookieDomain"
|
|
||||||
label=${msg("Cookie domain")}
|
|
||||||
value="${ifDefined(provider?.cookieDomain)}"
|
|
||||||
.errorMessages=${errors?.cookieDomain ?? []}
|
|
||||||
required
|
|
||||||
help=${msg(
|
|
||||||
"Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.",
|
|
||||||
)}
|
|
||||||
></ak-text-input>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AkForwardDomainProxyApplicationWizardPage;
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ak-application-wizard-authentication-for-forward-proxy-domain": AkForwardDomainProxyApplicationWizardPage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,55 +1,46 @@
|
|||||||
import { first } from "@goauthentik/common/utils";
|
import {
|
||||||
import "@goauthentik/components/ak-switch-input";
|
ProxyModeValue,
|
||||||
import "@goauthentik/components/ak-text-input";
|
renderForm,
|
||||||
|
} from "@goauthentik/admin/providers/proxy/ProxyProviderFormForm.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { customElement } from "@lit/reactive-element/decorators.js";
|
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
|
||||||
|
|
||||||
import { ProxyProvider } from "@goauthentik/api";
|
import BaseProviderPanel from "../BaseProviderPanel.js";
|
||||||
|
|
||||||
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
|
|
||||||
|
|
||||||
@customElement("ak-application-wizard-authentication-for-reverse-proxy")
|
@customElement("ak-application-wizard-authentication-for-reverse-proxy")
|
||||||
export class AkReverseProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
|
export class AkReverseProxyApplicationWizardPage extends BaseProviderPanel {
|
||||||
renderModeDescription() {
|
@state()
|
||||||
return html`<p class="pf-u-mb-xl">
|
showHttpBasic = true;
|
||||||
${msg(
|
|
||||||
"This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.",
|
|
||||||
)}
|
|
||||||
</p>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderProxyMode() {
|
render() {
|
||||||
const provider = this.wizard.provider as ProxyProvider | undefined;
|
const onSetMode: SetMode = (ev: CustomEvent<ProxyModeValue>) => {
|
||||||
const errors = this.wizard.errors.provider;
|
this.dispatchWizardUpdate({
|
||||||
|
update: {
|
||||||
|
...this.wizard,
|
||||||
|
proxyMode: ev.detail.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// We deliberately chose not to make the forms "controlled," but we do need this form to
|
||||||
|
// respond immediately to a state change in the wizard.
|
||||||
|
window.setTimeout(() => this.requestUpdate(), 0);
|
||||||
|
};
|
||||||
|
|
||||||
return html` <ak-text-input
|
const onSetShowHttpBasic: SetShowHttpBasic = (ev: Event) => {
|
||||||
name="externalHost"
|
const el = ev.target as HTMLInputElement;
|
||||||
value=${ifDefined(provider?.externalHost)}
|
this.showHttpBasic = el.checked;
|
||||||
required
|
};
|
||||||
label=${msg("External host")}
|
|
||||||
.errorMessages=${errors?.externalHost ?? []}
|
return html` <ak-wizard-title>${msg("Configure Proxy Provider")}</ak-wizard-title>
|
||||||
help=${msg(
|
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||||
"The external URL you'll access the application at. Include any non-standard port.",
|
${renderForm(this.wizard.provider ?? {}, this.wizard.errors.provider ?? [], {
|
||||||
)}
|
mode: this.wizard.proxyMode,
|
||||||
></ak-text-input>
|
onSetMode,
|
||||||
<ak-text-input
|
showHttpBasic: this.showHttpBasic,
|
||||||
name="internalHost"
|
onSetShowHttpBasic,
|
||||||
value=${ifDefined(provider?.internalHost)}
|
})}
|
||||||
.errorMessages=${errors?.internalHost ?? []}
|
</form>`;
|
||||||
required
|
|
||||||
label=${msg("Internal host")}
|
|
||||||
help=${msg("Upstream host that the requests are forwarded to.")}
|
|
||||||
></ak-text-input>
|
|
||||||
<ak-switch-input
|
|
||||||
name="internalHostSslValidation"
|
|
||||||
?checked=${first(provider?.internalHostSslValidation, true)}
|
|
||||||
label=${msg("Internal host SSL Validation")}
|
|
||||||
help=${msg("Validate SSL Certificates of upstream servers.")}
|
|
||||||
>
|
|
||||||
</ak-switch-input>`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
import "@goauthentik/components/ak-text-input";
|
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
|
||||||
import { customElement } from "@lit/reactive-element/decorators.js";
|
|
||||||
import { html } from "lit";
|
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
|
||||||
|
|
||||||
import { ProxyProvider } from "@goauthentik/api";
|
|
||||||
|
|
||||||
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
|
|
||||||
|
|
||||||
@customElement("ak-application-wizard-authentication-for-single-forward-proxy")
|
|
||||||
export class AkForwardSingleProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
|
|
||||||
renderModeDescription() {
|
|
||||||
return html`<p class="pf-u-mb-xl">
|
|
||||||
${msg(
|
|
||||||
html`Use this provider with nginx's <code>auth_request</code> or traefik's
|
|
||||||
<code>forwardAuth</code>. Each application/domain needs its own provider.
|
|
||||||
Additionally, on each domain, <code>/outpost.goauthentik.io</code> must be
|
|
||||||
routed to the outpost (when using a managed outpost, this is done for you).`,
|
|
||||||
)}
|
|
||||||
</p>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderProxyMode() {
|
|
||||||
const provider = this.wizard.provider as ProxyProvider | undefined;
|
|
||||||
const errors = this.wizard.errors.provider;
|
|
||||||
|
|
||||||
return html`<ak-text-input
|
|
||||||
name="externalHost"
|
|
||||||
value=${ifDefined(provider?.externalHost)}
|
|
||||||
required
|
|
||||||
label=${msg("External host")}
|
|
||||||
.errorMessages=${errors?.externalHost ?? []}
|
|
||||||
help=${msg(
|
|
||||||
"The external URL you'll access the application at. Include any non-standard port.",
|
|
||||||
)}
|
|
||||||
></ak-text-input>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AkForwardSingleProxyApplicationWizardPage;
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ak-application-wizard-authentication-for-single-forward-proxy": AkForwardSingleProxyApplicationWizardPage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { renderForm } from "@goauthentik/admin/providers/scim/SCIMProviderFormForm.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
type LDAPProviderRequest,
|
type LDAPProviderRequest,
|
||||||
type OAuth2ProviderRequest,
|
type OAuth2ProviderRequest,
|
||||||
type ProvidersSamlImportMetadataCreateRequest,
|
type ProvidersSamlImportMetadataCreateRequest,
|
||||||
|
ProxyMode,
|
||||||
type ProxyProviderRequest,
|
type ProxyProviderRequest,
|
||||||
type RACProviderRequest,
|
type RACProviderRequest,
|
||||||
type RadiusProviderRequest,
|
type RadiusProviderRequest,
|
||||||
@ -27,6 +28,7 @@ export interface ApplicationWizardState {
|
|||||||
providerModel: string;
|
providerModel: string;
|
||||||
app: Partial<ApplicationRequest>;
|
app: Partial<ApplicationRequest>;
|
||||||
provider: OneOfProvider;
|
provider: OneOfProvider;
|
||||||
|
proxyMode?: ProxyMode;
|
||||||
errors: ValidationError;
|
errors: ValidationError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,8 +43,12 @@ export class ProviderWizard extends AKElement {
|
|||||||
@query("ak-wizard")
|
@query("ak-wizard")
|
||||||
wizard?: Wizard;
|
wizard?: Wizard;
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
connectedCallback() {
|
||||||
this.providerTypes = await new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList();
|
super.connectedCallback();
|
||||||
|
new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((providerTypes) => {
|
||||||
|
console.log(providerTypes);
|
||||||
|
this.providerTypes = providerTypes;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
|
|||||||
@ -1,39 +1,18 @@
|
|||||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||||
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
|
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
|
||||||
import {
|
|
||||||
makeSourceSelector,
|
|
||||||
oauth2SourcesProvider,
|
|
||||||
} from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js";
|
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
|
||||||
import "@goauthentik/components/ak-toggle-group";
|
|
||||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
|
||||||
import "@goauthentik/elements/forms/SearchSelect";
|
|
||||||
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { CSSResult } from "lit";
|
||||||
import { CSSResult, TemplateResult, html } from "lit";
|
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
|
||||||
|
|
||||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||||
import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
|
import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
|
||||||
|
|
||||||
import {
|
import { ProvidersApi, ProxyMode, ProxyProvider } from "@goauthentik/api";
|
||||||
FlowsInstancesListDesignationEnum,
|
|
||||||
ProvidersApi,
|
|
||||||
ProxyMode,
|
|
||||||
ProxyProvider,
|
|
||||||
} from "@goauthentik/api";
|
|
||||||
|
|
||||||
import {
|
import { SetMode, SetShowHttpBasic, renderForm } from "./ProxyProviderFormForm.js";
|
||||||
makeProxyPropertyMappingsSelector,
|
|
||||||
proxyPropertyMappingsProvider,
|
|
||||||
} from "./ProxyProviderPropertyMappings.js";
|
|
||||||
|
|
||||||
@customElement("ak-provider-proxy-form")
|
@customElement("ak-provider-proxy-form")
|
||||||
export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> {
|
export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> {
|
||||||
@ -73,376 +52,22 @@ export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHttpBasic(): TemplateResult {
|
renderForm() {
|
||||||
return html`<ak-text-input
|
const onSetMode: SetMode = (ev) => {
|
||||||
name="basicAuthUserAttribute"
|
|
||||||
label=${msg("HTTP-Basic Username Key")}
|
|
||||||
value="${ifDefined(this.instance?.basicAuthUserAttribute)}"
|
|
||||||
help=${msg(
|
|
||||||
"User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used.",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ak-text-input>
|
|
||||||
|
|
||||||
<ak-text-input
|
|
||||||
name="basicAuthPasswordAttribute"
|
|
||||||
label=${msg("HTTP-Basic Password Key")}
|
|
||||||
value="${ifDefined(this.instance?.basicAuthPasswordAttribute)}"
|
|
||||||
help=${msg(
|
|
||||||
"User/Group Attribute used for the password part of the HTTP-Basic Header.",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ak-text-input>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderModeSelector(): TemplateResult {
|
|
||||||
const setMode = (ev: CustomEvent<{ value: ProxyMode }>) => {
|
|
||||||
this.mode = ev.detail.value;
|
this.mode = ev.detail.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// prettier-ignore
|
const onSetShowHttpBasic: SetShowHttpBasic = (ev: Event) => {
|
||||||
return html`
|
|
||||||
<ak-toggle-group value=${this.mode} @ak-toggle=${setMode} data-ouid-component-name="proxy-type-toggle">
|
|
||||||
<option value=${ProxyMode.Proxy}>${msg("Proxy")}</option>
|
|
||||||
<option value=${ProxyMode.ForwardSingle}>${msg("Forward auth (single application)")}</option>
|
|
||||||
<option value=${ProxyMode.ForwardDomain}>${msg("Forward auth (domain level)")}</option>
|
|
||||||
</ak-toggle-group>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSettings(): TemplateResult {
|
|
||||||
switch (this.mode) {
|
|
||||||
case ProxyMode.Proxy:
|
|
||||||
return html`<p class="pf-u-mb-xl">
|
|
||||||
${msg(
|
|
||||||
"This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("External host")}
|
|
||||||
?required=${true}
|
|
||||||
name="externalHost"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value="${ifDefined(this.instance?.externalHost)}"
|
|
||||||
class="pf-c-form-control"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"The external URL you'll access the application at. Include any non-standard port.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Internal host")}
|
|
||||||
?required=${true}
|
|
||||||
name="internalHost"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value="${ifDefined(this.instance?.internalHost)}"
|
|
||||||
class="pf-c-form-control"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Upstream host that the requests are forwarded to.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal name="internalHostSslValidation">
|
|
||||||
<label class="pf-c-switch">
|
|
||||||
<input
|
|
||||||
class="pf-c-switch__input"
|
|
||||||
type="checkbox"
|
|
||||||
?checked=${first(this.instance?.internalHostSslValidation, true)}
|
|
||||||
/>
|
|
||||||
<span class="pf-c-switch__toggle">
|
|
||||||
<span class="pf-c-switch__toggle-icon">
|
|
||||||
<i class="fas fa-check" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="pf-c-switch__label"
|
|
||||||
>${msg("Internal host SSL Validation")}</span
|
|
||||||
>
|
|
||||||
</label>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Validate SSL Certificates of upstream servers.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>`;
|
|
||||||
case ProxyMode.ForwardSingle:
|
|
||||||
return html`<p class="pf-u-mb-xl">
|
|
||||||
${msg(
|
|
||||||
"Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you).",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("External host")}
|
|
||||||
?required=${true}
|
|
||||||
name="externalHost"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value="${ifDefined(this.instance?.externalHost)}"
|
|
||||||
class="pf-c-form-control"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"The external URL you'll access the application at. Include any non-standard port.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>`;
|
|
||||||
case ProxyMode.ForwardDomain:
|
|
||||||
return html`<p class="pf-u-mb-xl">
|
|
||||||
${msg(
|
|
||||||
"Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div class="pf-u-mb-xl">
|
|
||||||
${msg("An example setup can look like this:")}
|
|
||||||
<ul class="pf-c-list">
|
|
||||||
<li>${msg("authentik running on auth.example.com")}</li>
|
|
||||||
<li>${msg("app1 running on app1.example.com")}</li>
|
|
||||||
</ul>
|
|
||||||
${msg(
|
|
||||||
"In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com.",
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Authentication URL")}
|
|
||||||
?required=${true}
|
|
||||||
name="externalHost"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value="${first(this.instance?.externalHost, window.location.origin)}"
|
|
||||||
class="pf-c-form-control"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"The external URL you'll authenticate at. The authentik core server should be reachable under this URL.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Cookie domain")}
|
|
||||||
name="cookieDomain"
|
|
||||||
?required=${true}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value="${ifDefined(this.instance?.cookieDomain)}"
|
|
||||||
class="pf-c-form-control"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>`;
|
|
||||||
case ProxyMode.UnknownDefaultOpenApi:
|
|
||||||
return html`<p>${msg("Unknown proxy mode")}</p>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderForm(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value="${ifDefined(this.instance?.name)}"
|
|
||||||
class="pf-c-form-control"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Authorization flow")}
|
|
||||||
required
|
|
||||||
name="authorizationFlow"
|
|
||||||
>
|
|
||||||
<ak-flow-search
|
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
|
||||||
.currentFlow=${this.instance?.authorizationFlow}
|
|
||||||
required
|
|
||||||
></ak-flow-search>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Flow used when authorizing this provider.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
|
|
||||||
<div class="pf-c-card pf-m-selectable pf-m-selected">
|
|
||||||
<div class="pf-c-card__body">${this.renderModeSelector()}</div>
|
|
||||||
<div class="pf-c-card__footer">${this.renderSettings()}</div>
|
|
||||||
</div>
|
|
||||||
<ak-form-element-horizontal label=${msg("Token validity")} name="accessTokenValidity">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value="${first(this.instance?.accessTokenValidity, "hours=24")}"
|
|
||||||
class="pf-c-form-control"
|
|
||||||
/>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Configure how long tokens are valid for.")}
|
|
||||||
</p>
|
|
||||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
|
|
||||||
<ak-form-group>
|
|
||||||
<span slot="header">${msg("Advanced protocol settings")}</span>
|
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
|
|
||||||
<ak-crypto-certificate-search
|
|
||||||
.certificate=${this.instance?.certificate}
|
|
||||||
></ak-crypto-certificate-search>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Additional scopes")}
|
|
||||||
name="propertyMappings"
|
|
||||||
>
|
|
||||||
<ak-dual-select-dynamic-selected
|
|
||||||
.provider=${proxyPropertyMappingsProvider}
|
|
||||||
.selector=${makeProxyPropertyMappingsSelector(
|
|
||||||
this.instance?.propertyMappings,
|
|
||||||
)}
|
|
||||||
available-label="${msg("Available Scopes")}"
|
|
||||||
selected-label="${msg("Selected Scopes")}"
|
|
||||||
></ak-dual-select-dynamic-selected>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Additional scope mappings, which are passed to the proxy.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label="${this.mode === ProxyMode.ForwardDomain
|
|
||||||
? msg("Unauthenticated URLs")
|
|
||||||
: msg("Unauthenticated Paths")}"
|
|
||||||
name="skipPathRegex"
|
|
||||||
>
|
|
||||||
<textarea class="pf-c-form-control">
|
|
||||||
${this.instance?.skipPathRegex}</textarea
|
|
||||||
>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
</div>
|
|
||||||
</ak-form-group>
|
|
||||||
<ak-form-group>
|
|
||||||
<span slot="header">${msg("Authentication settings")}</span>
|
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="interceptHeaderAuth">
|
|
||||||
<label class="pf-c-switch">
|
|
||||||
<input
|
|
||||||
class="pf-c-switch__input"
|
|
||||||
type="checkbox"
|
|
||||||
?checked=${first(this.instance?.interceptHeaderAuth, true)}
|
|
||||||
/>
|
|
||||||
<span class="pf-c-switch__toggle">
|
|
||||||
<span class="pf-c-switch__toggle-icon">
|
|
||||||
<i class="fas fa-check" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="pf-c-switch__label"
|
|
||||||
>${msg("Intercept header authentication")}</span
|
|
||||||
>
|
|
||||||
</label>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"When enabled, authentik will intercept the Authorization header to authenticate the request.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal name="basicAuthEnabled">
|
|
||||||
<label class="pf-c-switch">
|
|
||||||
<input
|
|
||||||
class="pf-c-switch__input"
|
|
||||||
type="checkbox"
|
|
||||||
?checked=${first(this.instance?.basicAuthEnabled, false)}
|
|
||||||
@change=${(ev: Event) => {
|
|
||||||
const el = ev.target as HTMLInputElement;
|
const el = ev.target as HTMLInputElement;
|
||||||
this.showHttpBasic = el.checked;
|
this.showHttpBasic = el.checked;
|
||||||
}}
|
};
|
||||||
/>
|
|
||||||
<span class="pf-c-switch__toggle">
|
|
||||||
<span class="pf-c-switch__toggle-icon">
|
|
||||||
<i class="fas fa-check" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="pf-c-switch__label"
|
|
||||||
>${msg("Send HTTP-Basic Authentication")}</span
|
|
||||||
>
|
|
||||||
</label>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"Send a custom HTTP-Basic Authentication header based on values from authentik.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
${this.showHttpBasic ? this.renderHttpBasic() : html``}
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Trusted OIDC Sources")}
|
|
||||||
name="jwksSources"
|
|
||||||
>
|
|
||||||
<ak-dual-select-dynamic-selected
|
|
||||||
.provider=${oauth2SourcesProvider}
|
|
||||||
.selector=${makeSourceSelector(this.instance?.jwksSources)}
|
|
||||||
available-label=${msg("Available Sources")}
|
|
||||||
selected-label=${msg("Selected Sources")}
|
|
||||||
></ak-dual-select-dynamic-selected>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
</div>
|
|
||||||
</ak-form-group>
|
|
||||||
|
|
||||||
<ak-form-group>
|
return renderForm(this.instance ?? {}, [], {
|
||||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
mode: this.mode,
|
||||||
<div slot="body" class="pf-c-form">
|
onSetMode,
|
||||||
<ak-form-element-horizontal
|
showHttpBasic: this.showHttpBasic,
|
||||||
label=${msg("Authentication flow")}
|
onSetShowHttpBasic,
|
||||||
?required=${false}
|
});
|
||||||
name="authenticationFlow"
|
|
||||||
>
|
|
||||||
<ak-flow-search
|
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
|
||||||
.currentFlow=${this.instance?.authenticationFlow}
|
|
||||||
></ak-flow-search>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"Flow used when a user access this provider and is not authenticated.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${msg("Invalidation flow")}
|
|
||||||
name="invalidationFlow"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<ak-flow-search
|
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
|
||||||
.currentFlow=${this.instance?.invalidationFlow}
|
|
||||||
defaultFlowSlug="default-provider-invalidation-flow"
|
|
||||||
required
|
|
||||||
></ak-flow-search>
|
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Flow used when logging out of this provider.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
</div>
|
|
||||||
</ak-form-group>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
381
web/src/admin/providers/proxy/ProxyProviderFormForm.ts
Normal file
381
web/src/admin/providers/proxy/ProxyProviderFormForm.ts
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||||
|
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||||
|
import {
|
||||||
|
makeSourceSelector,
|
||||||
|
oauth2SourcesProvider,
|
||||||
|
} from "@goauthentik/admin/providers/oauth2/OAuth2Sources.js";
|
||||||
|
import "@goauthentik/components/ak-toggle-group";
|
||||||
|
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||||
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
import "@goauthentik/elements/forms/SearchSelect";
|
||||||
|
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||||
|
import { match } from "ts-pattern";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { html, nothing } from "lit";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import { FlowsInstancesListDesignationEnum, ProxyMode, ProxyProvider } from "@goauthentik/api";
|
||||||
|
|
||||||
|
import {
|
||||||
|
makeProxyPropertyMappingsSelector,
|
||||||
|
proxyPropertyMappingsProvider,
|
||||||
|
} from "./ProxyProviderPropertyMappings.js";
|
||||||
|
|
||||||
|
export type ProxyModeValue = { value: ProxyMode };
|
||||||
|
export type SetMode = (ev: CustomEvent<ProvxyModeValue>) => void;
|
||||||
|
export type SetShowHttpBasic = (ev: Event) => void;
|
||||||
|
|
||||||
|
export interface ProxyModeExtraArgs {
|
||||||
|
mode: ProxyMode;
|
||||||
|
onSetMode: SetMode;
|
||||||
|
showHttpBasic: boolean;
|
||||||
|
onSetShowHttpBasic: SetShowHttpBasic;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHttpBasic(provider: ProxyProvider) {
|
||||||
|
return html`<ak-text-input
|
||||||
|
name="basicAuthUserAttribute"
|
||||||
|
label=${msg("HTTP-Basic Username Key")}
|
||||||
|
value="${ifDefined(provider?.basicAuthUserAttribute)}"
|
||||||
|
help=${msg(
|
||||||
|
"User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used.",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</ak-text-input>
|
||||||
|
|
||||||
|
<ak-text-input
|
||||||
|
name="basicAuthPasswordAttribute"
|
||||||
|
label=${msg("HTTP-Basic Password Key")}
|
||||||
|
value="${ifDefined(provider?.basicAuthPasswordAttribute)}"
|
||||||
|
help=${msg("User/Group Attribute used for the password part of the HTTP-Basic Header.")}
|
||||||
|
>
|
||||||
|
</ak-text-input>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderModeSelector(mode: ProxyMode, onSet: SetMode) {
|
||||||
|
// prettier-ignore
|
||||||
|
return html` <ak-toggle-group
|
||||||
|
value=${mode}
|
||||||
|
@ak-toggle=${onSet}
|
||||||
|
data-ouid-component-name="proxy-type-toggle"
|
||||||
|
>
|
||||||
|
<option value=${ProxyMode.Proxy}>${msg("Proxy")}</option>
|
||||||
|
<option value=${ProxyMode.ForwardSingle}>${msg("Forward auth (single application)")}</option>
|
||||||
|
<option value=${ProxyMode.ForwardDomain}>${msg("Forward auth (domain level)")}</option>
|
||||||
|
</ak-toggle-group>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProxySettings(provider: ProxyProvider) {
|
||||||
|
return html`<p class="pf-u-mb-xl">
|
||||||
|
${msg(
|
||||||
|
"This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<ak-form-element-horizontal label=${msg("External host")} required name="externalHost">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${ifDefined(provider?.externalHost)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"The external URL you'll access the application at. Include any non-standard port.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal label=${msg("Internal host")} required name="internalHost">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${ifDefined(provider?.internalHost)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Upstream host that the requests are forwarded to.")}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal name="internalHostSslValidation">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${provider?.internalHostSslValidation ?? true}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label">${msg("Internal host SSL Validation")}</span>
|
||||||
|
</label>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Validate SSL Certificates of upstream servers.")}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderForwardSingleSettings(provider: ProxyProvider) {
|
||||||
|
return html`<p class="pf-u-mb-xl">
|
||||||
|
${msg(
|
||||||
|
"Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you).",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<ak-form-element-horizontal label=${msg("External host")} required name="externalHost">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${ifDefined(provider?.externalHost)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"The external URL you'll access the application at. Include any non-standard port.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderForwardDomainSettings(provider: ProxyProvider) {
|
||||||
|
return html`<p class="pf-u-mb-xl">
|
||||||
|
${msg(
|
||||||
|
"Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div class="pf-u-mb-xl">
|
||||||
|
${msg("An example setup can look like this:")}
|
||||||
|
<ul class="pf-c-list">
|
||||||
|
<li>${msg("authentik running on auth.example.com")}</li>
|
||||||
|
<li>${msg("app1 running on app1.example.com")}</li>
|
||||||
|
</ul>
|
||||||
|
${msg(
|
||||||
|
"In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com.",
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ak-form-element-horizontal label=${msg("Authentication URL")} required name="externalHost">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${provider?.externalHost ?? window.location.origin}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"The external URL you'll authenticate at. The authentik core server should be reachable under this URL.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal label=${msg("Cookie domain")} name="cookieDomain" required>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${ifDefined(provider?.cookieDomain)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSettings(provider: ProxyProvider, mode: ProxyMode) {
|
||||||
|
return match(mode)
|
||||||
|
.with(ProxyMode.Proxy, () => renderProxySettings(provider))
|
||||||
|
.with(ProxyMode.ForwardSingle, () => renderForwardSingleSettings(provider))
|
||||||
|
.with(ProxyMode.ForwardDomain, () => renderForwardDomainSettings(provider))
|
||||||
|
.exhaustive();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderForm(
|
||||||
|
provider?: Partial<ProxyProvider>,
|
||||||
|
errors: ValidationError,
|
||||||
|
args: ProxyModeExtraArgs,
|
||||||
|
) {
|
||||||
|
const { mode, onSetMode, showHttpBasic, onSetShowHttpBasic } = args;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${ifDefined(provider?.name)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Authorization flow")}
|
||||||
|
required
|
||||||
|
name="authorizationFlow"
|
||||||
|
>
|
||||||
|
<ak-flow-search
|
||||||
|
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||||
|
.currentFlow=${provider?.authorizationFlow}
|
||||||
|
required
|
||||||
|
></ak-flow-search>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Flow used when authorizing this provider.")}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
|
<div class="pf-c-card pf-m-selectable pf-m-selected">
|
||||||
|
<div class="pf-c-card__body">${renderModeSelector(mode, onSetMode)}</div>
|
||||||
|
<div class="pf-c-card__footer">${renderSettings(provider, mode)}</div>
|
||||||
|
</div>
|
||||||
|
<ak-form-element-horizontal label=${msg("Token validity")} name="accessTokenValidity">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${provider?.accessTokenValidity ?? "hours=24"}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">${msg("Configure how long tokens are valid for.")}</p>
|
||||||
|
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
|
<ak-form-group>
|
||||||
|
<span slot="header">${msg("Advanced protocol settings")}</span>
|
||||||
|
<div slot="body" class="pf-c-form">
|
||||||
|
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
|
||||||
|
<ak-crypto-certificate-search
|
||||||
|
.certificate=${provider?.certificate}
|
||||||
|
></ak-crypto-certificate-search>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Additional scopes")}
|
||||||
|
name="propertyMappings"
|
||||||
|
>
|
||||||
|
<ak-dual-select-dynamic-selected
|
||||||
|
.provider=${proxyPropertyMappingsProvider}
|
||||||
|
.selector=${makeProxyPropertyMappingsSelector(provider?.propertyMappings)}
|
||||||
|
available-label="${msg("Available Scopes")}"
|
||||||
|
selected-label="${msg("Selected Scopes")}"
|
||||||
|
></ak-dual-select-dynamic-selected>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Additional scope mappings, which are passed to the proxy.")}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label="${mode === ProxyMode.ForwardDomain
|
||||||
|
? msg("Unauthenticated URLs")
|
||||||
|
: msg("Unauthenticated Paths")}"
|
||||||
|
name="skipPathRegex"
|
||||||
|
>
|
||||||
|
<textarea class="pf-c-form-control">${provider?.skipPathRegex}</textarea>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
</div>
|
||||||
|
</ak-form-group>
|
||||||
|
<ak-form-group>
|
||||||
|
<span slot="header">${msg("Authentication settings")}</span>
|
||||||
|
<div slot="body" class="pf-c-form">
|
||||||
|
<ak-form-element-horizontal name="interceptHeaderAuth">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${provider?.interceptHeaderAuth ?? true}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label"
|
||||||
|
>${msg("Intercept header authentication")}</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When enabled, authentik will intercept the Authorization header to authenticate the request.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal name="basicAuthEnabled">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${provider?.basicAuthEnabled ?? false}
|
||||||
|
@change=${onSetShowHttpBasic}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label"
|
||||||
|
>${msg("Send HTTP-Basic Authentication")}</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"Send a custom HTTP-Basic Authentication header based on values from authentik.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
${showHttpBasic ? renderHttpBasic(provider) : nothing}
|
||||||
|
<ak-form-element-horizontal label=${msg("Trusted OIDC Sources")} name="jwksSources">
|
||||||
|
<ak-dual-select-dynamic-selected
|
||||||
|
.provider=${oauth2SourcesProvider}
|
||||||
|
.selector=${makeSourceSelector(provider?.jwksSources)}
|
||||||
|
available-label=${msg("Available Sources")}
|
||||||
|
selected-label=${msg("Selected Sources")}
|
||||||
|
></ak-dual-select-dynamic-selected>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
</div>
|
||||||
|
</ak-form-group>
|
||||||
|
|
||||||
|
<ak-form-group>
|
||||||
|
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||||
|
<div slot="body" class="pf-c-form">
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Authentication flow")}
|
||||||
|
name="authenticationFlow"
|
||||||
|
>
|
||||||
|
<ak-flow-search
|
||||||
|
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||||
|
.currentFlow=${provider?.authenticationFlow}
|
||||||
|
></ak-flow-search>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"Flow used when a user access this provider and is not authenticated.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Invalidation flow")}
|
||||||
|
name="invalidationFlow"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<ak-flow-search
|
||||||
|
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||||
|
.currentFlow=${provider?.invalidationFlow}
|
||||||
|
defaultFlowSlug="default-provider-invalidation-flow"
|
||||||
|
required
|
||||||
|
></ak-flow-search>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Flow used when logging out of this provider.")}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
</div>
|
||||||
|
</ak-form-group>
|
||||||
|
`;
|
||||||
|
}
|
||||||
@ -350,14 +350,12 @@ export function renderForm(
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Default relay state")}
|
label=${msg("Default relay state")}
|
||||||
?required=${true}
|
|
||||||
name="defaultRelayState"
|
name="defaultRelayState"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${provider?.defaultRelayState || ""}"
|
value="${provider?.defaultRelayState || ""}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${msg(
|
${msg(
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import {
|
|||||||
SourcesApi,
|
SourcesApi,
|
||||||
Stage,
|
Stage,
|
||||||
StagesApi,
|
StagesApi,
|
||||||
|
StagesCaptchaListRequest,
|
||||||
StagesPasswordListRequest,
|
StagesPasswordListRequest,
|
||||||
UserFieldsEnum,
|
UserFieldsEnum,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
@ -140,19 +141,13 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
|
|||||||
).stagesPasswordList(args);
|
).stagesPasswordList(args);
|
||||||
return stages.results;
|
return stages.results;
|
||||||
}}
|
}}
|
||||||
.groupBy=${(items: Stage[]) => {
|
.groupBy=${(items: Stage[]) =>
|
||||||
return groupBy(items, (stage) => stage.verboseNamePlural);
|
groupBy(items, (stage) => stage.verboseNamePlural)}
|
||||||
}}
|
.renderElement=${(stage: Stage): string => stage.name}
|
||||||
.renderElement=${(stage: Stage): string => {
|
.value=${(stage: Stage | undefined): string | undefined => stage?.pk}
|
||||||
return stage.name;
|
.selected=${(stage: Stage): boolean =>
|
||||||
}}
|
stage.pk === this.instance?.passwordStage}
|
||||||
.value=${(stage: Stage | undefined): string | undefined => {
|
blankable
|
||||||
return stage?.pk;
|
|
||||||
}}
|
|
||||||
.selected=${(stage: Stage): boolean => {
|
|
||||||
return stage.pk === this.instance?.passwordStage;
|
|
||||||
}}
|
|
||||||
?blankable=${true}
|
|
||||||
>
|
>
|
||||||
</ak-search-select>
|
</ak-search-select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
@ -161,6 +156,35 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal label=${msg("Captcha stage")} name="captchaStage">
|
||||||
|
<ak-search-select
|
||||||
|
.fetchObjects=${async (query?: string): Promise<Stage[]> => {
|
||||||
|
const args: StagesCaptchaListRequest = {
|
||||||
|
ordering: "name",
|
||||||
|
};
|
||||||
|
if (query !== undefined) {
|
||||||
|
args.search = query;
|
||||||
|
}
|
||||||
|
const stages = await new StagesApi(
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
).stagesCaptchaList(args);
|
||||||
|
return stages.results;
|
||||||
|
}}
|
||||||
|
.groupBy=${(items: Stage[]) =>
|
||||||
|
groupBy(items, (stage) => stage.verboseNamePlural)}
|
||||||
|
.renderElement=${(stage: Stage): string => stage.name}
|
||||||
|
.value=${(stage: Stage | undefined): string | undefined => stage?.pk}
|
||||||
|
.selected=${(stage: Stage): boolean =>
|
||||||
|
stage.pk === this.instance?.captchaStage}
|
||||||
|
blankable
|
||||||
|
>
|
||||||
|
</ak-search-select>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When set, adds functionality exactly like a Captcha stage, but baked into the Identification stage.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal name="caseInsensitiveMatching">
|
<ak-form-element-horizontal name="caseInsensitiveMatching">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { BaseStage, StageHost, SubmitOptions } from "@goauthentik/flow/stages/ba
|
|||||||
import { PasswordManagerPrefill } from "@goauthentik/flow/stages/identification/IdentificationStage";
|
import { PasswordManagerPrefill } from "@goauthentik/flow/stages/identification/IdentificationStage";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
|
|
||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
@ -25,62 +25,7 @@ import {
|
|||||||
FlowsApi,
|
FlowsApi,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-stage-authenticator-validate")
|
const customCSS = css`
|
||||||
export class AuthenticatorValidateStage
|
|
||||||
extends BaseStage<
|
|
||||||
AuthenticatorValidationChallenge,
|
|
||||||
AuthenticatorValidationChallengeResponseRequest
|
|
||||||
>
|
|
||||||
implements StageHost
|
|
||||||
{
|
|
||||||
flowSlug = "";
|
|
||||||
|
|
||||||
set loading(value: boolean) {
|
|
||||||
this.host.loading = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get loading(): boolean {
|
|
||||||
return this.host.loading;
|
|
||||||
}
|
|
||||||
|
|
||||||
get brand(): CurrentBrand | undefined {
|
|
||||||
return this.host.brand;
|
|
||||||
}
|
|
||||||
|
|
||||||
@state()
|
|
||||||
_selectedDeviceChallenge?: DeviceChallenge;
|
|
||||||
|
|
||||||
set selectedDeviceChallenge(value: DeviceChallenge | undefined) {
|
|
||||||
const previousChallenge = this._selectedDeviceChallenge;
|
|
||||||
this._selectedDeviceChallenge = value;
|
|
||||||
if (!value) return;
|
|
||||||
if (value === previousChallenge) return;
|
|
||||||
// We don't use this.submit here, as we don't want to advance the flow.
|
|
||||||
// We just want to notify the backend which challenge has been selected.
|
|
||||||
new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
|
|
||||||
flowSlug: this.host?.flowSlug || "",
|
|
||||||
query: window.location.search.substring(1),
|
|
||||||
flowChallengeResponseRequest: {
|
|
||||||
// @ts-ignore
|
|
||||||
component: this.challenge.component || "",
|
|
||||||
selectedChallenge: value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedDeviceChallenge(): DeviceChallenge | undefined {
|
|
||||||
return this._selectedDeviceChallenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
submit(
|
|
||||||
payload: AuthenticatorValidationChallengeResponseRequest,
|
|
||||||
options?: SubmitOptions,
|
|
||||||
): Promise<boolean> {
|
|
||||||
return this.host?.submit(payload, options) || Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton].concat(css`
|
|
||||||
ul {
|
ul {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
@ -109,7 +54,103 @@ export class AuthenticatorValidateStage
|
|||||||
.right > * {
|
.right > * {
|
||||||
height: 50%;
|
height: 50%;
|
||||||
}
|
}
|
||||||
`);
|
`;
|
||||||
|
|
||||||
|
@customElement("ak-stage-authenticator-validate")
|
||||||
|
export class AuthenticatorValidateStage
|
||||||
|
extends BaseStage<
|
||||||
|
AuthenticatorValidationChallenge,
|
||||||
|
AuthenticatorValidationChallengeResponseRequest
|
||||||
|
>
|
||||||
|
implements StageHost
|
||||||
|
{
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, customCSS];
|
||||||
|
}
|
||||||
|
|
||||||
|
flowSlug = "";
|
||||||
|
|
||||||
|
set loading(value: boolean) {
|
||||||
|
this.host.loading = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get loading(): boolean {
|
||||||
|
return this.host.loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
get brand(): CurrentBrand | undefined {
|
||||||
|
return this.host.brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
@state()
|
||||||
|
_firstInitialized: boolean = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
_selectedDeviceChallenge?: DeviceChallenge;
|
||||||
|
|
||||||
|
set selectedDeviceChallenge(value: DeviceChallenge | undefined) {
|
||||||
|
const previousChallenge = this._selectedDeviceChallenge;
|
||||||
|
this._selectedDeviceChallenge = value;
|
||||||
|
if (value === undefined || value === previousChallenge) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We don't use this.submit here, as we don't want to advance the flow.
|
||||||
|
// We just want to notify the backend which challenge has been selected.
|
||||||
|
new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
|
||||||
|
flowSlug: this.host?.flowSlug || "",
|
||||||
|
query: window.location.search.substring(1),
|
||||||
|
flowChallengeResponseRequest: {
|
||||||
|
// @ts-ignore
|
||||||
|
component: this.challenge.component || "",
|
||||||
|
selectedChallenge: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedDeviceChallenge(): DeviceChallenge | undefined {
|
||||||
|
return this._selectedDeviceChallenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
submit(
|
||||||
|
payload: AuthenticatorValidationChallengeResponseRequest,
|
||||||
|
options?: SubmitOptions,
|
||||||
|
): Promise<boolean> {
|
||||||
|
return this.host?.submit(payload, options) || Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
willUpdate(_changed: PropertyValues<this>) {
|
||||||
|
if (this._firstInitialized || !this.challenge) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._firstInitialized = true;
|
||||||
|
|
||||||
|
// If user only has a single device, autoselect that device.
|
||||||
|
if (this.challenge.deviceChallenges.length === 1) {
|
||||||
|
this.selectedDeviceChallenge = this.challenge.deviceChallenges[0];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If TOTP is allowed from the backend and we have a pre-filled value
|
||||||
|
// from the password manager, autoselect TOTP.
|
||||||
|
const totpChallenge = this.challenge.deviceChallenges.find(
|
||||||
|
(challenge) => challenge.deviceClass === DeviceClassesEnum.Totp,
|
||||||
|
);
|
||||||
|
if (PasswordManagerPrefill.totp && totpChallenge) {
|
||||||
|
console.debug(
|
||||||
|
"authentik/stages/authenticator_validate: found prefill totp code, selecting totp challenge",
|
||||||
|
);
|
||||||
|
this.selectedDeviceChallenge = totpChallenge;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the last used device is not Static, autoselect that device.
|
||||||
|
const lastUsedChallenge = this.challenge.deviceChallenges
|
||||||
|
.filter((deviceChallenge) => deviceChallenge.lastUsed)
|
||||||
|
.sort((a, b) => b.lastUsed!.valueOf() - a.lastUsed!.valueOf())[0];
|
||||||
|
if (lastUsedChallenge && lastUsedChallenge.deviceClass !== DeviceClassesEnum.Static) {
|
||||||
|
this.selectedDeviceChallenge = lastUsedChallenge;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDevicePickerSingle(deviceChallenge: DeviceChallenge) {
|
renderDevicePickerSingle(deviceChallenge: DeviceChallenge) {
|
||||||
@ -228,26 +269,8 @@ export class AuthenticatorValidateStage
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.challenge) {
|
return this.challenge
|
||||||
return html`<ak-empty-state loading> </ak-empty-state>`;
|
? html`<header class="pf-c-login__main-header">
|
||||||
}
|
|
||||||
// User only has a single device class, so we don't show a picker
|
|
||||||
if (this.challenge?.deviceChallenges.length === 1) {
|
|
||||||
this.selectedDeviceChallenge = this.challenge.deviceChallenges[0];
|
|
||||||
}
|
|
||||||
// TOTP is a bit special, assuming that TOTP is allowed from the backend,
|
|
||||||
// and we have a pre-filled value from the password manager,
|
|
||||||
// directly set the the TOTP device Challenge as active.
|
|
||||||
const totpChallenge = this.challenge.deviceChallenges.find(
|
|
||||||
(challenge) => challenge.deviceClass === DeviceClassesEnum.Totp,
|
|
||||||
);
|
|
||||||
if (PasswordManagerPrefill.totp && totpChallenge) {
|
|
||||||
console.debug(
|
|
||||||
"authentik/stages/authenticator_validate: found prefill totp code, selecting totp challenge",
|
|
||||||
);
|
|
||||||
this.selectedDeviceChallenge = totpChallenge;
|
|
||||||
}
|
|
||||||
return html`<header class="pf-c-login__main-header">
|
|
||||||
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
||||||
</header>
|
</header>
|
||||||
${this.selectedDeviceChallenge
|
${this.selectedDeviceChallenge
|
||||||
@ -266,7 +289,8 @@ export class AuthenticatorValidateStage
|
|||||||
</div>
|
</div>
|
||||||
<footer class="pf-c-login__main-footer">
|
<footer class="pf-c-login__main-footer">
|
||||||
<ul class="pf-c-login__main-footer-links"></ul>
|
<ul class="pf-c-login__main-footer-links"></ul>
|
||||||
</footer>`}`;
|
</footer>`}`
|
||||||
|
: html`<ak-empty-state loading> </ak-empty-state>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,34 @@ export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
|
|||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deviceMessage(): string {
|
||||||
|
switch (this.deviceChallenge?.deviceClass) {
|
||||||
|
case DeviceClassesEnum.Sms:
|
||||||
|
return msg("A code has been sent to you via SMS.");
|
||||||
|
case DeviceClassesEnum.Totp:
|
||||||
|
return msg(
|
||||||
|
"Open your two-factor authenticator app to view your authentication code.",
|
||||||
|
);
|
||||||
|
case DeviceClassesEnum.Static:
|
||||||
|
return msg("Enter a one-time recovery code for this user.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg("Enter the code from your authenticator device.");
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceIcon(): string {
|
||||||
|
switch (this.deviceChallenge?.deviceClass) {
|
||||||
|
case DeviceClassesEnum.Sms:
|
||||||
|
return "fa-key";
|
||||||
|
case DeviceClassesEnum.Totp:
|
||||||
|
return "fa-mobile-alt";
|
||||||
|
case DeviceClassesEnum.Static:
|
||||||
|
return "fa-sticky-note";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "fa-mobile-alt";
|
||||||
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.challenge) {
|
if (!this.challenge) {
|
||||||
return html`<ak-empty-state loading> </ak-empty-state>`;
|
return html`<ak-empty-state loading> </ak-empty-state>`;
|
||||||
@ -44,19 +72,8 @@ export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
|
|||||||
>
|
>
|
||||||
${this.renderUserInfo()}
|
${this.renderUserInfo()}
|
||||||
<div class="icon-description">
|
<div class="icon-description">
|
||||||
<i
|
<i class="fa ${this.deviceIcon()}" aria-hidden="true"></i>
|
||||||
class="fa ${this.deviceChallenge?.deviceClass == DeviceClassesEnum.Sms
|
<p>${this.deviceMessage()}</p>
|
||||||
? "fa-key"
|
|
||||||
: "fa-mobile-alt"}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
${this.deviceChallenge?.deviceClass == DeviceClassesEnum.Sms
|
|
||||||
? html`<p>${msg("A code has been sent to you via SMS.")}</p>`
|
|
||||||
: html`<p>
|
|
||||||
${msg(
|
|
||||||
"Open your two-factor authenticator app to view your authentication code.",
|
|
||||||
)}
|
|
||||||
</p>`}
|
|
||||||
</div>
|
</div>
|
||||||
<ak-form-element
|
<ak-form-element
|
||||||
label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export class BaseDeviceStage<
|
|||||||
(this.host as AuthenticatorValidateStage).selectedDeviceChallenge = undefined;
|
(this.host as AuthenticatorValidateStage).selectedDeviceChallenge = undefined;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
${msg("Return to device picker")}
|
${msg("Select another authentication method")}
|
||||||
</button>`;
|
</button>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
|
|||||||
import type { TurnstileObject } from "turnstile-types";
|
import type { TurnstileObject } from "turnstile-types";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
|
import { CSSResult, PropertyValues, html } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
@ -22,6 +22,7 @@ import { CaptchaChallenge, CaptchaChallengeResponseRequest } from "@goauthentik/
|
|||||||
interface TurnstileWindow extends Window {
|
interface TurnstileWindow extends Window {
|
||||||
turnstile: TurnstileObject;
|
turnstile: TurnstileObject;
|
||||||
}
|
}
|
||||||
|
type TokenHandler = (token: string) => void;
|
||||||
|
|
||||||
const captchaContainerID = "captcha-container";
|
const captchaContainerID = "captcha-container";
|
||||||
|
|
||||||
@ -45,6 +46,11 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
|||||||
@state()
|
@state()
|
||||||
scriptElement?: HTMLScriptElement;
|
scriptElement?: HTMLScriptElement;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
onTokenChange: TokenHandler = (token: string) => {
|
||||||
|
this.host.submit({ component: "ak-stage-captcha", token });
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.captchaContainer = document.createElement("div");
|
this.captchaContainer = document.createElement("div");
|
||||||
@ -102,11 +108,7 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
|||||||
grecaptcha.ready(() => {
|
grecaptcha.ready(() => {
|
||||||
const captchaId = grecaptcha.render(this.captchaContainer, {
|
const captchaId = grecaptcha.render(this.captchaContainer, {
|
||||||
sitekey: this.challenge.siteKey,
|
sitekey: this.challenge.siteKey,
|
||||||
callback: (token) => {
|
callback: this.onTokenChange,
|
||||||
this.host?.submit({
|
|
||||||
token: token,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
size: "invisible",
|
size: "invisible",
|
||||||
});
|
});
|
||||||
grecaptcha.execute(captchaId);
|
grecaptcha.execute(captchaId);
|
||||||
@ -122,12 +124,8 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
|||||||
document.body.appendChild(this.captchaContainer);
|
document.body.appendChild(this.captchaContainer);
|
||||||
const captchaId = hcaptcha.render(this.captchaContainer, {
|
const captchaId = hcaptcha.render(this.captchaContainer, {
|
||||||
sitekey: this.challenge.siteKey,
|
sitekey: this.challenge.siteKey,
|
||||||
|
callback: this.onTokenChange,
|
||||||
size: "invisible",
|
size: "invisible",
|
||||||
callback: (token) => {
|
|
||||||
this.host?.submit({
|
|
||||||
token: token,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
hcaptcha.execute(captchaId);
|
hcaptcha.execute(captchaId);
|
||||||
return true;
|
return true;
|
||||||
@ -141,16 +139,12 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
|||||||
document.body.appendChild(this.captchaContainer);
|
document.body.appendChild(this.captchaContainer);
|
||||||
(window as unknown as TurnstileWindow).turnstile.render(`#${captchaContainerID}`, {
|
(window as unknown as TurnstileWindow).turnstile.render(`#${captchaContainerID}`, {
|
||||||
sitekey: this.challenge.siteKey,
|
sitekey: this.challenge.siteKey,
|
||||||
callback: (token) => {
|
callback: this.onTokenChange,
|
||||||
this.host?.submit({
|
|
||||||
token: token,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBody(): TemplateResult {
|
renderBody() {
|
||||||
if (this.error) {
|
if (this.error) {
|
||||||
return html`<ak-empty-state icon="fa-times" header=${this.error}> </ak-empty-state>`;
|
return html`<ak-empty-state icon="fa-times" header=${this.error}> </ak-empty-state>`;
|
||||||
}
|
}
|
||||||
@ -160,7 +154,7 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
|||||||
return html`<ak-empty-state loading header=${msg("Verifying...")}></ak-empty-state>`;
|
return html`<ak-empty-state loading header=${msg("Verifying...")}></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render() {
|
||||||
if (!this.challenge) {
|
if (!this.challenge) {
|
||||||
return html`<ak-empty-state loading> </ak-empty-state>`;
|
return html`<ak-empty-state loading> </ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,11 @@ import "@goauthentik/elements/EmptyState";
|
|||||||
import "@goauthentik/elements/forms/FormElement";
|
import "@goauthentik/elements/forms/FormElement";
|
||||||
import "@goauthentik/flow/components/ak-flow-password-input.js";
|
import "@goauthentik/flow/components/ak-flow-password-input.js";
|
||||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||||
|
import "@goauthentik/flow/stages/captcha/CaptchaStage";
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
import { msg, str } from "@lit/localize";
|
||||||
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
|
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
|
|
||||||
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
@ -46,6 +47,9 @@ export class IdentificationStage extends BaseStage<
|
|||||||
> {
|
> {
|
||||||
form?: HTMLFormElement;
|
form?: HTMLFormElement;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
captchaToken = "";
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
PFBase,
|
PFBase,
|
||||||
@ -274,6 +278,18 @@ export class IdentificationStage extends BaseStage<
|
|||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${this.renderNonFieldErrors()}
|
${this.renderNonFieldErrors()}
|
||||||
|
${this.challenge.captchaStage
|
||||||
|
? html`
|
||||||
|
<input name="captchaToken" type="hidden" .value="${this.captchaToken}" />
|
||||||
|
<ak-stage-captcha
|
||||||
|
style="visibility: hidden; position:absolute;"
|
||||||
|
.challenge=${this.challenge.captchaStage}
|
||||||
|
.onTokenChange=${(token: string) => {
|
||||||
|
this.captchaToken = token;
|
||||||
|
}}
|
||||||
|
></ak-stage-captcha>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
<div class="pf-c-form__group pf-m-action">
|
<div class="pf-c-form__group pf-m-action">
|
||||||
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
|
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
|
||||||
${this.challenge.primaryAction}
|
${this.challenge.primaryAction}
|
||||||
|
|||||||
@ -89,7 +89,12 @@ export async function setFormGroup(name: string | RegExp, setting: "open" | "clo
|
|||||||
typeof name === "string" ? (sample) => sample === name : (sample) => name.test(sample);
|
typeof name === "string" ? (sample) => sample === name : (sample) => name.test(sample);
|
||||||
|
|
||||||
const formGroup = await (async () => {
|
const formGroup = await (async () => {
|
||||||
for await (const group of $$("ak-form-group")) {
|
for await (const group of browser.$$("ak-form-group")) {
|
||||||
|
// Delightfully, wizards may have slotted elements that *exist* but are not *attached*,
|
||||||
|
// and this can break the damn tests.
|
||||||
|
if (!(await group.isDisplayed())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
comparator(await group.$("div.pf-c-form__field-group-header-title-text").getText())
|
comparator(await group.$("div.pf-c-form__field-group-header-title-text").getText())
|
||||||
) {
|
) {
|
||||||
@ -112,8 +117,7 @@ export async function clickButton(name: string, ctx?: WebdriverIO.Element) {
|
|||||||
const buttons = await context.$$("button");
|
const buttons = await context.$$("button");
|
||||||
let button: WebdriverIO.Element;
|
let button: WebdriverIO.Element;
|
||||||
for (const b of buttons) {
|
for (const b of buttons) {
|
||||||
const label = await b.getText();
|
if (b.isDisplayed() && (await b.getText()).indexOf(name) !== -1) {
|
||||||
if (label.indexOf(name) !== -1) {
|
|
||||||
button = b;
|
button = b;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -123,16 +127,9 @@ export async function clickButton(name: string, ctx?: WebdriverIO.Element) {
|
|||||||
await doBlur(button);
|
await doBlur(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tap = <T>(a: T): T => {
|
|
||||||
console.log(a);
|
|
||||||
return a;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function clickToggleGroup(name: string, value: string | RegExp) {
|
export async function clickToggleGroup(name: string, value: string | RegExp) {
|
||||||
const comparator =
|
const comparator =
|
||||||
typeof name === "string"
|
typeof name === "string" ? (sample) => sample === value : (sample) => value.test(sample);
|
||||||
? (sample) => tap(sample) === tap(value)
|
|
||||||
: (sample) => value.test(sample);
|
|
||||||
|
|
||||||
const button = await (async () => {
|
const button = await (async () => {
|
||||||
for await (const button of $(`[data-ouid-component-name=${name}]`).$$(
|
for await (const button of $(`[data-ouid-component-name=${name}]`).$$(
|
||||||
|
|||||||
@ -35,8 +35,8 @@ export const simpleLDAPProviderForm: TestProvider = () => [
|
|||||||
[clickButton, "Next"],
|
[clickButton, "Next"],
|
||||||
[setTextInput, "name", newObjectName("New LDAP Provider")],
|
[setTextInput, "name", newObjectName("New LDAP Provider")],
|
||||||
// This will never not weird me out.
|
// This will never not weird me out.
|
||||||
[setSearchSelect, "authorizationFlow", "default-authentication-flow"],
|
|
||||||
[setFormGroup, /Flow settings/, "open"],
|
[setFormGroup, /Flow settings/, "open"],
|
||||||
|
[setSearchSelect, "authorizationFlow", "default-authentication-flow"],
|
||||||
[setSearchSelect, "invalidationFlow", "default-invalidation-flow"],
|
[setSearchSelect, "invalidationFlow", "default-invalidation-flow"],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -5036,10 +5036,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s3cd84e82e83e35ad">
|
<trans-unit id="s3cd84e82e83e35ad">
|
||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>Zurück zur Geräteauswahl</target>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
<target>Authentifizierung erneut versuchen</target>
|
<target>Authentifizierung erneut versuchen</target>
|
||||||
@ -7015,6 +7011,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -5294,10 +5294,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
<target>Please enter your code</target>
|
<target>Please enter your code</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>Return to device picker</target>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
<target>Retry authentication</target>
|
<target>Retry authentication</target>
|
||||||
@ -7280,6 +7276,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -4962,10 +4962,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s3cd84e82e83e35ad">
|
<trans-unit id="s3cd84e82e83e35ad">
|
||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>Regresar al selector de dispositivos</target>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
<target>Reintentar la autenticación</target>
|
<target>Reintentar la autenticación</target>
|
||||||
@ -6932,6 +6928,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -6616,11 +6616,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
|||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
<target>Veuillez saisir votre code</target>
|
<target>Veuillez saisir votre code</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>Retourner à la sélection d'appareil</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
@ -9131,90 +9126,128 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sbfee780fa0a2c83e">
|
<trans-unit id="sbfee780fa0a2c83e">
|
||||||
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
|
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
|
||||||
|
<target>Le type d'appareil <x id="0" equiv-text="${device.verboseName}"/> ne peut pas être supprimé</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s336936629cdeb3e5">
|
<trans-unit id="s336936629cdeb3e5">
|
||||||
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
|
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
|
||||||
|
<target>Étape utilisée pour vérifier le navigateur des utilisateurs avec le connecteur de confiance des appareils Google Chrome Enterprise. Cette étape peut être utilisée dans les flux d'authentification et d'autorisation.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s85fe794c71b4ace8">
|
<trans-unit id="s85fe794c71b4ace8">
|
||||||
<source>Google Verified Access API</source>
|
<source>Google Verified Access API</source>
|
||||||
|
<target>API Google Verified Access</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s013620384af7c8b4">
|
<trans-unit id="s013620384af7c8b4">
|
||||||
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
|
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
|
||||||
|
<target>Le type d'appareil <x id="0" equiv-text="${device.verboseName}"/> ne peut pas être édité</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s4347135696fc7cde">
|
<trans-unit id="s4347135696fc7cde">
|
||||||
<source>Advanced flow settings</source>
|
<source>Advanced flow settings</source>
|
||||||
|
<target>Paramètres avancés des flux</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sf52ff57fd136cc2f">
|
<trans-unit id="sf52ff57fd136cc2f">
|
||||||
<source>Enable this option to write password changes made in authentik back to Kerberos. Ignored if sync is disabled.</source>
|
<source>Enable this option to write password changes made in authentik back to Kerberos. Ignored if sync is disabled.</source>
|
||||||
|
<target>Activer cette option pour écrire les changements de mot de passe fait dans authentik dans Kerberos. Ignoré si la synchronisation est désactivée.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s14a16542f956e11d">
|
<trans-unit id="s14a16542f956e11d">
|
||||||
<source>Realm settings</source>
|
<source>Realm settings</source>
|
||||||
|
<target>Paramètres du realm</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s9c2eae548d3c1c30">
|
<trans-unit id="s9c2eae548d3c1c30">
|
||||||
<source>Realm</source>
|
<source>Realm</source>
|
||||||
|
<target>Realm</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6b032212997e2491">
|
<trans-unit id="s6b032212997e2491">
|
||||||
<source>Kerberos 5 configuration</source>
|
<source>Kerberos 5 configuration</source>
|
||||||
|
<target>Configuration Kerberos 5</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sbf50181022f47de3">
|
<trans-unit id="sbf50181022f47de3">
|
||||||
<source>Kerberos 5 configuration. See man krb5.conf(5) for configuration format. If left empty, a default krb5.conf will be used.</source>
|
<source>Kerberos 5 configuration. See man krb5.conf(5) for configuration format. If left empty, a default krb5.conf will be used.</source>
|
||||||
|
<target>Configuration Kerbers 5. Cf. man krb5.conf(5) pour le format de configuration. Si laissé vide, un krb5.conf par défaut sera utilisé.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s2386539a0bd62fab">
|
<trans-unit id="s2386539a0bd62fab">
|
||||||
<source>Sync connection settings</source>
|
<source>Sync connection settings</source>
|
||||||
|
<target>Paramètres de synchronisation</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s0d1a6f3fe81351f8">
|
<trans-unit id="s0d1a6f3fe81351f8">
|
||||||
<source>Sync principal</source>
|
<source>Sync principal</source>
|
||||||
|
<target>Principal de synchronisation</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sa691d6e1974295fa">
|
<trans-unit id="sa691d6e1974295fa">
|
||||||
<source>Principal used to authenticate to the KDC for syncing.</source>
|
<source>Principal used to authenticate to the KDC for syncing.</source>
|
||||||
|
<target>Principal utilisé pour s'authentifier au KDC pour synchroniser.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s977b9c629eed3d33">
|
<trans-unit id="s977b9c629eed3d33">
|
||||||
<source>Sync password</source>
|
<source>Sync password</source>
|
||||||
|
<target>Mot de passe de synchronisation</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s77772860385de948">
|
<trans-unit id="s77772860385de948">
|
||||||
<source>Password used to authenticate to the KDC for syncing. Optional if Sync keytab or Sync credentials cache is provided.</source>
|
<source>Password used to authenticate to the KDC for syncing. Optional if Sync keytab or Sync credentials cache is provided.</source>
|
||||||
|
<target>Mot de passe utilisé pour s'authentifier au KDC pour synchroniser. Optional si une keytab de synchronisation ou un credentials cache de synchronisation est fourni.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sc59ec59c3d5e74dc">
|
<trans-unit id="sc59ec59c3d5e74dc">
|
||||||
<source>Sync keytab</source>
|
<source>Sync keytab</source>
|
||||||
|
<target>Keytab de synchronisation</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="scd42997958453f05">
|
<trans-unit id="scd42997958453f05">
|
||||||
<source>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.</source>
|
<source>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.</source>
|
||||||
|
<target>Keytab utilisée pour s'authentifier au KDC pour synchroniser. Optional si un mot de passe de synchronisation ou un credentials cache de synchronisation est fourni. Doit être encodé en base64 ou de la forme TYPE:residual.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s60eaf439ccdca1f2">
|
<trans-unit id="s60eaf439ccdca1f2">
|
||||||
<source>Sync credentials cache</source>
|
<source>Sync credentials cache</source>
|
||||||
|
<target>Credentials cache de synchronisation</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s95722900b0c9026f">
|
<trans-unit id="s95722900b0c9026f">
|
||||||
<source>Credentials cache used to authenticate to the KDC for syncing. Optional if Sync password or Sync keytab is provided. Must be in the form TYPE:residual.</source>
|
<source>Credentials cache used to authenticate to the KDC for syncing. Optional if Sync password or Sync keytab is provided. Must be in the form TYPE:residual.</source>
|
||||||
|
<target>Credentials cache utilisé pour s'authentifier au KDC pour synchroniser. Optional si un mot de passe de synchronisation ou une keytab de synchronisation est fourni. Doit être de la forme TYPE:residual.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sf9c055db98d7994a">
|
<trans-unit id="sf9c055db98d7994a">
|
||||||
<source>SPNEGO settings</source>
|
<source>SPNEGO settings</source>
|
||||||
|
<target>Paramètres SPNEGO</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sab580a45dc46937f">
|
<trans-unit id="sab580a45dc46937f">
|
||||||
<source>SPNEGO server name</source>
|
<source>SPNEGO server name</source>
|
||||||
|
<target>Nom de serveur SPNEGO</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s7a79d6174d17ab2d">
|
<trans-unit id="s7a79d6174d17ab2d">
|
||||||
<source>Force the use of a specific server name for SPNEGO. Must be in the form HTTP@domain</source>
|
<source>Force the use of a specific server name for SPNEGO. Must be in the form HTTP@domain</source>
|
||||||
|
<target>Force l'utilisation d'un nom de serveur spécifique pour SPNEGO. Doit être de la forme HTTP@hostname</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sa4ba2b2081472ccd">
|
<trans-unit id="sa4ba2b2081472ccd">
|
||||||
<source>SPNEGO keytab</source>
|
<source>SPNEGO keytab</source>
|
||||||
|
<target>Keytab SPNEGO</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s64adda975c1106c0">
|
<trans-unit id="s64adda975c1106c0">
|
||||||
<source>Keytab used for SPNEGO. Optional if SPNEGO credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.</source>
|
<source>Keytab used for SPNEGO. Optional if SPNEGO credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.</source>
|
||||||
|
<target>Keytab utilisée pour SPNEGO. Optional si un credentials cache SPNEGO est fourni. Doit être encodé en base64 ou de la forme TYPE:residual.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s92247825b92587b5">
|
<trans-unit id="s92247825b92587b5">
|
||||||
<source>SPNEGO credentials cache</source>
|
<source>SPNEGO credentials cache</source>
|
||||||
|
<target>Credentials cache SPNEGO</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd9757c345e4062f8">
|
<trans-unit id="sd9757c345e4062f8">
|
||||||
<source>Credentials cache used for SPNEGO. Optional if SPNEGO keytab is provided. Must be in the form TYPE:residual.</source>
|
<source>Credentials cache used for SPNEGO. Optional if SPNEGO keytab is provided. Must be in the form TYPE:residual.</source>
|
||||||
|
<target>Credentials cache utilisé pour SPNEGO. Optional si une keytab SPNEGO est fournie. Doit être de la forme TYPE:residual.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s734ab8fbcae0b69e">
|
<trans-unit id="s734ab8fbcae0b69e">
|
||||||
<source>Kerberos Attribute mapping</source>
|
<source>Kerberos Attribute mapping</source>
|
||||||
|
<target>Mappage d'attributs Kerberos</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s2c378e86e025fdb2">
|
<trans-unit id="s2c378e86e025fdb2">
|
||||||
<source>Update Kerberos Source</source>
|
<source>Update Kerberos Source</source>
|
||||||
|
<target>Mettre à jour la source Kerberos</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
<target>Base de données utilisateurs + mot de passe Kerberos</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -6590,11 +6590,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
<target>코드를 입력하세요.</target>
|
<target>코드를 입력하세요.</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>디바이스 선택기로 돌아가기</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
@ -8849,6 +8844,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -6575,11 +6575,6 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
|||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
<target>Voer uw code in</target>
|
<target>Voer uw code in</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>Terug naar apparaatkeuze</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
@ -8695,6 +8690,15 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -6620,11 +6620,6 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
|
|||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
<target>Proszę wprowadź swój kod</target>
|
<target>Proszę wprowadź swój kod</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>Wróć do wyboru urządzeń</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
@ -9114,6 +9109,15 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -6578,11 +6578,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
<target>Ƥĺēàśē ēńţēŕ ŷōũŕ ćōďē</target>
|
<target>Ƥĺēàśē ēńţēŕ ŷōũŕ ćōďē</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>Ŕēţũŕń ţō ďēvĩćē ƥĩćķēŕ</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
@ -9154,4 +9149,13 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
|
</trans-unit>
|
||||||
</body></file></xliff>
|
</body></file></xliff>
|
||||||
|
|||||||
@ -6619,11 +6619,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
<target>Пожалуйста, введите ваш код</target>
|
<target>Пожалуйста, введите ваш код</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>Вернуться к выбору устройства</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
@ -9177,6 +9172,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -4955,10 +4955,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s3cd84e82e83e35ad">
|
<trans-unit id="s3cd84e82e83e35ad">
|
||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>Aygıt seçiciye geri dön</target>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
<target>Kimlik doğrulamayı yeniden deneyin</target>
|
<target>Kimlik doğrulamayı yeniden deneyin</target>
|
||||||
@ -6925,6 +6921,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -4712,9 +4712,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s3cd84e82e83e35ad">
|
<trans-unit id="s3cd84e82e83e35ad">
|
||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -5863,6 +5860,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@ -6618,11 +6618,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
<target>请输入您的代码</target>
|
<target>请输入您的代码</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>返回设备选择器</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
@ -9217,6 +9212,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -4999,10 +4999,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s3cd84e82e83e35ad">
|
<trans-unit id="s3cd84e82e83e35ad">
|
||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>返回设备选择器</target>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
<target>重试身份验证</target>
|
<target>重试身份验证</target>
|
||||||
@ -6973,6 +6969,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -6566,11 +6566,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<source>Please enter your code</source>
|
<source>Please enter your code</source>
|
||||||
<target>請輸入您的認證碼</target>
|
<target>請輸入您的認證碼</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s18b910437b73e8e8">
|
|
||||||
<source>Return to device picker</source>
|
|
||||||
<target>回到選擇裝置頁面</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se409d01b52c4e12f">
|
<trans-unit id="se409d01b52c4e12f">
|
||||||
<source>Retry authentication</source>
|
<source>Retry authentication</source>
|
||||||
@ -8810,6 +8805,15 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s03e4044abe0b556c">
|
<trans-unit id="s03e4044abe0b556c">
|
||||||
<source>User database + Kerberos password</source>
|
<source>User database + Kerberos password</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s98bb2ae796f1ceef">
|
||||||
|
<source>Select another authentication method</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s21d95b4651ad7a1e">
|
||||||
|
<source>Enter a one-time recovery code for this user.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e1d5a7d320c25ef">
|
||||||
|
<source>Enter the code from your authenticator device.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@ -5,10 +5,10 @@ title: Authenticator validation stage
|
|||||||
This stage validates an already configured Authenticator Device. This device has to be configured using any of the other authenticator stages:
|
This stage validates an already configured Authenticator Device. This device has to be configured using any of the other authenticator stages:
|
||||||
|
|
||||||
- [Duo authenticator stage](../authenticator_duo/index.md)
|
- [Duo authenticator stage](../authenticator_duo/index.md)
|
||||||
- [SMS authenticator stage](../authenticator_sms/index.md).
|
- [SMS authenticator stage](../authenticator_sms/index.md)
|
||||||
- [Static authenticator stage](../authenticator_static/index.md).
|
- [Static authenticator stage](../authenticator_static/index.md)
|
||||||
- [TOTP authenticator stage](../authenticator_totp/index.md)
|
- [TOTP authenticator stage](../authenticator_totp/index.md)
|
||||||
- [WebAuth authenticator stage](../authenticator_webauthn/index.md).
|
- [WebAuthn authenticator stage](../authenticator_webauthn/index.md)
|
||||||
|
|
||||||
You can select which type of device classes are allowed.
|
You can select which type of device classes are allowed.
|
||||||
|
|
||||||
@ -75,3 +75,7 @@ Optionally restrict which WebAuthn device types can be used to authenticate.
|
|||||||
When no restriction is set, all WebAuthn devices a user has registered are allowed.
|
When no restriction is set, all WebAuthn devices a user has registered are allowed.
|
||||||
|
|
||||||
These restrictions only apply to WebAuthn devices created with authentik 2024.4 or later.
|
These restrictions only apply to WebAuthn devices created with authentik 2024.4 or later.
|
||||||
|
|
||||||
|
#### Automatic device selection
|
||||||
|
|
||||||
|
If the user has more than one device, the user is prompted to select which device they want to use for validation. After the user successfully authenticates with a certain device, that device is marked as "last used". In subsequent prompts by the Authenticator validation stage, the last used device is automatically selected for the user. Should they wish to use another device, the user can return to the device selection screen.
|
||||||
|
|||||||
@ -16,7 +16,15 @@ Select which fields the user can use to identify themselves. Multiple fields can
|
|||||||
|
|
||||||
## Password stage
|
## Password stage
|
||||||
|
|
||||||
To prompt users for their password on the same step as identifying themselves, a password stage can be selected here. If a password stage is selected in the Identification stage, the password stage should not be bound to the flow.
|
To prompt users for their password on the same step as identifying themselves, a Password stage can be selected here. If a Password stage is selected in the Identification stage, the Password stage should not be bound to the flow.
|
||||||
|
|
||||||
|
## CAPTCHA stage
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
The CAPTCHA stage you use must be configured to use the "Invisible" mode, otherwise the widget will be rendered incorrectly.
|
||||||
|
:::
|
||||||
|
|
||||||
|
To run a CAPTCHA process in the background while the user is entering their identification, a CAPTCHA stage can be selected here. If a CAPTCHA stage is selected in the Identification stage, the CAPTCHA stage should not be bound to the flow.
|
||||||
|
|
||||||
## Enrollment/Recovery Flow
|
## Enrollment/Recovery Flow
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import { Redirect } from "@docusaurus/router";
|
||||||
import Layout from "@theme/Layout";
|
|
||||||
import BrowserOnly from "@docusaurus/BrowserOnly";
|
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
return (
|
return <Redirect to="/docs" />;
|
||||||
<Layout title={`authentik Documentation`}>
|
|
||||||
<BrowserOnly>
|
|
||||||
{() => {
|
|
||||||
window.location.href = "/docs";
|
|
||||||
}}
|
|
||||||
</BrowserOnly>
|
|
||||||
<header className={clsx("hero hero--primary")}>
|
|
||||||
<div className="container">
|
|
||||||
<h1 className="hero__title">authentik Documentation</h1>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
|
|||||||
Reference in New Issue
Block a user