Compare commits

...

1 Commits

Author SHA1 Message Date
5797a51993 initial steps for concurrent execution
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-03-19 23:03:59 +00:00
9 changed files with 250 additions and 32 deletions

View File

@ -54,6 +54,7 @@ class Challenge(PassiveSerializer):
flow_info = ContextualFlowInfo(required=False) flow_info = ContextualFlowInfo(required=False)
component = CharField(default="") component = CharField(default="")
xid = CharField(required=False)
response_errors = DictField( response_errors = DictField(
child=ErrorDetailSerializer(many=True), allow_empty=True, required=False child=ErrorDetailSerializer(many=True), allow_empty=True, required=False

View File

@ -143,10 +143,12 @@ class FlowPlan:
request: HttpRequest, request: HttpRequest,
flow: Flow, flow: Flow,
allowed_silent_types: list["StageView"] | None = None, allowed_silent_types: list["StageView"] | None = None,
**get_params,
) -> HttpResponse: ) -> HttpResponse:
"""Redirect to the flow executor for this flow plan""" """Redirect to the flow executor for this flow plan"""
from authentik.flows.views.executor import ( from authentik.flows.views.executor import (
SESSION_KEY_PLAN, SESSION_KEY_PLAN,
FlowContainer,
FlowExecutorView, FlowExecutorView,
) )
@ -157,6 +159,7 @@ class FlowPlan:
# No unskippable stages found, so we can directly return the response of the last stage # No unskippable stages found, so we can directly return the response of the last stage
final_stage: type[StageView] = self.bindings[-1].stage.view final_stage: type[StageView] = self.bindings[-1].stage.view
temp_exec = FlowExecutorView(flow=flow, request=request, plan=self) temp_exec = FlowExecutorView(flow=flow, request=request, plan=self)
temp_exec.container = FlowContainer(request)
temp_exec.current_stage = self.bindings[-1].stage temp_exec.current_stage = self.bindings[-1].stage
temp_exec.current_stage_view = final_stage temp_exec.current_stage_view = final_stage
temp_exec.setup(request, flow.slug) temp_exec.setup(request, flow.slug)
@ -174,6 +177,9 @@ class FlowPlan:
): ):
get_qs["inspector"] = "available" get_qs["inspector"] = "available"
for key, value in get_params:
get_qs[key] = value
return redirect_with_qs( return redirect_with_qs(
"authentik_core:if-flow", "authentik_core:if-flow",
get_qs, get_qs,

View File

@ -191,6 +191,7 @@ class ChallengeStageView(StageView):
) )
flow_info.is_valid() flow_info.is_valid()
challenge.initial_data["flow_info"] = flow_info.data challenge.initial_data["flow_info"] = flow_info.data
challenge.initial_data["xid"] = self.executor.container.exec_id
if isinstance(challenge, WithUserInfoChallenge): if isinstance(challenge, WithUserInfoChallenge):
# If there's a pending user, update the `username` field # If there's a pending user, update the `username` field
# this field is only used by password managers. # this field is only used by password managers.

View File

@ -28,7 +28,7 @@ window.authentik.flow = {
{% block body %} {% block body %}
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-flow-executor flowSlug="{{ flow.slug }}"> <ak-flow-executor flowSlug="{{ flow.slug }}" xid="{{ xid }}">
<ak-loading></ak-loading> <ak-loading></ak-loading>
</ak-flow-executor> </ak-flow-executor>
{% endblock %} {% endblock %}

View File

@ -1,6 +1,7 @@
"""authentik multi-stage authentication engine""" """authentik multi-stage authentication engine"""
from copy import deepcopy from copy import deepcopy
from uuid import uuid4
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
@ -64,6 +65,7 @@ from authentik.policies.engine import PolicyEngine
LOGGER = get_logger() LOGGER = get_logger()
# Argument used to redirect user after login # Argument used to redirect user after login
NEXT_ARG_NAME = "next" NEXT_ARG_NAME = "next"
SESSION_KEY_PLAN_CONTAINER = "authentik/flows/plan_container/%s"
SESSION_KEY_PLAN = "authentik/flows/plan" SESSION_KEY_PLAN = "authentik/flows/plan"
SESSION_KEY_APPLICATION_PRE = "authentik/flows/application_pre" SESSION_KEY_APPLICATION_PRE = "authentik/flows/application_pre"
SESSION_KEY_GET = "authentik/flows/get" SESSION_KEY_GET = "authentik/flows/get"
@ -71,6 +73,7 @@ SESSION_KEY_POST = "authentik/flows/post"
SESSION_KEY_HISTORY = "authentik/flows/history" SESSION_KEY_HISTORY = "authentik/flows/history"
QS_KEY_TOKEN = "flow_token" # nosec QS_KEY_TOKEN = "flow_token" # nosec
QS_QUERY = "query" QS_QUERY = "query"
QS_EXEC_ID = "xid"
def challenge_types(): def challenge_types():
@ -97,6 +100,88 @@ class InvalidStageError(SentryIgnoredException):
"""Error raised when a challenge from a stage is not valid""" """Error raised when a challenge from a stage is not valid"""
class FlowContainer:
"""Allow for multiple concurrent flow executions in the same session"""
def __init__(self, request: HttpRequest, exec_id: str | None = None) -> None:
self.request = request
self.exec_id = exec_id
@staticmethod
def new(request: HttpRequest):
exec_id = str(uuid4())
request.session[SESSION_KEY_PLAN_CONTAINER % exec_id] = {}
return FlowContainer(request, exec_id)
def exists(self) -> bool:
"""Check if flow exists in container/session"""
return SESSION_KEY_PLAN in self.session
def save(self):
self.request.session.modified = True
@property
def session(self):
# Backwards compatibility: store session plan/etc directly in session
if not self.exec_id:
return self.request.session
self.request.session.setdefault(SESSION_KEY_PLAN_CONTAINER % self.exec_id, {})
return self.request.session.get(SESSION_KEY_PLAN_CONTAINER % self.exec_id, {})
@property
def plan(self) -> FlowPlan:
return self.session.get(SESSION_KEY_PLAN)
def to_redirect(
self,
request: HttpRequest,
flow: Flow,
allowed_silent_types: list[StageView] | None = None,
**get_params,
) -> HttpResponse:
get_params[QS_EXEC_ID] = self.exec_id
return self.plan.to_redirect(
request, flow, allowed_silent_types=allowed_silent_types, **get_params
)
@plan.setter
def plan(self, value: FlowPlan):
self.session[SESSION_KEY_PLAN] = value
self.request.session.modified = True
self.save()
@property
def application_pre(self):
return self.session.get(SESSION_KEY_APPLICATION_PRE)
@property
def get(self) -> QueryDict:
return self.session.get(SESSION_KEY_GET)
@get.setter
def get(self, value: QueryDict):
self.session[SESSION_KEY_GET] = value
self.save()
@property
def post(self) -> QueryDict:
return self.session.get(SESSION_KEY_POST)
@post.setter
def post(self, value: QueryDict):
self.session[SESSION_KEY_POST] = value
self.save()
@property
def history(self) -> list[FlowPlan]:
return self.session.get(SESSION_KEY_HISTORY)
@history.setter
def history(self, value: list[FlowPlan]):
self.session[SESSION_KEY_HISTORY] = value
self.save()
@method_decorator(xframe_options_sameorigin, name="dispatch") @method_decorator(xframe_options_sameorigin, name="dispatch")
class FlowExecutorView(APIView): class FlowExecutorView(APIView):
"""Flow executor, passing requests to Stage Views""" """Flow executor, passing requests to Stage Views"""
@ -104,8 +189,9 @@ class FlowExecutorView(APIView):
permission_classes = [AllowAny] permission_classes = [AllowAny]
flow: Flow = None flow: Flow = None
plan: FlowPlan | None = None plan: FlowPlan | None = None
container: FlowContainer
current_binding: FlowStageBinding | None = None current_binding: FlowStageBinding | None = None
current_stage: Stage current_stage: Stage
current_stage_view: View current_stage_view: View
@ -160,10 +246,12 @@ class FlowExecutorView(APIView):
if QS_KEY_TOKEN in get_params: if QS_KEY_TOKEN in get_params:
plan = self._check_flow_token(get_params[QS_KEY_TOKEN]) plan = self._check_flow_token(get_params[QS_KEY_TOKEN])
if plan: if plan:
self.request.session[SESSION_KEY_PLAN] = plan container = FlowContainer.new(request)
container.plan = plan
# Early check if there's an active Plan for the current session # Early check if there's an active Plan for the current session
if SESSION_KEY_PLAN in self.request.session: self.container = FlowContainer(request, request.GET.get(QS_EXEC_ID))
self.plan: FlowPlan = self.request.session[SESSION_KEY_PLAN] if self.container.exists():
self.plan: FlowPlan = self.container.plan
if self.plan.flow_pk != self.flow.pk.hex: if self.plan.flow_pk != self.flow.pk.hex:
self._logger.warning( self._logger.warning(
"f(exec): Found existing plan for other flow, deleting plan", "f(exec): Found existing plan for other flow, deleting plan",
@ -176,13 +264,14 @@ class FlowExecutorView(APIView):
self._logger.debug("f(exec): Continuing existing plan") self._logger.debug("f(exec): Continuing existing plan")
# Initial flow request, check if we have an upstream query string passed in # Initial flow request, check if we have an upstream query string passed in
request.session[SESSION_KEY_GET] = get_params self.container.get = get_params
# Don't check session again as we've either already loaded the plan or we need to plan # Don't check session again as we've either already loaded the plan or we need to plan
if not self.plan: if not self.plan:
request.session[SESSION_KEY_HISTORY] = [] self.container.history = []
self._logger.debug("f(exec): No active Plan found, initiating planner") self._logger.debug("f(exec): No active Plan found, initiating planner")
try: try:
self.plan = self._initiate_plan() self.plan = self._initiate_plan()
self.container.plan = self.plan
except FlowNonApplicableException as exc: except FlowNonApplicableException as exc:
self._logger.warning("f(exec): Flow not applicable to current user", exc=exc) self._logger.warning("f(exec): Flow not applicable to current user", exc=exc)
return self.handle_invalid_flow(exc) return self.handle_invalid_flow(exc)
@ -254,12 +343,19 @@ class FlowExecutorView(APIView):
request=OpenApiTypes.NONE, request=OpenApiTypes.NONE,
parameters=[ parameters=[
OpenApiParameter( OpenApiParameter(
name="query", name=QS_QUERY,
location=OpenApiParameter.QUERY, location=OpenApiParameter.QUERY,
required=True, required=True,
description="Querystring as received", description="Querystring as received",
type=OpenApiTypes.STR, type=OpenApiTypes.STR,
) ),
OpenApiParameter(
name=QS_EXEC_ID,
location=OpenApiParameter.QUERY,
required=False,
description="Flow execution ID",
type=OpenApiTypes.STR,
),
], ],
operation_id="flows_executor_get", operation_id="flows_executor_get",
) )
@ -286,7 +382,7 @@ class FlowExecutorView(APIView):
span.set_data("authentik Stage", self.current_stage_view) span.set_data("authentik Stage", self.current_stage_view)
span.set_data("authentik Flow", self.flow.slug) span.set_data("authentik Flow", self.flow.slug)
stage_response = self.current_stage_view.dispatch(request) stage_response = self.current_stage_view.dispatch(request)
return to_stage_response(request, stage_response) return to_stage_response(request, stage_response, self.container.exec_id)
except Exception as exc: except Exception as exc:
return self.handle_exception(exc) return self.handle_exception(exc)
@ -305,12 +401,19 @@ class FlowExecutorView(APIView):
), ),
parameters=[ parameters=[
OpenApiParameter( OpenApiParameter(
name="query", name=QS_QUERY,
location=OpenApiParameter.QUERY, location=OpenApiParameter.QUERY,
required=True, required=True,
description="Querystring as received", description="Querystring as received",
type=OpenApiTypes.STR, type=OpenApiTypes.STR,
) ),
OpenApiParameter(
name=QS_EXEC_ID,
location=OpenApiParameter.QUERY,
required=True,
description="Flow execution ID",
type=OpenApiTypes.STR,
),
], ],
operation_id="flows_executor_solve", operation_id="flows_executor_solve",
) )
@ -337,14 +440,15 @@ class FlowExecutorView(APIView):
span.set_data("authentik Stage", self.current_stage_view) span.set_data("authentik Stage", self.current_stage_view)
span.set_data("authentik Flow", self.flow.slug) span.set_data("authentik Flow", self.flow.slug)
stage_response = self.current_stage_view.dispatch(request) stage_response = self.current_stage_view.dispatch(request)
return to_stage_response(request, stage_response) return to_stage_response(request, stage_response, self.container.exec_id)
except Exception as exc: except Exception as exc:
return self.handle_exception(exc) return self.handle_exception(exc)
def _initiate_plan(self) -> FlowPlan: def _initiate_plan(self) -> FlowPlan:
planner = FlowPlanner(self.flow) planner = FlowPlanner(self.flow)
plan = planner.plan(self.request) plan = planner.plan(self.request)
self.request.session[SESSION_KEY_PLAN] = plan container = FlowContainer.new(self.request)
container.plan = plan
try: try:
# Call the has_stages getter to check that # Call the has_stages getter to check that
# there are no issues with the class we might've gotten # there are no issues with the class we might've gotten
@ -368,7 +472,7 @@ class FlowExecutorView(APIView):
except FlowNonApplicableException as exc: except FlowNonApplicableException as exc:
self._logger.warning("f(exec): Flow restart not applicable to current user", exc=exc) self._logger.warning("f(exec): Flow restart not applicable to current user", exc=exc)
return self.handle_invalid_flow(exc) return self.handle_invalid_flow(exc)
self.request.session[SESSION_KEY_PLAN] = plan self.container.plan = plan
kwargs = self.kwargs kwargs = self.kwargs
kwargs.update({"flow_slug": self.flow.slug}) kwargs.update({"flow_slug": self.flow.slug})
return redirect_with_qs("authentik_api:flow-executor", self.request.GET, **kwargs) return redirect_with_qs("authentik_api:flow-executor", self.request.GET, **kwargs)
@ -390,9 +494,13 @@ class FlowExecutorView(APIView):
) )
self.cancel() self.cancel()
if next_param and not is_url_absolute(next_param): if next_param and not is_url_absolute(next_param):
return to_stage_response(self.request, redirect_with_qs(next_param)) return to_stage_response(
self.request, redirect_with_qs(next_param), self.container.exec_id
)
return to_stage_response( return to_stage_response(
self.request, self.stage_invalid(error_message=_("Invalid next URL")) self.request,
self.stage_invalid(error_message=_("Invalid next URL")),
self.container.exec_id,
) )
def stage_ok(self) -> HttpResponse: def stage_ok(self) -> HttpResponse:
@ -406,7 +514,7 @@ class FlowExecutorView(APIView):
self.current_stage_view.cleanup() self.current_stage_view.cleanup()
self.request.session.get(SESSION_KEY_HISTORY, []).append(deepcopy(self.plan)) self.request.session.get(SESSION_KEY_HISTORY, []).append(deepcopy(self.plan))
self.plan.pop() self.plan.pop()
self.request.session[SESSION_KEY_PLAN] = self.plan self.container.plan = self.plan
if self.plan.bindings: if self.plan.bindings:
self._logger.debug( self._logger.debug(
"f(exec): Continuing with next stage", "f(exec): Continuing with next stage",
@ -449,6 +557,7 @@ class FlowExecutorView(APIView):
def cancel(self): def cancel(self):
"""Cancel current flow execution""" """Cancel current flow execution"""
# TODO: Clean up container
keys_to_delete = [ keys_to_delete = [
SESSION_KEY_APPLICATION_PRE, SESSION_KEY_APPLICATION_PRE,
SESSION_KEY_PLAN, SESSION_KEY_PLAN,
@ -471,8 +580,8 @@ class CancelView(View):
def get(self, request: HttpRequest) -> HttpResponse: def get(self, request: HttpRequest) -> HttpResponse:
"""View which canels the currently active plan""" """View which canels the currently active plan"""
if SESSION_KEY_PLAN in request.session: if FlowContainer(request, request.GET.get(QS_EXEC_ID)).exists():
del request.session[SESSION_KEY_PLAN] del request.session[SESSION_KEY_PLAN_CONTAINER % request.GET.get(QS_EXEC_ID)]
LOGGER.debug("Canceled current plan") LOGGER.debug("Canceled current plan")
return redirect("authentik_flows:default-invalidation") return redirect("authentik_flows:default-invalidation")
@ -520,19 +629,12 @@ class ToDefaultFlow(View):
def dispatch(self, request: HttpRequest) -> HttpResponse: def dispatch(self, request: HttpRequest) -> HttpResponse:
flow = self.get_flow() flow = self.get_flow()
# If user already has a pending plan, clear it so we don't have to later. get_qs = request.GET.copy()
if SESSION_KEY_PLAN in self.request.session: get_qs[QS_EXEC_ID] = str(uuid4())
plan: FlowPlan = self.request.session[SESSION_KEY_PLAN] return redirect_with_qs("authentik_core:if-flow", get_qs, flow_slug=flow.slug)
if plan.flow_pk != flow.pk.hex:
LOGGER.warning(
"f(def): Found existing plan for other flow, deleting plan",
flow_slug=flow.slug,
)
del self.request.session[SESSION_KEY_PLAN]
return redirect_with_qs("authentik_core:if-flow", request.GET, flow_slug=flow.slug)
def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpResponse: def to_stage_response(request: HttpRequest, source: HttpResponse, xid: str) -> HttpResponse:
"""Convert normal HttpResponse into JSON Response""" """Convert normal HttpResponse into JSON Response"""
if ( if (
isinstance(source, HttpResponseRedirect) isinstance(source, HttpResponseRedirect)
@ -551,6 +653,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons
RedirectChallenge( RedirectChallenge(
{ {
"to": str(redirect_url), "to": str(redirect_url),
"xid": xid,
} }
) )
) )
@ -559,6 +662,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons
ShellChallenge( ShellChallenge(
{ {
"body": source.render().content.decode("utf-8"), "body": source.render().content.decode("utf-8"),
"xid": xid,
} }
) )
) )
@ -568,6 +672,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons
ShellChallenge( ShellChallenge(
{ {
"body": source.content.decode("utf-8"), "body": source.content.decode("utf-8"),
"xid": xid,
} }
) )
) )
@ -599,4 +704,6 @@ class ConfigureFlowInitView(LoginRequiredMixin, View):
except FlowNonApplicableException: except FlowNonApplicableException:
LOGGER.warning("Flow not applicable to user") LOGGER.warning("Flow not applicable to user")
raise Http404 from None raise Http404 from None
return plan.to_redirect(request, stage.configure_flow) container = FlowContainer.new(request)
container.plan = plan
return container.to_redirect(request, stage.configure_flow)

View File

@ -7,6 +7,7 @@ from ua_parser.user_agent_parser import Parse
from authentik.core.views.interface import InterfaceView from authentik.core.views.interface import InterfaceView
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.flows.views.executor import QS_EXEC_ID
class FlowInterfaceView(InterfaceView): class FlowInterfaceView(InterfaceView):
@ -15,6 +16,7 @@ class FlowInterfaceView(InterfaceView):
def get_context_data(self, **kwargs: Any) -> dict[str, Any]: def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug")) kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
kwargs["inspector"] = "inspector" in self.request.GET kwargs["inspector"] = "inspector" in self.request.GET
kwargs["xid"] = self.request.GET.get(QS_EXEC_ID)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def compat_needs_sfe(self) -> bool: def compat_needs_sfe(self) -> bool:

2
go.mod
View File

@ -82,3 +82,5 @@ require (
google.golang.org/protobuf v1.36.1 // indirect google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
replace goauthentik.io/api/v3 => ./gen-go-api

View File

@ -8917,6 +8917,11 @@ paths:
type: string type: string
description: Querystring as received description: Querystring as received
required: true required: true
- in: query
name: xid
schema:
type: string
description: Flow execution ID
tags: tags:
- flows - flows
security: security:
@ -8957,6 +8962,12 @@ paths:
type: string type: string
description: Querystring as received description: Querystring as received
required: true required: true
- in: query
name: xid
schema:
type: string
description: Flow execution ID
required: true
tags: tags:
- flows - flows
requestBody: requestBody:
@ -39437,6 +39448,8 @@ components:
component: component:
type: string type: string
default: ak-stage-access-denied default: ak-stage-access-denied
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -39452,6 +39465,7 @@ components:
required: required:
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- xid
AlgEnum: AlgEnum:
enum: enum:
- rsa - rsa
@ -39551,6 +39565,8 @@ components:
component: component:
type: string type: string
default: ak-source-oauth-apple default: ak-source-oauth-apple
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -39570,6 +39586,7 @@ components:
- redirect_uri - redirect_uri
- scope - scope
- state - state
- xid
Application: Application:
type: object type: object
description: Application Serializer description: Application Serializer
@ -39878,6 +39895,8 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-duo default: ak-stage-authenticator-duo
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -39900,6 +39919,7 @@ components:
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- stage_uuid - stage_uuid
- xid
AuthenticatorDuoChallengeResponseRequest: AuthenticatorDuoChallengeResponseRequest:
type: object type: object
description: Pseudo class for duo response description: Pseudo class for duo response
@ -40037,6 +40057,8 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-email default: ak-stage-authenticator-email
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40056,6 +40078,7 @@ components:
required: required:
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- xid
AuthenticatorEmailChallengeResponseRequest: AuthenticatorEmailChallengeResponseRequest:
type: object type: object
description: Authenticator Email Challenge response, device is set by get_response_instance description: Authenticator Email Challenge response, device is set by get_response_instance
@ -40293,6 +40316,8 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-sms default: ak-stage-authenticator-sms
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40309,6 +40334,7 @@ components:
required: required:
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- xid
AuthenticatorSMSChallengeResponseRequest: AuthenticatorSMSChallengeResponseRequest:
type: object type: object
description: SMS Challenge response, device is set by get_response_instance description: SMS Challenge response, device is set by get_response_instance
@ -40456,6 +40482,8 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-static default: ak-stage-authenticator-static
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40474,6 +40502,7 @@ components:
- codes - codes
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- xid
AuthenticatorStaticChallengeResponseRequest: AuthenticatorStaticChallengeResponseRequest:
type: object type: object
description: Pseudo class for static response description: Pseudo class for static response
@ -40577,6 +40606,8 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-totp default: ak-stage-authenticator-totp
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40593,6 +40624,7 @@ components:
- config_url - config_url
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- xid
AuthenticatorTOTPChallengeResponseRequest: AuthenticatorTOTPChallengeResponseRequest:
type: object type: object
description: TOTP Challenge response, device is set by get_response_instance description: TOTP Challenge response, device is set by get_response_instance
@ -40804,6 +40836,8 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-validate default: ak-stage-authenticator-validate
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40827,6 +40861,7 @@ components:
- device_challenges - device_challenges
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- xid
AuthenticatorValidationChallengeResponseRequest: AuthenticatorValidationChallengeResponseRequest:
type: object type: object
description: Challenge used for Code-based and WebAuthn authenticators description: Challenge used for Code-based and WebAuthn authenticators
@ -40857,6 +40892,8 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-webauthn default: ak-stage-authenticator-webauthn
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40874,6 +40911,7 @@ components:
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- registration - registration
- xid
AuthenticatorWebAuthnChallengeResponseRequest: AuthenticatorWebAuthnChallengeResponseRequest:
type: object type: object
description: WebAuthn Challenge response description: WebAuthn Challenge response
@ -41006,6 +41044,8 @@ components:
component: component:
type: string type: string
default: ak-stage-autosubmit default: ak-stage-autosubmit
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -41023,6 +41063,7 @@ components:
required: required:
- attrs - attrs
- url - url
- xid
BackendsEnum: BackendsEnum:
enum: enum:
- authentik.core.auth.InbuiltBackend - authentik.core.auth.InbuiltBackend
@ -41269,6 +41310,8 @@ components:
component: component:
type: string type: string
default: ak-stage-captcha default: ak-stage-captcha
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -41291,6 +41334,7 @@ components:
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- site_key - site_key
- xid
CaptchaChallengeResponseRequest: CaptchaChallengeResponseRequest:
type: object type: object
description: Validate captcha token description: Validate captcha token
@ -41674,6 +41718,8 @@ components:
component: component:
type: string type: string
default: ak-stage-consent default: ak-stage-consent
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -41702,6 +41748,7 @@ components:
- pending_user_avatar - pending_user_avatar
- permissions - permissions
- token - token
- xid
ConsentChallengeResponseRequest: ConsentChallengeResponseRequest:
type: object type: object
description: Consent challenge response, any valid response request is valid description: Consent challenge response, any valid response request is valid
@ -42475,6 +42522,8 @@ components:
component: component:
type: string type: string
default: ak-stage-dummy default: ak-stage-dummy
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -42485,6 +42534,7 @@ components:
type: string type: string
required: required:
- name - name
- xid
DummyChallengeResponseRequest: DummyChallengeResponseRequest:
type: object type: object
description: Dummy challenge response description: Dummy challenge response
@ -42677,12 +42727,16 @@ components:
component: component:
type: string type: string
default: ak-stage-email default: ak-stage-email
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
type: array type: array
items: items:
$ref: '#/components/schemas/ErrorDetail' $ref: '#/components/schemas/ErrorDetail'
required:
- xid
EmailChallengeResponseRequest: EmailChallengeResponseRequest:
type: object type: object
description: |- description: |-
@ -43601,6 +43655,8 @@ components:
component: component:
type: string type: string
default: ak-stage-flow-error default: ak-stage-flow-error
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -43615,6 +43671,7 @@ components:
type: string type: string
required: required:
- request_id - request_id
- xid
FlowImportResult: FlowImportResult:
type: object type: object
description: Logs of an attempted flow import description: Logs of an attempted flow import
@ -43929,6 +43986,8 @@ components:
component: component:
type: string type: string
default: xak-flow-frame default: xak-flow-frame
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -43945,6 +44004,7 @@ components:
required: required:
- loading_text - loading_text
- url - url
- xid
FrameChallengeResponseRequest: FrameChallengeResponseRequest:
type: object type: object
description: Base class for all challenge responses description: Base class for all challenge responses
@ -44747,6 +44807,8 @@ components:
component: component:
type: string type: string
default: ak-stage-identification default: ak-stage-identification
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -44791,6 +44853,7 @@ components:
- primary_action - primary_action
- show_source_labels - show_source_labels
- user_fields - user_fields
- xid
IdentificationChallengeResponseRequest: IdentificationChallengeResponseRequest:
type: object type: object
description: Identification challenge description: Identification challenge
@ -47233,12 +47296,16 @@ components:
component: component:
type: string type: string
default: ak-provider-oauth2-device-code default: ak-provider-oauth2-device-code
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
type: array type: array
items: items:
$ref: '#/components/schemas/ErrorDetail' $ref: '#/components/schemas/ErrorDetail'
required:
- xid
OAuthDeviceCodeChallengeResponseRequest: OAuthDeviceCodeChallengeResponseRequest:
type: object type: object
description: Response that includes the user-entered device code description: Response that includes the user-entered device code
@ -47261,12 +47328,16 @@ components:
component: component:
type: string type: string
default: ak-provider-oauth2-device-code-finish default: ak-provider-oauth2-device-code-finish
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
type: array type: array
items: items:
$ref: '#/components/schemas/ErrorDetail' $ref: '#/components/schemas/ErrorDetail'
required:
- xid
OAuthDeviceCodeFinishChallengeResponseRequest: OAuthDeviceCodeFinishChallengeResponseRequest:
type: object type: object
description: Response that device has been authenticated and tab can be closed description: Response that device has been authenticated and tab can be closed
@ -49411,6 +49482,8 @@ components:
component: component:
type: string type: string
default: ak-stage-password default: ak-stage-password
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -49429,6 +49502,7 @@ components:
required: required:
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- xid
PasswordChallengeResponseRequest: PasswordChallengeResponseRequest:
type: object type: object
description: Password challenge response description: Password challenge response
@ -52990,6 +53064,8 @@ components:
component: component:
type: string type: string
default: ak-source-plex default: ak-source-plex
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -53003,6 +53079,7 @@ components:
required: required:
- client_id - client_id
- slug - slug
- xid
PlexAuthenticationChallengeResponseRequest: PlexAuthenticationChallengeResponseRequest:
type: object type: object
description: Pseudo class for plex response description: Pseudo class for plex response
@ -53515,6 +53592,8 @@ components:
component: component:
type: string type: string
default: ak-stage-prompt default: ak-stage-prompt
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -53527,6 +53606,7 @@ components:
$ref: '#/components/schemas/StagePrompt' $ref: '#/components/schemas/StagePrompt'
required: required:
- fields - fields
- xid
PromptChallengeResponseRequest: PromptChallengeResponseRequest:
type: object type: object
description: |- description: |-
@ -54711,6 +54791,8 @@ components:
component: component:
type: string type: string
default: xak-flow-redirect default: xak-flow-redirect
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -54721,6 +54803,7 @@ components:
type: string type: string
required: required:
- to - to
- xid
RedirectChallengeResponseRequest: RedirectChallengeResponseRequest:
type: object type: object
description: Redirect challenge response description: Redirect challenge response
@ -56616,6 +56699,8 @@ components:
component: component:
type: string type: string
default: ak-stage-session-end default: ak-stage-session-end
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -56638,6 +56723,7 @@ components:
- brand_name - brand_name
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- xid
SessionUser: SessionUser:
type: object type: object
description: |- description: |-
@ -56750,6 +56836,8 @@ components:
component: component:
type: string type: string
default: xak-flow-shell default: xak-flow-shell
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -56760,6 +56848,7 @@ components:
type: string type: string
required: required:
- body - body
- xid
SignatureAlgorithmEnum: SignatureAlgorithmEnum:
enum: enum:
- http://www.w3.org/2000/09/xmldsig#rsa-sha1 - http://www.w3.org/2000/09/xmldsig#rsa-sha1
@ -58034,6 +58123,8 @@ components:
component: component:
type: string type: string
default: ak-stage-user-login default: ak-stage-user-login
xid:
type: string
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -58047,6 +58138,7 @@ components:
required: required:
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- xid
UserLoginChallengeResponseRequest: UserLoginChallengeResponseRequest:
type: object type: object
description: User login challenge description: User login challenge

View File

@ -85,6 +85,9 @@ export class FlowExecutor extends Interface implements StageHost {
ws: WebsocketClient; ws: WebsocketClient;
@property()
xid?: string;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage].concat(css` return [PFBase, PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage].concat(css`
:host { :host {
@ -219,6 +222,7 @@ export class FlowExecutor extends Interface implements StageHost {
const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({ const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
flowSlug: this.flowSlug, flowSlug: this.flowSlug,
query: window.location.search.substring(1), query: window.location.search.substring(1),
xid: this.xid || "",
flowChallengeResponseRequest: payload, flowChallengeResponseRequest: payload,
}); });
if (this.inspectorOpen) { if (this.inspectorOpen) {
@ -252,6 +256,7 @@ export class FlowExecutor extends Interface implements StageHost {
const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({ const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({
flowSlug: this.flowSlug, flowSlug: this.flowSlug,
query: window.location.search.substring(1), query: window.location.search.substring(1),
xid: this.xid,
}); });
if (this.inspectorOpen) { if (this.inspectorOpen) {
window.dispatchEvent( window.dispatchEvent(
@ -262,6 +267,7 @@ export class FlowExecutor extends Interface implements StageHost {
); );
} }
this.challenge = challenge; this.challenge = challenge;
this.xid = challenge.xid ?? undefined;
if (this.challenge.flowInfo) { if (this.challenge.flowInfo) {
this.flowInfo = this.challenge.flowInfo; this.flowInfo = this.challenge.flowInfo;
} }
@ -286,6 +292,7 @@ export class FlowExecutor extends Interface implements StageHost {
component: "ak-stage-flow-error", component: "ak-stage-flow-error",
error: body, error: body,
requestId: "", requestId: "",
xid: "",
}; };
this.challenge = challenge as ChallengeTypes; this.challenge = challenge as ChallengeTypes;
} }