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 django.apps import AppConfig
from django.db.utils import ProgrammingError
from authentik.lib.utils.reflection import all_subclasses
class AuthentikFlowsConfig(AppConfig):
@ -14,3 +17,10 @@ class AuthentikFlowsConfig(AppConfig):
def ready(self):
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(
choices=[(x.value, x.name) for x in ChallengeTypes],
)
component = CharField(required=False)
title = CharField(required=False)
background = CharField(required=False)
component = CharField(default="")
response_errors = DictField(
child=ErrorDetailSerializer(many=True), allow_empty=True, required=False
@ -48,12 +48,14 @@ class RedirectChallenge(Challenge):
"""Challenge type to redirect the client"""
to = CharField()
component = CharField(default="xak-flow-redirect")
class ShellChallenge(Challenge):
"""Legacy challenge type to render HTML as-is"""
"""challenge type to render HTML as-is"""
body = CharField()
component = CharField(default="xak-flow-shell")
class WithUserInfoChallenge(Challenge):
@ -67,6 +69,7 @@ class AccessDeniedChallenge(Challenge):
"""Challenge when a flow's active stage calls `stage_invalid()`."""
error_message = CharField(required=False)
component = CharField(default="ak-stage-access-denied")
class PermissionSerializer(PassiveSerializer):
@ -80,6 +83,7 @@ class ChallengeResponse(PassiveSerializer):
"""Base class for all challenge responses"""
stage: Optional["StageView"]
component = CharField(default="")
def __init__(self, instance=None, data=None, **kwargs):
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.generic import View
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.views import APIView
from sentry_sdk import capture_exception
@ -22,10 +27,12 @@ from authentik.events.models import cleanse_dict
from authentik.flows.challenge import (
AccessDeniedChallenge,
Challenge,
ChallengeResponse,
ChallengeTypes,
HttpChallengeResponse,
RedirectChallenge,
ShellChallenge,
WithUserInfoChallenge,
)
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.models import ConfigurableStage, Flow, FlowDesignation, Stage
@ -35,7 +42,7 @@ from authentik.flows.planner import (
FlowPlan,
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
LOGGER = get_logger()
@ -46,6 +53,43 @@ SESSION_KEY_APPLICATION_PRE = "authentik_flows_application_pre"
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")
class FlowExecutorView(APIView):
"""Stage 1 Flow executor, passing requests to Stage Views"""
@ -126,7 +170,11 @@ class FlowExecutorView(APIView):
@extend_schema(
responses={
200: Challenge(),
200: PolymorphicProxySerializer(
component_name="Challenge",
serializers=challenge_types(),
resource_type_field_name="component",
),
404: OpenApiResponse(
description="No Token found"
), # This error can be raised by the email stage
@ -159,8 +207,18 @@ class FlowExecutorView(APIView):
return to_stage_response(request, FlowErrorResponse(request, exc))
@extend_schema(
responses={200: Challenge()},
request=OpenApiTypes.OBJECT,
responses={
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=[
OpenApiParameter(
name="query",

View File

@ -34,6 +34,7 @@ class AutosubmitChallenge(Challenge):
url = 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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,18 +30,20 @@ LOGGER = get_logger()
PER_DEVICE_CLASSES = [DeviceClasses.WEBAUTHN]
class AuthenticatorChallenge(WithUserInfoChallenge):
class AuthenticatorValidationChallenge(WithUserInfoChallenge):
"""Authenticator challenge"""
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"""
code = CharField(required=False)
webauthn = JSONField(required=False)
duo = IntegerField(required=False)
component = CharField(default="ak-stage-authenticator-validate")
def _challenge_allowed(self, classes: list):
device_challenges: list[dict] = self.stage.request.session.get(
@ -83,7 +85,7 @@ class AuthenticatorChallengeResponse(ChallengeResponse):
class AuthenticatorValidateStageView(ChallengeStageView):
"""Authenticator Validation"""
response_class = AuthenticatorChallengeResponse
response_class = AuthenticatorValidationChallengeResponse
def get_device_challenges(self) -> list[dict]:
"""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 super().get(request, *args, **kwargs)
def get_challenge(self) -> AuthenticatorChallenge:
def get_challenge(self) -> AuthenticatorValidationChallenge:
challenges = self.request.session["device_challenges"]
return AuthenticatorChallenge(
return AuthenticatorValidationChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-authenticator-validate",
"device_challenges": challenges,
}
)
# pylint: disable=unused-argument
def challenge_valid(
self, challenge: AuthenticatorChallengeResponse
self, challenge: AuthenticatorValidationChallengeResponse
) -> HttpResponse:
# All validation is done by the serializer
return self.executor.stage_ok()

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
"""authentik multi-stage authentication engine"""
from django.http.response import HttpResponse
from rest_framework.fields import CharField
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.stage import ChallengeStageView
@ -8,10 +9,14 @@ from authentik.flows.stage import ChallengeStageView
class DummyChallenge(Challenge):
"""Dummy challenge"""
component = CharField(default="ak-stage-dummy")
class DummyChallengeResponse(ChallengeResponse):
"""Dummy challenge response"""
component = CharField(default="ak-stage-dummy")
class DummyStageView(ChallengeStageView):
"""Dummy stage for testing with multiple stages"""
@ -25,7 +30,6 @@ class DummyStageView(ChallengeStageView):
return DummyChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-dummy",
"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.timezone import now
from django.utils.translation import gettext as _
from rest_framework.fields import CharField
from rest_framework.serializers import ValidationError
from structlog.stdlib import get_logger
@ -28,11 +29,15 @@ PLAN_CONTEXT_EMAIL_SENT = "email_sent"
class EmailChallenge(Challenge):
"""Email challenge"""
component = CharField(default="ak-stage-email")
class EmailChallengeResponse(ChallengeResponse):
"""Email challenge resposen. No fields. This challenge is
always declared invalid to give the user a chance to retry"""
component = CharField(default="ak-stage-email")
def validate(self, data):
raise ValidationError("")
@ -97,7 +102,6 @@ class EmailStageView(ChallengeStageView):
challenge = EmailChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-email",
"title": "Email sent.",
}
)

View File

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

View File

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

View File

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

View File

@ -3550,16 +3550,13 @@ paths:
content:
application/json:
schema:
type: object
additionalProperties: {}
$ref: '#/components/schemas/ChallengeResponseRequest'
application/x-www-form-urlencoded:
schema:
type: object
additionalProperties: {}
$ref: '#/components/schemas/ChallengeResponseRequest'
multipart/form-data:
schema:
type: object
additionalProperties: {}
$ref: '#/components/schemas/ChallengeResponseRequest'
security:
- authentik: []
- cookieAuth: []
@ -14924,6 +14921,29 @@ paths:
$ref: '#/components/schemas/GenericError'
components:
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:
enum:
- login
@ -15138,6 +15158,42 @@ components:
If empty, user will not be able to configure this stage.
required:
- 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:
type: object
description: AuthenticatorDuoStage Serializer
@ -15208,6 +15264,38 @@ components:
- client_id
- client_secret
- 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:
type: object
description: AuthenticatorStaticStage Serializer
@ -15270,6 +15358,47 @@ components:
minimum: -2147483648
required:
- 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:
type: object
description: AuthenticatorTOTPStage Serializer
@ -15406,6 +15535,124 @@ components:
is not prompted again.
required:
- 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:
enum:
- django.contrib.auth.backends.ModelBackend
@ -15430,6 +15677,47 @@ components:
enum:
- can_save_media
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:
type: object
description: CaptchaStage Serializer
@ -15557,33 +15845,75 @@ components:
- certificate_data
- name
Challenge:
type: object
description: |-
Challenge that gets sent to the client based on which stage
is currently active
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
component:
type: string
title:
type: string
background:
type: string
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
required:
- type
oneOf:
- $ref: '#/components/schemas/AccessDeniedChallenge'
- $ref: '#/components/schemas/AuthenticatorDuoChallenge'
- $ref: '#/components/schemas/AuthenticatorStaticChallenge'
- $ref: '#/components/schemas/AuthenticatorTOTPChallenge'
- $ref: '#/components/schemas/AuthenticatorValidationChallenge'
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallenge'
- $ref: '#/components/schemas/AutosubmitChallenge'
- $ref: '#/components/schemas/CaptchaChallenge'
- $ref: '#/components/schemas/ConsentChallenge'
- $ref: '#/components/schemas/DummyChallenge'
- $ref: '#/components/schemas/EmailChallenge'
- $ref: '#/components/schemas/IdentificationChallenge'
- $ref: '#/components/schemas/PasswordChallenge'
- $ref: '#/components/schemas/PlexAuthenticationChallenge'
- $ref: '#/components/schemas/PromptChallenge'
- $ref: '#/components/schemas/RedirectChallenge'
- $ref: '#/components/schemas/ShellChallenge'
discriminator:
propertyName: component
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:
enum:
- native
- shell
- redirect
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:
enum:
- confidential
@ -15625,6 +15955,48 @@ components:
- error_reporting_environment
- error_reporting_send_pii
- 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:
type: object
description: ConsentStage Serializer
@ -15740,6 +16112,21 @@ components:
$ref: '#/components/schemas/FlowRequest'
required:
- 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:
enum:
- static
@ -15837,6 +16224,34 @@ components:
required:
- name
- 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:
type: object
description: Dummy Policy Serializer
@ -15944,6 +16359,36 @@ components:
$ref: '#/components/schemas/FlowRequest'
required:
- 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:
type: object
description: EmailStage Serializer
@ -16640,6 +17085,57 @@ components:
minimum: -2147483648
required:
- 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:
type: object
description: IdentificationStage Serializer
@ -20375,6 +20871,46 @@ components:
required:
- pagination
- 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:
type: object
description: Password Expiry Policy Serializer
@ -22038,6 +22574,44 @@ components:
name:
type: string
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:
type: object
description: Plex Source Serializer
@ -22359,6 +22933,32 @@ components:
- label
- pk
- 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:
type: object
description: Prompt Serializer
@ -22388,6 +22988,15 @@ components:
- field_key
- label
- type
PromptResponseChallengeRequest:
type: object
description: |-
Validate response, fields are dynamically created based
on the stage
properties:
component:
type: string
default: ak-stage-prompt
PromptStage:
type: object
description: PromptStage Serializer
@ -22789,12 +23398,13 @@ components:
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
component:
type: string
title:
type: string
background:
type: string
component:
type: string
default: xak-flow-redirect
response_errors:
type: object
additionalProperties:
@ -23462,6 +24072,30 @@ components:
- warning
- alert
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:
enum:
- http://www.w3.org/2000/09/xmldsig#rsa-sha1
@ -23591,6 +24225,29 @@ components:
- pk
- verbose_name
- 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:
type: object
description: Stage Serializer
@ -23818,6 +24475,21 @@ components:
- description
- model_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:
type: object
description: User Serializer

View File

@ -8,23 +8,3 @@ export interface Error {
export interface ErrorDict {
[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/prompt/PromptStage";
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 { 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 { ifDefined } from "lit-html/directives/if-defined";
import { until } from "lit-html/directives/until";
import { AccessDeniedChallenge } from "./access_denied/FlowAccessDenied";
import { PFSize } from "../elements/Spinner";
import { TITLE_DEFAULT } from "../constants";
import { configureSentry } from "../api/Sentry";
import { PlexAuthenticationChallenge } from "./sources/plex/PlexLoginInit";
import { AuthenticatorDuoChallenge } from "./stages/authenticator_duo/AuthenticatorDuoStage";
import { ChallengeResponseRequest } from "authentik-api/dist/models/ChallengeResponseRequest";
@customElement("ak-flow-executor")
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;
return new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolveRaw({
return new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
flowSlug: this.flowSlug,
requestBody: formData || {},
query: window.location.search.substring(1),
}).then((challengeRaw) => {
return challengeRaw.raw.json();
challengeResponseRequest: payload,
}).then((data) => {
this.challenge = data;
this.postUpdate();
}).catch((e: Response) => {
console.debug(e);
this.errorMessage(e.statusText);
}).finally(() => {
this.loading = false;
@ -135,19 +122,18 @@ export class FlowExecutor extends LitElement implements StageHost {
this.config = config;
});
this.loading = true;
new FlowsApi(DEFAULT_CONFIG).flowsExecutorGetRaw({
new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({
flowSlug: this.flowSlug,
query: window.location.search.substring(1),
}).then((challengeRaw) => {
return challengeRaw.raw.json();
}).then((challenge) => {
this.challenge = challenge as Challenge;
this.challenge = challenge;
// Only set background on first update, flow won't change throughout execution
if (this.challenge?.background) {
this.setBackground(this.challenge.background);
}
this.postUpdate();
}).catch((e: Response) => {
console.debug(e);
// Catch JSON or Update errors
this.errorMessage(e.statusText);
}).finally(() => {
@ -202,35 +188,35 @@ export class FlowExecutor extends LitElement implements StageHost {
case ChallengeChoices.Native:
switch (this.challenge.component) {
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":
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":
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":
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":
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":
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":
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":
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":
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":
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":
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":
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":
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":
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":
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:
break;
}
@ -288,8 +274,7 @@ export class FlowExecutor extends LitElement implements StageHost {
</li>`;
}))}
${this.config?.brandingTitle != "authentik" ? html`
<li><a href="https://goauthentik.io">${t`Powered by authentik`}</a></li>
` : html``}
<li><a href="https://goauthentik.io">${t`Powered by authentik`}</a></li>` : html``}
</ul>
</footer>
</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 { BaseStage } from "../stages/base";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
@ -12,10 +12,6 @@ import { t } from "@lingui/macro";
import "../../elements/EmptyState";
export interface AccessDeniedChallenge extends Challenge {
error_message?: string;
}
@customElement("ak-stage-access-denied")
export class FlowAccessDenied extends BaseStage {
@ -45,9 +41,9 @@ export class FlowAccessDenied extends BaseStage {
<i class="pf-icon pf-icon-error-circle-o"></i>
${t`Request has been denied.`}
</p>
${this.challenge?.error_message &&
${this.challenge?.errorMessage &&
html`<hr>
<p>${this.challenge.error_message}</p>`}
<p>${this.challenge.errorMessage}</p>`}
</div>
</form>
</div>

View File

@ -1,5 +1,5 @@
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 PFForm from "@patternfly/patternfly/components/Form/form.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 { MessageLevel } from "../../../elements/messages/Message";
export interface PlexAuthenticationChallenge extends Challenge {
client_id: string;
slug: string;
}
@customElement("ak-flow-sources-plex")
export class PlexLoginInit extends BaseStage {
@ -34,9 +28,9 @@ export class PlexLoginInit extends BaseStage {
}
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);
PlexAPIClient.pinPoll(this.challenge?.client_id || "", authInfo.pin.id).then(token => {
PlexAPIClient.pinPoll(this.challenge?.clientId || "", authInfo.pin.id).then(token => {
authWindow?.close();
new SourcesApi(DEFAULT_CONFIG).sourcesPlexRedeemTokenCreate({
plexTokenRedeemRequest: {

View File

@ -1,6 +1,5 @@
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -13,15 +12,9 @@ import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState";
import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy";
import { StagesApi } from "authentik-api";
import { AuthenticatorDuoChallenge, StagesApi } from "authentik-api";
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")
export class AuthenticatorDuoStage extends BaseStage {
@ -42,10 +35,11 @@ export class AuthenticatorDuoStage extends BaseStage {
checkEnrollStatus(): Promise<void> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoEnrollmentStatusCreate({
stageUuid: this.challenge?.stage_uuid || "",
}).then(r => {
stageUuid: this.challenge?.stageUuid || "",
}).then(() => {
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); }}>
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}"
user=${this.challenge.pending_user}>
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}>
<div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div>
</ak-form-static>
<img src=${this.challenge.activation_barcode} />
<img src=${this.challenge.activationBarcode} />
<p>
${t`Alternatively, if your current device has Duo installed, click on this link:`}
</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">
<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 { css, CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -13,6 +12,7 @@ import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState";
import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy";
import { AuthenticatorStaticChallenge } from "authentik-api";
export const STATIC_TOKEN_STYLE = css`
/* 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")
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); }}>
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}"
user=${this.challenge.pending_user}>
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}>
<div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div>

View File

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

View File

@ -1,6 +1,5 @@
import { t } from "@lingui/macro";
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 PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -13,6 +12,9 @@ import "./AuthenticatorValidateStageWebAuthn";
import "./AuthenticatorValidateStageCode";
import "./AuthenticatorValidateStageDuo";
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 {
STATIC = "static",
@ -21,33 +23,17 @@ export enum DeviceClasses {
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")
export class AuthenticatorValidateStage extends BaseStage implements StageHost {
@property({ attribute: false })
challenge?: AuthenticatorValidateStageChallenge;
challenge?: AuthenticatorValidationChallenge;
@property({attribute: false})
selectedDeviceChallenge?: DeviceChallenge;
submit<T>(formData?: T): Promise<void> {
return this.host?.submit<T>(formData) || Promise.resolve();
submit(payload: ChallengeResponseRequest): Promise<void> {
return this.host?.submit(payload) || Promise.resolve();
}
static get styles(): CSSResult[] {
@ -79,7 +65,7 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
}
renderDevicePickerSingle(deviceChallenge: DeviceChallenge): TemplateResult {
switch (deviceChallenge.device_class) {
switch (deviceChallenge.deviceClass) {
case DeviceClasses.DUO:
return html`<i class="fas fa-mobile-alt"></i>
<div class="right">
@ -124,7 +110,7 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
renderDevicePicker(): TemplateResult {
return html`
<ul>
${this.challenge?.device_challenges.map((challenges) => {
${this.challenge?.deviceChallenges.map((challenges) => {
return html`<li>
<button class="pf-c-button authenticator-button" type="button" @click=${() => {
this.selectedDeviceChallenge = challenges;
@ -140,30 +126,31 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
if (!this.selectedDeviceChallenge) {
return html``;
}
switch (this.selectedDeviceChallenge?.device_class) {
switch (this.selectedDeviceChallenge?.deviceClass) {
case DeviceClasses.STATIC:
case DeviceClasses.TOTP:
return html`<ak-stage-authenticator-validate-code
.host=${this}
.host=${this as StageHost}
.challenge=${this.challenge}
.deviceChallenge=${this.selectedDeviceChallenge}
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}>
.showBackButton=${(this.challenge?.deviceChallenges.length || []) > 1}>
</ak-stage-authenticator-validate-code>`;
case DeviceClasses.WEBAUTHN:
return html`<ak-stage-authenticator-validate-webauthn
.host=${this}
.host=${this as StageHost}
.challenge=${this.challenge}
.deviceChallenge=${this.selectedDeviceChallenge}
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}>
.showBackButton=${(this.challenge?.deviceChallenges.length || []) > 1}>
</ak-stage-authenticator-validate-webauthn>`;
case DeviceClasses.DUO:
return html`<ak-stage-authenticator-validate-duo
.host=${this}
.host=${this as StageHost}
.challenge=${this.challenge}
.deviceChallenge=${this.selectedDeviceChallenge}
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}>
.showBackButton=${(this.challenge?.deviceChallenges.length || []) > 1}>
</ak-stage-authenticator-validate-duo>`;
}
return html``;
}
render(): TemplateResult {
@ -174,8 +161,8 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
</ak-empty-state>`;
}
// User only has a single device class, so we don't show a picker
if (this.challenge?.device_challenges.length === 1) {
this.selectedDeviceChallenge = this.challenge.device_challenges[0];
if (this.challenge?.deviceChallenges.length === 1) {
this.selectedDeviceChallenge = this.challenge.deviceChallenges[0];
}
return html`<header class="pf-c-login__main-header">
<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 AKGlobal from "../../../authentik.css";
import { BaseStage } from "../base";
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage";
import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState";
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge";
import { DeviceChallenge } from "authentik-api";
@customElement("ak-stage-authenticator-validate-code")
export class AuthenticatorValidateStageWebCode extends BaseStage {
@property({ attribute: false })
challenge?: AuthenticatorValidateStageChallenge;
challenge?: AuthenticatorValidationChallenge;
@property({ attribute: false })
deviceChallenge?: DeviceChallenge;
@ -42,8 +44,8 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}"
user=${this.challenge.pending_user}>
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}>
<div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div>
@ -52,7 +54,7 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
label="${t`Code`}"
?required="${true}"
class="pf-c-form__group"
.errors=${(this.challenge?.response_errors || {})["code"]}>
.errors=${(this.challenge?.responseErrors || {})["code"]}>
<!-- @ts-ignore -->
<input type="text"
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 AKGlobal from "../../../authentik.css";
import { BaseStage } from "../base";
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage";
import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState";
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge";
import { DeviceChallenge } from "authentik-api";
@customElement("ak-stage-authenticator-validate-duo")
export class AuthenticatorValidateStageWebDuo extends BaseStage {
@property({ attribute: false })
challenge?: AuthenticatorValidateStageChallenge;
challenge?: AuthenticatorValidationChallenge;
@property({ attribute: false })
deviceChallenge?: DeviceChallenge;
@ -33,7 +34,7 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage {
firstUpdated(): void {
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); }}>
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}"
user=${this.challenge.pending_user}>
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}>
<div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div>

View File

@ -10,13 +10,15 @@ import AKGlobal from "../../../authentik.css";
import { PFSize } from "../../../elements/Spinner";
import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils";
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")
export class AuthenticatorValidateStageWebAuthn extends BaseStage {
@property({attribute: false})
challenge?: AuthenticatorValidateStageChallenge;
challenge?: AuthenticatorValidationChallenge;
@property({attribute: false})
deviceChallenge?: DeviceChallenge;

View File

@ -1,6 +1,5 @@
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.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 { BaseStage } from "../base";
import { Assertion, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils";
export interface WebAuthnAuthenticatorRegisterChallenge extends WithUserInfoChallenge {
registration: PublicKeyCredentialCreationOptions;
}
import { AuthenticatorWebAuthnChallenge } from "authentik-api";
export interface WebAuthnAuthenticatorRegisterChallengeResponse {
response: Assertion;
@ -24,7 +20,7 @@ export interface WebAuthnAuthenticatorRegisterChallengeResponse {
export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
@property({ attribute: false })
challenge?: WebAuthnAuthenticatorRegisterChallenge;
challenge?: AuthenticatorWebAuthnChallenge;
@property({type: Boolean})
registerRunning = false;
@ -42,7 +38,7 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
}
// convert certain members of the PublicKeyCredentialCreateOptions into
// 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.
let credential;
@ -106,8 +102,8 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
</div>`:
html`
<div class="pf-c-form__group pf-m-action">
${this.challenge?.response_errors ?
html`<p class="pf-m-block">${this.challenge.response_errors["response"][0].string}</p>`:
${this.challenge?.responseErrors ?
html`<p class="pf-m-block">${this.challenge.responseErrors["response"][0].string}</p>`:
html``}
<p class="pf-m-block">${this.registerMessage}</p>
<button class="pf-c-button pf-m-primary pf-m-block" @click=${() => {

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -12,18 +11,8 @@ import { BaseStage } from "../base";
import "../../../elements/EmptyState";
import "../../FormStatic";
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")
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); }}>
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}"
user=${this.challenge.pending_user}>
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}>
<div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div>
</ak-form-static>
<div class="pf-c-form__group">
<p id="header-text">
${this.challenge.header_text}
${this.challenge.headerText}
</p>
<p>${t`Application requires following permissions`}</p>
<ul class="pf-c-list" id="permmissions">

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { WithUserInfoChallenge } from "../../../api/Flows";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -14,10 +13,7 @@ import "../../../elements/EmptyState";
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import "../../FormStatic";
import { FlowURLManager } from "../../../api/legacy";
export interface PasswordChallenge extends WithUserInfoChallenge {
recovery_url?: string;
}
import { PasswordChallenge } from "authentik-api";
@customElement("ak-stage-password")
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);}}>
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pending_user_avatar}"
user=${this.challenge.pending_user}>
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}>
<div slot="link">
<a href="${FlowURLManager.cancel()}">${t`Not you?`}</a>
</div>
</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
label="${t`Password`}"
?required="${true}"
class="pf-c-form__group"
.errors=${(this.challenge?.response_errors || {})["password"]}>
.errors=${(this.challenge?.responseErrors || {})["password"]}>
<input type="password"
name="password"
placeholder="${t`Please enter your password`}"
@ -67,8 +63,8 @@ export class PasswordStage extends BaseStage {
value=${PasswordManagerPrefill.password || ""}>
</ak-form-element>
${this.challenge.recovery_url ?
html`<a href="${this.challenge.recovery_url}">
${this.challenge.recoveryUrl ?
html`<a href="${this.challenge.recoveryUrl}">
${t`Forgot password?`}</a>` : ""}
<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/EmptyState";
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")
export class PromptStage extends BaseStage {
@ -43,7 +32,7 @@ export class PromptStage extends BaseStage {
case "text":
return `<input
type="text"
name="${prompt.field_key}"
name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}"
autocomplete="off"
class="pf-c-form-control"
@ -52,7 +41,7 @@ export class PromptStage extends BaseStage {
case "username":
return `<input
type="text"
name="${prompt.field_key}"
name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}"
autocomplete="username"
class="pf-c-form-control"
@ -61,7 +50,7 @@ export class PromptStage extends BaseStage {
case "email":
return `<input
type="email"
name="${prompt.field_key}"
name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}"
class="pf-c-form-control"
?required=${prompt.required}
@ -69,7 +58,7 @@ export class PromptStage extends BaseStage {
case "password":
return `<input
type="password"
name="${prompt.field_key}"
name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}"
autocomplete="new-password"
class="pf-c-form-control"
@ -77,28 +66,28 @@ export class PromptStage extends BaseStage {
case "number":
return `<input
type="number"
name="${prompt.field_key}"
name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}"
class="pf-c-form-control"
?required=${prompt.required}>`;
case "checkbox":
return `<input
type="checkbox"
name="${prompt.field_key}"
name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}"
class="pf-c-form-control"
?required=${prompt.required}>`;
case "date":
return `<input
type="date"
name="${prompt.field_key}"
name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}"
class="pf-c-form-control"
?required=${prompt.required}>`;
case "date-time":
return `<input
type="datetime"
name="${prompt.field_key}"
name="${prompt.fieldKey}"
placeholder="${prompt.placeholder}"
class="pf-c-form-control"
?required=${prompt.required}>`;
@ -107,7 +96,7 @@ export class PromptStage extends BaseStage {
case "hidden":
return `<input
type="hidden"
name="${prompt.field_key}"
name="${prompt.fieldKey}"
value="${prompt.placeholder}"
class="pf-c-form-control"
?required=${prompt.required}>`;
@ -158,12 +147,12 @@ export class PromptStage extends BaseStage {
label="${prompt.label}"
?required="${prompt.required}"
class="pf-c-form__group"
.errors=${(this.challenge?.response_errors || {})[prompt.field_key]}>
.errors=${(this.challenge?.responseErrors || {})[prompt.fieldKey]}>
${unsafeHTML(this.renderPromptInner(prompt))}
</ak-form-element>`;
})}
${"non_field_errors" in (this.challenge?.response_errors || {}) ?
this.renderNonFieldErrors(this.challenge?.response_errors?.non_field_errors || []):
${"non_field_errors" in (this.challenge?.responseErrors || {}) ?
this.renderNonFieldErrors(this.challenge?.responseErrors?.non_field_errors || []):
html``}
<div class="pf-c-form__group pf-m-action">
<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."
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
msgid "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."
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
msgid "Always require consent"
msgstr "Always require consent"
@ -522,6 +530,10 @@ msgstr "Check IP"
msgid "Check Username"
msgstr "Check Username"
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
msgid "Check status"
msgstr "Check status"
#: src/flows/stages/email/EmailStage.ts
msgid "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/OAuth2ProviderViewPage.ts
#: src/pages/sources/plex/PlexSourceForm.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
msgid "Client ID"
msgstr "Client ID"
@ -583,6 +596,7 @@ msgid "Client IP"
msgstr "Client IP"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
msgid "Client Secret"
msgstr "Client Secret"
@ -616,6 +630,7 @@ msgstr "Confidential clients are capable of maintaining the confidentiality of t
msgid "Configuration"
msgstr "Configuration"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.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_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts
#: src/flows/stages/autosubmit/AutosubmitStage.ts
#: src/flows/stages/consent/ConsentStage.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."
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
msgid "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"
msgstr "Disable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "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."
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
msgid "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"
msgstr "Enable StartTLS"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "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."
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_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."
@ -1803,10 +1842,12 @@ msgstr "Load servers"
#: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.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_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts
#: src/flows/stages/autosubmit/AutosubmitStage.ts
#: src/flows/stages/captcha/CaptchaStage.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/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.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/SAMLSourceViewPage.ts
#: src/pages/stages/StageListPage.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -2173,9 +2216,11 @@ msgstr "Not found"
msgid "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_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts
#: src/flows/stages/captcha/CaptchaStage.ts
#: src/flows/stages/consent/ConsentStage.ts
#: src/flows/stages/password/PasswordStage.ts
@ -2617,6 +2662,10 @@ msgstr "RSA-SHA512"
msgid "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
msgid "Recovery"
msgstr "Recovery"
@ -2728,6 +2777,7 @@ msgid "Return home"
msgstr "Return home"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts
msgid "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)."
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
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."
@ -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."
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_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -3074,7 +3129,7 @@ msgstr "State"
msgid "Static Tokens"
msgstr "Static Tokens"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Static tokens"
msgstr "Static tokens"
@ -3090,11 +3145,13 @@ msgstr "Statically deny the flow. To use this stage effectively, disable *Evalua
msgid "Status"
msgstr "Status"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Disabled"
msgstr "Status: Disabled"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Enabled"
@ -3204,6 +3261,7 @@ msgstr "Successfully created service-connection."
msgid "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_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -3342,6 +3400,7 @@ msgstr "Successfully updated service-connection."
msgid "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_totp/AuthenticatorTOTPStageForm.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)."
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"
msgstr "Time-based One-Time Passwords"
@ -3893,7 +3952,6 @@ msgstr "User details"
msgid "User events"
msgstr "User events"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "User fields"
msgstr "User fields"

View File

@ -63,6 +63,10 @@ msgstr ""
msgid "ANY, any policy must match to include this stage access."
msgstr ""
#:
msgid "API Hostname"
msgstr ""
#:
msgid "API Requests"
msgstr ""
@ -180,6 +184,10 @@ msgstr ""
msgid "Also known as Entity ID. Defaults the Metadata URL."
msgstr ""
#:
msgid "Alternatively, if your current device has Duo installed, click on this link:"
msgstr ""
#:
msgid "Always require consent"
msgstr ""
@ -518,6 +526,10 @@ msgstr ""
msgid "Check Username"
msgstr ""
#:
msgid "Check status"
msgstr ""
#:
msgid "Check your Emails for a password reset link."
msgstr ""
@ -566,6 +578,7 @@ msgstr ""
#:
#:
#:
#:
msgid "Client ID"
msgstr ""
@ -576,6 +589,7 @@ msgstr ""
msgid "Client IP"
msgstr ""
#:
#:
msgid "Client Secret"
msgstr ""
@ -614,6 +628,7 @@ msgstr ""
#:
#:
#:
#:
msgid "Configuration flow"
msgstr ""
@ -715,6 +730,7 @@ msgstr ""
#:
#:
#:
#:
msgid "Continue"
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."
msgstr ""
#:
msgid "Device classes"
msgstr ""
#:
msgid "Device classes which can be used to authenticate."
msgstr ""
@ -1024,6 +1044,7 @@ msgstr ""
msgid "Disable"
msgstr ""
#:
#:
msgid "Disable Static Tokens"
msgstr ""
@ -1062,6 +1083,22 @@ msgstr ""
msgid "Dummy stage used for testing. Shows a simple continue button and always passes."
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."
msgstr ""
@ -1151,6 +1188,7 @@ msgstr ""
msgid "Enable StartTLS"
msgstr ""
#:
#:
msgid "Enable Static Tokens"
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."
msgstr ""
#:
#:
#:
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"
msgstr ""
@ -1867,6 +1908,7 @@ msgstr ""
#:
#:
#:
#:
msgid "Loading..."
msgstr ""
@ -2054,6 +2096,7 @@ msgstr ""
#:
#:
#:
#:
msgid "Name"
msgstr ""
@ -2171,6 +2214,8 @@ msgstr ""
#:
#:
#:
#:
#:
msgid "Not you?"
msgstr ""
@ -2609,6 +2654,10 @@ msgstr ""
msgid "Re-evaluate policies"
msgstr ""
#:
msgid "Receive a push notification on your phone to prove your identity."
msgstr ""
#:
msgid "Recovery"
msgstr ""
@ -2719,6 +2768,7 @@ msgstr ""
msgid "Return home"
msgstr ""
#:
#:
#:
msgid "Return to device picker"
@ -3025,6 +3075,10 @@ msgstr ""
msgid "Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello)."
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."
msgstr ""
@ -3044,6 +3098,7 @@ msgstr ""
#:
#:
#:
#:
msgid "Stage-specific settings"
msgstr ""
@ -3082,11 +3137,13 @@ msgstr ""
msgid "Status"
msgstr ""
#:
#:
#:
msgid "Status: Disabled"
msgstr ""
#:
#:
#:
msgid "Status: Enabled"
@ -3213,6 +3270,7 @@ msgstr ""
#:
#:
#:
#:
msgid "Successfully created stage."
msgstr ""
@ -3351,6 +3409,7 @@ msgstr ""
#:
#:
#:
#:
msgid "Successfully updated stage."
msgstr ""
@ -3881,7 +3940,6 @@ msgstr ""
msgid "User events"
msgstr ""
#:
#:
msgid "User fields"
msgstr ""