flows: make use of oneOf OpenAPI to annotate all challenge types

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-05-24 14:08:54 +02:00
parent 3b41c662ed
commit 6f6ae7831e
41 changed files with 1102 additions and 335 deletions

View file

@ -2,6 +2,9 @@
from importlib import import_module from importlib import import_module
from django.apps import AppConfig from django.apps import AppConfig
from django.db.utils import ProgrammingError
from authentik.lib.utils.reflection import all_subclasses
class AuthentikFlowsConfig(AppConfig): class AuthentikFlowsConfig(AppConfig):
@ -14,3 +17,10 @@ class AuthentikFlowsConfig(AppConfig):
def ready(self): def ready(self):
import_module("authentik.flows.signals") import_module("authentik.flows.signals")
try:
from authentik.flows.models import Stage
for stage in all_subclasses(Stage):
_ = stage().type
except ProgrammingError:
pass

View file

@ -35,9 +35,9 @@ class Challenge(PassiveSerializer):
type = ChoiceField( type = ChoiceField(
choices=[(x.value, x.name) for x in ChallengeTypes], choices=[(x.value, x.name) for x in ChallengeTypes],
) )
component = CharField(required=False)
title = CharField(required=False) title = CharField(required=False)
background = CharField(required=False) background = CharField(required=False)
component = CharField(default="")
response_errors = DictField( response_errors = DictField(
child=ErrorDetailSerializer(many=True), allow_empty=True, required=False child=ErrorDetailSerializer(many=True), allow_empty=True, required=False
@ -48,12 +48,14 @@ class RedirectChallenge(Challenge):
"""Challenge type to redirect the client""" """Challenge type to redirect the client"""
to = CharField() to = CharField()
component = CharField(default="xak-flow-redirect")
class ShellChallenge(Challenge): class ShellChallenge(Challenge):
"""Legacy challenge type to render HTML as-is""" """challenge type to render HTML as-is"""
body = CharField() body = CharField()
component = CharField(default="xak-flow-shell")
class WithUserInfoChallenge(Challenge): class WithUserInfoChallenge(Challenge):
@ -67,6 +69,7 @@ class AccessDeniedChallenge(Challenge):
"""Challenge when a flow's active stage calls `stage_invalid()`.""" """Challenge when a flow's active stage calls `stage_invalid()`."""
error_message = CharField(required=False) error_message = CharField(required=False)
component = CharField(default="ak-stage-access-denied")
class PermissionSerializer(PassiveSerializer): class PermissionSerializer(PassiveSerializer):
@ -80,6 +83,7 @@ class ChallengeResponse(PassiveSerializer):
"""Base class for all challenge responses""" """Base class for all challenge responses"""
stage: Optional["StageView"] stage: Optional["StageView"]
component = CharField(default="")
def __init__(self, instance=None, data=None, **kwargs): def __init__(self, instance=None, data=None, **kwargs):
self.stage = kwargs.pop("stage", None) self.stage = kwargs.pop("stage", None)

View file

@ -11,7 +11,12 @@ from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import View from django.views.generic import View
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema from drf_spectacular.utils import (
OpenApiParameter,
OpenApiResponse,
PolymorphicProxySerializer,
extend_schema,
)
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.views import APIView from rest_framework.views import APIView
from sentry_sdk import capture_exception from sentry_sdk import capture_exception
@ -22,10 +27,12 @@ from authentik.events.models import cleanse_dict
from authentik.flows.challenge import ( from authentik.flows.challenge import (
AccessDeniedChallenge, AccessDeniedChallenge,
Challenge, Challenge,
ChallengeResponse,
ChallengeTypes, ChallengeTypes,
HttpChallengeResponse, HttpChallengeResponse,
RedirectChallenge, RedirectChallenge,
ShellChallenge, ShellChallenge,
WithUserInfoChallenge,
) )
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.models import ConfigurableStage, Flow, FlowDesignation, Stage from authentik.flows.models import ConfigurableStage, Flow, FlowDesignation, Stage
@ -35,7 +42,7 @@ from authentik.flows.planner import (
FlowPlan, FlowPlan,
FlowPlanner, FlowPlanner,
) )
from authentik.lib.utils.reflection import class_to_path from authentik.lib.utils.reflection import all_subclasses, class_to_path
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
LOGGER = get_logger() LOGGER = get_logger()
@ -46,6 +53,43 @@ SESSION_KEY_APPLICATION_PRE = "authentik_flows_application_pre"
SESSION_KEY_GET = "authentik_flows_get" SESSION_KEY_GET = "authentik_flows_get"
def challenge_types():
"""This is a workaround for PolymorphicProxySerializer not accepting a callable for
`serializers`. This function returns a class which is an iterator, which returns the
subclasses of Challenge, and Challenge itself."""
class Inner(dict):
"""dummy class with custom callback on .items()"""
def items(self):
mapping = {}
classes = all_subclasses(Challenge)
classes.remove(WithUserInfoChallenge)
for cls in classes:
mapping[cls().fields["component"].default] = cls
return mapping.items()
return Inner()
def challenge_response_types():
"""This is a workaround for PolymorphicProxySerializer not accepting a callable for
`serializers`. This function returns a class which is an iterator, which returns the
subclasses of Challenge, and Challenge itself."""
class Inner(dict):
"""dummy class with custom callback on .items()"""
def items(self):
mapping = {}
classes = all_subclasses(ChallengeResponse)
for cls in classes:
mapping[cls(stage=None).fields["component"].default] = cls
return mapping.items()
return Inner()
@method_decorator(xframe_options_sameorigin, name="dispatch") @method_decorator(xframe_options_sameorigin, name="dispatch")
class FlowExecutorView(APIView): class FlowExecutorView(APIView):
"""Stage 1 Flow executor, passing requests to Stage Views""" """Stage 1 Flow executor, passing requests to Stage Views"""
@ -126,7 +170,11 @@ class FlowExecutorView(APIView):
@extend_schema( @extend_schema(
responses={ responses={
200: Challenge(), 200: PolymorphicProxySerializer(
component_name="Challenge",
serializers=challenge_types(),
resource_type_field_name="component",
),
404: OpenApiResponse( 404: OpenApiResponse(
description="No Token found" description="No Token found"
), # This error can be raised by the email stage ), # This error can be raised by the email stage
@ -159,8 +207,18 @@ class FlowExecutorView(APIView):
return to_stage_response(request, FlowErrorResponse(request, exc)) return to_stage_response(request, FlowErrorResponse(request, exc))
@extend_schema( @extend_schema(
responses={200: Challenge()}, responses={
request=OpenApiTypes.OBJECT, 200: PolymorphicProxySerializer(
component_name="Challenge",
serializers=challenge_types(),
resource_type_field_name="component",
),
},
request=PolymorphicProxySerializer(
component_name="ChallengeResponse",
serializers=challenge_response_types(),
resource_type_field_name="component",
),
parameters=[ parameters=[
OpenApiParameter( OpenApiParameter(
name="query", name="query",

View file

@ -34,6 +34,7 @@ class AutosubmitChallenge(Challenge):
url = CharField() url = CharField()
attrs = DictField(child=CharField()) attrs = DictField(child=CharField())
component = CharField(default="ak-stage-autosubmit")
# This View doesn't have a URL on purpose, as its called by the FlowExecutor # This View doesn't have a URL on purpose, as its called by the FlowExecutor

View file

@ -17,6 +17,7 @@ class PlexAuthenticationChallenge(Challenge):
client_id = CharField() client_id = CharField()
slug = CharField() slug = CharField()
component = CharField(default="ak-flow-sources-plex")
class PlexSource(Source): class PlexSource(Source):

View file

@ -25,6 +25,7 @@ class AuthenticatorDuoChallenge(WithUserInfoChallenge):
activation_barcode = CharField() activation_barcode = CharField()
activation_code = CharField() activation_code = CharField()
stage_uuid = CharField() stage_uuid = CharField()
component = CharField(default="ak-stage-authenticator-duo")
class AuthenticatorDuoStageView(ChallengeStageView): class AuthenticatorDuoStageView(ChallengeStageView):
@ -42,7 +43,6 @@ class AuthenticatorDuoStageView(ChallengeStageView):
return AuthenticatorDuoChallenge( return AuthenticatorDuoChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-authenticator-duo",
"activation_barcode": enroll["activation_barcode"], "activation_barcode": enroll["activation_barcode"],
"activation_code": enroll["activation_code"], "activation_code": enroll["activation_code"],
"stage_uuid": stage.stage_uuid, "stage_uuid": stage.stage_uuid,

View file

@ -22,6 +22,7 @@ class AuthenticatorStaticChallenge(WithUserInfoChallenge):
"""Static authenticator challenge""" """Static authenticator challenge"""
codes = ListField(child=CharField()) codes = ListField(child=CharField())
component = CharField(default="ak-stage-authenticator-static")
class AuthenticatorStaticStageView(ChallengeStageView): class AuthenticatorStaticStageView(ChallengeStageView):
@ -32,7 +33,6 @@ class AuthenticatorStaticStageView(ChallengeStageView):
return AuthenticatorStaticChallenge( return AuthenticatorStaticChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-authenticator-static",
"codes": [token.token for token in tokens], "codes": [token.token for token in tokens],
} }
) )

View file

@ -25,6 +25,7 @@ class AuthenticatorTOTPChallenge(WithUserInfoChallenge):
"""TOTP Setup challenge""" """TOTP Setup challenge"""
config_url = CharField() config_url = CharField()
component = CharField(default="ak-stage-authenticator-totp")
class AuthenticatorTOTPChallengeResponse(ChallengeResponse): class AuthenticatorTOTPChallengeResponse(ChallengeResponse):
@ -33,6 +34,7 @@ class AuthenticatorTOTPChallengeResponse(ChallengeResponse):
device: TOTPDevice device: TOTPDevice
code = IntegerField() code = IntegerField()
component = CharField(default="ak-stage-authenticator-totp")
def validate_code(self, code: int) -> int: def validate_code(self, code: int) -> int:
"""Validate totp code""" """Validate totp code"""
@ -52,7 +54,6 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
return AuthenticatorTOTPChallenge( return AuthenticatorTOTPChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-authenticator-totp",
"config_url": device.config_url, "config_url": device.config_url,
} }
) )

View file

@ -30,18 +30,20 @@ LOGGER = get_logger()
PER_DEVICE_CLASSES = [DeviceClasses.WEBAUTHN] PER_DEVICE_CLASSES = [DeviceClasses.WEBAUTHN]
class AuthenticatorChallenge(WithUserInfoChallenge): class AuthenticatorValidationChallenge(WithUserInfoChallenge):
"""Authenticator challenge""" """Authenticator challenge"""
device_challenges = ListField(child=DeviceChallenge()) device_challenges = ListField(child=DeviceChallenge())
component = CharField(default="ak-stage-authenticator-validate")
class AuthenticatorChallengeResponse(ChallengeResponse): class AuthenticatorValidationChallengeResponse(ChallengeResponse):
"""Challenge used for Code-based and WebAuthn authenticators""" """Challenge used for Code-based and WebAuthn authenticators"""
code = CharField(required=False) code = CharField(required=False)
webauthn = JSONField(required=False) webauthn = JSONField(required=False)
duo = IntegerField(required=False) duo = IntegerField(required=False)
component = CharField(default="ak-stage-authenticator-validate")
def _challenge_allowed(self, classes: list): def _challenge_allowed(self, classes: list):
device_challenges: list[dict] = self.stage.request.session.get( device_challenges: list[dict] = self.stage.request.session.get(
@ -83,7 +85,7 @@ class AuthenticatorChallengeResponse(ChallengeResponse):
class AuthenticatorValidateStageView(ChallengeStageView): class AuthenticatorValidateStageView(ChallengeStageView):
"""Authenticator Validation""" """Authenticator Validation"""
response_class = AuthenticatorChallengeResponse response_class = AuthenticatorValidationChallengeResponse
def get_device_challenges(self) -> list[dict]: def get_device_challenges(self) -> list[dict]:
"""Get a list of all device challenges applicable for the current stage""" """Get a list of all device challenges applicable for the current stage"""
@ -144,19 +146,18 @@ class AuthenticatorValidateStageView(ChallengeStageView):
return self.executor.stage_ok() return self.executor.stage_ok()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_challenge(self) -> AuthenticatorChallenge: def get_challenge(self) -> AuthenticatorValidationChallenge:
challenges = self.request.session["device_challenges"] challenges = self.request.session["device_challenges"]
return AuthenticatorChallenge( return AuthenticatorValidationChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-authenticator-validate",
"device_challenges": challenges, "device_challenges": challenges,
} }
) )
# pylint: disable=unused-argument # pylint: disable=unused-argument
def challenge_valid( def challenge_valid(
self, challenge: AuthenticatorChallengeResponse self, challenge: AuthenticatorValidationChallengeResponse
) -> HttpResponse: ) -> HttpResponse:
# All validation is done by the serializer # All validation is done by the serializer
return self.executor.stage_ok() return self.executor.stage_ok()

View file

@ -2,7 +2,7 @@
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict from django.http.request import QueryDict
from rest_framework.fields import JSONField from rest_framework.fields import CharField, JSONField
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.webauthn import ( from webauthn.webauthn import (
@ -41,12 +41,14 @@ class AuthenticatorWebAuthnChallenge(WithUserInfoChallenge):
"""WebAuthn Challenge""" """WebAuthn Challenge"""
registration = JSONField() registration = JSONField()
component = CharField(default="ak-stage-authenticator-webauthn")
class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse): class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
"""WebAuthn Challenge response""" """WebAuthn Challenge response"""
response = JSONField() response = JSONField()
component = CharField(default="ak-stage-authenticator-webauthn")
request: HttpRequest request: HttpRequest
user: User user: User
@ -134,7 +136,6 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
return AuthenticatorWebAuthnChallenge( return AuthenticatorWebAuthnChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-authenticator-webauthn",
"registration": registration_dict, "registration": registration_dict,
} }
) )

View file

@ -21,12 +21,14 @@ class CaptchaChallenge(WithUserInfoChallenge):
"""Site public key""" """Site public key"""
site_key = CharField() site_key = CharField()
component = CharField(default="ak-stage-captcha")
class CaptchaChallengeResponse(ChallengeResponse): class CaptchaChallengeResponse(ChallengeResponse):
"""Validate captcha token""" """Validate captcha token"""
token = CharField() token = CharField()
component = CharField(default="ak-stage-captcha")
def validate_token(self, token: str) -> str: def validate_token(self, token: str) -> str:
"""Validate captcha token""" """Validate captcha token"""
@ -64,7 +66,6 @@ class CaptchaStageView(ChallengeStageView):
return CaptchaChallenge( return CaptchaChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-captcha",
"site_key": self.executor.current_stage.public_key, "site_key": self.executor.current_stage.public_key,
} }
) )

View file

@ -25,11 +25,14 @@ class ConsentChallenge(WithUserInfoChallenge):
header_text = CharField() header_text = CharField()
permissions = PermissionSerializer(many=True) permissions = PermissionSerializer(many=True)
component = CharField(default="ak-stage-consent")
class ConsentChallengeResponse(ChallengeResponse): class ConsentChallengeResponse(ChallengeResponse):
"""Consent challenge response, any valid response request is valid""" """Consent challenge response, any valid response request is valid"""
component = CharField(default="ak-stage-consent")
class ConsentStageView(ChallengeStageView): class ConsentStageView(ChallengeStageView):
"""Simple consent checker.""" """Simple consent checker."""
@ -40,7 +43,6 @@ class ConsentStageView(ChallengeStageView):
challenge = ConsentChallenge( challenge = ConsentChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-consent",
} }
) )
if PLAN_CONTEXT_CONSENT_TITLE in self.executor.plan.context: if PLAN_CONTEXT_CONSENT_TITLE in self.executor.plan.context:

View file

@ -1,5 +1,6 @@
"""authentik multi-stage authentication engine""" """authentik multi-stage authentication engine"""
from django.http.response import HttpResponse from django.http.response import HttpResponse
from rest_framework.fields import CharField
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
@ -8,10 +9,14 @@ from authentik.flows.stage import ChallengeStageView
class DummyChallenge(Challenge): class DummyChallenge(Challenge):
"""Dummy challenge""" """Dummy challenge"""
component = CharField(default="ak-stage-dummy")
class DummyChallengeResponse(ChallengeResponse): class DummyChallengeResponse(ChallengeResponse):
"""Dummy challenge response""" """Dummy challenge response"""
component = CharField(default="ak-stage-dummy")
class DummyStageView(ChallengeStageView): class DummyStageView(ChallengeStageView):
"""Dummy stage for testing with multiple stages""" """Dummy stage for testing with multiple stages"""
@ -25,7 +30,6 @@ class DummyStageView(ChallengeStageView):
return DummyChallenge( return DummyChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-dummy",
"title": self.executor.current_stage.name, "title": self.executor.current_stage.name,
} }
) )

View file

@ -8,6 +8,7 @@ from django.urls import reverse
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
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 structlog.stdlib import get_logger
@ -28,11 +29,15 @@ PLAN_CONTEXT_EMAIL_SENT = "email_sent"
class EmailChallenge(Challenge): class EmailChallenge(Challenge):
"""Email challenge""" """Email challenge"""
component = CharField(default="ak-stage-email")
class EmailChallengeResponse(ChallengeResponse): class EmailChallengeResponse(ChallengeResponse):
"""Email challenge resposen. No fields. This challenge is """Email challenge resposen. No fields. This challenge is
always declared invalid to give the user a chance to retry""" always declared invalid to give the user a chance to retry"""
component = CharField(default="ak-stage-email")
def validate(self, data): def validate(self, data):
raise ValidationError("") raise ValidationError("")
@ -97,7 +102,6 @@ class EmailStageView(ChallengeStageView):
challenge = EmailChallenge( challenge = EmailChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-email",
"title": "Email sent.", "title": "Email sent.",
} }
) )

View file

@ -36,11 +36,15 @@ class IdentificationChallenge(Challenge):
primary_action = CharField() primary_action = CharField()
sources = UILoginButtonSerializer(many=True, required=False) sources = UILoginButtonSerializer(many=True, required=False)
component = CharField(default="ak-stage-identification")
class IdentificationChallengeResponse(ChallengeResponse): class IdentificationChallengeResponse(ChallengeResponse):
"""Identification challenge""" """Identification challenge"""
uid_field = CharField() uid_field = CharField()
component = CharField(default="ak-stage-identification")
pre_user: Optional[User] = None pre_user: Optional[User] = None
def validate_uid_field(self, value: str) -> str: def validate_uid_field(self, value: str) -> str:
@ -81,7 +85,6 @@ class IdentificationStageView(ChallengeStageView):
challenge = IdentificationChallenge( challenge = IdentificationChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"primary_action": _("Log in"), "primary_action": _("Log in"),
"user_fields": current_stage.user_fields, "user_fields": current_stage.user_fields,
} }

View file

@ -63,12 +63,16 @@ class PasswordChallenge(WithUserInfoChallenge):
recovery_url = CharField(required=False) recovery_url = CharField(required=False)
component = CharField(default="ak-stage-password")
class PasswordChallengeResponse(ChallengeResponse): class PasswordChallengeResponse(ChallengeResponse):
"""Password challenge response""" """Password challenge response"""
password = CharField() password = CharField()
component = CharField(default="ak-stage-password")
class PasswordStageView(ChallengeStageView): class PasswordStageView(ChallengeStageView):
"""Authentication stage which authenticates against django's AuthBackend""" """Authentication stage which authenticates against django's AuthBackend"""
@ -79,7 +83,6 @@ class PasswordStageView(ChallengeStageView):
challenge = PasswordChallenge( challenge = PasswordChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-password",
} }
) )
recovery_flow = Flow.objects.filter(designation=FlowDesignation.RECOVERY) recovery_flow = Flow.objects.filter(designation=FlowDesignation.RECOVERY)

View file

@ -26,7 +26,7 @@ LOGGER = get_logger()
PLAN_CONTEXT_PROMPT = "prompt_data" PLAN_CONTEXT_PROMPT = "prompt_data"
class PromptSerializer(PassiveSerializer): class StagePromptSerializer(PassiveSerializer):
"""Serializer for a single Prompt field""" """Serializer for a single Prompt field"""
field_key = CharField() field_key = CharField()
@ -40,17 +40,22 @@ class PromptSerializer(PassiveSerializer):
class PromptChallenge(Challenge): class PromptChallenge(Challenge):
"""Initial challenge being sent, define fields""" """Initial challenge being sent, define fields"""
fields = PromptSerializer(many=True) fields = StagePromptSerializer(many=True)
component = CharField(default="ak-stage-prompt")
class PromptResponseChallenge(ChallengeResponse): class PromptResponseChallenge(ChallengeResponse):
"""Validate response, fields are dynamically created based """Validate response, fields are dynamically created based
on the stage""" on the stage"""
def __init__(self, *args, stage: PromptStage, plan: FlowPlan, **kwargs): component = CharField(default="ak-stage-prompt")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.stage = stage self.stage: PromptStage = kwargs.pop("stage", None)
self.plan = plan self.plan: FlowPlan = kwargs.pop("plan", None)
if not self.stage:
return
# list() is called so we only load the fields once # list() is called so we only load the fields once
fields = list(self.stage.fields.all()) fields = list(self.stage.fields.all())
for field in fields: for field in fields:
@ -159,8 +164,7 @@ class PromptStageView(ChallengeStageView):
challenge = PromptChallenge( challenge = PromptChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-prompt", "fields": [StagePromptSerializer(field).data for field in fields],
"fields": [PromptSerializer(field).data for field in fields],
}, },
) )
return challenge return challenge

View file

@ -3550,16 +3550,13 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type: object $ref: '#/components/schemas/ChallengeResponseRequest'
additionalProperties: {}
application/x-www-form-urlencoded: application/x-www-form-urlencoded:
schema: schema:
type: object $ref: '#/components/schemas/ChallengeResponseRequest'
additionalProperties: {}
multipart/form-data: multipart/form-data:
schema: schema:
type: object $ref: '#/components/schemas/ChallengeResponseRequest'
additionalProperties: {}
security: security:
- authentik: [] - authentik: []
- cookieAuth: [] - cookieAuth: []
@ -14924,6 +14921,29 @@ paths:
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
components: components:
schemas: schemas:
AccessDeniedChallenge:
type: object
description: Challenge when a flow's active stage calls `stage_invalid()`.
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-access-denied
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
error_message:
type: string
required:
- type
ActionEnum: ActionEnum:
enum: enum:
- login - login
@ -15138,6 +15158,42 @@ components:
If empty, user will not be able to configure this stage. If empty, user will not be able to configure this stage.
required: required:
- name - name
AuthenticatorDuoChallenge:
type: object
description: Duo Challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-authenticator-duo
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
activation_barcode:
type: string
activation_code:
type: string
stage_uuid:
type: string
required:
- activation_barcode
- activation_code
- pending_user
- pending_user_avatar
- stage_uuid
- type
AuthenticatorDuoStage: AuthenticatorDuoStage:
type: object type: object
description: AuthenticatorDuoStage Serializer description: AuthenticatorDuoStage Serializer
@ -15208,6 +15264,38 @@ components:
- client_id - client_id
- client_secret - client_secret
- name - name
AuthenticatorStaticChallenge:
type: object
description: Static authenticator challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-authenticator-static
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
codes:
type: array
items:
type: string
required:
- codes
- pending_user
- pending_user_avatar
- type
AuthenticatorStaticStage: AuthenticatorStaticStage:
type: object type: object
description: AuthenticatorStaticStage Serializer description: AuthenticatorStaticStage Serializer
@ -15270,6 +15358,47 @@ components:
minimum: -2147483648 minimum: -2147483648
required: required:
- name - name
AuthenticatorTOTPChallenge:
type: object
description: TOTP Setup challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-authenticator-totp
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
config_url:
type: string
required:
- config_url
- pending_user
- pending_user_avatar
- type
AuthenticatorTOTPChallengeResponseRequest:
type: object
description: TOTP Challenge response, device is set by get_response_instance
properties:
component:
type: string
default: ak-stage-authenticator-totp
code:
type: integer
required:
- code
AuthenticatorTOTPStage: AuthenticatorTOTPStage:
type: object type: object
description: AuthenticatorTOTPStage Serializer description: AuthenticatorTOTPStage Serializer
@ -15406,6 +15535,124 @@ components:
is not prompted again. is not prompted again.
required: required:
- name - name
AuthenticatorValidationChallenge:
type: object
description: Authenticator challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-authenticator-validate
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
device_challenges:
type: array
items:
$ref: '#/components/schemas/DeviceChallenge'
required:
- device_challenges
- pending_user
- pending_user_avatar
- type
AuthenticatorValidationChallengeResponseRequest:
type: object
description: Challenge used for Code-based and WebAuthn authenticators
properties:
component:
type: string
default: ak-stage-authenticator-validate
code:
type: string
webauthn:
type: object
additionalProperties: {}
duo:
type: integer
AuthenticatorWebAuthnChallenge:
type: object
description: WebAuthn Challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-authenticator-webauthn
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
registration:
type: object
additionalProperties: {}
required:
- pending_user
- pending_user_avatar
- registration
- type
AuthenticatorWebAuthnChallengeResponseRequest:
type: object
description: WebAuthn Challenge response
properties:
component:
type: string
default: ak-stage-authenticator-webauthn
response:
type: object
additionalProperties: {}
required:
- response
AutosubmitChallenge:
type: object
description: Autosubmit challenge used to send and navigate a POST request
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-autosubmit
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
url:
type: string
attrs:
type: object
additionalProperties:
type: string
required:
- attrs
- type
- url
BackendsEnum: BackendsEnum:
enum: enum:
- django.contrib.auth.backends.ModelBackend - django.contrib.auth.backends.ModelBackend
@ -15430,6 +15677,47 @@ components:
enum: enum:
- can_save_media - can_save_media
type: string type: string
CaptchaChallenge:
type: object
description: Site public key
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-captcha
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
site_key:
type: string
required:
- pending_user
- pending_user_avatar
- site_key
- type
CaptchaChallengeResponseRequest:
type: object
description: Validate captcha token
properties:
component:
type: string
default: ak-stage-captcha
token:
type: string
required:
- token
CaptchaStage: CaptchaStage:
type: object type: object
description: CaptchaStage Serializer description: CaptchaStage Serializer
@ -15557,33 +15845,75 @@ components:
- certificate_data - certificate_data
- name - name
Challenge: Challenge:
type: object oneOf:
description: |- - $ref: '#/components/schemas/AccessDeniedChallenge'
Challenge that gets sent to the client based on which stage - $ref: '#/components/schemas/AuthenticatorDuoChallenge'
is currently active - $ref: '#/components/schemas/AuthenticatorStaticChallenge'
properties: - $ref: '#/components/schemas/AuthenticatorTOTPChallenge'
type: - $ref: '#/components/schemas/AuthenticatorValidationChallenge'
$ref: '#/components/schemas/ChallengeChoices' - $ref: '#/components/schemas/AuthenticatorWebAuthnChallenge'
component: - $ref: '#/components/schemas/AutosubmitChallenge'
type: string - $ref: '#/components/schemas/CaptchaChallenge'
title: - $ref: '#/components/schemas/ConsentChallenge'
type: string - $ref: '#/components/schemas/DummyChallenge'
background: - $ref: '#/components/schemas/EmailChallenge'
type: string - $ref: '#/components/schemas/IdentificationChallenge'
response_errors: - $ref: '#/components/schemas/PasswordChallenge'
type: object - $ref: '#/components/schemas/PlexAuthenticationChallenge'
additionalProperties: - $ref: '#/components/schemas/PromptChallenge'
type: array - $ref: '#/components/schemas/RedirectChallenge'
items: - $ref: '#/components/schemas/ShellChallenge'
$ref: '#/components/schemas/ErrorDetail' discriminator:
required: propertyName: component
- type mapping:
ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge'
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge'
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallenge'
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallenge'
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallenge'
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallenge'
ak-stage-autosubmit: '#/components/schemas/AutosubmitChallenge'
ak-stage-captcha: '#/components/schemas/CaptchaChallenge'
ak-stage-consent: '#/components/schemas/ConsentChallenge'
ak-stage-dummy: '#/components/schemas/DummyChallenge'
ak-stage-email: '#/components/schemas/EmailChallenge'
ak-stage-identification: '#/components/schemas/IdentificationChallenge'
ak-stage-password: '#/components/schemas/PasswordChallenge'
ak-flow-sources-plex: '#/components/schemas/PlexAuthenticationChallenge'
ak-stage-prompt: '#/components/schemas/PromptChallenge'
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
xak-flow-shell: '#/components/schemas/ShellChallenge'
ChallengeChoices: ChallengeChoices:
enum: enum:
- native - native
- shell - shell
- redirect - redirect
type: string type: string
ChallengeResponseRequest:
oneOf:
- $ref: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
- $ref: '#/components/schemas/CaptchaChallengeResponseRequest'
- $ref: '#/components/schemas/ConsentChallengeResponseRequest'
- $ref: '#/components/schemas/DummyChallengeResponseRequest'
- $ref: '#/components/schemas/EmailChallengeResponseRequest'
- $ref: '#/components/schemas/IdentificationChallengeResponseRequest'
- $ref: '#/components/schemas/PasswordChallengeResponseRequest'
- $ref: '#/components/schemas/PromptResponseChallengeRequest'
discriminator:
propertyName: component
mapping:
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
ak-stage-captcha: '#/components/schemas/CaptchaChallengeResponseRequest'
ak-stage-consent: '#/components/schemas/ConsentChallengeResponseRequest'
ak-stage-dummy: '#/components/schemas/DummyChallengeResponseRequest'
ak-stage-email: '#/components/schemas/EmailChallengeResponseRequest'
ak-stage-identification: '#/components/schemas/IdentificationChallengeResponseRequest'
ak-stage-password: '#/components/schemas/PasswordChallengeResponseRequest'
ak-stage-prompt: '#/components/schemas/PromptResponseChallengeRequest'
ClientTypeEnum: ClientTypeEnum:
enum: enum:
- confidential - confidential
@ -15625,6 +15955,48 @@ components:
- error_reporting_environment - error_reporting_environment
- error_reporting_send_pii - error_reporting_send_pii
- ui_footer_links - ui_footer_links
ConsentChallenge:
type: object
description: Challenge info for consent screens
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-consent
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
header_text:
type: string
permissions:
type: array
items:
$ref: '#/components/schemas/Permission'
required:
- header_text
- pending_user
- pending_user_avatar
- permissions
- type
ConsentChallengeResponseRequest:
type: object
description: Consent challenge response, any valid response request is valid
properties:
component:
type: string
default: ak-stage-consent
ConsentStage: ConsentStage:
type: object type: object
description: ConsentStage Serializer description: ConsentStage Serializer
@ -15740,6 +16112,21 @@ components:
$ref: '#/components/schemas/FlowRequest' $ref: '#/components/schemas/FlowRequest'
required: required:
- name - name
DeviceChallenge:
type: object
description: Single device challenge
properties:
device_class:
type: string
device_uid:
type: string
challenge:
type: object
additionalProperties: {}
required:
- challenge
- device_class
- device_uid
DeviceClassesEnum: DeviceClassesEnum:
enum: enum:
- static - static
@ -15837,6 +16224,34 @@ components:
required: required:
- name - name
- url - url
DummyChallenge:
type: object
description: Dummy challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-dummy
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
required:
- type
DummyChallengeResponseRequest:
type: object
description: Dummy challenge response
properties:
component:
type: string
default: ak-stage-dummy
DummyPolicy: DummyPolicy:
type: object type: object
description: Dummy Policy Serializer description: Dummy Policy Serializer
@ -15944,6 +16359,36 @@ components:
$ref: '#/components/schemas/FlowRequest' $ref: '#/components/schemas/FlowRequest'
required: required:
- name - name
EmailChallenge:
type: object
description: Email challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-email
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
required:
- type
EmailChallengeResponseRequest:
type: object
description: |-
Email challenge resposen. No fields. This challenge is
always declared invalid to give the user a chance to retry
properties:
component:
type: string
default: ak-stage-email
EmailStage: EmailStage:
type: object type: object
description: EmailStage Serializer description: EmailStage Serializer
@ -16640,6 +17085,57 @@ components:
minimum: -2147483648 minimum: -2147483648
required: required:
- ip - ip
IdentificationChallenge:
type: object
description: Identification challenges with all UI elements
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-identification
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
user_fields:
type: array
items:
type: string
nullable: true
application_pre:
type: string
enroll_url:
type: string
recovery_url:
type: string
primary_action:
type: string
sources:
type: array
items:
$ref: '#/components/schemas/UILoginButton'
required:
- primary_action
- type
- user_fields
IdentificationChallengeResponseRequest:
type: object
description: Identification challenge
properties:
component:
type: string
default: ak-stage-identification
uid_field:
type: string
required:
- uid_field
IdentificationStage: IdentificationStage:
type: object type: object
description: IdentificationStage Serializer description: IdentificationStage Serializer
@ -20375,6 +20871,46 @@ components:
required: required:
- pagination - pagination
- results - results
PasswordChallenge:
type: object
description: Password challenge UI fields
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-password
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
recovery_url:
type: string
required:
- pending_user
- pending_user_avatar
- type
PasswordChallengeResponseRequest:
type: object
description: Password challenge response
properties:
component:
type: string
default: ak-stage-password
password:
type: string
required:
- password
PasswordExpiryPolicy: PasswordExpiryPolicy:
type: object type: object
description: Password Expiry Policy Serializer description: Password Expiry Policy Serializer
@ -22038,6 +22574,44 @@ components:
name: name:
type: string type: string
maxLength: 200 maxLength: 200
Permission:
type: object
description: Permission used for consent
properties:
name:
type: string
id:
type: string
required:
- id
- name
PlexAuthenticationChallenge:
type: object
description: Challenge shown to the user in identification stage
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-flow-sources-plex
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
client_id:
type: string
slug:
type: string
required:
- client_id
- slug
- type
PlexSource: PlexSource:
type: object type: object
description: Plex Source Serializer description: Plex Source Serializer
@ -22359,6 +22933,32 @@ components:
- label - label
- pk - pk
- type - type
PromptChallenge:
type: object
description: Initial challenge being sent, define fields
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: ak-stage-prompt
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
fields:
type: array
items:
$ref: '#/components/schemas/StagePrompt'
required:
- fields
- type
PromptRequest: PromptRequest:
type: object type: object
description: Prompt Serializer description: Prompt Serializer
@ -22388,6 +22988,15 @@ components:
- field_key - field_key
- label - label
- type - type
PromptResponseChallengeRequest:
type: object
description: |-
Validate response, fields are dynamically created based
on the stage
properties:
component:
type: string
default: ak-stage-prompt
PromptStage: PromptStage:
type: object type: object
description: PromptStage Serializer description: PromptStage Serializer
@ -22789,12 +23398,13 @@ components:
properties: properties:
type: type:
$ref: '#/components/schemas/ChallengeChoices' $ref: '#/components/schemas/ChallengeChoices'
component:
type: string
title: title:
type: string type: string
background: background:
type: string type: string
component:
type: string
default: xak-flow-redirect
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -23462,6 +24072,30 @@ components:
- warning - warning
- alert - alert
type: string type: string
ShellChallenge:
type: object
description: challenge type to render HTML as-is
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
title:
type: string
background:
type: string
component:
type: string
default: xak-flow-shell
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
body:
type: string
required:
- body
- type
SignatureAlgorithmEnum: SignatureAlgorithmEnum:
enum: enum:
- http://www.w3.org/2000/09/xmldsig#rsa-sha1 - http://www.w3.org/2000/09/xmldsig#rsa-sha1
@ -23591,6 +24225,29 @@ components:
- pk - pk
- verbose_name - verbose_name
- verbose_name_plural - verbose_name_plural
StagePrompt:
type: object
description: Serializer for a single Prompt field
properties:
field_key:
type: string
label:
type: string
type:
type: string
required:
type: boolean
placeholder:
type: string
order:
type: integer
required:
- field_key
- label
- order
- placeholder
- required
- type
StageRequest: StageRequest:
type: object type: object
description: Stage Serializer description: Stage Serializer
@ -23818,6 +24475,21 @@ components:
- description - description
- model_name - model_name
- name - name
UILoginButton:
type: object
description: Serializer for Login buttons of sources
properties:
name:
type: string
challenge:
type: object
additionalProperties: {}
icon_url:
type: string
nullable: true
required:
- challenge
- name
User: User:
type: object type: object
description: User Serializer description: User Serializer

View file

@ -8,23 +8,3 @@ export interface Error {
export interface ErrorDict { export interface ErrorDict {
[key: string]: Error[]; [key: string]: Error[];
} }
export interface Challenge {
type: ChallengeChoices;
component?: string;
title?: string;
response_errors?: ErrorDict;
}
export interface WithUserInfoChallenge extends Challenge {
pending_user: string;
pending_user_avatar: string;
}
export interface ShellChallenge extends Challenge {
body: string;
}
export interface RedirectChallenge extends Challenge {
to: string;
}

View file

@ -25,29 +25,16 @@ import "./stages/identification/IdentificationStage";
import "./stages/password/PasswordStage"; import "./stages/password/PasswordStage";
import "./stages/prompt/PromptStage"; import "./stages/prompt/PromptStage";
import "./sources/plex/PlexLoginInit"; import "./sources/plex/PlexLoginInit";
import { ShellChallenge, RedirectChallenge } from "../api/Flows";
import { IdentificationChallenge } from "./stages/identification/IdentificationStage";
import { PasswordChallenge } from "./stages/password/PasswordStage";
import { ConsentChallenge } from "./stages/consent/ConsentStage";
import { EmailChallenge } from "./stages/email/EmailStage";
import { AutosubmitChallenge } from "./stages/autosubmit/AutosubmitStage";
import { PromptChallenge } from "./stages/prompt/PromptStage";
import { AuthenticatorTOTPChallenge } from "./stages/authenticator_totp/AuthenticatorTOTPStage";
import { AuthenticatorStaticChallenge } from "./stages/authenticator_static/AuthenticatorStaticStage";
import { AuthenticatorValidateStageChallenge } from "./stages/authenticator_validate/AuthenticatorValidateStage";
import { WebAuthnAuthenticatorRegisterChallenge } from "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
import { CaptchaChallenge } from "./stages/captcha/CaptchaStage";
import { StageHost } from "./stages/base"; import { StageHost } from "./stages/base";
import { Challenge, ChallengeChoices, Config, FlowsApi } from "authentik-api"; import { Challenge, ChallengeChoices, Config, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api";
import { config, DEFAULT_CONFIG } from "../api/Config"; import { config, DEFAULT_CONFIG } from "../api/Config";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { AccessDeniedChallenge } from "./access_denied/FlowAccessDenied";
import { PFSize } from "../elements/Spinner"; import { PFSize } from "../elements/Spinner";
import { TITLE_DEFAULT } from "../constants"; import { TITLE_DEFAULT } from "../constants";
import { configureSentry } from "../api/Sentry"; import { configureSentry } from "../api/Sentry";
import { PlexAuthenticationChallenge } from "./sources/plex/PlexLoginInit"; import { ChallengeResponseRequest } from "authentik-api/dist/models/ChallengeResponseRequest";
import { AuthenticatorDuoChallenge } from "./stages/authenticator_duo/AuthenticatorDuoStage";
@customElement("ak-flow-executor") @customElement("ak-flow-executor")
export class FlowExecutor extends LitElement implements StageHost { export class FlowExecutor extends LitElement implements StageHost {
@ -112,18 +99,18 @@ export class FlowExecutor extends LitElement implements StageHost {
}); });
} }
submit<T>(formData?: T): Promise<void> { submit(payload: ChallengeResponseRequest): Promise<void> {
payload.component = this.challenge.component;
this.loading = true; this.loading = true;
return new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolveRaw({ return new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
flowSlug: this.flowSlug, flowSlug: this.flowSlug,
requestBody: formData || {},
query: window.location.search.substring(1), query: window.location.search.substring(1),
}).then((challengeRaw) => { challengeResponseRequest: payload,
return challengeRaw.raw.json();
}).then((data) => { }).then((data) => {
this.challenge = data; this.challenge = data;
this.postUpdate(); this.postUpdate();
}).catch((e: Response) => { }).catch((e: Response) => {
console.debug(e);
this.errorMessage(e.statusText); this.errorMessage(e.statusText);
}).finally(() => { }).finally(() => {
this.loading = false; this.loading = false;
@ -135,19 +122,18 @@ export class FlowExecutor extends LitElement implements StageHost {
this.config = config; this.config = config;
}); });
this.loading = true; this.loading = true;
new FlowsApi(DEFAULT_CONFIG).flowsExecutorGetRaw({ new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({
flowSlug: this.flowSlug, flowSlug: this.flowSlug,
query: window.location.search.substring(1), query: window.location.search.substring(1),
}).then((challengeRaw) => {
return challengeRaw.raw.json();
}).then((challenge) => { }).then((challenge) => {
this.challenge = challenge as Challenge; this.challenge = challenge;
// Only set background on first update, flow won't change throughout execution // Only set background on first update, flow won't change throughout execution
if (this.challenge?.background) { if (this.challenge?.background) {
this.setBackground(this.challenge.background); this.setBackground(this.challenge.background);
} }
this.postUpdate(); this.postUpdate();
}).catch((e: Response) => { }).catch((e: Response) => {
console.debug(e);
// Catch JSON or Update errors // Catch JSON or Update errors
this.errorMessage(e.statusText); this.errorMessage(e.statusText);
}).finally(() => { }).finally(() => {
@ -202,35 +188,35 @@ export class FlowExecutor extends LitElement implements StageHost {
case ChallengeChoices.Native: case ChallengeChoices.Native:
switch (this.challenge.component) { switch (this.challenge.component) {
case "ak-stage-access-denied": case "ak-stage-access-denied":
return html`<ak-stage-access-denied .host=${this} .challenge=${this.challenge as AccessDeniedChallenge}></ak-stage-access-denied>`; return html`<ak-stage-access-denied .host=${this} .challenge=${this.challenge}></ak-stage-access-denied>`;
case "ak-stage-identification": case "ak-stage-identification":
return html`<ak-stage-identification .host=${this} .challenge=${this.challenge as IdentificationChallenge}></ak-stage-identification>`; return html`<ak-stage-identification .host=${this} .challenge=${this.challenge}></ak-stage-identification>`;
case "ak-stage-password": case "ak-stage-password":
return html`<ak-stage-password .host=${this} .challenge=${this.challenge as PasswordChallenge}></ak-stage-password>`; return html`<ak-stage-password .host=${this} .challenge=${this.challenge}></ak-stage-password>`;
case "ak-stage-captcha": case "ak-stage-captcha":
return html`<ak-stage-captcha .host=${this} .challenge=${this.challenge as CaptchaChallenge}></ak-stage-captcha>`; return html`<ak-stage-captcha .host=${this} .challenge=${this.challenge}></ak-stage-captcha>`;
case "ak-stage-consent": case "ak-stage-consent":
return html`<ak-stage-consent .host=${this} .challenge=${this.challenge as ConsentChallenge}></ak-stage-consent>`; return html`<ak-stage-consent .host=${this} .challenge=${this.challenge}></ak-stage-consent>`;
case "ak-stage-dummy": case "ak-stage-dummy":
return html`<ak-stage-dummy .host=${this} .challenge=${this.challenge as Challenge}></ak-stage-dummy>`; return html`<ak-stage-dummy .host=${this} .challenge=${this.challenge}></ak-stage-dummy>`;
case "ak-stage-email": case "ak-stage-email":
return html`<ak-stage-email .host=${this} .challenge=${this.challenge as EmailChallenge}></ak-stage-email>`; return html`<ak-stage-email .host=${this} .challenge=${this.challenge}></ak-stage-email>`;
case "ak-stage-autosubmit": case "ak-stage-autosubmit":
return html`<ak-stage-autosubmit .host=${this} .challenge=${this.challenge as AutosubmitChallenge}></ak-stage-autosubmit>`; return html`<ak-stage-autosubmit .host=${this} .challenge=${this.challenge}></ak-stage-autosubmit>`;
case "ak-stage-prompt": case "ak-stage-prompt":
return html`<ak-stage-prompt .host=${this} .challenge=${this.challenge as PromptChallenge}></ak-stage-prompt>`; return html`<ak-stage-prompt .host=${this} .challenge=${this.challenge}></ak-stage-prompt>`;
case "ak-stage-authenticator-totp": case "ak-stage-authenticator-totp":
return html`<ak-stage-authenticator-totp .host=${this} .challenge=${this.challenge as AuthenticatorTOTPChallenge}></ak-stage-authenticator-totp>`; return html`<ak-stage-authenticator-totp .host=${this} .challenge=${this.challenge}></ak-stage-authenticator-totp>`;
case "ak-stage-authenticator-duo": case "ak-stage-authenticator-duo":
return html`<ak-stage-authenticator-duo .host=${this} .challenge=${this.challenge as AuthenticatorDuoChallenge}></ak-stage-authenticator-duo>`; return html`<ak-stage-authenticator-duo .host=${this} .challenge=${this.challenge}></ak-stage-authenticator-duo>`;
case "ak-stage-authenticator-static": case "ak-stage-authenticator-static":
return html`<ak-stage-authenticator-static .host=${this} .challenge=${this.challenge as AuthenticatorStaticChallenge}></ak-stage-authenticator-static>`; return html`<ak-stage-authenticator-static .host=${this} .challenge=${this.challenge}></ak-stage-authenticator-static>`;
case "ak-stage-authenticator-webauthn": case "ak-stage-authenticator-webauthn":
return html`<ak-stage-authenticator-webauthn .host=${this} .challenge=${this.challenge as WebAuthnAuthenticatorRegisterChallenge}></ak-stage-authenticator-webauthn>`; return html`<ak-stage-authenticator-webauthn .host=${this} .challenge=${this.challenge}></ak-stage-authenticator-webauthn>`;
case "ak-stage-authenticator-validate": case "ak-stage-authenticator-validate":
return html`<ak-stage-authenticator-validate .host=${this} .challenge=${this.challenge as AuthenticatorValidateStageChallenge}></ak-stage-authenticator-validate>`; return html`<ak-stage-authenticator-validate .host=${this} .challenge=${this.challenge}></ak-stage-authenticator-validate>`;
case "ak-flow-sources-plex": case "ak-flow-sources-plex":
return html`<ak-flow-sources-plex .host=${this} .challenge=${this.challenge as PlexAuthenticationChallenge}></ak-flow-sources-plex>`; return html`<ak-flow-sources-plex .host=${this} .challenge=${this.challenge}></ak-flow-sources-plex>`;
default: default:
break; break;
} }
@ -288,8 +274,7 @@ export class FlowExecutor extends LitElement implements StageHost {
</li>`; </li>`;
}))} }))}
${this.config?.brandingTitle != "authentik" ? html` ${this.config?.brandingTitle != "authentik" ? html`
<li><a href="https://goauthentik.io">${t`Powered by authentik`}</a></li> <li><a href="https://goauthentik.io">${t`Powered by authentik`}</a></li>` : html``}
` : html``}
</ul> </ul>
</footer> </footer>
</div> </div>

View file

@ -1,4 +1,4 @@
import { Challenge } from "authentik-api"; import { AccessDeniedChallenge } from "authentik-api";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { BaseStage } from "../stages/base"; import { BaseStage } from "../stages/base";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
@ -12,10 +12,6 @@ import { t } from "@lingui/macro";
import "../../elements/EmptyState"; import "../../elements/EmptyState";
export interface AccessDeniedChallenge extends Challenge {
error_message?: string;
}
@customElement("ak-stage-access-denied") @customElement("ak-stage-access-denied")
export class FlowAccessDenied extends BaseStage { export class FlowAccessDenied extends BaseStage {
@ -45,9 +41,9 @@ export class FlowAccessDenied extends BaseStage {
<i class="pf-icon pf-icon-error-circle-o"></i> <i class="pf-icon pf-icon-error-circle-o"></i>
${t`Request has been denied.`} ${t`Request has been denied.`}
</p> </p>
${this.challenge?.error_message && ${this.challenge?.errorMessage &&
html`<hr> html`<hr>
<p>${this.challenge.error_message}</p>`} <p>${this.challenge.errorMessage}</p>`}
</div> </div>
</form> </form>
</div> </div>

View file

@ -1,5 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { Challenge } from "authentik-api"; import { PlexAuthenticationChallenge } from "authentik-api";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -16,12 +16,6 @@ import { SourcesApi } from "authentik-api";
import { showMessage } from "../../../elements/messages/MessageContainer"; import { showMessage } from "../../../elements/messages/MessageContainer";
import { MessageLevel } from "../../../elements/messages/Message"; import { MessageLevel } from "../../../elements/messages/Message";
export interface PlexAuthenticationChallenge extends Challenge {
client_id: string;
slug: string;
}
@customElement("ak-flow-sources-plex") @customElement("ak-flow-sources-plex")
export class PlexLoginInit extends BaseStage { export class PlexLoginInit extends BaseStage {
@ -34,9 +28,9 @@ export class PlexLoginInit extends BaseStage {
} }
async firstUpdated(): Promise<void> { async firstUpdated(): Promise<void> {
const authInfo = await PlexAPIClient.getPin(this.challenge?.client_id || ""); const authInfo = await PlexAPIClient.getPin(this.challenge?.clientId || "");
const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700); const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700);
PlexAPIClient.pinPoll(this.challenge?.client_id || "", authInfo.pin.id).then(token => { PlexAPIClient.pinPoll(this.challenge?.clientId || "", authInfo.pin.id).then(token => {
authWindow?.close(); authWindow?.close();
new SourcesApi(DEFAULT_CONFIG).sourcesPlexRedeemTokenCreate({ new SourcesApi(DEFAULT_CONFIG).sourcesPlexRedeemTokenCreate({
plexTokenRedeemRequest: { plexTokenRedeemRequest: {

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -13,15 +12,9 @@ import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../FormStatic"; import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy"; import { FlowURLManager } from "../../../api/legacy";
import { StagesApi } from "authentik-api"; import { AuthenticatorDuoChallenge, StagesApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
export interface AuthenticatorDuoChallenge extends WithUserInfoChallenge {
activation_barcode: string;
activation_code: string;
stage_uuid: string;
}
@customElement("ak-stage-authenticator-duo") @customElement("ak-stage-authenticator-duo")
export class AuthenticatorDuoStage extends BaseStage { export class AuthenticatorDuoStage extends BaseStage {
@ -42,10 +35,11 @@ export class AuthenticatorDuoStage extends BaseStage {
checkEnrollStatus(): Promise<void> { checkEnrollStatus(): Promise<void> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoEnrollmentStatusCreate({ return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoEnrollmentStatusCreate({
stageUuid: this.challenge?.stage_uuid || "", stageUuid: this.challenge?.stageUuid || "",
}).then(r => { }).then(() => {
this.host?.submit({}); this.host?.submit({});
}).catch(e => { }).catch(() => {
console.debug("authentik/flows/duo: Waiting for auth status");
}); });
} }
@ -65,17 +59,17 @@ export class AuthenticatorDuoStage extends BaseStage {
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}> <form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<ak-form-static <ak-form-static
class="pf-c-form__group" class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}" userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pending_user}> user=${this.challenge.pendingUser}>
<div slot="link"> <div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a> <a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div> </div>
</ak-form-static> </ak-form-static>
<img src=${this.challenge.activation_barcode} /> <img src=${this.challenge.activationBarcode} />
<p> <p>
${t`Alternatively, if your current device has Duo installed, click on this link:`} ${t`Alternatively, if your current device has Duo installed, click on this link:`}
</p> </p>
<a href=${this.challenge.activation_code}>${t`Duo activation`}</a> <a href=${this.challenge.activationCode}>${t`Duo activation`}</a>
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">
<button type="button" class="pf-c-button pf-m-primary pf-m-block" @click=${() => { <button type="button" class="pf-c-button pf-m-primary pf-m-block" @click=${() => {

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { css, CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -13,6 +12,7 @@ import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../FormStatic"; import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy"; import { FlowURLManager } from "../../../api/legacy";
import { AuthenticatorStaticChallenge } from "authentik-api";
export const STATIC_TOKEN_STYLE = css` export const STATIC_TOKEN_STYLE = css`
/* Static OTP Tokens */ /* Static OTP Tokens */
@ -29,9 +29,6 @@ export const STATIC_TOKEN_STYLE = css`
} }
`; `;
export interface AuthenticatorStaticChallenge extends WithUserInfoChallenge {
codes: number[];
}
@customElement("ak-stage-authenticator-static") @customElement("ak-stage-authenticator-static")
export class AuthenticatorStaticStage extends BaseStage { export class AuthenticatorStaticStage extends BaseStage {
@ -59,8 +56,8 @@ export class AuthenticatorStaticStage extends BaseStage {
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}> <form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<ak-form-static <ak-form-static
class="pf-c-form__group" class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}" userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pending_user}> user=${this.challenge.pendingUser}>
<div slot="link"> <div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a> <a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div> </div>

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -16,10 +15,8 @@ import "../../../elements/EmptyState";
import "../../FormStatic"; import "../../FormStatic";
import { MessageLevel } from "../../../elements/messages/Message"; import { MessageLevel } from "../../../elements/messages/Message";
import { FlowURLManager } from "../../../api/legacy"; import { FlowURLManager } from "../../../api/legacy";
import { AuthenticatorTOTPChallenge } from "authentik-api";
export interface AuthenticatorTOTPChallenge extends WithUserInfoChallenge {
config_url: string;
}
@customElement("ak-stage-authenticator-totp") @customElement("ak-stage-authenticator-totp")
export class AuthenticatorTOTPStage extends BaseStage { export class AuthenticatorTOTPStage extends BaseStage {
@ -47,20 +44,20 @@ export class AuthenticatorTOTPStage extends BaseStage {
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}> <form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<ak-form-static <ak-form-static
class="pf-c-form__group" class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}" userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pending_user}> user=${this.challenge.pendingUser}>
<div slot="link"> <div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a> <a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div> </div>
</ak-form-static> </ak-form-static>
<input type="hidden" name="otp_uri" value=${this.challenge.config_url} /> <input type="hidden" name="otp_uri" value=${this.challenge.configUrl} />
<ak-form-element> <ak-form-element>
<!-- @ts-ignore --> <!-- @ts-ignore -->
<qr-code data="${this.challenge.config_url}"></qr-code> <qr-code data="${this.challenge.configUrl}"></qr-code>
<button type="button" class="pf-c-button pf-m-secondary pf-m-progress pf-m-in-progress" @click=${(e: Event) => { <button type="button" class="pf-c-button pf-m-secondary pf-m-progress pf-m-in-progress" @click=${(e: Event) => {
e.preventDefault(); e.preventDefault();
if (!this.challenge?.config_url) return; if (!this.challenge?.configUrl) return;
navigator.clipboard.writeText(this.challenge?.config_url).then(() => { navigator.clipboard.writeText(this.challenge?.configUrl).then(() => {
showMessage({ showMessage({
level: MessageLevel.success, level: MessageLevel.success,
message: t`Successfully copied TOTP Config.` message: t`Successfully copied TOTP Config.`
@ -75,7 +72,7 @@ export class AuthenticatorTOTPStage extends BaseStage {
label="${t`Code`}" label="${t`Code`}"
?required="${true}" ?required="${true}"
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.response_errors || {})["code"]}> .errors=${(this.challenge?.responseErrors || {})["code"]}>
<!-- @ts-ignore --> <!-- @ts-ignore -->
<input type="text" <input type="text"
name="code" name="code"

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { css, CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -13,6 +12,9 @@ import "./AuthenticatorValidateStageWebAuthn";
import "./AuthenticatorValidateStageCode"; import "./AuthenticatorValidateStageCode";
import "./AuthenticatorValidateStageDuo"; import "./AuthenticatorValidateStageDuo";
import { PasswordManagerPrefill } from "../identification/IdentificationStage"; import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import { DeviceChallenge } from "authentik-api";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge";
import { ChallengeResponseRequest } from "authentik-api/dist/models/ChallengeResponseRequest";
export enum DeviceClasses { export enum DeviceClasses {
STATIC = "static", STATIC = "static",
@ -21,33 +23,17 @@ export enum DeviceClasses {
DUO = "duo", DUO = "duo",
} }
export interface DeviceChallenge {
device_class: DeviceClasses;
device_uid: string;
challenge: unknown;
}
export interface AuthenticatorValidateStageChallenge extends WithUserInfoChallenge {
device_challenges: DeviceChallenge[];
}
export interface AuthenticatorValidateStageChallengeResponse {
code?: string;
webauthn?: string;
duo?: number;
}
@customElement("ak-stage-authenticator-validate") @customElement("ak-stage-authenticator-validate")
export class AuthenticatorValidateStage extends BaseStage implements StageHost { export class AuthenticatorValidateStage extends BaseStage implements StageHost {
@property({ attribute: false }) @property({ attribute: false })
challenge?: AuthenticatorValidateStageChallenge; challenge?: AuthenticatorValidationChallenge;
@property({attribute: false}) @property({attribute: false})
selectedDeviceChallenge?: DeviceChallenge; selectedDeviceChallenge?: DeviceChallenge;
submit<T>(formData?: T): Promise<void> { submit(payload: ChallengeResponseRequest): Promise<void> {
return this.host?.submit<T>(formData) || Promise.resolve(); return this.host?.submit(payload) || Promise.resolve();
} }
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
@ -79,7 +65,7 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
} }
renderDevicePickerSingle(deviceChallenge: DeviceChallenge): TemplateResult { renderDevicePickerSingle(deviceChallenge: DeviceChallenge): TemplateResult {
switch (deviceChallenge.device_class) { switch (deviceChallenge.deviceClass) {
case DeviceClasses.DUO: case DeviceClasses.DUO:
return html`<i class="fas fa-mobile-alt"></i> return html`<i class="fas fa-mobile-alt"></i>
<div class="right"> <div class="right">
@ -124,7 +110,7 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
renderDevicePicker(): TemplateResult { renderDevicePicker(): TemplateResult {
return html` return html`
<ul> <ul>
${this.challenge?.device_challenges.map((challenges) => { ${this.challenge?.deviceChallenges.map((challenges) => {
return html`<li> return html`<li>
<button class="pf-c-button authenticator-button" type="button" @click=${() => { <button class="pf-c-button authenticator-button" type="button" @click=${() => {
this.selectedDeviceChallenge = challenges; this.selectedDeviceChallenge = challenges;
@ -140,30 +126,31 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
if (!this.selectedDeviceChallenge) { if (!this.selectedDeviceChallenge) {
return html``; return html``;
} }
switch (this.selectedDeviceChallenge?.device_class) { switch (this.selectedDeviceChallenge?.deviceClass) {
case DeviceClasses.STATIC: case DeviceClasses.STATIC:
case DeviceClasses.TOTP: case DeviceClasses.TOTP:
return html`<ak-stage-authenticator-validate-code return html`<ak-stage-authenticator-validate-code
.host=${this} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}
.deviceChallenge=${this.selectedDeviceChallenge} .deviceChallenge=${this.selectedDeviceChallenge}
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}> .showBackButton=${(this.challenge?.deviceChallenges.length || []) > 1}>
</ak-stage-authenticator-validate-code>`; </ak-stage-authenticator-validate-code>`;
case DeviceClasses.WEBAUTHN: case DeviceClasses.WEBAUTHN:
return html`<ak-stage-authenticator-validate-webauthn return html`<ak-stage-authenticator-validate-webauthn
.host=${this} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}
.deviceChallenge=${this.selectedDeviceChallenge} .deviceChallenge=${this.selectedDeviceChallenge}
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}> .showBackButton=${(this.challenge?.deviceChallenges.length || []) > 1}>
</ak-stage-authenticator-validate-webauthn>`; </ak-stage-authenticator-validate-webauthn>`;
case DeviceClasses.DUO: case DeviceClasses.DUO:
return html`<ak-stage-authenticator-validate-duo return html`<ak-stage-authenticator-validate-duo
.host=${this} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}
.deviceChallenge=${this.selectedDeviceChallenge} .deviceChallenge=${this.selectedDeviceChallenge}
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}> .showBackButton=${(this.challenge?.deviceChallenges.length || []) > 1}>
</ak-stage-authenticator-validate-duo>`; </ak-stage-authenticator-validate-duo>`;
} }
return html``;
} }
render(): TemplateResult { render(): TemplateResult {
@ -174,8 +161,8 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
</ak-empty-state>`; </ak-empty-state>`;
} }
// User only has a single device class, so we don't show a picker // User only has a single device class, so we don't show a picker
if (this.challenge?.device_challenges.length === 1) { if (this.challenge?.deviceChallenges.length === 1) {
this.selectedDeviceChallenge = this.challenge.device_challenges[0]; this.selectedDeviceChallenge = this.challenge.deviceChallenges[0];
} }
return html`<header class="pf-c-login__main-header"> return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl"> <h1 class="pf-c-title pf-m-3xl">

View file

@ -8,18 +8,20 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../../../authentik.css"; import AKGlobal from "../../../authentik.css";
import { BaseStage } from "../base"; import { BaseStage } from "../base";
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage"; import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage";
import "../../../elements/forms/FormElement"; import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import { PasswordManagerPrefill } from "../identification/IdentificationStage"; import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import "../../FormStatic"; import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy"; import { FlowURLManager } from "../../../api/legacy";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge";
import { DeviceChallenge } from "authentik-api";
@customElement("ak-stage-authenticator-validate-code") @customElement("ak-stage-authenticator-validate-code")
export class AuthenticatorValidateStageWebCode extends BaseStage { export class AuthenticatorValidateStageWebCode extends BaseStage {
@property({ attribute: false }) @property({ attribute: false })
challenge?: AuthenticatorValidateStageChallenge; challenge?: AuthenticatorValidationChallenge;
@property({ attribute: false }) @property({ attribute: false })
deviceChallenge?: DeviceChallenge; deviceChallenge?: DeviceChallenge;
@ -42,8 +44,8 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}> <form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<ak-form-static <ak-form-static
class="pf-c-form__group" class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}" userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pending_user}> user=${this.challenge.pendingUser}>
<div slot="link"> <div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a> <a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div> </div>
@ -52,7 +54,7 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
label="${t`Code`}" label="${t`Code`}"
?required="${true}" ?required="${true}"
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.response_errors || {})["code"]}> .errors=${(this.challenge?.responseErrors || {})["code"]}>
<!-- @ts-ignore --> <!-- @ts-ignore -->
<input type="text" <input type="text"
name="code" name="code"

View file

@ -8,18 +8,19 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../../../authentik.css"; import AKGlobal from "../../../authentik.css";
import { BaseStage } from "../base"; import { BaseStage } from "../base";
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage"; import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage";
import "../../../elements/forms/FormElement"; import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import "../../FormStatic"; import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy"; import { FlowURLManager } from "../../../api/legacy";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge";
import { DeviceChallenge } from "authentik-api";
@customElement("ak-stage-authenticator-validate-duo") @customElement("ak-stage-authenticator-validate-duo")
export class AuthenticatorValidateStageWebDuo extends BaseStage { export class AuthenticatorValidateStageWebDuo extends BaseStage {
@property({ attribute: false }) @property({ attribute: false })
challenge?: AuthenticatorValidateStageChallenge; challenge?: AuthenticatorValidationChallenge;
@property({ attribute: false }) @property({ attribute: false })
deviceChallenge?: DeviceChallenge; deviceChallenge?: DeviceChallenge;
@ -33,7 +34,7 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage {
firstUpdated(): void { firstUpdated(): void {
this.host?.submit({ this.host?.submit({
"duo": this.deviceChallenge?.device_uid "duo": this.deviceChallenge?.deviceUid
}); });
} }
@ -48,8 +49,8 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage {
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}> <form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<ak-form-static <ak-form-static
class="pf-c-form__group" class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}" userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pending_user}> user=${this.challenge.pendingUser}>
<div slot="link"> <div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a> <a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div> </div>

View file

@ -10,13 +10,15 @@ import AKGlobal from "../../../authentik.css";
import { PFSize } from "../../../elements/Spinner"; import { PFSize } from "../../../elements/Spinner";
import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils"; import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils";
import { BaseStage } from "../base"; import { BaseStage } from "../base";
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage"; import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge";
import { DeviceChallenge } from "authentik-api";
@customElement("ak-stage-authenticator-validate-webauthn") @customElement("ak-stage-authenticator-validate-webauthn")
export class AuthenticatorValidateStageWebAuthn extends BaseStage { export class AuthenticatorValidateStageWebAuthn extends BaseStage {
@property({attribute: false}) @property({attribute: false})
challenge?: AuthenticatorValidateStageChallenge; challenge?: AuthenticatorValidationChallenge;
@property({attribute: false}) @property({attribute: false})
deviceChallenge?: DeviceChallenge; deviceChallenge?: DeviceChallenge;

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -11,10 +10,7 @@ import AKGlobal from "../../../authentik.css";
import { PFSize } from "../../../elements/Spinner"; import { PFSize } from "../../../elements/Spinner";
import { BaseStage } from "../base"; import { BaseStage } from "../base";
import { Assertion, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils"; import { Assertion, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils";
import { AuthenticatorWebAuthnChallenge } from "authentik-api";
export interface WebAuthnAuthenticatorRegisterChallenge extends WithUserInfoChallenge {
registration: PublicKeyCredentialCreationOptions;
}
export interface WebAuthnAuthenticatorRegisterChallengeResponse { export interface WebAuthnAuthenticatorRegisterChallengeResponse {
response: Assertion; response: Assertion;
@ -24,7 +20,7 @@ export interface WebAuthnAuthenticatorRegisterChallengeResponse {
export class WebAuthnAuthenticatorRegisterStage extends BaseStage { export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
@property({ attribute: false }) @property({ attribute: false })
challenge?: WebAuthnAuthenticatorRegisterChallenge; challenge?: AuthenticatorWebAuthnChallenge;
@property({type: Boolean}) @property({type: Boolean})
registerRunning = false; registerRunning = false;
@ -42,7 +38,7 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
} }
// convert certain members of the PublicKeyCredentialCreateOptions into // convert certain members of the PublicKeyCredentialCreateOptions into
// byte arrays as expected by the spec. // byte arrays as expected by the spec.
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(this.challenge?.registration); const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(this.challenge?.registration as PublicKeyCredentialCreationOptions);
// request the authenticator(s) to create a new credential keypair. // request the authenticator(s) to create a new credential keypair.
let credential; let credential;
@ -106,8 +102,8 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
</div>`: </div>`:
html` html`
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">
${this.challenge?.response_errors ? ${this.challenge?.responseErrors ?
html`<p class="pf-m-block">${this.challenge.response_errors["response"][0].string}</p>`: html`<p class="pf-m-block">${this.challenge.responseErrors["response"][0].string}</p>`:
html``} html``}
<p class="pf-m-block">${this.registerMessage}</p> <p class="pf-m-block">${this.registerMessage}</p>
<button class="pf-c-button pf-m-primary pf-m-block" @click=${() => { <button class="pf-c-button pf-m-primary pf-m-block" @click=${() => {

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -10,11 +9,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../../../authentik.css"; import AKGlobal from "../../../authentik.css";
import { BaseStage } from "../base"; import { BaseStage } from "../base";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import { AutosubmitChallenge } from "authentik-api";
export interface AutosubmitChallenge extends WithUserInfoChallenge {
url: string;
attrs: { [key: string]: string };
}
@customElement("ak-stage-autosubmit") @customElement("ak-stage-autosubmit")
export class AutosubmitStage extends BaseStage { export class AutosubmitStage extends BaseStage {

View file

@ -1,20 +1,25 @@
import { Challenge } from "authentik-api"; import { Challenge } from "authentik-api";
import { ChallengeResponseRequest } from "authentik-api/dist/models/ChallengeResponseRequest";
import { LitElement } from "lit-element"; import { LitElement } from "lit-element";
export interface StageHost { export interface StageHost {
challenge?: Challenge; challenge?: Challenge;
submit<T>(formData?: T): Promise<void>; submit(payload: ChallengeResponseRequest): Promise<void>;
} }
export class BaseStage extends LitElement { export class BaseStage extends LitElement {
host?: StageHost; host?: StageHost;
challenge?: Challenge;
submitForm(e: Event): void { submitForm(e: Event): void {
e.preventDefault(); e.preventDefault();
const object: { const object: {
component: string;
[key: string]: unknown; [key: string]: unknown;
} = {}; } = {
component: this.challenge.component,
};
const form = new FormData(this.shadowRoot?.querySelector("form") || undefined); const form = new FormData(this.shadowRoot?.querySelector("form") || undefined);
form.forEach((value, key) => object[key] = value); form.forEach((value, key) => object[key] = value);
this.host?.submit(object); this.host?.submit(object);

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -14,10 +13,7 @@ import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../FormStatic"; import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy"; import { FlowURLManager } from "../../../api/legacy";
import { CaptchaChallenge } from "authentik-api";
export interface CaptchaChallenge extends WithUserInfoChallenge {
site_key: string;
}
@customElement("ak-stage-captcha") @customElement("ak-stage-captcha")
export class CaptchaStage extends BaseStage { export class CaptchaStage extends BaseStage {
@ -39,10 +35,10 @@ export class CaptchaStage extends BaseStage {
script.onload = () => { script.onload = () => {
console.debug("authentik/stages/captcha: script loaded"); console.debug("authentik/stages/captcha: script loaded");
grecaptcha.ready(() => { grecaptcha.ready(() => {
if (!this.challenge?.site_key) return; if (!this.challenge?.siteKey) return;
console.debug("authentik/stages/captcha: ready"); console.debug("authentik/stages/captcha: ready");
const captchaId = grecaptcha.render(captchaContainer, { const captchaId = grecaptcha.render(captchaContainer, {
sitekey: this.challenge.site_key, sitekey: this.challenge.siteKey,
callback: (token) => { callback: (token) => {
this.host?.submit({ this.host?.submit({
"token": token, "token": token,
@ -72,8 +68,8 @@ export class CaptchaStage extends BaseStage {
<form class="pf-c-form"> <form class="pf-c-form">
<ak-form-static <ak-form-static
class="pf-c-form__group" class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}" userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pending_user}> user=${this.challenge.pendingUser}>
<div slot="link"> <div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a> <a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div> </div>

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -12,18 +11,8 @@ import { BaseStage } from "../base";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../FormStatic"; import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy"; import { FlowURLManager } from "../../../api/legacy";
import { ConsentChallenge } from "authentik-api";
export interface Permission {
name: string;
id: string;
}
export interface ConsentChallenge extends WithUserInfoChallenge {
header_text: string;
permissions?: Permission[];
}
@customElement("ak-stage-consent") @customElement("ak-stage-consent")
export class ConsentStage extends BaseStage { export class ConsentStage extends BaseStage {
@ -51,15 +40,15 @@ export class ConsentStage extends BaseStage {
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}> <form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<ak-form-static <ak-form-static
class="pf-c-form__group" class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}" userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pending_user}> user=${this.challenge.pendingUser}>
<div slot="link"> <div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a> <a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div> </div>
</ak-form-static> </ak-form-static>
<div class="pf-c-form__group"> <div class="pf-c-form__group">
<p id="header-text"> <p id="header-text">
${this.challenge.header_text} ${this.challenge.headerText}
</p> </p>
<p>${t`Application requires following permissions`}</p> <p>${t`Application requires following permissions`}</p>
<ul class="pf-c-list" id="permmissions"> <ul class="pf-c-list" id="permmissions">

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { Challenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -11,12 +10,13 @@ import AKGlobal from "../../../authentik.css";
import { BaseStage } from "../base"; import { BaseStage } from "../base";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../FormStatic"; import "../../FormStatic";
import { DummyChallenge } from "authentik-api";
@customElement("ak-stage-dummy") @customElement("ak-stage-dummy")
export class DummyStage extends BaseStage { export class DummyStage extends BaseStage {
@property({ attribute: false }) @property({ attribute: false })
challenge?: Challenge; challenge?: DummyChallenge;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal]; return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal];

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { Challenge } from "authentik-api";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -10,8 +9,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../../../authentik.css"; import AKGlobal from "../../../authentik.css";
import { BaseStage } from "../base"; import { BaseStage } from "../base";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import { EmailChallenge } from "authentik-api";
export type EmailChallenge = Challenge;
@customElement("ak-stage-email") @customElement("ak-stage-email")
export class EmailStage extends BaseStage { export class EmailStage extends BaseStage {

View file

@ -10,7 +10,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../../../authentik.css"; import AKGlobal from "../../../authentik.css";
import "../../../elements/forms/FormElement"; import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import { Challenge } from "../../../api/Flows"; import { IdentificationChallenge, UILoginButton } from "authentik-api";
export const PasswordManagerPrefill: { export const PasswordManagerPrefill: {
password: string | undefined; password: string | undefined;
@ -20,24 +20,6 @@ export const PasswordManagerPrefill: {
totp: undefined, totp: undefined,
}; };
export interface IdentificationChallenge extends Challenge {
user_fields?: string[];
primary_action: string;
sources?: UILoginButton[];
application_pre?: string;
enroll_url?: string;
recovery_url?: string;
}
export interface UILoginButton {
name: string;
challenge: Challenge;
icon_url?: string;
}
@customElement("ak-stage-identification") @customElement("ak-stage-identification")
export class IdentificationStage extends BaseStage { export class IdentificationStage extends BaseStage {
@ -131,8 +113,8 @@ export class IdentificationStage extends BaseStage {
renderSource(source: UILoginButton): TemplateResult { renderSource(source: UILoginButton): TemplateResult {
let icon = html`<i class="fas fas fa-share-square" title="${source.name}"></i>`; let icon = html`<i class="fas fas fa-share-square" title="${source.name}"></i>`;
if (source.icon_url) { if (source.iconUrl) {
icon = html`<img src="${source.icon_url}" alt="${source.name}">`; icon = html`<img src="${source.iconUrl}" alt="${source.name}">`;
} }
return html`<li class="pf-c-login__main-footer-links-item"> return html`<li class="pf-c-login__main-footer-links-item">
<button type="button" @click=${() => { <button type="button" @click=${() => {
@ -145,18 +127,18 @@ export class IdentificationStage extends BaseStage {
} }
renderFooter(): TemplateResult { renderFooter(): TemplateResult {
if (!this.challenge?.enroll_url && !this.challenge?.recovery_url) { if (!this.challenge?.enrollUrl && !this.challenge?.recoveryUrl) {
return html``; return html``;
} }
return html`<div class="pf-c-login__main-footer-band"> return html`<div class="pf-c-login__main-footer-band">
${this.challenge.enroll_url ? html` ${this.challenge.enrollUrl ? html`
<p class="pf-c-login__main-footer-band-item"> <p class="pf-c-login__main-footer-band-item">
${t`Need an account?`} ${t`Need an account?`}
<a id="enroll" href="${this.challenge.enroll_url}">${t`Sign up.`}</a> <a id="enroll" href="${this.challenge.enrollUrl}">${t`Sign up.`}</a>
</p>` : html``} </p>` : html``}
${this.challenge.recovery_url ? html` ${this.challenge.recoveryUrl ? html`
<p class="pf-c-login__main-footer-band-item"> <p class="pf-c-login__main-footer-band-item">
<a id="recovery" href="${this.challenge.recovery_url}">${t`Forgot username or password?`}</a> <a id="recovery" href="${this.challenge.recoveryUrl}">${t`Forgot username or password?`}</a>
</p>` : html``} </p>` : html``}
</div>`; </div>`;
} }
@ -164,15 +146,15 @@ export class IdentificationStage extends BaseStage {
renderInput(): TemplateResult { renderInput(): TemplateResult {
let label = ""; let label = "";
let type = "text"; let type = "text";
if (!this.challenge?.user_fields) { if (!this.challenge?.userFields) {
return html`<p> return html`<p>
${t`Select one of the sources below to login.`} ${t`Select one of the sources below to login.`}
</p>`; </p>`;
} }
if (this.challenge?.user_fields === ["email"]) { if (this.challenge?.userFields === ["email"]) {
label = t`Email`; label = t`Email`;
type = "email"; type = "email";
} else if (this.challenge?.user_fields === ["username"]) { } else if (this.challenge?.userFields === ["username"]) {
label = t`Username`; label = t`Username`;
} else { } else {
label = t`Email or username`; label = t`Email or username`;
@ -181,10 +163,10 @@ export class IdentificationStage extends BaseStage {
label=${label} label=${label}
?required="${true}" ?required="${true}"
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.response_errors || {})["uid_field"]}> .errors=${(this.challenge?.responseErrors || {})["uidField"]}>
<!-- @ts-ignore --> <!-- @ts-ignore -->
<input type=${type} <input type=${type}
name="uid_field" name="uidField"
placeholder="Email or Username" placeholder="Email or Username"
autofocus="" autofocus=""
autocomplete="username" autocomplete="username"
@ -193,7 +175,7 @@ export class IdentificationStage extends BaseStage {
</ak-form-element> </ak-form-element>
<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.primary_action} ${this.challenge.primaryAction}
</button> </button>
</div>`; </div>`;
} }
@ -212,9 +194,9 @@ export class IdentificationStage extends BaseStage {
</header> </header>
<div class="pf-c-login__main-body"> <div class="pf-c-login__main-body">
<form class="pf-c-form" @submit=${(e: Event) => {this.submitForm(e);}}> <form class="pf-c-form" @submit=${(e: Event) => {this.submitForm(e);}}>
${this.challenge.application_pre ? ${this.challenge.applicationPre ?
html`<p> html`<p>
${t`Login to continue to ${this.challenge.application_pre}.`} ${t`Login to continue to ${this.challenge.applicationPre}.`}
</p>`: </p>`:
html``} html``}
${this.renderInput()} ${this.renderInput()}

View file

@ -1,6 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -14,10 +13,7 @@ import "../../../elements/EmptyState";
import { PasswordManagerPrefill } from "../identification/IdentificationStage"; import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import "../../FormStatic"; import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy"; import { FlowURLManager } from "../../../api/legacy";
import { PasswordChallenge } from "authentik-api";
export interface PasswordChallenge extends WithUserInfoChallenge {
recovery_url?: string;
}
@customElement("ak-stage-password") @customElement("ak-stage-password")
export class PasswordStage extends BaseStage { export class PasswordStage extends BaseStage {
@ -45,18 +41,18 @@ export class PasswordStage extends BaseStage {
<form class="pf-c-form" @submit=${(e: Event) => {this.submitForm(e);}}> <form class="pf-c-form" @submit=${(e: Event) => {this.submitForm(e);}}>
<ak-form-static <ak-form-static
class="pf-c-form__group" class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}" userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pending_user}> user=${this.challenge.pendingUser}>
<div slot="link"> <div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a> <a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div> </div>
</ak-form-static> </ak-form-static>
<input name="username" autocomplete="username" type="hidden" value="${this.challenge.pending_user}"> <input name="username" autocomplete="username" type="hidden" value="${this.challenge.pendingUser}">
<ak-form-element <ak-form-element
label="${t`Password`}" label="${t`Password`}"
?required="${true}" ?required="${true}"
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.response_errors || {})["password"]}> .errors=${(this.challenge?.responseErrors || {})["password"]}>
<input type="password" <input type="password"
name="password" name="password"
placeholder="${t`Please enter your password`}" placeholder="${t`Please enter your password`}"
@ -67,8 +63,8 @@ export class PasswordStage extends BaseStage {
value=${PasswordManagerPrefill.password || ""}> value=${PasswordManagerPrefill.password || ""}>
</ak-form-element> </ak-form-element>
${this.challenge.recovery_url ? ${this.challenge.recoveryUrl ?
html`<a href="${this.challenge.recovery_url}"> html`<a href="${this.challenge.recoveryUrl}">
${t`Forgot password?`}</a>` : ""} ${t`Forgot password?`}</a>` : ""}
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">

View file

@ -13,20 +13,9 @@ import { BaseStage } from "../base";
import "../../../elements/forms/FormElement"; import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../../elements/Divider"; import "../../../elements/Divider";
import { Challenge, Error } from "../../../api/Flows"; import { Error } from "../../../api/Flows";
import { Prompt, PromptChallenge } from "authentik-api";
export interface Prompt {
field_key: string;
label: string;
type: string;
required: boolean;
placeholder: string;
order: number;
}
export interface PromptChallenge extends Challenge {
fields: Prompt[];
}
@customElement("ak-stage-prompt") @customElement("ak-stage-prompt")
export class PromptStage extends BaseStage { export class PromptStage extends BaseStage {
@ -43,7 +32,7 @@ export class PromptStage extends BaseStage {
case "text": case "text":
return `<input return `<input
type="text" type="text"
name="${prompt.field_key}" name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}" placeholder="${prompt.placeholder}"
autocomplete="off" autocomplete="off"
class="pf-c-form-control" class="pf-c-form-control"
@ -52,7 +41,7 @@ export class PromptStage extends BaseStage {
case "username": case "username":
return `<input return `<input
type="text" type="text"
name="${prompt.field_key}" name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}" placeholder="${prompt.placeholder}"
autocomplete="username" autocomplete="username"
class="pf-c-form-control" class="pf-c-form-control"
@ -61,7 +50,7 @@ export class PromptStage extends BaseStage {
case "email": case "email":
return `<input return `<input
type="email" type="email"
name="${prompt.field_key}" name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}" placeholder="${prompt.placeholder}"
class="pf-c-form-control" class="pf-c-form-control"
?required=${prompt.required} ?required=${prompt.required}
@ -69,7 +58,7 @@ export class PromptStage extends BaseStage {
case "password": case "password":
return `<input return `<input
type="password" type="password"
name="${prompt.field_key}" name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}" placeholder="${prompt.placeholder}"
autocomplete="new-password" autocomplete="new-password"
class="pf-c-form-control" class="pf-c-form-control"
@ -77,28 +66,28 @@ export class PromptStage extends BaseStage {
case "number": case "number":
return `<input return `<input
type="number" type="number"
name="${prompt.field_key}" name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}" placeholder="${prompt.placeholder}"
class="pf-c-form-control" class="pf-c-form-control"
?required=${prompt.required}>`; ?required=${prompt.required}>`;
case "checkbox": case "checkbox":
return `<input return `<input
type="checkbox" type="checkbox"
name="${prompt.field_key}" name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}" placeholder="${prompt.placeholder}"
class="pf-c-form-control" class="pf-c-form-control"
?required=${prompt.required}>`; ?required=${prompt.required}>`;
case "date": case "date":
return `<input return `<input
type="date" type="date"
name="${prompt.field_key}" name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}" placeholder="${prompt.placeholder}"
class="pf-c-form-control" class="pf-c-form-control"
?required=${prompt.required}>`; ?required=${prompt.required}>`;
case "date-time": case "date-time":
return `<input return `<input
type="datetime" type="datetime"
name="${prompt.field_key}" name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}" placeholder="${prompt.placeholder}"
class="pf-c-form-control" class="pf-c-form-control"
?required=${prompt.required}>`; ?required=${prompt.required}>`;
@ -107,7 +96,7 @@ export class PromptStage extends BaseStage {
case "hidden": case "hidden":
return `<input return `<input
type="hidden" type="hidden"
name="${prompt.field_key}" name="${prompt.fieldKey}"
value="${prompt.placeholder}" value="${prompt.placeholder}"
class="pf-c-form-control" class="pf-c-form-control"
?required=${prompt.required}>`; ?required=${prompt.required}>`;
@ -158,12 +147,12 @@ export class PromptStage extends BaseStage {
label="${prompt.label}" label="${prompt.label}"
?required="${prompt.required}" ?required="${prompt.required}"
class="pf-c-form__group" class="pf-c-form__group"
.errors=${(this.challenge?.response_errors || {})[prompt.field_key]}> .errors=${(this.challenge?.responseErrors || {})[prompt.fieldKey]}>
${unsafeHTML(this.renderPromptInner(prompt))} ${unsafeHTML(this.renderPromptInner(prompt))}
</ak-form-element>`; </ak-form-element>`;
})} })}
${"non_field_errors" in (this.challenge?.response_errors || {}) ? ${"non_field_errors" in (this.challenge?.responseErrors || {}) ?
this.renderNonFieldErrors(this.challenge?.response_errors?.non_field_errors || []): this.renderNonFieldErrors(this.challenge?.responseErrors?.non_field_errors || []):
html``} html``}
<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">

View file

@ -63,6 +63,10 @@ msgstr "ANY, any policy must match to grant access."
msgid "ANY, any policy must match to include this stage access." msgid "ANY, any policy must match to include this stage access."
msgstr "ANY, any policy must match to include this stage access." msgstr "ANY, any policy must match to include this stage access."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
msgid "API Hostname"
msgstr "API Hostname"
#: src/elements/notifications/APIDrawer.ts #: src/elements/notifications/APIDrawer.ts
msgid "API Requests" msgid "API Requests"
msgstr "API Requests" msgstr "API Requests"
@ -180,6 +184,10 @@ msgstr "Allows/denys requests based on the users and/or the IPs reputation."
msgid "Also known as Entity ID. Defaults the Metadata URL." msgid "Also known as Entity ID. Defaults the Metadata URL."
msgstr "Also known as Entity ID. Defaults the Metadata URL." msgstr "Also known as Entity ID. Defaults the Metadata URL."
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
msgid "Alternatively, if your current device has Duo installed, click on this link:"
msgstr "Alternatively, if your current device has Duo installed, click on this link:"
#: src/pages/stages/consent/ConsentStageForm.ts #: src/pages/stages/consent/ConsentStageForm.ts
msgid "Always require consent" msgid "Always require consent"
msgstr "Always require consent" msgstr "Always require consent"
@ -522,6 +530,10 @@ msgstr "Check IP"
msgid "Check Username" msgid "Check Username"
msgstr "Check Username" msgstr "Check Username"
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
msgid "Check status"
msgstr "Check status"
#: src/flows/stages/email/EmailStage.ts #: src/flows/stages/email/EmailStage.ts
msgid "Check your Emails for a password reset link." msgid "Check your Emails for a password reset link."
msgstr "Check your Emails for a password reset link." msgstr "Check your Emails for a password reset link."
@ -572,6 +584,7 @@ msgstr "Click to copy token"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts #: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts #: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
#: src/pages/sources/plex/PlexSourceForm.ts #: src/pages/sources/plex/PlexSourceForm.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
msgid "Client ID" msgid "Client ID"
msgstr "Client ID" msgstr "Client ID"
@ -583,6 +596,7 @@ msgid "Client IP"
msgstr "Client IP" msgstr "Client IP"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts #: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
msgid "Client Secret" msgid "Client Secret"
msgstr "Client Secret" msgstr "Client Secret"
@ -616,6 +630,7 @@ msgstr "Confidential clients are capable of maintaining the confidentiality of t
msgid "Configuration" msgid "Configuration"
msgstr "Configuration" msgstr "Configuration"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts #: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -715,6 +730,7 @@ msgstr "Context"
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts #: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts #: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts #: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts
#: src/flows/stages/autosubmit/AutosubmitStage.ts #: src/flows/stages/autosubmit/AutosubmitStage.ts
#: src/flows/stages/consent/ConsentStage.ts #: src/flows/stages/consent/ConsentStage.ts
#: src/flows/stages/dummy/DummyStage.ts #: src/flows/stages/dummy/DummyStage.ts
@ -1010,6 +1026,10 @@ msgstr "Determines how authentik sends the response back to the Service Provider
msgid "Determines how long a session lasts. Default of 0 seconds means that the sessions lasts until the browser is closed." msgid "Determines how long a session lasts. Default of 0 seconds means that the sessions lasts until the browser is closed."
msgstr "Determines how long a session lasts. Default of 0 seconds means that the sessions lasts until the browser is closed." msgstr "Determines how long a session lasts. Default of 0 seconds means that the sessions lasts until the browser is closed."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Device classes"
msgstr "Device classes"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Device classes which can be used to authenticate." msgid "Device classes which can be used to authenticate."
msgstr "Device classes which can be used to authenticate." msgstr "Device classes which can be used to authenticate."
@ -1032,6 +1052,7 @@ msgstr "Digits"
msgid "Disable" msgid "Disable"
msgstr "Disable" msgstr "Disable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens" msgid "Disable Static Tokens"
msgstr "Disable Static Tokens" msgstr "Disable Static Tokens"
@ -1070,6 +1091,22 @@ msgstr "Download Private key"
msgid "Dummy stage used for testing. Shows a simple continue button and always passes." msgid "Dummy stage used for testing. Shows a simple continue button and always passes."
msgstr "Dummy stage used for testing. Shows a simple continue button and always passes." msgstr "Dummy stage used for testing. Shows a simple continue button and always passes."
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Duo"
msgstr "Duo"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Duo Authenticators"
msgstr "Duo Authenticators"
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
msgid "Duo activation"
msgstr "Duo activation"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
msgid "Duo push-notifications"
msgstr "Duo push-notifications"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts #: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "Each provider has a different issuer, based on the application slug." msgid "Each provider has a different issuer, based on the application slug."
msgstr "Each provider has a different issuer, based on the application slug." msgstr "Each provider has a different issuer, based on the application slug."
@ -1159,6 +1196,7 @@ msgstr "Enable"
msgid "Enable StartTLS" msgid "Enable StartTLS"
msgstr "Enable StartTLS" msgstr "Enable StartTLS"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Enable Static Tokens" msgid "Enable Static Tokens"
msgstr "Enable Static Tokens" msgstr "Enable Static Tokens"
@ -1431,6 +1469,7 @@ msgstr "Flow used before authentication."
msgid "Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password." msgid "Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password."
msgstr "Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password." msgstr "Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts #: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage." msgid "Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage."
@ -1803,10 +1842,12 @@ msgstr "Load servers"
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/access_denied/FlowAccessDenied.ts #: src/flows/access_denied/FlowAccessDenied.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts #: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts #: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts #: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts #: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts
#: src/flows/stages/autosubmit/AutosubmitStage.ts #: src/flows/stages/autosubmit/AutosubmitStage.ts
#: src/flows/stages/captcha/CaptchaStage.ts #: src/flows/stages/captcha/CaptchaStage.ts
#: src/flows/stages/consent/ConsentStage.ts #: src/flows/stages/consent/ConsentStage.ts
@ -1866,6 +1907,7 @@ msgstr "Loading"
#: src/pages/sources/saml/SAMLSourceForm.ts #: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/sources/saml/SAMLSourceForm.ts #: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/sources/saml/SAMLSourceForm.ts #: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts #: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -2041,6 +2083,7 @@ msgstr "My Applications"
#: src/pages/sources/saml/SAMLSourceForm.ts #: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/sources/saml/SAMLSourceViewPage.ts #: src/pages/sources/saml/SAMLSourceViewPage.ts
#: src/pages/stages/StageListPage.ts #: src/pages/stages/StageListPage.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts #: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -2173,9 +2216,11 @@ msgstr "Not found"
msgid "Not synced yet." msgid "Not synced yet."
msgstr "Not synced yet." msgstr "Not synced yet."
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts #: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts #: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts #: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts
#: src/flows/stages/captcha/CaptchaStage.ts #: src/flows/stages/captcha/CaptchaStage.ts
#: src/flows/stages/consent/ConsentStage.ts #: src/flows/stages/consent/ConsentStage.ts
#: src/flows/stages/password/PasswordStage.ts #: src/flows/stages/password/PasswordStage.ts
@ -2617,6 +2662,10 @@ msgstr "RSA-SHA512"
msgid "Re-evaluate policies" msgid "Re-evaluate policies"
msgstr "Re-evaluate policies" msgstr "Re-evaluate policies"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
msgid "Receive a push notification on your phone to prove your identity."
msgstr "Receive a push notification on your phone to prove your identity."
#: src/pages/flows/FlowForm.ts #: src/pages/flows/FlowForm.ts
msgid "Recovery" msgid "Recovery"
msgstr "Recovery" msgstr "Recovery"
@ -2728,6 +2777,7 @@ msgid "Return home"
msgstr "Return home" msgstr "Return home"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts #: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts #: src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts
msgid "Return to device picker" msgid "Return to device picker"
msgstr "Return to device picker" msgstr "Return to device picker"
@ -3033,6 +3083,10 @@ msgstr "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenti
msgid "Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello)." msgid "Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello)."
msgstr "Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello)." msgstr "Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello)."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
msgid "Stage used to configure a duo-based authenticator. This stage should be used for configuration flows."
msgstr "Stage used to configure a duo-based authenticator. This stage should be used for configuration flows."
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts #: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
msgid "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows." msgid "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows."
msgstr "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows." msgstr "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows."
@ -3041,6 +3095,7 @@ msgstr "Stage used to configure a static authenticator (i.e. static tokens). Thi
msgid "Stage used to validate any authenticator. This stage should be used during authentication or authorization flows." msgid "Stage used to validate any authenticator. This stage should be used during authentication or authorization flows."
msgstr "Stage used to validate any authenticator. This stage should be used during authentication or authorization flows." msgstr "Stage used to validate any authenticator. This stage should be used during authentication or authorization flows."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts #: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -3074,7 +3129,7 @@ msgstr "State"
msgid "Static Tokens" msgid "Static Tokens"
msgstr "Static Tokens" msgstr "Static Tokens"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Static tokens" msgid "Static tokens"
msgstr "Static tokens" msgstr "Static tokens"
@ -3090,11 +3145,13 @@ msgstr "Statically deny the flow. To use this stage effectively, disable *Evalua
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Disabled" msgid "Status: Disabled"
msgstr "Status: Disabled" msgstr "Status: Disabled"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Enabled" msgid "Status: Enabled"
@ -3204,6 +3261,7 @@ msgstr "Successfully created service-connection."
msgid "Successfully created source." msgid "Successfully created source."
msgstr "Successfully created source." msgstr "Successfully created source."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts #: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -3342,6 +3400,7 @@ msgstr "Successfully updated service-connection."
msgid "Successfully updated source." msgid "Successfully updated source."
msgstr "Successfully updated source." msgstr "Successfully updated source."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts #: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -3533,7 +3592,7 @@ msgstr "Time in minutes the token sent is valid."
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)." msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)." msgstr "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Time-based One-Time Passwords" msgid "Time-based One-Time Passwords"
msgstr "Time-based One-Time Passwords" msgstr "Time-based One-Time Passwords"
@ -3893,7 +3952,6 @@ msgstr "User details"
msgid "User events" msgid "User events"
msgstr "User events" msgstr "User events"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts #: src/pages/stages/identification/IdentificationStageForm.ts
msgid "User fields" msgid "User fields"
msgstr "User fields" msgstr "User fields"

View file

@ -63,6 +63,10 @@ msgstr ""
msgid "ANY, any policy must match to include this stage access." msgid "ANY, any policy must match to include this stage access."
msgstr "" msgstr ""
#:
msgid "API Hostname"
msgstr ""
#: #:
msgid "API Requests" msgid "API Requests"
msgstr "" msgstr ""
@ -180,6 +184,10 @@ msgstr ""
msgid "Also known as Entity ID. Defaults the Metadata URL." msgid "Also known as Entity ID. Defaults the Metadata URL."
msgstr "" msgstr ""
#:
msgid "Alternatively, if your current device has Duo installed, click on this link:"
msgstr ""
#: #:
msgid "Always require consent" msgid "Always require consent"
msgstr "" msgstr ""
@ -518,6 +526,10 @@ msgstr ""
msgid "Check Username" msgid "Check Username"
msgstr "" msgstr ""
#:
msgid "Check status"
msgstr ""
#: #:
msgid "Check your Emails for a password reset link." msgid "Check your Emails for a password reset link."
msgstr "" msgstr ""
@ -566,6 +578,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Client ID" msgid "Client ID"
msgstr "" msgstr ""
@ -576,6 +589,7 @@ msgstr ""
msgid "Client IP" msgid "Client IP"
msgstr "" msgstr ""
#:
#: #:
msgid "Client Secret" msgid "Client Secret"
msgstr "" msgstr ""
@ -614,6 +628,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Configuration flow" msgid "Configuration flow"
msgstr "" msgstr ""
@ -715,6 +730,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Continue" msgid "Continue"
msgstr "" msgstr ""
@ -1002,6 +1018,10 @@ msgstr ""
msgid "Determines how long a session lasts. Default of 0 seconds means that the sessions lasts until the browser is closed." msgid "Determines how long a session lasts. Default of 0 seconds means that the sessions lasts until the browser is closed."
msgstr "" msgstr ""
#:
msgid "Device classes"
msgstr ""
#: #:
msgid "Device classes which can be used to authenticate." msgid "Device classes which can be used to authenticate."
msgstr "" msgstr ""
@ -1024,6 +1044,7 @@ msgstr ""
msgid "Disable" msgid "Disable"
msgstr "" msgstr ""
#:
#: #:
msgid "Disable Static Tokens" msgid "Disable Static Tokens"
msgstr "" msgstr ""
@ -1062,6 +1083,22 @@ msgstr ""
msgid "Dummy stage used for testing. Shows a simple continue button and always passes." msgid "Dummy stage used for testing. Shows a simple continue button and always passes."
msgstr "" msgstr ""
#:
msgid "Duo"
msgstr ""
#:
msgid "Duo Authenticators"
msgstr ""
#:
msgid "Duo activation"
msgstr ""
#:
msgid "Duo push-notifications"
msgstr ""
#: #:
msgid "Each provider has a different issuer, based on the application slug." msgid "Each provider has a different issuer, based on the application slug."
msgstr "" msgstr ""
@ -1151,6 +1188,7 @@ msgstr ""
msgid "Enable StartTLS" msgid "Enable StartTLS"
msgstr "" msgstr ""
#:
#: #:
msgid "Enable Static Tokens" msgid "Enable Static Tokens"
msgstr "" msgstr ""
@ -1423,6 +1461,7 @@ msgstr ""
msgid "Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password." msgid "Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password."
msgstr "" msgstr ""
#:
#: #:
#: #:
msgid "Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage." msgid "Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage."
@ -1811,6 +1850,8 @@ msgstr ""
#: #:
#: #:
#: #:
#:
#:
msgid "Loading" msgid "Loading"
msgstr "" msgstr ""
@ -1867,6 +1908,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr ""
@ -2054,6 +2096,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Name" msgid "Name"
msgstr "" msgstr ""
@ -2171,6 +2214,8 @@ msgstr ""
#: #:
#: #:
#: #:
#:
#:
msgid "Not you?" msgid "Not you?"
msgstr "" msgstr ""
@ -2609,6 +2654,10 @@ msgstr ""
msgid "Re-evaluate policies" msgid "Re-evaluate policies"
msgstr "" msgstr ""
#:
msgid "Receive a push notification on your phone to prove your identity."
msgstr ""
#: #:
msgid "Recovery" msgid "Recovery"
msgstr "" msgstr ""
@ -2719,6 +2768,7 @@ msgstr ""
msgid "Return home" msgid "Return home"
msgstr "" msgstr ""
#:
#: #:
#: #:
msgid "Return to device picker" msgid "Return to device picker"
@ -3025,6 +3075,10 @@ msgstr ""
msgid "Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello)." msgid "Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello)."
msgstr "" msgstr ""
#:
msgid "Stage used to configure a duo-based authenticator. This stage should be used for configuration flows."
msgstr ""
#: #:
msgid "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows." msgid "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows."
msgstr "" msgstr ""
@ -3044,6 +3098,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Stage-specific settings" msgid "Stage-specific settings"
msgstr "" msgstr ""
@ -3082,11 +3137,13 @@ msgstr ""
msgid "Status" msgid "Status"
msgstr "" msgstr ""
#:
#: #:
#: #:
msgid "Status: Disabled" msgid "Status: Disabled"
msgstr "" msgstr ""
#:
#: #:
#: #:
msgid "Status: Enabled" msgid "Status: Enabled"
@ -3213,6 +3270,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Successfully created stage." msgid "Successfully created stage."
msgstr "" msgstr ""
@ -3351,6 +3409,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Successfully updated stage." msgid "Successfully updated stage."
msgstr "" msgstr ""
@ -3881,7 +3940,6 @@ msgstr ""
msgid "User events" msgid "User events"
msgstr "" msgstr ""
#:
#: #:
msgid "User fields" msgid "User fields"
msgstr "" msgstr ""