diff --git a/authentik/stages/authenticator_validate/forms.py b/authentik/stages/authenticator_validate/forms.py deleted file mode 100644 index 16223a326..000000000 --- a/authentik/stages/authenticator_validate/forms.py +++ /dev/null @@ -1,43 +0,0 @@ -"""OTP Validate stage forms""" -from django import forms - -from authentik.flows.models import NotConfiguredAction -from authentik.stages.authenticator_validate.models import ( - AuthenticatorValidateStage, - DeviceClasses, -) - - -class AuthenticatorValidateStageForm(forms.ModelForm): - """OTP Validate stage forms""" - - def clean_not_configured_action(self): - """Ensure that a configuration stage is set when not_configured_action is configure""" - not_configured_action = self.cleaned_data.get("not_configured_action") - configuration_stage = self.cleaned_data.get("configuration_stage") - if ( - not_configured_action == NotConfiguredAction.CONFIGURE - and configuration_stage is None - ): - raise forms.ValidationError( - ( - 'When "Not configured action" is set to "Configure", ' - "you must set a configuration stage." - ) - ) - return not_configured_action - - class Meta: - - model = AuthenticatorValidateStage - fields = [ - "name", - "not_configured_action", - "device_classes", - "configuration_stage", - ] - - widgets = { - "name": forms.TextInput(), - "device_classes": forms.SelectMultiple(choices=DeviceClasses.choices), - } diff --git a/authentik/stages/authenticator_validate/models.py b/authentik/stages/authenticator_validate/models.py index dce7e0b59..cb1b2cca9 100644 --- a/authentik/stages/authenticator_validate/models.py +++ b/authentik/stages/authenticator_validate/models.py @@ -3,7 +3,6 @@ from typing import Type from django.contrib.postgres.fields.array import ArrayField from django.db import models -from django.forms import ModelForm from django.utils.translation import gettext_lazy as _ from django.views import View from rest_framework.serializers import BaseSerializer @@ -74,12 +73,8 @@ class AuthenticatorValidateStage(Stage): return AuthenticatorValidateStageView @property - def form(self) -> Type[ModelForm]: - from authentik.stages.authenticator_validate.forms import ( - AuthenticatorValidateStageForm, - ) - - return AuthenticatorValidateStageForm + def component(self) -> str: + return "ak-stage-authenticator-validate-form" class Meta: diff --git a/web/src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts b/web/src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts index 3d1805c61..e3dfe4a1f 100644 --- a/web/src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts +++ b/web/src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts @@ -61,7 +61,7 @@ export class AuthenticatorTOTPStageForm extends Form { label=${gettext("Digits")} ?required=${true} name="digits"> - diff --git a/web/src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts b/web/src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts new file mode 100644 index 000000000..30260c02a --- /dev/null +++ b/web/src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts @@ -0,0 +1,136 @@ +import { AuthenticatorValidateStage, AuthenticatorValidateStageNotConfiguredActionEnum, StagesApi } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../../api/Config"; +import { Form } from "../../../elements/forms/Form"; +import { ifDefined } from "lit-html/directives/if-defined"; +import "../../../elements/forms/HorizontalFormElement"; +import "../../../elements/forms/FormGroup"; +import { DeviceClasses } from "authentik-api/dist/src/flows/stages/authenticator_validate/AuthenticatorValidateStage"; +import { until } from "lit-html/directives/until"; + +@customElement("ak-stage-authenticator-validate-form") +export class AuthenticatorValidateStageForm extends Form { + + set stageUUID(value: string) { + new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorValidateRead({ + stageUuid: value, + }).then(stage => { + this.stage = stage; + this.showConfigureFlow = stage.notConfiguredAction === AuthenticatorValidateStageNotConfiguredActionEnum.Configure; + }); + } + + @property({attribute: false}) + stage?: AuthenticatorValidateStage; + + @property({ type: Boolean }) + showConfigureFlow = false; + + getSuccessMessage(): string { + if (this.stage) { + return gettext("Successfully updated stage."); + } else { + return gettext("Successfully created stage."); + } + } + + send = (data: AuthenticatorValidateStage): Promise => { + if (this.stage) { + return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorValidateUpdate({ + stageUuid: this.stage.pk || "", + data: data + }); + } else { + return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorValidateCreate({ + data: data + }); + } + }; + + isDeviceClassSelected(field: DeviceClasses): boolean { + return (this.stage?.deviceClasses || []).filter(isField => { + return field === isField; + }).length > 0; + } + + renderForm(): TemplateResult { + return html`
+ + + + + + ${gettext("Stage-specific settings")} + +
+ + + + + +

${gettext("Device classes which can be used to authenticate.")}

+

${gettext("Hold control/command to select multiple items.")}

+
+ ${this.showConfigureFlow ? html` + + +

${gettext("Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.")}

+
+ `: html``} +
+
+
`; + } + +} diff --git a/web/src/pages/stages/password/PasswordStageForm.ts b/web/src/pages/stages/password/PasswordStageForm.ts index 9985f2ea5..a22e597b7 100644 --- a/web/src/pages/stages/password/PasswordStageForm.ts +++ b/web/src/pages/stages/password/PasswordStageForm.ts @@ -64,9 +64,9 @@ export class PasswordStageForm extends Form {
+ name="backends">