modified: index.html modified: index2.html modified: remarkbox/__init__.py modified: remarkbox/lib/mail.py modified: remarkbox/models/meta.py modified: remarkbox/views/__init__.py
221 lines
6.3 KiB
Python
221 lines
6.3 KiB
Python
# quote email address in OTP so that a plus address
|
|
# is not decoded as a space during authentication.
|
|
try:
|
|
# Python 2.
|
|
from urllib import quote_plus
|
|
except ImportError:
|
|
# Python 3.
|
|
from urllib.parse import quote_plus
|
|
|
|
from remarkbox.lib.mail_messages import (
|
|
WELCOME_1_TEXT,
|
|
WELCOME_1_HTML,
|
|
WELCOME_2_TEXT,
|
|
WELCOME_2_HTML,
|
|
OPERATOR_HTML,
|
|
)
|
|
|
|
import dkim
|
|
|
|
import smtplib
|
|
|
|
from email.mime.multipart import MIMEMultipart
|
|
|
|
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(
|
|
loader=PackageLoader("remarkbox", "templates"),
|
|
autoescape=select_autoescape(["html", "xml"]),
|
|
)
|
|
|
|
|
|
def send_email(
|
|
to_email,
|
|
sender_email,
|
|
subject,
|
|
message_text,
|
|
message_html,
|
|
relay="localhost",
|
|
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()
|
|
|
|
if isinstance(message_html, bytes):
|
|
# needed for Python 3.
|
|
message_html = message_html.decode()
|
|
|
|
sender_domain = sender_email.split("@")[-1]
|
|
msg = MIMEMultipart("alternative")
|
|
msg.attach(MIMEText(message_text, "plain"))
|
|
msg.attach(MIMEText(message_html, "html"))
|
|
msg["To"] = to_email
|
|
msg["From"] = sender_email
|
|
msg["Subject"] = subject
|
|
|
|
try:
|
|
# Python 3 libraries expect bytes.
|
|
msg_data = msg.as_bytes()
|
|
except:
|
|
# Python 2 libraries expect strings.
|
|
msg_data = msg.as_string()
|
|
|
|
if dkim_private_key_path and dkim_selector:
|
|
try:
|
|
# 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()
|
|
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)
|
|
)
|
|
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"
|
|
)
|
|
|
|
send_email(
|
|
to_email,
|
|
sender_email,
|
|
subject,
|
|
message_text,
|
|
message_html,
|
|
relay,
|
|
dkim_private_key_path,
|
|
dkim_selector,
|
|
dkim_signature_algorithm,
|
|
request.debug_mode,
|
|
)
|
|
|
|
|
|
def send_verification_digits_to_email(request, to_email, raw_digits):
|
|
"""
|
|
Send email with raw_digits a user may pass to verify & authenticate.
|
|
|
|
request
|
|
the request (of the successful log in attempt)
|
|
|
|
to_email
|
|
the email address to send the raw_digits
|
|
|
|
raw_digits:
|
|
the raw (unencrypted) digits the user may use to verify & authenticate.
|
|
"""
|
|
subject = "Verification Code - {}".format(raw_digits)
|
|
|
|
message_text = WELCOME_1_TEXT.format(raw_digits)
|
|
message_html = WELCOME_1_HTML.format(subject, raw_digits)
|
|
|
|
if request.user and request.user.verified:
|
|
message_text = WELCOME_2_TEXT.format(raw_digits)
|
|
message_html = WELCOME_2_HTML.format(subject, raw_digits)
|
|
else:
|
|
message_text = WELCOME_1_TEXT.format(raw_digits)
|
|
message_html = WELCOME_1_HTML.format(subject, raw_digits)
|
|
|
|
send_pyramid_email(request, to_email, subject, message_text, message_html)
|
|
|
|
|
|
def send_operator_email(request, msg):
|
|
send_pyramid_email(
|
|
request,
|
|
"russell.ballestrini@gmail.com",
|
|
msg,
|
|
msg,
|
|
OPERATOR_HTML.format(msg),
|
|
)
|
|
|
|
|
|
def send_template_email(
|
|
request, to_email, subject, text_template_name, html_template_name, context
|
|
):
|
|
text_template = jinja2_env.get_template(text_template_name)
|
|
html_template = jinja2_env.get_template(html_template_name)
|
|
send_pyramid_email(
|
|
request,
|
|
to_email,
|
|
subject,
|
|
text_template.render(**context),
|
|
html_template.render(**context),
|
|
)
|