diff --git a/authentik/stages/authenticator_mobile/stage.py b/authentik/stages/authenticator_mobile/stage.py index e120f0ac6..b2e1ef7ca 100644 --- a/authentik/stages/authenticator_mobile/stage.py +++ b/authentik/stages/authenticator_mobile/stage.py @@ -12,7 +12,8 @@ from authentik.flows.challenge import ( from authentik.flows.stage import ChallengeStageView from authentik.stages.authenticator_mobile.models import MobileDevice, MobileDeviceToken -FLOW_PLAN_MOBILE_ENROLL = "authentik/stages/authenticator_mobile/enroll" +FLOW_PLAN_MOBILE_ENROLL_TOKEN = "authentik/stages/authenticator_mobile/enroll/token" +FLOW_PLAN_MOBILE_ENROLL_DEVICE = "authentik/stages/authenticator_mobile/enroll/device" class AuthenticatorMobilePayloadChallenge(PassiveSerializer): @@ -43,7 +44,7 @@ class AuthenticatorMobileStageView(ChallengeStageView): def prepare(self): """Prepare the token""" - if FLOW_PLAN_MOBILE_ENROLL in self.executor.plan.context: + if FLOW_PLAN_MOBILE_ENROLL_TOKEN in self.executor.plan.context: return device = MobileDevice.objects.create( user=self.get_pending_user(), @@ -54,7 +55,8 @@ class AuthenticatorMobileStageView(ChallengeStageView): user=device.user, device=device, ) - self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL] = token + self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL_TOKEN] = token + self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL_DEVICE] = device def get_challenge(self, *args, **kwargs) -> Challenge: self.prepare() @@ -62,8 +64,8 @@ class AuthenticatorMobileStageView(ChallengeStageView): data={ # TODO: use cloud gateway? "u": self.request.build_absolute_uri("/"), - "s": str(self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL].device.pk), - "t": self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL].token, + "s": str(self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL_DEVICE].pk), + "t": self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL_TOKEN].token, } ) payload.is_valid() @@ -75,4 +77,8 @@ class AuthenticatorMobileStageView(ChallengeStageView): ) def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: + device: MobileDevice = self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL_DEVICE] + device.refresh_from_db() + if not device.confirmed: + return self.challenge_invalid(response) return self.executor.stage_ok() diff --git a/web/src/flow/stages/authenticator_mobile/AuthenticatorMobileStage.ts b/web/src/flow/stages/authenticator_mobile/AuthenticatorMobileStage.ts index 76e7d07d6..b5b7dc6f9 100644 --- a/web/src/flow/stages/authenticator_mobile/AuthenticatorMobileStage.ts +++ b/web/src/flow/stages/authenticator_mobile/AuthenticatorMobileStage.ts @@ -1,3 +1,4 @@ +import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/forms/FormElement"; import "@goauthentik/flow/FormStatic"; @@ -18,6 +19,8 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { AuthenticatorMobileChallenge, AuthenticatorMobileChallengeResponseRequest, + AuthenticatorsApi, + MobileDeviceEnrollmentStatusStatusEnum, } from "@goauthentik/api"; @customElement("ak-stage-authenticator-mobile") @@ -43,6 +46,33 @@ export class AuthenticatorMobileStage extends BaseStage< ]; } + firstUpdated(): void { + const i = setInterval(() => { + this.checkEnrollStatus().then((shouldStop) => { + if (shouldStop) { + clearInterval(i); + } + }); + }, 3000); + } + + async checkEnrollStatus(): Promise { + const status = await new AuthenticatorsApi( + DEFAULT_CONFIG, + ).authenticatorsMobileEnrollmentStatusCreate({ + uuid: this.challenge?.payload.s || "", + }); + console.debug(`authentik/stages/authenticator_mobile: Enrollment status: ${status.status}`); + switch (status.status) { + case MobileDeviceEnrollmentStatusStatusEnum.Success: + this.host?.submit({}); + return true; + case MobileDeviceEnrollmentStatusStatusEnum.Waiting: + break; + } + return false; + } + render(): TemplateResult { if (!this.challenge) { return html`