stages/email: migrate to SPA

This commit is contained in:
Jens Langhammer 2021-02-21 13:42:45 +01:00
parent b9f409d6d9
commit 14962eb6cc
5 changed files with 81 additions and 37 deletions

View File

@ -5,12 +5,6 @@ from django.utils.translation import gettext_lazy as _
from authentik.stages.email.models import EmailStage, get_template_choices 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): class EmailStageForm(forms.ModelForm):
"""Form to create/edit Email Stage""" """Form to create/edit Email Stage"""

View File

@ -3,18 +3,19 @@ from datetime import timedelta
from django.contrib import messages from django.contrib import messages
from django.http import HttpRequest, HttpResponse 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.http import urlencode
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext as _ 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 structlog.stdlib import get_logger
from authentik.core.models import Token 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.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.flows.views import SESSION_KEY_GET
from authentik.stages.email.forms import EmailStageSendForm
from authentik.stages.email.models import EmailStage from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils import TemplateEmailMessage
@ -24,11 +25,22 @@ QS_KEY_TOKEN = "token" # nosec
PLAN_CONTEXT_EMAIL_SENT = "email_sent" 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""" """Email stage which sends Email for verification"""
form_class = EmailStageSendForm response_class = EmailChallengeResponse
template_name = "stages/email/waiting_message.html"
def get_full_url(self, **kwargs) -> str: def get_full_url(self, **kwargs) -> str:
"""Get full URL to be used in template""" """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 self.executor.plan.context[PLAN_CONTEXT_EMAIL_SENT] = True
return super().get(request, *args, **kwargs) 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: if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
messages.error(self.request, _("No pending user.")) messages.error(self.request, _("No pending user."))
return super().form_invalid(form) return super().challenge_invalid(response)
self.send_email() self.send_email()
# We can't call stage_ok yet, as we're still waiting # We can't call stage_ok yet, as we're still waiting
# for the user to click the link in the email # for the user to click the link in the email
return super().form_invalid(form) return super().challenge_invalid(response)

View File

@ -1,21 +0,0 @@
{% extends 'login/base.html' %}
{% load static %}
{% load i18n %}
{% block card %}
<form method="POST" class="pf-c-form">
<p>
{% blocktrans %}
Check your Emails for a password reset link.
{% endblocktrans %}
</p>
{% csrf_token %}
{% block beneath_form %}
{% endblock %}
<div class="pf-c-form__group pf-m-action">
<button class="pf-c-button pf-m-block" type="submit">{% trans "Send Email again." %}</button>
</div>
</form>
{% endblock %}

View File

@ -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`<ak-loading-state></ak-loading-state>`;
}
return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
${this.challenge.title}
</h1>
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form" @submit=${(e: Event) => { this.submit(e); }}>
<div class="pf-c-form__group">
<p>
${gettext("Check your Emails for a password reset link.")}
</p>
</div>
<div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${gettext("Send Email again.")}
</button>
</div>
</form>
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links">
</ul>
</footer>`;
}
}

View File

@ -5,11 +5,13 @@ import { getCookie } from "../../utils";
import "../../elements/stages/identification/IdentificationStage"; import "../../elements/stages/identification/IdentificationStage";
import "../../elements/stages/password/PasswordStage"; import "../../elements/stages/password/PasswordStage";
import "../../elements/stages/consent/ConsentStage"; import "../../elements/stages/consent/ConsentStage";
import "../../elements/stages/email/EmailStage";
import { ShellChallenge, Challenge, ChallengeTypes, Flow, RedirectChallenge } from "../../api/Flows"; import { ShellChallenge, Challenge, ChallengeTypes, Flow, RedirectChallenge } from "../../api/Flows";
import { DefaultClient } from "../../api/Client"; import { DefaultClient } from "../../api/Client";
import { IdentificationChallenge } from "../../elements/stages/identification/IdentificationStage"; import { IdentificationChallenge } from "../../elements/stages/identification/IdentificationStage";
import { PasswordChallenge } from "../../elements/stages/password/PasswordStage"; import { PasswordChallenge } from "../../elements/stages/password/PasswordStage";
import { ConsentChallenge } from "../../elements/stages/consent/ConsentStage"; import { ConsentChallenge } from "../../elements/stages/consent/ConsentStage";
import { EmailChallenge } from "../../elements/stages/email/EmailStage";
@customElement("ak-flow-executor") @customElement("ak-flow-executor")
export class FlowExecutor extends LitElement { export class FlowExecutor extends LitElement {
@ -112,6 +114,8 @@ export class FlowExecutor extends LitElement {
return html`<ak-stage-password .host=${this} .challenge=${this.challenge as PasswordChallenge}></ak-stage-password>`; return html`<ak-stage-password .host=${this} .challenge=${this.challenge as PasswordChallenge}></ak-stage-password>`;
case "ak-stage-consent": case "ak-stage-consent":
return html`<ak-stage-consent .host=${this} .challenge=${this.challenge as ConsentChallenge}></ak-stage-consent>`; return html`<ak-stage-consent .host=${this} .challenge=${this.challenge as ConsentChallenge}></ak-stage-consent>`;
case "ak-stage-email":
return html`<ak-stage-email .host=${this} .challenge=${this.challenge as EmailChallenge}></ak-stage-email>`;
default: default:
break; break;
} }