modified: web/blueprints/group_admin.py
modified: web/blueprints/site_admin.py modified: web/mailer.py
This commit is contained in:
@@ -7,7 +7,7 @@ from datetime import datetime, timedelta
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
|
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
|
||||||
from config import Config
|
from config import Config
|
||||||
from mailer import send_mail
|
from mailer import send_mail, build_invite_email
|
||||||
import panel_db as db
|
import panel_db as db
|
||||||
from roles import GROUP_MANAGEMENT_ROLES, GROUP_ROLE_OPTIONS, GROUP_ROLE_SET, OWNER_ONLY_ROLES, role_label
|
from roles import GROUP_MANAGEMENT_ROLES, GROUP_ROLE_OPTIONS, GROUP_ROLE_SET, OWNER_ONLY_ROLES, role_label
|
||||||
|
|
||||||
@@ -150,14 +150,15 @@ def member_invite():
|
|||||||
|
|
||||||
if mail_settings:
|
if mail_settings:
|
||||||
subject = f"Invitation to join {session.get('group_name', 'your group')}"
|
subject = f"Invitation to join {session.get('group_name', 'your group')}"
|
||||||
text_body = (
|
text_body, html_body = build_invite_email(
|
||||||
f"Hello {username},\n\n"
|
username=username,
|
||||||
f"You have been invited to join the group '{session.get('group_name', 'your group')}' on MCLogger as {role_label(role)}.\n"
|
invite_url=invite_url,
|
||||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
expiry_text=f"in {Config.INVITE_EXPIRY_HOURS} hours",
|
||||||
f"This invite expires in {Config.INVITE_EXPIRY_HOURS} hours.\n"
|
group_name=session.get("group_name", "your group"),
|
||||||
|
role_name=role_label(role),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
send_mail(mail_settings, email, subject, text_body)
|
send_mail(mail_settings, email, subject, text_body, html_body=html_body)
|
||||||
if invite:
|
if invite:
|
||||||
db.mark_group_invite_sent(invite["id"], group_id)
|
db.mark_group_invite_sent(invite["id"], group_id)
|
||||||
flash(f"Invitation email sent to '{email}'.", "success")
|
flash(f"Invitation email sent to '{email}'.", "success")
|
||||||
@@ -193,14 +194,15 @@ def resend_invite(invite_id):
|
|||||||
|
|
||||||
invite_url = url_for("auth.accept_invite", token=invite["token"], _external=True)
|
invite_url = url_for("auth.accept_invite", token=invite["token"], _external=True)
|
||||||
subject = f"Invitation to join {session.get('group_name', 'your group')}"
|
subject = f"Invitation to join {session.get('group_name', 'your group')}"
|
||||||
text_body = (
|
text_body, html_body = build_invite_email(
|
||||||
f"Hello {invite['invited_username']},\n\n"
|
username=invite["invited_username"],
|
||||||
f"You have been invited to join the group '{session.get('group_name', 'your group')}' on MCLogger as {role_label(invite['role'])}.\n"
|
invite_url=invite_url,
|
||||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
expiry_text=f"on {invite['expires_at']}",
|
||||||
f"This invite expires on {invite['expires_at']}.\n"
|
group_name=session.get("group_name", "your group"),
|
||||||
|
role_name=role_label(invite["role"]),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
send_mail(mail_settings, invite["invited_email"], subject, text_body)
|
send_mail(mail_settings, invite["invited_email"], subject, text_body, html_body=html_body)
|
||||||
db.mark_group_invite_sent(invite_id, group_id)
|
db.mark_group_invite_sent(invite_id, group_id)
|
||||||
flash("Invitation email resent.", "success")
|
flash("Invitation email resent.", "success")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from functools import wraps
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
|
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
|
||||||
from config import Config
|
from config import Config
|
||||||
from mailer import send_mail
|
from mailer import send_mail, build_invite_email
|
||||||
import panel_db as db
|
import panel_db as db
|
||||||
from roles import GROUP_MANAGEMENT_ROLES, GROUP_ROLE_OPTIONS, GROUP_ROLE_SET, role_label
|
from roles import GROUP_MANAGEMENT_ROLES, GROUP_ROLE_OPTIONS, GROUP_ROLE_SET, role_label
|
||||||
|
|
||||||
@@ -283,14 +283,15 @@ def group_member_invite(group_id):
|
|||||||
|
|
||||||
if mail_settings:
|
if mail_settings:
|
||||||
subject = f"Invitation to join {group['name']}"
|
subject = f"Invitation to join {group['name']}"
|
||||||
text_body = (
|
text_body, html_body = build_invite_email(
|
||||||
f"Hello {username},\n\n"
|
username=username,
|
||||||
f"You have been invited to join the group '{group['name']}' on MCLogger as {role_label(role)}.\n"
|
invite_url=invite_url,
|
||||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
expiry_text=f"in {Config.INVITE_EXPIRY_HOURS} hours",
|
||||||
f"This invite expires in {Config.INVITE_EXPIRY_HOURS} hours.\n"
|
group_name=group["name"],
|
||||||
|
role_name=role_label(role),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
send_mail(mail_settings, email, subject, text_body)
|
send_mail(mail_settings, email, subject, text_body, html_body=html_body)
|
||||||
if invite:
|
if invite:
|
||||||
db.mark_group_invite_sent(invite["id"], group_id)
|
db.mark_group_invite_sent(invite["id"], group_id)
|
||||||
flash(f"Invitation email sent to '{email}'.", "success")
|
flash(f"Invitation email sent to '{email}'.", "success")
|
||||||
@@ -330,14 +331,15 @@ def group_invite_resend(group_id, invite_id):
|
|||||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||||
invite_url = url_for("auth.accept_invite", token=invite["token"], _external=True)
|
invite_url = url_for("auth.accept_invite", token=invite["token"], _external=True)
|
||||||
subject = f"Invitation to join {group['name']}"
|
subject = f"Invitation to join {group['name']}"
|
||||||
text_body = (
|
text_body, html_body = build_invite_email(
|
||||||
f"Hello {invite['invited_username']},\n\n"
|
username=invite["invited_username"],
|
||||||
f"You have been invited to join the group '{group['name']}' on MCLogger as {role_label(invite['role'])}.\n"
|
invite_url=invite_url,
|
||||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
expiry_text=f"on {invite['expires_at']}",
|
||||||
f"This invite expires on {invite['expires_at']}.\n"
|
group_name=group["name"],
|
||||||
|
role_name=role_label(invite["role"]),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
send_mail(mail_settings, invite["invited_email"], subject, text_body)
|
send_mail(mail_settings, invite["invited_email"], subject, text_body, html_body=html_body)
|
||||||
db.mark_group_invite_sent(invite_id, group_id)
|
db.mark_group_invite_sent(invite_id, group_id)
|
||||||
flash("Invitation email resent.", "success")
|
flash("Invitation email resent.", "success")
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -399,23 +401,22 @@ def user_new():
|
|||||||
if group_id:
|
if group_id:
|
||||||
group = db.get_group_by_id(group_id)
|
group = db.get_group_by_id(group_id)
|
||||||
subject = f"Invitation to join {group['name']}"
|
subject = f"Invitation to join {group['name']}"
|
||||||
body = (
|
body, html_body = build_invite_email(
|
||||||
f"Hello {username},\n\n"
|
username=username,
|
||||||
f"You have been invited to join the group '{group['name']}' on MCLogger"
|
invite_url=invite_url,
|
||||||
f" as {role_label(effective_role)}.\n"
|
expiry_text=f"in {Config.INVITE_EXPIRY_HOURS} hours",
|
||||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
group_name=group["name"],
|
||||||
f"This invite expires in {Config.INVITE_EXPIRY_HOURS} hours.\n"
|
role_name=role_label(effective_role),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
subject = "You have been invited to MCLogger"
|
subject = "You have been invited to MCLogger"
|
||||||
body = (
|
body, html_body = build_invite_email(
|
||||||
f"Hello {username},\n\n"
|
username=username,
|
||||||
f"You have been invited to create an account on MCLogger.\n"
|
invite_url=invite_url,
|
||||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
expiry_text=f"in {Config.INVITE_EXPIRY_HOURS} hours",
|
||||||
f"This invite expires in {Config.INVITE_EXPIRY_HOURS} hours.\n"
|
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
send_mail(mail_settings, email, subject, body)
|
send_mail(mail_settings, email, subject, body, html_body=html_body)
|
||||||
invite = db.get_invite_by_token(token)
|
invite = db.get_invite_by_token(token)
|
||||||
if invite:
|
if invite:
|
||||||
db.mark_invite_sent_global(invite["id"])
|
db.mark_invite_sent_global(invite["id"])
|
||||||
@@ -463,23 +464,22 @@ def user_invite_resend(invite_id):
|
|||||||
if invite["group_id"]:
|
if invite["group_id"]:
|
||||||
group = db.get_group_by_id(invite["group_id"])
|
group = db.get_group_by_id(invite["group_id"])
|
||||||
subject = f"Invitation to join {group['name']}"
|
subject = f"Invitation to join {group['name']}"
|
||||||
body = (
|
body, html_body = build_invite_email(
|
||||||
f"Hello {invite['invited_username']},\n\n"
|
username=invite["invited_username"],
|
||||||
f"You have been invited to join the group '{group['name']}' on MCLogger"
|
invite_url=invite_url,
|
||||||
f" as {role_label(invite['role'])}.\n"
|
expiry_text=f"on {invite['expires_at']}",
|
||||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
group_name=group["name"],
|
||||||
f"This invite expires on {invite['expires_at']}.\n"
|
role_name=role_label(invite["role"]),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
subject = "You have been invited to MCLogger"
|
subject = "You have been invited to MCLogger"
|
||||||
body = (
|
body, html_body = build_invite_email(
|
||||||
f"Hello {invite['invited_username']},\n\n"
|
username=invite["invited_username"],
|
||||||
f"You have been invited to create an account on MCLogger.\n"
|
invite_url=invite_url,
|
||||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
expiry_text=f"on {invite['expires_at']}",
|
||||||
f"This invite expires on {invite['expires_at']}.\n"
|
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
send_mail(mail_settings, invite["invited_email"], subject, body)
|
send_mail(mail_settings, invite["invited_email"], subject, body, html_body=html_body)
|
||||||
db.mark_invite_sent_global(invite_id)
|
db.mark_invite_sent_global(invite_id)
|
||||||
flash("Invitation email resent.", "success")
|
flash("Invitation email resent.", "success")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import smtplib
|
import smtplib
|
||||||
|
from html import escape
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
@@ -10,13 +11,83 @@ def build_from_header(from_email: str, from_name: str | None = None) -> str:
|
|||||||
return from_email
|
return from_email
|
||||||
|
|
||||||
|
|
||||||
|
def build_invite_email(
|
||||||
|
username: str,
|
||||||
|
invite_url: str,
|
||||||
|
expiry_text: str,
|
||||||
|
group_name: str | None = None,
|
||||||
|
role_name: str | None = None,
|
||||||
|
) -> tuple[str, str]:
|
||||||
|
safe_user = escape(username)
|
||||||
|
safe_url = escape(invite_url)
|
||||||
|
safe_expiry = escape(expiry_text)
|
||||||
|
safe_group = escape(group_name) if group_name else None
|
||||||
|
safe_role = escape(role_name) if role_name else None
|
||||||
|
|
||||||
def send_mail(settings: dict, recipient: str, subject: str, text_body: str):
|
if safe_group:
|
||||||
|
role_part = f" as <strong>{safe_role}</strong>" if safe_role else ""
|
||||||
|
intro_html = (
|
||||||
|
f"You have been invited to join the group <strong>{safe_group}</strong> "
|
||||||
|
f"on MCLogger{role_part}."
|
||||||
|
)
|
||||||
|
role_text = f" as {role_name}" if role_name else ""
|
||||||
|
intro_text = f"You have been invited to join the group '{group_name}' on MCLogger{role_text}."
|
||||||
|
else:
|
||||||
|
intro_html = "You have been invited to create an account on <strong>MCLogger</strong>."
|
||||||
|
intro_text = "You have been invited to create an account on MCLogger."
|
||||||
|
|
||||||
|
text_body = (
|
||||||
|
f"Hello {username},\n\n"
|
||||||
|
f"{intro_text}\n"
|
||||||
|
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
||||||
|
f"This invite expires {expiry_text}.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
html_body = f"""
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<body style="margin:0;padding:0;background:#f6f8fb;font-family:Arial,Helvetica,sans-serif;color:#1f2937;">
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="padding:24px 12px;">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width:620px;background:#ffffff;border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;">
|
||||||
|
<tr>
|
||||||
|
<td style="background:#111827;color:#ffffff;padding:16px 20px;font-size:18px;font-weight:700;">
|
||||||
|
MCLogger Invitation
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:24px 20px 20px 20px;font-size:14px;line-height:1.6;">
|
||||||
|
<p style="margin:0 0 12px 0;">Hello <strong>{safe_user}</strong>,</p>
|
||||||
|
<p style="margin:0 0 12px 0;">{intro_html}</p>
|
||||||
|
<p style="margin:0 0 20px 0;">Click the button below to create your account:</p>
|
||||||
|
<p style="margin:0 0 20px 0;">
|
||||||
|
<a href="{safe_url}" style="display:inline-block;background:#2563eb;color:#ffffff;text-decoration:none;font-weight:700;padding:11px 16px;border-radius:8px;">Create Account</a>
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 12px 0;">This invite expires <strong>{safe_expiry}</strong>.</p>
|
||||||
|
<p style="margin:16px 0 0 0;font-size:12px;color:#6b7280;word-break:break-all;">If the button does not work, use this link:<br>{safe_url}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
return text_body, html_body
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def send_mail(settings: dict, recipient: str, subject: str, text_body: str, html_body: str | None = None):
|
||||||
msg = EmailMessage()
|
msg = EmailMessage()
|
||||||
msg["Subject"] = subject
|
msg["Subject"] = subject
|
||||||
msg["From"] = build_from_header(settings["from_email"], settings.get("from_name"))
|
msg["From"] = build_from_header(settings["from_email"], settings.get("from_name"))
|
||||||
msg["To"] = recipient
|
msg["To"] = recipient
|
||||||
msg.set_content(text_body)
|
msg.set_content(text_body)
|
||||||
|
if html_body:
|
||||||
|
msg.add_alternative(html_body, subtype="html")
|
||||||
|
|
||||||
with smtplib.SMTP(settings["host"], settings["port"], timeout=Config.MAIL_TIMEOUT) as smtp:
|
with smtplib.SMTP(settings["host"], settings["port"], timeout=Config.MAIL_TIMEOUT) as smtp:
|
||||||
smtp.ehlo()
|
smtp.ehlo()
|
||||||
|
|||||||
Reference in New Issue
Block a user