|
@@ -23,21 +23,21 @@ logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
def generate_secure_password(length: int = 16) -> str:
|
|
def generate_secure_password(length: int = 16) -> str:
|
|
|
"""Generate a secure random password.
|
|
"""Generate a secure random password.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
length: Length of the password (default: 16)
|
|
length: Length of the password (default: 16)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
A secure random password containing uppercase, lowercase, digits, and special characters
|
|
A secure random password containing uppercase, lowercase, digits, and special characters
|
|
|
"""
|
|
"""
|
|
|
import random
|
|
import random
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Define character sets
|
|
# Define character sets
|
|
|
lowercase = string.ascii_lowercase
|
|
lowercase = string.ascii_lowercase
|
|
|
uppercase = string.ascii_uppercase
|
|
uppercase = string.ascii_uppercase
|
|
|
digits = string.digits
|
|
digits = string.digits
|
|
|
special = "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
|
special = "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Ensure at least one character from each set
|
|
# Ensure at least one character from each set
|
|
|
password_chars = [
|
|
password_chars = [
|
|
|
secrets.choice(lowercase),
|
|
secrets.choice(lowercase),
|
|
@@ -45,24 +45,24 @@ def generate_secure_password(length: int = 16) -> str:
|
|
|
secrets.choice(digits),
|
|
secrets.choice(digits),
|
|
|
secrets.choice(special),
|
|
secrets.choice(special),
|
|
|
]
|
|
]
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Fill the rest with random characters from all sets
|
|
# Fill the rest with random characters from all sets
|
|
|
all_chars = lowercase + uppercase + digits + special
|
|
all_chars = lowercase + uppercase + digits + special
|
|
|
password_chars.extend(secrets.choice(all_chars) for _ in range(length - 4))
|
|
password_chars.extend(secrets.choice(all_chars) for _ in range(length - 4))
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Shuffle to avoid predictable patterns
|
|
# Shuffle to avoid predictable patterns
|
|
|
random.shuffle(password_chars)
|
|
random.shuffle(password_chars)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return "".join(password_chars)
|
|
return "".join(password_chars)
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_notification_template(db: AsyncSession, event_type: str) -> NotificationTemplate | None:
|
|
async def get_notification_template(db: AsyncSession, event_type: str) -> NotificationTemplate | None:
|
|
|
"""Get a notification template by event type from database.
|
|
"""Get a notification template by event type from database.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
db: Database session
|
|
db: Database session
|
|
|
event_type: Type of event (e.g., 'user_created', 'password_reset')
|
|
event_type: Type of event (e.g., 'user_created', 'password_reset')
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
NotificationTemplate object or None if not found
|
|
NotificationTemplate object or None if not found
|
|
|
"""
|
|
"""
|
|
@@ -74,11 +74,11 @@ async def get_notification_template(db: AsyncSession, event_type: str) -> Notifi
|
|
|
|
|
|
|
|
def render_template(template_str: str, variables: dict[str, Any]) -> str:
|
|
def render_template(template_str: str, variables: dict[str, Any]) -> str:
|
|
|
"""Render a template string with variables.
|
|
"""Render a template string with variables.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
template_str: Template string with {variable} placeholders
|
|
template_str: Template string with {variable} placeholders
|
|
|
variables: Dictionary of variables to substitute
|
|
variables: Dictionary of variables to substitute
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
Rendered template string
|
|
Rendered template string
|
|
|
"""
|
|
"""
|
|
@@ -92,10 +92,10 @@ def render_template(template_str: str, variables: dict[str, Any]) -> str:
|
|
|
|
|
|
|
|
async def get_smtp_settings(db: AsyncSession) -> SMTPSettings | None:
|
|
async def get_smtp_settings(db: AsyncSession) -> SMTPSettings | None:
|
|
|
"""Get SMTP settings from database.
|
|
"""Get SMTP settings from database.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
db: Database session
|
|
db: Database session
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
SMTPSettings object or None if not configured
|
|
SMTPSettings object or None if not configured
|
|
|
"""
|
|
"""
|
|
@@ -116,21 +116,21 @@ async def get_smtp_settings(db: AsyncSession) -> SMTPSettings | None:
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
settings_dict = {s.key: s.value for s in result.scalars().all()}
|
|
settings_dict = {s.key: s.value for s in result.scalars().all()}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Check if minimum required settings are present
|
|
# Check if minimum required settings are present
|
|
|
required_keys = ["smtp_host", "smtp_port", "smtp_from_email"]
|
|
required_keys = ["smtp_host", "smtp_port", "smtp_from_email"]
|
|
|
if not all(key in settings_dict for key in required_keys):
|
|
if not all(key in settings_dict for key in required_keys):
|
|
|
return None
|
|
return None
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Handle migration: convert old smtp_use_tls to smtp_security if needed
|
|
# Handle migration: convert old smtp_use_tls to smtp_security if needed
|
|
|
smtp_security = settings_dict.get("smtp_security")
|
|
smtp_security = settings_dict.get("smtp_security")
|
|
|
if not smtp_security:
|
|
if not smtp_security:
|
|
|
# Migrate from old smtp_use_tls format
|
|
# Migrate from old smtp_use_tls format
|
|
|
smtp_use_tls = settings_dict.get("smtp_use_tls", "true").lower() == "true"
|
|
smtp_use_tls = settings_dict.get("smtp_use_tls", "true").lower() == "true"
|
|
|
smtp_security = "starttls" if smtp_use_tls else "ssl"
|
|
smtp_security = "starttls" if smtp_use_tls else "ssl"
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
smtp_auth_enabled = settings_dict.get("smtp_auth_enabled", "true").lower() == "true"
|
|
smtp_auth_enabled = settings_dict.get("smtp_auth_enabled", "true").lower() == "true"
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return SMTPSettings(
|
|
return SMTPSettings(
|
|
|
smtp_host=settings_dict["smtp_host"],
|
|
smtp_host=settings_dict["smtp_host"],
|
|
|
smtp_port=int(settings_dict["smtp_port"]),
|
|
smtp_port=int(settings_dict["smtp_port"]),
|
|
@@ -145,14 +145,14 @@ async def get_smtp_settings(db: AsyncSession) -> SMTPSettings | None:
|
|
|
|
|
|
|
|
async def save_smtp_settings(db: AsyncSession, smtp_settings: SMTPSettings) -> None:
|
|
async def save_smtp_settings(db: AsyncSession, smtp_settings: SMTPSettings) -> None:
|
|
|
"""Save SMTP settings to database.
|
|
"""Save SMTP settings to database.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
db: Database session
|
|
db: Database session
|
|
|
smtp_settings: SMTP settings to save
|
|
smtp_settings: SMTP settings to save
|
|
|
"""
|
|
"""
|
|
|
from sqlalchemy import func
|
|
from sqlalchemy import func
|
|
|
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
|
|
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
settings_data = {
|
|
settings_data = {
|
|
|
"smtp_host": smtp_settings.smtp_host,
|
|
"smtp_host": smtp_settings.smtp_host,
|
|
|
"smtp_port": str(smtp_settings.smtp_port),
|
|
"smtp_port": str(smtp_settings.smtp_port),
|
|
@@ -161,15 +161,15 @@ async def save_smtp_settings(db: AsyncSession, smtp_settings: SMTPSettings) -> N
|
|
|
"smtp_from_email": smtp_settings.smtp_from_email,
|
|
"smtp_from_email": smtp_settings.smtp_from_email,
|
|
|
"smtp_from_name": smtp_settings.smtp_from_name,
|
|
"smtp_from_name": smtp_settings.smtp_from_name,
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Only save username if auth is enabled or if provided
|
|
# Only save username if auth is enabled or if provided
|
|
|
if smtp_settings.smtp_username:
|
|
if smtp_settings.smtp_username:
|
|
|
settings_data["smtp_username"] = smtp_settings.smtp_username
|
|
settings_data["smtp_username"] = smtp_settings.smtp_username
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Only save password if provided
|
|
# Only save password if provided
|
|
|
if smtp_settings.smtp_password:
|
|
if smtp_settings.smtp_password:
|
|
|
settings_data["smtp_password"] = smtp_settings.smtp_password
|
|
settings_data["smtp_password"] = smtp_settings.smtp_password
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
for key, value in settings_data.items():
|
|
for key, value in settings_data.items():
|
|
|
stmt = sqlite_insert(Settings).values(key=key, value=value)
|
|
stmt = sqlite_insert(Settings).values(key=key, value=value)
|
|
|
stmt = stmt.on_conflict_do_update(
|
|
stmt = stmt.on_conflict_do_update(
|
|
@@ -187,14 +187,14 @@ def send_email(
|
|
|
body_html: str | None = None,
|
|
body_html: str | None = None,
|
|
|
) -> None:
|
|
) -> None:
|
|
|
"""Send an email using SMTP.
|
|
"""Send an email using SMTP.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
smtp_settings: SMTP configuration
|
|
smtp_settings: SMTP configuration
|
|
|
to_email: Recipient email address
|
|
to_email: Recipient email address
|
|
|
subject: Email subject
|
|
subject: Email subject
|
|
|
body_text: Plain text body
|
|
body_text: Plain text body
|
|
|
body_html: Optional HTML body
|
|
body_html: Optional HTML body
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Raises:
|
|
Raises:
|
|
|
Exception: If email sending fails
|
|
Exception: If email sending fails
|
|
|
"""
|
|
"""
|
|
@@ -202,24 +202,24 @@ def send_email(
|
|
|
msg["From"] = f"{smtp_settings.smtp_from_name} <{smtp_settings.smtp_from_email}>"
|
|
msg["From"] = f"{smtp_settings.smtp_from_name} <{smtp_settings.smtp_from_email}>"
|
|
|
msg["To"] = to_email
|
|
msg["To"] = to_email
|
|
|
msg["Subject"] = subject
|
|
msg["Subject"] = subject
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Attach plain text part
|
|
# Attach plain text part
|
|
|
msg.attach(MIMEText(body_text, "plain"))
|
|
msg.attach(MIMEText(body_text, "plain"))
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Attach HTML part if provided
|
|
# Attach HTML part if provided
|
|
|
if body_html:
|
|
if body_html:
|
|
|
msg.attach(MIMEText(body_html, "html"))
|
|
msg.attach(MIMEText(body_html, "html"))
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Send email
|
|
# Send email
|
|
|
try:
|
|
try:
|
|
|
security = smtp_settings.smtp_security
|
|
security = smtp_settings.smtp_security
|
|
|
auth_enabled = smtp_settings.smtp_auth_enabled
|
|
auth_enabled = smtp_settings.smtp_auth_enabled
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Validate username is provided when authentication is enabled
|
|
# Validate username is provided when authentication is enabled
|
|
|
if auth_enabled and smtp_settings.smtp_password:
|
|
if auth_enabled and smtp_settings.smtp_password:
|
|
|
if not smtp_settings.smtp_username:
|
|
if not smtp_settings.smtp_username:
|
|
|
raise ValueError("SMTP username is required when authentication is enabled")
|
|
raise ValueError("SMTP username is required when authentication is enabled")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if security == "ssl":
|
|
if security == "ssl":
|
|
|
# Direct SSL connection (typically port 465)
|
|
# Direct SSL connection (typically port 465)
|
|
|
with smtplib.SMTP_SSL(smtp_settings.smtp_host, smtp_settings.smtp_port, timeout=10) as server:
|
|
with smtplib.SMTP_SSL(smtp_settings.smtp_host, smtp_settings.smtp_port, timeout=10) as server:
|
|
@@ -247,17 +247,17 @@ def send_email(
|
|
|
|
|
|
|
|
def create_welcome_email(username: str, password: str, login_url: str) -> tuple[str, str, str]:
|
|
def create_welcome_email(username: str, password: str, login_url: str) -> tuple[str, str, str]:
|
|
|
"""Create welcome email content for new user.
|
|
"""Create welcome email content for new user.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
username: Username of the new user
|
|
username: Username of the new user
|
|
|
password: Auto-generated password
|
|
password: Auto-generated password
|
|
|
login_url: URL to login page
|
|
login_url: URL to login page
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
Tuple of (subject, text_body, html_body)
|
|
Tuple of (subject, text_body, html_body)
|
|
|
"""
|
|
"""
|
|
|
subject = "Welcome to BamBuddy - Your Account Details"
|
|
subject = "Welcome to BamBuddy - Your Account Details"
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
text_body = f"""Welcome to BamBuddy!
|
|
text_body = f"""Welcome to BamBuddy!
|
|
|
|
|
|
|
|
Your account has been created. Here are your login details:
|
|
Your account has been created. Here are your login details:
|
|
@@ -272,7 +272,7 @@ For security reasons, please change your password after your first login.
|
|
|
Best regards,
|
|
Best regards,
|
|
|
BamBuddy Team
|
|
BamBuddy Team
|
|
|
"""
|
|
"""
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
html_body = f"""<!DOCTYPE html>
|
|
html_body = f"""<!DOCTYPE html>
|
|
|
<html>
|
|
<html>
|
|
|
<head>
|
|
<head>
|
|
@@ -285,20 +285,20 @@ BamBuddy Team
|
|
|
</div>
|
|
</div>
|
|
|
<div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; border: 1px solid #ddd; border-top: none;">
|
|
<div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; border: 1px solid #ddd; border-top: none;">
|
|
|
<p style="font-size: 16px;">Your account has been created. Here are your login details:</p>
|
|
<p style="font-size: 16px;">Your account has been created. Here are your login details:</p>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<div style="background: white; padding: 20px; border-radius: 4px; margin: 20px 0; border-left: 4px solid #667eea;">
|
|
<div style="background: white; padding: 20px; border-radius: 4px; margin: 20px 0; border-left: 4px solid #667eea;">
|
|
|
<p style="margin: 0 0 10px 0;"><strong>Username:</strong> <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">{username}</code></p>
|
|
<p style="margin: 0 0 10px 0;"><strong>Username:</strong> <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">{username}</code></p>
|
|
|
<p style="margin: 0;"><strong>Password:</strong> <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">{password}</code></p>
|
|
<p style="margin: 0;"><strong>Password:</strong> <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">{password}</code></p>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
|
<a href="{login_url}" style="display: inline-block; background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 4px; font-weight: bold;">Login Now</a>
|
|
<a href="{login_url}" style="display: inline-block; background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 4px; font-weight: bold;">Login Now</a>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<p style="font-size: 14px; color: #666; border-top: 1px solid #ddd; padding-top: 20px; margin-top: 20px;">
|
|
<p style="font-size: 14px; color: #666; border-top: 1px solid #ddd; padding-top: 20px; margin-top: 20px;">
|
|
|
<strong>Security Note:</strong> For security reasons, please change your password after your first login.
|
|
<strong>Security Note:</strong> For security reasons, please change your password after your first login.
|
|
|
</p>
|
|
</p>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<p style="font-size: 14px; color: #999; margin-top: 30px;">
|
|
<p style="font-size: 14px; color: #999; margin-top: 30px;">
|
|
|
Best regards,<br>
|
|
Best regards,<br>
|
|
|
BamBuddy Team
|
|
BamBuddy Team
|
|
@@ -307,23 +307,23 @@ BamBuddy Team
|
|
|
</body>
|
|
</body>
|
|
|
</html>
|
|
</html>
|
|
|
"""
|
|
"""
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return subject, text_body, html_body
|
|
return subject, text_body, html_body
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_password_reset_email(username: str, password: str, login_url: str) -> tuple[str, str, str]:
|
|
def create_password_reset_email(username: str, password: str, login_url: str) -> tuple[str, str, str]:
|
|
|
"""Create password reset email content.
|
|
"""Create password reset email content.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
username: Username of the user
|
|
username: Username of the user
|
|
|
password: New auto-generated password
|
|
password: New auto-generated password
|
|
|
login_url: URL to login page
|
|
login_url: URL to login page
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
Tuple of (subject, text_body, html_body)
|
|
Tuple of (subject, text_body, html_body)
|
|
|
"""
|
|
"""
|
|
|
subject = "BamBuddy - Your Password Has Been Reset"
|
|
subject = "BamBuddy - Your Password Has Been Reset"
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
text_body = f"""Your BamBuddy password has been reset.
|
|
text_body = f"""Your BamBuddy password has been reset.
|
|
|
|
|
|
|
|
Your login details:
|
|
Your login details:
|
|
@@ -340,7 +340,7 @@ If you did not request this password reset, please contact your administrator im
|
|
|
Best regards,
|
|
Best regards,
|
|
|
BamBuddy Team
|
|
BamBuddy Team
|
|
|
"""
|
|
"""
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
html_body = f"""<!DOCTYPE html>
|
|
html_body = f"""<!DOCTYPE html>
|
|
|
<html>
|
|
<html>
|
|
|
<head>
|
|
<head>
|
|
@@ -353,26 +353,26 @@ BamBuddy Team
|
|
|
</div>
|
|
</div>
|
|
|
<div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; border: 1px solid #ddd; border-top: none;">
|
|
<div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; border: 1px solid #ddd; border-top: none;">
|
|
|
<p style="font-size: 16px;">Your BamBuddy password has been reset.</p>
|
|
<p style="font-size: 16px;">Your BamBuddy password has been reset.</p>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<div style="background: white; padding: 20px; border-radius: 4px; margin: 20px 0; border-left: 4px solid #667eea;">
|
|
<div style="background: white; padding: 20px; border-radius: 4px; margin: 20px 0; border-left: 4px solid #667eea;">
|
|
|
<p style="margin: 0 0 10px 0;"><strong>Username:</strong> <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">{username}</code></p>
|
|
<p style="margin: 0 0 10px 0;"><strong>Username:</strong> <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">{username}</code></p>
|
|
|
<p style="margin: 0;"><strong>New Password:</strong> <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">{password}</code></p>
|
|
<p style="margin: 0;"><strong>New Password:</strong> <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">{password}</code></p>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
|
<a href="{login_url}" style="display: inline-block; background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 4px; font-weight: bold;">Login Now</a>
|
|
<a href="{login_url}" style="display: inline-block; background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 4px; font-weight: bold;">Login Now</a>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin: 20px 0;">
|
|
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin: 20px 0;">
|
|
|
<p style="margin: 0; font-size: 14px; color: #856404;">
|
|
<p style="margin: 0; font-size: 14px; color: #856404;">
|
|
|
<strong>⚠️ Security Alert:</strong> If you did not request this password reset, please contact your administrator immediately.
|
|
<strong>⚠️ Security Alert:</strong> If you did not request this password reset, please contact your administrator immediately.
|
|
|
</p>
|
|
</p>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<p style="font-size: 14px; color: #666; border-top: 1px solid #ddd; padding-top: 20px; margin-top: 20px;">
|
|
<p style="font-size: 14px; color: #666; border-top: 1px solid #ddd; padding-top: 20px; margin-top: 20px;">
|
|
|
<strong>Security Note:</strong> For security reasons, please change your password after logging in.
|
|
<strong>Security Note:</strong> For security reasons, please change your password after logging in.
|
|
|
</p>
|
|
</p>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<p style="font-size: 14px; color: #999; margin-top: 30px;">
|
|
<p style="font-size: 14px; color: #999; margin-top: 30px;">
|
|
|
Best regards,<br>
|
|
Best regards,<br>
|
|
|
BamBuddy Team
|
|
BamBuddy Team
|
|
@@ -381,7 +381,7 @@ BamBuddy Team
|
|
|
</body>
|
|
</body>
|
|
|
</html>
|
|
</html>
|
|
|
"""
|
|
"""
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return subject, text_body, html_body
|
|
return subject, text_body, html_body
|
|
|
|
|
|
|
|
|
|
|
|
@@ -389,20 +389,20 @@ async def create_welcome_email_from_template(
|
|
|
db: AsyncSession, username: str, password: str, login_url: str, app_name: str = "BamBuddy"
|
|
db: AsyncSession, username: str, password: str, login_url: str, app_name: str = "BamBuddy"
|
|
|
) -> tuple[str, str, str]:
|
|
) -> tuple[str, str, str]:
|
|
|
"""Create welcome email content using notification template from database.
|
|
"""Create welcome email content using notification template from database.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
db: Database session
|
|
db: Database session
|
|
|
username: Username of the new user
|
|
username: Username of the new user
|
|
|
password: Auto-generated password
|
|
password: Auto-generated password
|
|
|
login_url: URL to login page
|
|
login_url: URL to login page
|
|
|
app_name: Application name (default: BamBuddy)
|
|
app_name: Application name (default: BamBuddy)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
Tuple of (subject, text_body, html_body)
|
|
Tuple of (subject, text_body, html_body)
|
|
|
"""
|
|
"""
|
|
|
# Try to get template from database
|
|
# Try to get template from database
|
|
|
template = await get_notification_template(db, "user_created")
|
|
template = await get_notification_template(db, "user_created")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if template:
|
|
if template:
|
|
|
# Render template with variables
|
|
# Render template with variables
|
|
|
variables = {
|
|
variables = {
|
|
@@ -411,10 +411,10 @@ async def create_welcome_email_from_template(
|
|
|
"password": password,
|
|
"password": password,
|
|
|
"login_url": login_url,
|
|
"login_url": login_url,
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
subject = render_template(template.title_template, variables)
|
|
subject = render_template(template.title_template, variables)
|
|
|
text_body = render_template(template.body_template, variables)
|
|
text_body = render_template(template.body_template, variables)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Create HTML version with embedded login button
|
|
# Create HTML version with embedded login button
|
|
|
html_body = f"""<!DOCTYPE html>
|
|
html_body = f"""<!DOCTYPE html>
|
|
|
<html>
|
|
<html>
|
|
@@ -428,7 +428,7 @@ async def create_welcome_email_from_template(
|
|
|
</div>
|
|
</div>
|
|
|
<div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; border: 1px solid #ddd; border-top: none;">
|
|
<div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; border: 1px solid #ddd; border-top: none;">
|
|
|
<div style="white-space: pre-wrap; font-size: 16px;">{text_body}</div>
|
|
<div style="white-space: pre-wrap; font-size: 16px;">{text_body}</div>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
|
<a href="{login_url}" style="display: inline-block; background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 4px; font-weight: bold;">Login Now</a>
|
|
<a href="{login_url}" style="display: inline-block; background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 4px; font-weight: bold;">Login Now</a>
|
|
|
</div>
|
|
</div>
|
|
@@ -436,7 +436,7 @@ async def create_welcome_email_from_template(
|
|
|
</body>
|
|
</body>
|
|
|
</html>
|
|
</html>
|
|
|
"""
|
|
"""
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
logger.info("Using custom welcome email template from database")
|
|
logger.info("Using custom welcome email template from database")
|
|
|
return subject, text_body, html_body
|
|
return subject, text_body, html_body
|
|
|
else:
|
|
else:
|
|
@@ -449,20 +449,20 @@ async def create_password_reset_email_from_template(
|
|
|
db: AsyncSession, username: str, password: str, login_url: str, app_name: str = "BamBuddy"
|
|
db: AsyncSession, username: str, password: str, login_url: str, app_name: str = "BamBuddy"
|
|
|
) -> tuple[str, str, str]:
|
|
) -> tuple[str, str, str]:
|
|
|
"""Create password reset email content using notification template from database.
|
|
"""Create password reset email content using notification template from database.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
db: Database session
|
|
db: Database session
|
|
|
username: Username of the user
|
|
username: Username of the user
|
|
|
password: New auto-generated password
|
|
password: New auto-generated password
|
|
|
login_url: URL to login page
|
|
login_url: URL to login page
|
|
|
app_name: Application name (default: BamBuddy)
|
|
app_name: Application name (default: BamBuddy)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
Tuple of (subject, text_body, html_body)
|
|
Tuple of (subject, text_body, html_body)
|
|
|
"""
|
|
"""
|
|
|
# Try to get template from database
|
|
# Try to get template from database
|
|
|
template = await get_notification_template(db, "password_reset")
|
|
template = await get_notification_template(db, "password_reset")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if template:
|
|
if template:
|
|
|
# Render template with variables
|
|
# Render template with variables
|
|
|
variables = {
|
|
variables = {
|
|
@@ -471,10 +471,10 @@ async def create_password_reset_email_from_template(
|
|
|
"password": password,
|
|
"password": password,
|
|
|
"login_url": login_url,
|
|
"login_url": login_url,
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
subject = render_template(template.title_template, variables)
|
|
subject = render_template(template.title_template, variables)
|
|
|
text_body = render_template(template.body_template, variables)
|
|
text_body = render_template(template.body_template, variables)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Create HTML version with embedded login button
|
|
# Create HTML version with embedded login button
|
|
|
html_body = f"""<!DOCTYPE html>
|
|
html_body = f"""<!DOCTYPE html>
|
|
|
<html>
|
|
<html>
|
|
@@ -488,11 +488,11 @@ async def create_password_reset_email_from_template(
|
|
|
</div>
|
|
</div>
|
|
|
<div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; border: 1px solid #ddd; border-top: none;">
|
|
<div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; border: 1px solid #ddd; border-top: none;">
|
|
|
<div style="white-space: pre-wrap; font-size: 16px;">{text_body}</div>
|
|
<div style="white-space: pre-wrap; font-size: 16px;">{text_body}</div>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
|
<a href="{login_url}" style="display: inline-block; background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 4px; font-weight: bold;">Login Now</a>
|
|
<a href="{login_url}" style="display: inline-block; background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 4px; font-weight: bold;">Login Now</a>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin: 20px 0;">
|
|
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin: 20px 0;">
|
|
|
<p style="margin: 0; font-size: 14px; color: #856404;">
|
|
<p style="margin: 0; font-size: 14px; color: #856404;">
|
|
|
<strong>⚠️ Security Alert:</strong> If you did not request this password reset, please contact your administrator immediately.
|
|
<strong>⚠️ Security Alert:</strong> If you did not request this password reset, please contact your administrator immediately.
|
|
@@ -502,7 +502,7 @@ async def create_password_reset_email_from_template(
|
|
|
</body>
|
|
</body>
|
|
|
</html>
|
|
</html>
|
|
|
"""
|
|
"""
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
logger.info("Using custom password reset email template from database")
|
|
logger.info("Using custom password reset email template from database")
|
|
|
return subject, text_body, html_body
|
|
return subject, text_body, html_body
|
|
|
else:
|
|
else:
|