stages/email: prevent authentik emails from being marked as spam (also add text template support) (#7949)
* use <> style email address with name Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add support for text templates Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix icon display in event log Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add text email templates Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update docs, update email screenshot Signed-off-by: Jens Langhammer <jens@goauthentik.io> * prevent prettier from breaking example template Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Optimised images with calibre/image-actions * Apply suggestions from code review Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> Signed-off-by: Jens L. <jens@beryju.org> * reword docs Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Signed-off-by: Jens L. <jens@beryju.org> Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com> Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
parent
218d61648b
commit
ec8f2d4bf9
|
@ -461,7 +461,7 @@ class NotificationTransport(SerializerModel):
|
|||
}
|
||||
mail = TemplateEmailMessage(
|
||||
subject=subject_prefix + context["title"],
|
||||
to=[notification.user.email],
|
||||
to=[f"{notification.user.name} <{notification.user.email}>"],
|
||||
language=notification.user.locale(),
|
||||
template_name="email/event_notification.html",
|
||||
template_context=context,
|
||||
|
|
|
@ -110,7 +110,7 @@ class EmailStageView(ChallengeStageView):
|
|||
try:
|
||||
message = TemplateEmailMessage(
|
||||
subject=_(current_stage.subject),
|
||||
to=[email],
|
||||
to=[f"{pending_user.name} <{email}>"],
|
||||
language=pending_user.locale(self.request),
|
||||
template_name=current_stage.template,
|
||||
template_context={
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{% load i18n %}{% translate "Welcome!" %}
|
||||
|
||||
{% translate "We're excited to have you get started. First, you need to confirm your account. Just open the link below." %}
|
||||
|
||||
{{ url }}
|
||||
|
||||
--
|
||||
Powered by goauthentik.io.
|
|
@ -44,7 +44,7 @@
|
|||
<tr>
|
||||
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
|
||||
{% blocktranslate with name=source.from %}
|
||||
This email was sent from the notification transport <code>{{name}}</code>.
|
||||
This email was sent from the notification transport <code>{{ name }}</code>.
|
||||
{% endblocktranslate %}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{% load authentik_stages_email %}{% load i18n %}{% translate "Dear authentik user," %}
|
||||
|
||||
{% translate "The following notification was created:" %}
|
||||
|
||||
{{ body|indent }}
|
||||
|
||||
{% if key_value %}
|
||||
{% translate "Additional attributes:" %}
|
||||
{% for key, value in key_value.items %}
|
||||
{{ key }}: {{ value|indent }}{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if source %}{% blocktranslate with name=source.from %}
|
||||
This email was sent from the notification transport {{ name }}.
|
||||
{% endblocktranslate %}{% endif %}
|
||||
|
||||
--
|
||||
Powered by goauthentik.io.
|
|
@ -0,0 +1,12 @@
|
|||
{% load i18n %}{% load humanize %}{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}
|
||||
|
||||
{% blocktrans %}
|
||||
You recently requested to change your password for your authentik account. Use the link below to set a new password.
|
||||
{% endblocktrans %}
|
||||
{{ url }}
|
||||
{% blocktrans with expires=expires|naturaltime %}
|
||||
If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
|
||||
{% endblocktrans %}
|
||||
|
||||
--
|
||||
Powered by goauthentik.io.
|
|
@ -0,0 +1,7 @@
|
|||
{% load i18n %}authentik Test-Email
|
||||
{% blocktrans %}
|
||||
This is a test email to inform you, that you've successfully configured authentik emails.
|
||||
{% endblocktrans %}
|
||||
|
||||
--
|
||||
Powered by goauthentik.io.
|
|
@ -29,3 +29,9 @@ def inline_static_binary(path: str) -> str:
|
|||
b64content = b64encode(_file.read().encode())
|
||||
return f"data:image/{result.suffix};base64,{b64content.decode('utf-8')}"
|
||||
return path
|
||||
|
||||
|
||||
@register.filter(name="indent")
|
||||
def indent_string(val, num_spaces=4):
|
||||
"""Intent text by a given amount of spaces"""
|
||||
return val.replace("\n", "\n" + " " * num_spaces)
|
||||
|
|
|
@ -58,9 +58,11 @@ class TestEmailStageSending(FlowTestCase):
|
|||
events = Event.objects.filter(action=EventAction.EMAIL_SENT)
|
||||
self.assertEqual(len(events), 1)
|
||||
event = events.first()
|
||||
self.assertEqual(event.context["message"], f"Email to {self.user.email} sent")
|
||||
self.assertEqual(
|
||||
event.context["message"], f"Email to {self.user.name} <{self.user.email}> sent"
|
||||
)
|
||||
self.assertEqual(event.context["subject"], "authentik")
|
||||
self.assertEqual(event.context["to_email"], [self.user.email])
|
||||
self.assertEqual(event.context["to_email"], [f"{self.user.name} <{self.user.email}>"])
|
||||
self.assertEqual(event.context["from_email"], "system@authentik.local")
|
||||
|
||||
def test_pending_fake_user(self):
|
||||
|
|
|
@ -94,7 +94,7 @@ class TestEmailStage(FlowTestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||
self.assertEqual(mail.outbox[0].to, [self.user.email])
|
||||
self.assertEqual(mail.outbox[0].to, [f"{self.user.name} <{self.user.email}>"])
|
||||
|
||||
@patch(
|
||||
"authentik.stages.email.models.EmailStage.backend_class",
|
||||
|
@ -114,7 +114,7 @@ class TestEmailStage(FlowTestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||
self.assertEqual(mail.outbox[0].to, ["foo@bar.baz"])
|
||||
self.assertEqual(mail.outbox[0].to, [f"{self.user.name} <foo@bar.baz>"])
|
||||
|
||||
@patch(
|
||||
"authentik.stages.email.models.EmailStage.backend_class",
|
||||
|
|
|
@ -4,6 +4,7 @@ from functools import lru_cache
|
|||
from pathlib import Path
|
||||
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.template.exceptions import TemplateDoesNotExist
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import translation
|
||||
|
||||
|
@ -24,9 +25,15 @@ class TemplateEmailMessage(EmailMultiAlternatives):
|
|||
"""Wrapper around EmailMultiAlternatives with integrated template rendering"""
|
||||
|
||||
def __init__(self, template_name=None, template_context=None, language="", **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
with translation.override(language):
|
||||
html_content = render_to_string(template_name, template_context)
|
||||
super().__init__(**kwargs)
|
||||
self.content_subtype = "html"
|
||||
try:
|
||||
text_content = render_to_string(
|
||||
template_name.replace("html", "txt"), template_context
|
||||
)
|
||||
self.body = text_content
|
||||
except TemplateDoesNotExist:
|
||||
pass
|
||||
self.mixed_subtype = "related"
|
||||
self.attach_alternative(html_content, "text/html")
|
||||
|
|
|
@ -285,10 +285,12 @@ export class EventInfo extends AKElement {
|
|||
}
|
||||
|
||||
renderEmailSent() {
|
||||
let body = this.event.context.body as string;
|
||||
body = body.replace("cid:logo.png", "/static/dist/assets/icons/icon_left_brand.png");
|
||||
return html`<div class="pf-c-card__title">${msg("Email info:")}</div>
|
||||
<div class="pf-c-card__body">${this.getEmailInfo(this.event.context)}</div>
|
||||
<ak-expand>
|
||||
<iframe srcdoc=${this.event.context.body}></iframe>
|
||||
<iframe srcdoc=${body}></iframe>
|
||||
</ak-expand>`;
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 25 KiB |
|
@ -25,6 +25,10 @@ return True
|
|||
|
||||
You can also use custom email templates, to use your own design or layout.
|
||||
|
||||
:::info
|
||||
Starting with authentik 2024.1, it is possible to create `.txt` files with the same name as the `.html` template. If a matching `.txt` file exists, the email sent will be a multipart email with both the text and HTML template.
|
||||
:::
|
||||
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
|
@ -81,13 +85,17 @@ Templates are rendered using Django's templating engine. The following variables
|
|||
- `user`: The pending user object.
|
||||
- `expires`: The timestamp when the token expires.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```html
|
||||
{# This is how you can write comments which aren't rendered. #} {# Extend this
|
||||
template from the base email template, which includes base layout and CSS. #} {%
|
||||
extends "email/base.html" %} {# Load the internationalization module to
|
||||
translate strings, and humanize to show date-time #} {% load i18n %} {% load
|
||||
humanize %} {# The email/base.html template uses a single "content" block #} {%
|
||||
block content %}
|
||||
{# This is how you can write comments which aren't rendered. #}
|
||||
{# Extend this template from the base email template, which includes base layout and CSS. #}
|
||||
{% extends "email/base.html" %}
|
||||
{# Load the internationalization module to translate strings, and humanize to show date-time #}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{# The email/base.html template uses a single "content" block #}
|
||||
{% block content %}
|
||||
<tr>
|
||||
<td class="alert alert-success">
|
||||
{% blocktrans with username=user.username %} Hi {{ username }}, {%
|
||||
|
@ -99,9 +107,9 @@ block content %}
|
|||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="content-block">
|
||||
{% blocktrans %} You recently requested to change your
|
||||
password for you authentik account. Use the button below to
|
||||
set a new password. {% endblocktrans %}
|
||||
{% blocktrans %}
|
||||
You recently requested to change your password for you authentik account. Use the button below to set a new password.
|
||||
{% endblocktrans %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -130,8 +138,7 @@ block content %}
|
|||
href="{{ url }}"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>{% trans 'Reset
|
||||
Password' %}</a
|
||||
>{% trans 'Reset Password' %}</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -145,9 +152,9 @@ block content %}
|
|||
</tr>
|
||||
<tr>
|
||||
<td class="content-block">
|
||||
{% blocktrans with expires=expires|naturaltime %} If you did
|
||||
not request a password change, please ignore this Email. The
|
||||
link above is valid for {{ expires }}. {% endblocktrans %}
|
||||
{% blocktrans with expires=expires|naturaltime %}
|
||||
If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
|
||||
{% endblocktrans %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -155,3 +162,5 @@ block content %}
|
|||
</tr>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
|
Reference in New Issue