diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 004a2c9d3..f6903e6e9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,7 +16,6 @@ variables:
POSTGRES_DB: passbook
POSTGRES_USER: passbook
POSTGRES_PASSWORD: 'EK-5jnKfjrGRm<77'
- SUPERVISR_ENV: ci
include:
- /allauth/.gitlab-ci.yml
diff --git a/passbook/core/auth/factors/password.py b/passbook/core/auth/factors/password.py
index b9072bb08..136759517 100644
--- a/passbook/core/auth/factors/password.py
+++ b/passbook/core/auth/factors/password.py
@@ -5,7 +5,7 @@ from django.contrib import messages
from django.contrib.auth import authenticate
from django.core.exceptions import PermissionDenied
from django.forms.utils import ErrorList
-from django.shortcuts import redirect
+from django.shortcuts import redirect, reverse
from django.utils.translation import gettext as _
from django.views.generic import FormView
@@ -13,6 +13,7 @@ from passbook.core.auth.factor import AuthenticationFactor
from passbook.core.auth.view import AuthenticationView
from passbook.core.forms.authentication import PasswordFactorForm
from passbook.core.models import Nonce
+from passbook.core.tasks import send_email
from passbook.lib.config import CONFIG
LOGGER = getLogger(__name__)
@@ -32,7 +33,16 @@ class PasswordFactor(FormView, AuthenticationFactor):
if 'password-forgotten' in request.GET:
nonce = Nonce.objects.create(user=self.pending_user)
LOGGER.debug("DEBUG %s", str(nonce.uuid))
- # TODO: Send email to user
+ # Send mail to user
+ send_email.delay(self.pending_user.email, _('Forgotten password'),
+ 'email/account_password_reset.html', {
+ 'url': self.request.build_absolute_uri(
+ reverse('passbook_core:passbook_core:auth-password-reset',
+ kwargs={
+ 'nonce': nonce.uuid
+ })
+ )
+ })
self.authenticator.cleanup()
messages.success(request, _('Check your E-Mails for a password reset link.'))
return redirect('passbook_core:auth-login')
diff --git a/passbook/core/tasks.py b/passbook/core/tasks.py
new file mode 100644
index 000000000..6d390ecda
--- /dev/null
+++ b/passbook/core/tasks.py
@@ -0,0 +1,17 @@
+"""passbook core tasks"""
+from django.core.mail import EmailMultiAlternatives
+from django.template.loader import render_to_string
+from django.utils.html import strip_tags
+
+from passbook.core.celery import CELERY_APP
+from passbook.lib.config import CONFIG
+
+
+@CELERY_APP.task()
+def send_email(to_address, subject, template, context):
+ """Send Email to user(s)"""
+ html_content = render_to_string(template, context=context)
+ text_content = strip_tags(html_content)
+ msg = EmailMultiAlternatives(subject, text_content, CONFIG.y('email.from'), [to_address])
+ msg.attach_alternative(html_content, "text/html")
+ msg.send()
diff --git a/passbook/core/templates/email/account_confirm.html b/passbook/core/templates/email/account_confirm.html
new file mode 100644
index 000000000..5a4488162
--- /dev/null
+++ b/passbook/core/templates/email/account_confirm.html
@@ -0,0 +1,84 @@
+{% extends 'email/base.html' %}
+
+{% load inline %}
+{% load i18n %}
+
+{% block pre_header %}
+{% trans "We're thrilled to have you here! Get ready to dive into your new account." %}
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+ {% trans 'Welcome!' %}
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+ {% trans "We're excited to have you get started. First, you need to confirm your account. Just press the button below."%}
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+ {% trans "If that doesn't work, copy and paste the following link in your browser:" %}
+ |
+
+
+
+
+ {{ url }}
+ |
+
+
+
+
+
+ {% trans "If you have any questions, just reply to this email—we're always happy to help out." %}
+
+ |
+
+
+ |
+
+{% endblock %}
diff --git a/passbook/core/templates/email/account_password_reset.html b/passbook/core/templates/email/account_password_reset.html
new file mode 100644
index 000000000..c201e811e
--- /dev/null
+++ b/passbook/core/templates/email/account_password_reset.html
@@ -0,0 +1,78 @@
+{% extends "email/base.html" %}
+
+{% load utils %}
+{% load i18n %}
+
+{% block pre_header %}
+{% trans "Looks like you tried signing in a few too many times. Let's see if we can get you back into your account." %}
+{% endblock %}
+
+{% block content %}
+{% config 'passbook.branding' as branding %}
+
+
+
+
+
+
+ {% trans 'Trouble signing in?' %}
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+ {% trans "Resetting your password is easy. Just press the button below and follow the instructions. We'll have you up and running in no time." %}
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+{% endblock %}
diff --git a/passbook/core/templates/email/base.html b/passbook/core/templates/email/base.html
new file mode 100644
index 000000000..d2038808c
--- /dev/null
+++ b/passbook/core/templates/email/base.html
@@ -0,0 +1,129 @@
+{% load inline %}
+{% load utils %}
+{% load static %}
+{% load i18n %}
+
+
+
+ {% config passbook.branding %}
+
+
+
+
+
+
+
+
+
+
+ {% block pre_header %}
+ {% endblock %}
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+ {% block content %}
+ {% endblock %}
+
+
+
+
+
+
+ |
+
+
+
+
diff --git a/passbook/core/templates/email/generic_email.html b/passbook/core/templates/email/generic_email.html
new file mode 100644
index 000000000..915e16398
--- /dev/null
+++ b/passbook/core/templates/email/generic_email.html
@@ -0,0 +1,26 @@
+{% extends "email/base.html" %}
+
+{% block content %}
+
+
+
+ |
+
+
+
+
+ |
+
+{% endblock %}
diff --git a/passbook/core/views/authentication.py b/passbook/core/views/authentication.py
index 2a463dd6f..26621eb2f 100644
--- a/passbook/core/views/authentication.py
+++ b/passbook/core/views/authentication.py
@@ -15,10 +15,12 @@ from passbook.core.auth.view import AuthenticationView
from passbook.core.forms.authentication import LoginForm, SignUpForm
from passbook.core.models import Invitation, Nonce, Source, User
from passbook.core.signals import invitation_used, user_signed_up
+from passbook.core.tasks import send_email
from passbook.lib.config import CONFIG
LOGGER = getLogger(__name__)
+
class LoginView(UserPassesTestMixin, FormView):
"""Allow users to sign in"""
@@ -76,6 +78,7 @@ class LoginView(UserPassesTestMixin, FormView):
messages.error(request, _('Failed to authenticate.'))
return self.render_to_response(self.get_context_data())
+
class LogoutView(LoginRequiredMixin, View):
"""Log current user out"""
@@ -145,7 +148,15 @@ class SignUpView(UserPassesTestMixin, FormView):
if needs_confirmation:
nonce = Nonce.objects.create(user=self._user)
LOGGER.debug(str(nonce.uuid))
- # TODO: Send E-Mail to user
+ # Send email to user
+ send_email.delay(self._user.email, _('Confirm your account.'),
+ 'email/account_confirm.html', {
+ 'url': self.request.build_absolute_uri(
+ reverse('passbook_core:auth-sign-up-confirm', kwargs={
+ 'nonce': nonce.uuid
+ })
+ )
+ })
self._user.is_active = False
self._user.save()
self.consume_invitation()
@@ -196,6 +207,7 @@ class SignUpView(UserPassesTestMixin, FormView):
request=request)
return new_user
+
class SignUpConfirmView(View):
"""Confirm registration from Nonce"""
diff --git a/passbook/ldap/forms.py b/passbook/ldap/forms.py
index 62d9efeaf..cb47328c1 100644
--- a/passbook/ldap/forms.py
+++ b/passbook/ldap/forms.py
@@ -39,7 +39,7 @@ class LDAPSourceForm(forms.ModelForm):
# (MODE_CREATE_USERS, _('Create Users'))
# )
-# namespace = 'supervisr.mod.auth.ldap'
+# namespace = 'passbook.ldap'
# settings = ['enabled', 'mode']
# widgets = {
@@ -51,7 +51,7 @@ class LDAPSourceForm(forms.ModelForm):
# class ConnectionSettings(SettingsForm):
# """Connection settings form"""
-# namespace = 'supervisr.mod.auth.ldap'
+# namespace = 'passbook.ldap'
# settings = ['server', 'server:tls', 'bind:user', 'bind:password', 'domain']
# attrs_map = {
@@ -68,7 +68,7 @@ class LDAPSourceForm(forms.ModelForm):
# class AuthenticationBackendSettings(SettingsForm):
# """Authentication backend settings"""
-# namespace = 'supervisr.mod.auth.ldap'
+# namespace = 'passbook.ldap'
# settings = ['base']
# attrs_map = {
@@ -79,7 +79,7 @@ class LDAPSourceForm(forms.ModelForm):
# class CreateUsersSettings(SettingsForm):
# """Create users settings"""
-# namespace = 'supervisr.mod.auth.ldap'
+# namespace = 'passbook.ldap'
# settings = ['create_base']
# attrs_map = {
diff --git a/passbook/ldap/models.py b/passbook/ldap/models.py
index ca98c7276..6d112cfc7 100644
--- a/passbook/ldap/models.py
+++ b/passbook/ldap/models.py
@@ -57,7 +57,7 @@ class LDAPSource(Source):
# class LDAPGroupMapping(UUIDModel, CreatedUpdatedModel):
-# """Model to map an LDAP Group to a supervisr group"""
+# """Model to map an LDAP Group to a passbook group"""
# ldap_dn = models.TextField()
# group = models.ForeignKey(Group, on_delete=models.CASCADE)
diff --git a/passbook/ldap/urls.py b/passbook/ldap/urls.py
index 13fc15a44..b643ba6bc 100644
--- a/passbook/ldap/urls.py
+++ b/passbook/ldap/urls.py
@@ -2,7 +2,7 @@
# from django.conf.urls import url
-# from supervisr.mod.auth.ldap import views
+# from passbook.mod.auth.ldap import views
# urlpatterns = [
# url(r'^settings/$', views.admin_settings, name='admin_settings'),
diff --git a/passbook/ldap/views.py b/passbook/ldap/views.py
index 22255e8b5..5eb73d82e 100644
--- a/passbook/ldap/views.py
+++ b/passbook/ldap/views.py
@@ -1,4 +1,4 @@
-# """Supervisr Mod LDAP Views"""
+# """passbook LDAP Views"""
# from django.contrib import messages
@@ -8,7 +8,7 @@
# from django.urls import reverse
# from django.utils.translation import ugettext as _
-# from supervisr.mod.auth.ldap.forms import (AuthenticationBackendSettings,
+# from passbook.ldap.forms import (AuthenticationBackendSettings,
# ConnectionSettings,
# CreateUsersSettings,
# GeneralSettingsForm)
@@ -34,5 +34,5 @@
# if form.is_valid():
# update_count += form.save()
# messages.success(request, _('Successfully updated %d settings.' % update_count))
-# return redirect(reverse('supervisr_mod_auth_ldap:admin_settings'))
+# return redirect(reverse('passbook_ldap:admin_settings'))
# return render(request, 'ldap/settings.html', render_data)
diff --git a/passbook/lib/utils/inline.py b/passbook/lib/utils/inline.py
new file mode 100644
index 000000000..4a0c8bae9
--- /dev/null
+++ b/passbook/lib/utils/inline.py
@@ -0,0 +1,20 @@
+"""passbook core inlining template tags"""
+import os
+
+from django import template
+from django.conf import settings
+
+register = template.Library()
+
+
+@register.simple_tag()
+def inline_static(path):
+ """Inline static asset. If file is binary, return b64 representation"""
+ prefix = 'data:image/svg+xml;utf8,'
+ data = ''
+ full_path = settings.STATIC_ROOT + '/' + path
+ if os.path.exists(full_path):
+ if full_path.endswith('.svg'):
+ with open(full_path) as _file:
+ data = _file.read()
+ return prefix + data