Merge branch 'development-smtp-solution' into 'main'
when in development log SMTP errors & email Closes #3 See merge request engineering/remarkbox/remarkbox!4
This commit is contained in:
commit
84fabcd1c0
4 changed files with 84 additions and 44 deletions
4
Makefile
4
Makefile
|
|
@ -29,7 +29,3 @@ serve: dev
|
|||
http: dev
|
||||
# In the second shell, run a "mock" simple HTTP webserver to serve index.html:
|
||||
env/bin/python -m http.server 8000
|
||||
|
||||
smtp: dev
|
||||
# In the third shell, run a "mock" SMTP server to catch log in emails:
|
||||
sudo env/bin/python -m smtpd -n -c DebuggingServer localhost:25
|
||||
|
|
|
|||
13
README.rst
13
README.rst
|
|
@ -70,7 +70,7 @@ We utilize a ``Makefile`` to capture targets for building a local Remarkbox envi
|
|||
Functional testing environment
|
||||
-------------------------------
|
||||
|
||||
To setup a "functional testing" environment on your personal workstation, open three terminal shells.
|
||||
To setup a "functional testing" environment on your personal workstation, open two terminal shells.
|
||||
|
||||
In the first shell, run a copy of Remarkbox using:
|
||||
|
||||
|
|
@ -84,14 +84,11 @@ In the second shell, run a "mock" simple HTTP web server to serve index.html:
|
|||
|
||||
make http
|
||||
|
||||
In the third shell, run a "mock" SMTP server to catch log in emails:
|
||||
Now browse to http://127.0.0.1:8000 and index.html will load.
|
||||
This has an embedded copy of Remarkbox which is also running on localhost.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make smtp
|
||||
|
||||
Now browse to http://127.0.0.1:8000 and index.html will load and will have an embedded copy of Remarkbox which running on localhost.
|
||||
If you attempt to log in, your third shell will capture the email and you can copy / paste the verification link to log in!
|
||||
If you attempt to log in, a verification one-time-password code will be sent over SMTP to log in!
|
||||
If you do not have an SMTP server the socket error will log email to console when in development.
|
||||
|
||||
|
||||
New Environments
|
||||
|
|
|
|||
|
|
@ -192,6 +192,12 @@ def main(global_config, **settings):
|
|||
# compile the email validator regex outside of the functions.
|
||||
_email_regex = re.compile("^[^@]+@[^@]+\.[^.@]+$")
|
||||
|
||||
def add_debug_mode(request):
|
||||
"""Return True if debug toolbar is enabled."""
|
||||
return "pyramid_debugtoolbar" in request.registry.settings.get(
|
||||
"pyramid.includes", ""
|
||||
)
|
||||
|
||||
'''
|
||||
def add_redis(request):
|
||||
"""Return Redis Connection"""
|
||||
|
|
@ -490,6 +496,7 @@ def main(global_config, **settings):
|
|||
# each request instance will run these functions and attach results.
|
||||
# cache result with `reify=True` to prevent multiple db lookups.
|
||||
# config.add_request_method(add_redis, "redis", reify=True)
|
||||
config.add_request_method(add_debug_mode, "debug_mode", reify=True)
|
||||
config.add_request_method(add_user, "user", reify=True)
|
||||
config.add_request_method(add_csrf_token, "csrf_token", reify=True)
|
||||
config.add_request_method(add_email, "email", reify=True)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ from email.mime.text import MIMEText
|
|||
# catch socket errors when postfix isn't running...
|
||||
from socket import error as socket_error
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||
|
||||
jinja2_env = Environment(
|
||||
|
|
@ -44,12 +48,11 @@ def send_email(
|
|||
dkim_private_key_path="",
|
||||
dkim_selector="",
|
||||
dkim_signature_algorithm="ed25519-sha256",
|
||||
debug_mode=False,
|
||||
):
|
||||
|
||||
# the `email` library assumes it is working with string objects.
|
||||
# the `dkim` library assumes it is working with byte objects.
|
||||
# this function performs the acrobatics to make them both happy.
|
||||
|
||||
if isinstance(message_text, bytes):
|
||||
# needed for Python 3.
|
||||
message_text = message_text.decode()
|
||||
|
|
@ -74,47 +77,83 @@ def send_email(
|
|||
msg_data = msg.as_string()
|
||||
|
||||
if dkim_private_key_path and dkim_selector:
|
||||
# the dkim library uses regex on byte strings so everything
|
||||
# needs to be encoded from strings to bytes.
|
||||
with open(dkim_private_key_path) as fh:
|
||||
dkim_private_key = fh.read()
|
||||
headers = [b"To", b"From", b"Subject"]
|
||||
sig = dkim.sign(
|
||||
message=msg_data,
|
||||
selector=str(dkim_selector).encode(),
|
||||
domain=sender_domain.encode(),
|
||||
privkey=dkim_private_key.encode(),
|
||||
include_headers=headers,
|
||||
signature_algorithm=dkim_signature_algorithm.encode(),
|
||||
)
|
||||
# add the dkim signature to the email message headers.
|
||||
# decode the signature back to string_type because later on
|
||||
# the call to msg.as_string() performs it's own bytes encoding...
|
||||
msg["DKIM-Signature"] = sig[len("DKIM-Signature: ") :].decode()
|
||||
|
||||
try:
|
||||
# Python 3 libraries expect bytes.
|
||||
msg_data = msg.as_bytes()
|
||||
except:
|
||||
# Python 2 libraries expect strings.
|
||||
msg_data = msg.as_string()
|
||||
# the dkim library uses regex on byte strings so everything
|
||||
# needs to be encoded from strings to bytes.
|
||||
with open(dkim_private_key_path) as fh:
|
||||
dkim_private_key = fh.read()
|
||||
headers = [b"To", b"From", b"Subject"]
|
||||
sig = dkim.sign(
|
||||
message=msg_data,
|
||||
selector=str(dkim_selector).encode(),
|
||||
domain=sender_domain.encode(),
|
||||
privkey=dkim_private_key.encode(),
|
||||
include_headers=headers,
|
||||
signature_algorithm=dkim_signature_algorithm.encode(),
|
||||
)
|
||||
# add the dkim signature to the email message headers.
|
||||
# decode the signature back to string_type because later on
|
||||
# the call to msg.as_string() performs it's own bytes encoding...
|
||||
msg["DKIM-Signature"] = sig[len("DKIM-Signature: ") :].decode()
|
||||
|
||||
# TODO: react if connecting to relay (localhost postfix) is a socket error.
|
||||
s = smtplib.SMTP(relay)
|
||||
s.sendmail(sender_email, [to_email], msg_data)
|
||||
s.quit()
|
||||
return msg
|
||||
try:
|
||||
# Python 3 libraries expect bytes.
|
||||
msg_data = msg.as_bytes()
|
||||
except:
|
||||
# Python 2 libraries expect strings.
|
||||
msg_data = msg.as_string()
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
log.error(f"DKIM signing failed: {str(e)}")
|
||||
raise
|
||||
|
||||
try:
|
||||
s = smtplib.SMTP(relay)
|
||||
s.sendmail(sender_email, [to_email], msg_data)
|
||||
s.quit()
|
||||
return msg
|
||||
|
||||
except (socket_error, smtplib.SMTPException) as e:
|
||||
error_msg = f"Failed to send email: {str(e)}"
|
||||
|
||||
if debug_mode:
|
||||
# Log the error first for quick scanning
|
||||
log.error(error_msg)
|
||||
# Then log the email details
|
||||
log.info(
|
||||
f"""
|
||||
|
||||
Email Contents:
|
||||
To: {to_email}
|
||||
From: {sender_email}
|
||||
Subject: {subject}
|
||||
|
||||
Text Content:
|
||||
{message_text}
|
||||
|
||||
HTML Content:
|
||||
{message_html}
|
||||
"""
|
||||
)
|
||||
|
||||
if not debug_mode:
|
||||
raise
|
||||
return None
|
||||
|
||||
|
||||
def send_pyramid_email(request, to_email, subject, message_text, message_html):
|
||||
"""Thin wrapper around `send_email` to customise settings using request object."""
|
||||
default_sender = "no-reply@{}".format(request.domain)
|
||||
sender_email = request.app.get("email.sender", default_sender)
|
||||
subject = "{} | {}".format(subject, request.app.get("email.subject_postfix", request.domain))
|
||||
subject = "{} | {}".format(
|
||||
subject, request.app.get("email.subject_postfix", request.domain)
|
||||
)
|
||||
relay = request.app.get("email.relay", "localhost")
|
||||
dkim_private_key_path = request.app.get("email.dkim_private_key_path", "")
|
||||
dkim_selector = request.app.get("email.dkim_selector", "")
|
||||
dkim_signature_algorithm = request.app.get("email.dkim_signature_algorithm", "ed25519-sha256")
|
||||
dkim_signature_algorithm = request.app.get(
|
||||
"email.dkim_signature_algorithm", "ed25519-sha256"
|
||||
)
|
||||
|
||||
send_email(
|
||||
to_email,
|
||||
|
|
@ -126,6 +165,7 @@ def send_pyramid_email(request, to_email, subject, message_text, message_html):
|
|||
dkim_private_key_path,
|
||||
dkim_selector,
|
||||
dkim_signature_algorithm,
|
||||
request.debug_mode,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue