diff --git a/authentik/stages/email/forms.py b/authentik/stages/email/forms.py index 303d7834b..5e87b6544 100644 --- a/authentik/stages/email/forms.py +++ b/authentik/stages/email/forms.py @@ -5,12 +5,6 @@ from django.utils.translation import gettext_lazy as _ from authentik.stages.email.models import EmailStage, get_template_choices -class EmailStageSendForm(forms.Form): - """Form used when sending the email to prevent multiple emails being sent""" - - invalid = forms.CharField(widget=forms.HiddenInput, required=True) - - class EmailStageForm(forms.ModelForm): """Form to create/edit Email Stage""" diff --git a/authentik/stages/email/stage.py b/authentik/stages/email/stage.py index 5d2ef8991..f6cea9422 100644 --- a/authentik/stages/email/stage.py +++ b/authentik/stages/email/stage.py @@ -3,18 +3,19 @@ from datetime import timedelta from django.contrib import messages from django.http import HttpRequest, HttpResponse -from django.shortcuts import get_object_or_404, reverse +from django.shortcuts import get_object_or_404 +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 django.views.generic import FormView +from rest_framework.serializers import ValidationError from structlog.stdlib import get_logger from authentik.core.models import Token +from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER -from authentik.flows.stage import StageView +from authentik.flows.stage import ChallengeStageView from authentik.flows.views import SESSION_KEY_GET -from authentik.stages.email.forms import EmailStageSendForm from authentik.stages.email.models import EmailStage from authentik.stages.email.tasks import send_mails from authentik.stages.email.utils import TemplateEmailMessage @@ -24,11 +25,22 @@ QS_KEY_TOKEN = "token" # nosec PLAN_CONTEXT_EMAIL_SENT = "email_sent" -class EmailStageView(FormView, StageView): +class EmailChallenge(Challenge): + """Email challenge""" + + +class EmailChallengeResponse(ChallengeResponse): + """Email challenge resposen. No fields. This challenge is + always declared invalid to give the user a chance to retry""" + + def validate(self, data): + raise ValidationError("") + + +class EmailStageView(ChallengeStageView): """Email stage which sends Email for verification""" - form_class = EmailStageSendForm - template_name = "stages/email/waiting_message.html" + response_class = EmailChallengeResponse def get_full_url(self, **kwargs) -> str: """Get full URL to be used in template""" @@ -80,11 +92,17 @@ class EmailStageView(FormView, StageView): self.executor.plan.context[PLAN_CONTEXT_EMAIL_SENT] = True return super().get(request, *args, **kwargs) - def form_invalid(self, form: EmailStageSendForm) -> HttpResponse: + def get_challenge(self) -> Challenge: + challenge = EmailChallenge( + data={"type": ChallengeTypes.native, "component": "ak-stage-email"} + ) + return challenge + + def challenge_invalid(self, response: ChallengeResponse) -> HttpResponse: if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: messages.error(self.request, _("No pending user.")) - return super().form_invalid(form) + return super().challenge_invalid(response) self.send_email() # We can't call stage_ok yet, as we're still waiting # for the user to click the link in the email - return super().form_invalid(form) + return super().challenge_invalid(response) diff --git a/authentik/stages/email/templates/stages/email/waiting_message.html b/authentik/stages/email/templates/stages/email/waiting_message.html deleted file mode 100644 index 5f1762624..000000000 --- a/authentik/stages/email/templates/stages/email/waiting_message.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends 'login/base.html' %} - -{% load static %} -{% load i18n %} - -{% block card %} -
-{% endblock %} diff --git a/web/src/elements/stages/email/EmailStage.ts b/web/src/elements/stages/email/EmailStage.ts new file mode 100644 index 000000000..cb01ebe07 --- /dev/null +++ b/web/src/elements/stages/email/EmailStage.ts @@ -0,0 +1,49 @@ +import { gettext } from "django"; +import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; +import { Challenge } from "../../../api/Flows"; +import { COMMON_STYLES } from "../../../common/styles"; +import { BaseStage } from "../base"; + +export type EmailChallenge = Challenge + +@customElement("ak-stage-email") +export class EmailStage extends BaseStage { + + @property({ attribute: false }) + challenge?: EmailChallenge; + + static get styles(): CSSResult[] { + return COMMON_STYLES; + } + + render(): TemplateResult { + if (!this.challenge) { + return html`