Finally got mail working for good

This commit is contained in:
Kumi 2020-05-21 14:54:59 +02:00
parent d549897186
commit 89f971117f
20 changed files with 135 additions and 30 deletions

View file

@ -30,6 +30,8 @@ class BaseMailProvider:
for header, value in headers.items():
if value:
message.add_header(header, value)
message.set_charset("base64")
return self.send_message(message)
class SMTPMailProvider(BaseMailProvider):
@ -43,4 +45,4 @@ class SMTPMailProvider(BaseMailProvider):
return "SMTP Mail"
def send_message(self, message):
return self.smtp.send_message(message)
return self.smtp.send_message(message, rcpt_options=['NOTIFY=SUCCESS,DELAY,FAILURE'])

2
core/exceptions/mail.py Normal file
View file

@ -0,0 +1,2 @@
class NoSuchTemplate(ValueError):
pass

View file

@ -1,2 +1,11 @@
from core.helpers.mail import get_template
from core.helpers.urls import relative_to_absolute as reltoabs
from django.urls import reverse
from dbsettings.functions import getValue
def generate_pwreset_mail(user, token):
pass
link = reltoabs(reverse("pwreset", kwargs={"pk": str(token.token)}))
template = get_template("backend/auth/pwreset", first_name=user.first_name, link=link, sitename=getValue("core.title", "Expephalon"))
return template

View file

@ -1,8 +1,13 @@
from core.modules.mail import providers
from django.conf import settings
from core.modules.mail import providers, templates
from core.tasks.mail import send_mail as send_mail_task
from core.exceptions.mail import NoSuchTemplate
from dbsettings.functions import getValue
import os.path
def get_provider_by_name(name, fallback=True):
return providers.get(name, None) or providers["smtp"]
@ -16,5 +21,16 @@ def send_mail(provider=get_default_provider(), **kwargs):
def simple_send_mail(subject, content, recipients, cc=[], bcc=[], headers={}):
return send_mail(subject=subject, content=content, recipients=recipients, cc=cc, bcc=bcc, headers=headers)
def fetch_templates(template_name):
pass
def get_template(template_name, format="txt", **kwargs):
try:
template = templates[template_name][format]
except KeyError:
raise NoSuchTemplate(f"No email template called {template_name} of format {format} loaded")
with open(template, "r") as templatefile:
templatetext = templatefile.read()
for key, value in kwargs.items():
templatetext = templatetext.replace('{§%s§}' % key, value)
return templatetext

6
core/helpers/urls.py Normal file
View file

@ -0,0 +1,6 @@
from urllib.parse import urljoin
from dbsettings.functions import getValue
def relative_to_absolute(path, domain=getValue("core.base_url", "http://localhost:8000")):
return urljoin(domain, path)

18
core/mixins/auth.py Normal file
View file

@ -0,0 +1,18 @@
from django.contrib.auth.mixins import AccessMixin
from django.contrib.messages import error
from core.models.profiles import AdminProfile
class AdminMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
self.permission_denied_message = "You must be logged in to access this area."
else:
try:
AdminProfile.objects.get(user=request.user)
return super().dispatch(request, *args, **kwargs)
except AdminProfile.DoesNotExist:
self.permission_denied_message = "You must be an administrator to access this area."
return self.handle_no_permission()

View file

@ -1,10 +1,28 @@
from core.classes.mail import SMTPMailProvider
import importlib
import pathlib
import os.path
import logging
from django.conf import settings
providers = { "smtp": SMTPMailProvider }
templates = {}
logger = logging.getLogger(__name__)
for module in settings.EXPEPHALON_MODULES + [""]:
for template in pathlib.Path(os.path.join(settings.BASE_DIR, module, "templates/mail/")).rglob("*.*"):
if os.path.isfile(template):
template_name = str(template).rsplit("templates/mail/")[-1].rsplit(".")[0]
template_format = str(template).rsplit(".")[-1].lower()
if not template_name in templates.keys():
templates[template_name] = dict()
if template_format in templates[template_name].keys():
logger.warning("Mail Template %s, that was seen at %s, was also found at %s. Using latter.",
template_name, templates[template_name][template_format], str(template))
templates[template_name][template_format] = str(template)
for module in settings.EXPEPHALON_MODULES:
try:

View file

@ -10,6 +10,7 @@ from core.views import (
LogoutView,
OTPValidatorView,
PWResetView,
PWRequestView,
BackendNotImplementedView,
AdminListView,
AdminDeleteView,
@ -29,7 +30,8 @@ URLPATTERNS.append(path('login/', LoginView.as_view(), name="login"))
URLPATTERNS.append(path('login/otp/select/', OTPSelectorView.as_view(), name="otpselector"))
URLPATTERNS.append(path('login/otp/validate/', OTPValidatorView.as_view(), name="otpvalidator"))
URLPATTERNS.append(path('logout/', LogoutView.as_view(), name="logout"))
URLPATTERNS.append(path('login/reset/', PWResetView.as_view(), name="pwreset"))
URLPATTERNS.append(path('login/reset/', PWRequestView.as_view(), name="pwrequest"))
URLPATTERNS.append(path('login/reset/<pk>/', PWResetView.as_view(), name="pwreset"))
# Base Backend URLs

View file

@ -5,6 +5,8 @@ from django.conf import settings
from core.views.dbsettings import *
from core.views.auth import *
from core.views.profiles import *
from core.views.generic import *
from core.mixins.auth import AdminMixin
# Create your views here.
@ -16,7 +18,7 @@ class IndexView(TemplateView):
context["title"] = "Home"
return context
class DashboardView(TemplateView):
class DashboardView(BackendTemplateView):
template_name = f"{settings.EXPEPHALON_BACKEND}/index.html"
def get_context_data(self, **kwargs):
@ -24,10 +26,10 @@ class DashboardView(TemplateView):
context["title"] = "Dashboard"
return context
class BackendNotImplementedView(TemplateView):
class BackendNotImplementedView(BackendTemplateView):
template_name = f"{settings.EXPEPHALON_BACKEND}/notimplemented.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Oops!"
return context
return context

View file

@ -9,7 +9,7 @@ from django.utils import timezone
from core.forms import LoginForm, OTPSelectorForm, OTPVerificationForm, PWResetForm, PWRequestForm
from core.models.auth import LoginSession, PWResetToken
from core.helpers.otp import get_user_otps, get_otp_choices, get_otp_by_name
from core.helpers.mail import send_mail
from core.helpers.mail import simple_send_mail
from core.helpers.auth import generate_pwreset_mail
from dbsettings.functions import getValue
@ -164,7 +164,10 @@ class PWRequestView(FormView):
try:
user = get_user_model().objects.get(username=form.cleaned_data["email"])
token = PWResetToken.objects.create(user=user)
finally:
messages.success(self.request, "If a matching account was found, you should shortly receive an email containing password reset instructions. If you have not received this message after five minutes, please verify that you have entered the correct email address, or contact support.")
return redirect("login")
mail = generate_pwreset_mail(user, token)
simple_send_mail("Password Reset", mail, user.email)
except:
raise
# finally:
# messages.success(self.request, "If a matching account was found, you should shortly receive an email containing password reset instructions. If you have not received this message after five minutes, please verify that you have entered the correct email address, or contact support.")
# return redirect("login")

17
core/views/generic.py Normal file
View file

@ -0,0 +1,17 @@
from django.views.generic import TemplateView, ListView, CreateView, FormView, DeleteView
from core.mixins.auth import AdminMixin
class BackendTemplateView(AdminMixin, TemplateView):
pass
class BackendListView(AdminMixin, ListView):
pass
class BackendCreateView(AdminMixin, CreateView):
pass
class BackendFormView(AdminMixin, FormView):
pass
class BackendDeleteView(AdminMixin, DeleteView):
pass

View file

@ -1,10 +1,10 @@
from django.conf import settings
from django.views.generic import FormView, ListView, DeleteView
from django.urls import reverse_lazy
from django.contrib.auth import get_user_model
from core.models import AdminProfile
from core.forms import AdminEditForm
from core.views.generic import BackendFormView as FormView, BackendListView as ListView, BackendDeleteView as DeleteView
class AdminListView(ListView):
template_name = f"{settings.EXPEPHALON_BACKEND}/profiles/index.html"

View file

@ -16,7 +16,6 @@ app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))

View file

@ -1,5 +1,7 @@
import os
from django.urls import reverse_lazy
from expephalon.custom_settings import * # pylint: disable=unused-wildcard-import
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@ -141,6 +143,14 @@ if MEMCACHED_LOCATION:
CELERY_TASK_SERIALIZER = "pickle"
CELERY_RESULT_SERIALIZER = "pickle"
CELERY_ACCEPT_CONTENT = ['pickle']
CELERY_CACHE_BACKEND = 'default'
CELERY_RESULT_BACKEND = 'django-db'
CELERY_CACHE_BACKEND = 'django-cache'
CELERY_BROKER_URL = f"amqp://{RABBITMQ_USER}:{RABBITMQ_PASS}@{RABBITMQ_LOCATION}/{RABBITMQ_VHOST}"
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
CELERY_TASK_RESULT_EXPIRES = 12 * 60 * 60
# Auth URLs
LOGIN_REDIRECT_URL = reverse_lazy('dashboard')
LOGIN_URL = reverse_lazy('login')
LOGOUT_URL = reverse_lazy('logout')

View file

@ -1,11 +0,0 @@
Hi {{ first_name }},
Somebody (hopefully you) requested a new password for your {{ sitename }} account. If this was you, please click the following link to reset your password:
{{ link }}
If it was not you, you can ignore this message. The link will expire in 24 hours.
Best regards
Your {{ sitename }} Team

View file

@ -11,3 +11,4 @@ git+https://kumig.it/kumisystems/django-dbsettings.git
celery
django-celery-results
django-celery-beat
python-memcached

View file

@ -24,7 +24,7 @@
<div class="position-relative form-check"><input name="check" id="exampleCheck" type="checkbox" class="form-check-input"><label for="exampleCheck" class="form-check-label">Keep me logged in</label></div>
<div class="divider row"></div>
<div class="d-flex align-items-center">
<div class="ml-auto"><a href="javascript:void(0);" class="btn-lg btn btn-link">Recover Password</a>
<div class="ml-auto"><a href="{% url "pwrequest" %}" class="btn-lg btn btn-link">Recover Password</a>
<button class="btn btn-primary btn-lg">Login to Dashboard</button>
</div>
</div>

View file

View file

@ -0,0 +1,11 @@
Hi {§first_name§},
Somebody (hopefully you) requested a new password for your {§sitename§} account. If this was you, please click the following link to reset your password:
{§link§}
If it was not you, you can ignore this message. The link will expire in 24 hours.
Best regards
Your {§sitename§} Team