diff --git a/authentik/admin/fields.py b/authentik/admin/fields.py deleted file mode 100644 index 10add9be5..000000000 --- a/authentik/admin/fields.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Additional fields""" -from django import forms -from django.utils.datastructures import MultiValueDict - - -class ArrayFieldSelectMultiple(forms.SelectMultiple): - """This is a Form Widget for use with a Postgres ArrayField. It implements - a multi-select interface that can be given a set of `choices`. - You can provide a `delimiter` keyword argument to specify the delimeter used. - - https://gist.github.com/stephane/00e73c0002de52b1c601""" - - def __init__(self, *args, **kwargs): - # Accept a `delimiter` argument, and grab it (defaulting to a comma) - self.delimiter = kwargs.pop("delimiter", ",") - super().__init__(*args, **kwargs) - - def value_from_datadict(self, data, files, name): - if isinstance(data, MultiValueDict): - # Normally, we'd want a list here, which is what we get from the - # SelectMultiple superclass, but the SimpleArrayField expects to - # get a delimited string, so we're doing a little extra work. - return self.delimiter.join(data.getlist(name)) - - return data.get(name) - - def get_context(self, name, value, attrs): - return super().get_context(name, value.split(self.delimiter), attrs) diff --git a/authentik/stages/identification/forms.py b/authentik/stages/identification/forms.py deleted file mode 100644 index 29e9f37b0..000000000 --- a/authentik/stages/identification/forms.py +++ /dev/null @@ -1,38 +0,0 @@ -"""authentik flows identification forms""" -from django import forms -from structlog.stdlib import get_logger - -from authentik.admin.fields import ArrayFieldSelectMultiple -from authentik.flows.models import Flow, FlowDesignation -from authentik.stages.identification.models import IdentificationStage, UserFields - -LOGGER = get_logger() - - -class IdentificationStageForm(forms.ModelForm): - """Form to create/edit IdentificationStage instances""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["enrollment_flow"].queryset = Flow.objects.filter( - designation=FlowDesignation.ENROLLMENT - ) - self.fields["recovery_flow"].queryset = Flow.objects.filter( - designation=FlowDesignation.RECOVERY - ) - - class Meta: - - model = IdentificationStage - fields = [ - "name", - "user_fields", - "case_insensitive_matching", - "show_matched_user", - "enrollment_flow", - "recovery_flow", - ] - widgets = { - "name": forms.TextInput(), - "user_fields": ArrayFieldSelectMultiple(choices=UserFields.choices), - } diff --git a/authentik/stages/identification/models.py b/authentik/stages/identification/models.py index a7b2f4218..c00f3e2b8 100644 --- a/authentik/stages/identification/models.py +++ b/authentik/stages/identification/models.py @@ -3,7 +3,6 @@ from typing import Type from django.contrib.postgres.fields 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 @@ -84,10 +83,8 @@ class IdentificationStage(Stage): return IdentificationStageView @property - def form(self) -> Type[ModelForm]: - from authentik.stages.identification.forms import IdentificationStageForm - - return IdentificationStageForm + def component(self) -> str: + return "ak-stage-identification-form" class Meta: diff --git a/web/src/pages/stages/identification/IdentificationStageForm.ts b/web/src/pages/stages/identification/IdentificationStageForm.ts new file mode 100644 index 000000000..28501c164 --- /dev/null +++ b/web/src/pages/stages/identification/IdentificationStageForm.ts @@ -0,0 +1,139 @@ +import { FlowDesignationEnum, FlowsApi, IdentificationStage, IdentificationStageUserFieldsEnum, 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 { until } from "lit-html/directives/until"; + +@customElement("ak-stage-identification-form") +export class IdentificationStageForm extends Form { + + set stageUUID(value: string) { + new StagesApi(DEFAULT_CONFIG).stagesIdentificationRead({ + stageUuid: value, + }).then(stage => { + this.stage = stage; + }); + } + + @property({attribute: false}) + stage?: IdentificationStage; + + getSuccessMessage(): string { + if (this.stage) { + return gettext("Successfully updated stage."); + } else { + return gettext("Successfully created stage."); + } + } + + send = (data: IdentificationStage): Promise => { + if (this.stage) { + return new StagesApi(DEFAULT_CONFIG).stagesIdentificationUpdate({ + stageUuid: this.stage.pk || "", + data: data + }); + } else { + return new StagesApi(DEFAULT_CONFIG).stagesIdentificationCreate({ + data: data + }); + } + }; + + isUserFieldSelected(field: IdentificationStageUserFieldsEnum): boolean { + return (this.stage?.userFields || []).filter(isField => { + return field === isField; + }).length > 0; + } + + renderForm(): TemplateResult { + return html`
+ + + + + + ${gettext("Stage-specific settings")} + +
+ + +

${gettext("Fields a user can identify themselves with.")}

+

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

+
+ +
+ + +
+

${gettext("When enabled, user fields are matched regardless of their casing.")}

+
+ +
+ + +
+

${gettext("When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown.")}

+
+ + +

${gettext("Optional enrollment flow, which is linked at the bottom of the page..")}

+
+ + +

${gettext("Optional recovery flow, which is linked at the bottom of the page.")}

+
+
+
+
`; + } + +}