Emails.
This commit is contained in:
parent
ed4cd5c14f
commit
83ac1eb001
47
retoors/helpers/email_sender.py
Normal file
47
retoors/helpers/email_sender.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import aiosmtplib
|
||||||
|
from email.message import EmailMessage
|
||||||
|
from aiohttp import web
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from retoors.services.config_service import ConfigService
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def send_email(app: web.Application, recipient_email: str, subject: str, body: str):
|
||||||
|
"""
|
||||||
|
Sends an email asynchronously using the configured SMTP settings.
|
||||||
|
"""
|
||||||
|
config_service: ConfigService = app["config_service"]
|
||||||
|
|
||||||
|
smtp_host = config_service.get_smtp_host()
|
||||||
|
smtp_port = config_service.get_smtp_port()
|
||||||
|
smtp_username = config_service.get_smtp_username()
|
||||||
|
smtp_password = config_service.get_smtp_password()
|
||||||
|
smtp_use_tls = config_service.get_smtp_use_tls()
|
||||||
|
smtp_sender_email = config_service.get_smtp_sender_email()
|
||||||
|
|
||||||
|
if not smtp_host or not smtp_sender_email:
|
||||||
|
logger.error("SMTP host or sender email not configured. Cannot send email.")
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = EmailMessage()
|
||||||
|
msg["From"] = smtp_sender_email
|
||||||
|
msg["To"] = recipient_email
|
||||||
|
msg["Subject"] = subject
|
||||||
|
msg.set_content(body)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await aiosmtplib.send(
|
||||||
|
msg,
|
||||||
|
hostname=smtp_host,
|
||||||
|
port=smtp_port,
|
||||||
|
username=smtp_username,
|
||||||
|
password=smtp_password,
|
||||||
|
use_tls=False, # Always False when using start_tls
|
||||||
|
start_tls=smtp_use_tls, # Use start_tls for explicit TLS negotiation
|
||||||
|
)
|
||||||
|
logger.info(f"Email sent successfully to {recipient_email} with subject '{subject}'")
|
||||||
|
except aiosmtplib.SMTPException as e:
|
||||||
|
logger.error(f"Failed to send email to {recipient_email}: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An unexpected error occurred while sending email to {recipient_email}: {e}")
|
||||||
52
retoors/helpers/env_manager.py
Normal file
52
retoors/helpers/env_manager.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def ensure_env_file_exists(env_path: Path):
|
||||||
|
"""
|
||||||
|
Ensures that a .env file exists at the specified path.
|
||||||
|
If it doesn't exist, it creates one with default values.
|
||||||
|
"""
|
||||||
|
if not env_path.exists():
|
||||||
|
default_env_content = """# .env - Environment variables for Retoor's Cloud Solutions
|
||||||
|
#
|
||||||
|
# This file is used to configure various aspects of the application,
|
||||||
|
# including pricing, email settings, and other sensitive information.
|
||||||
|
#
|
||||||
|
# For production, it is recommended to manage these variables through
|
||||||
|
# your hosting provider's environment variable management system
|
||||||
|
# rather than committing this file to version control.
|
||||||
|
|
||||||
|
# Pricing Configuration
|
||||||
|
# PRICE_PER_GB: Cost per gigabyte of storage.
|
||||||
|
PRICE_PER_GB=0.05
|
||||||
|
|
||||||
|
# SMTP (Email Sending) Configuration
|
||||||
|
# Used for sending transactional emails (e.g., welcome, password reset).
|
||||||
|
# SMTP_HOST: The hostname of your SMTP server.
|
||||||
|
# SMTP_PORT: The port of your SMTP server (e.g., 587 for TLS, 465 for SSL, 25 for unencrypted).
|
||||||
|
# SMTP_USERNAME: The username for authenticating with your SMTP server.
|
||||||
|
# SMTP_PASSWORD: The password for authenticating with your SMTP server.
|
||||||
|
# SMTP_USE_TLS: Set to True to enable STARTTLS encryption (recommended for most servers).
|
||||||
|
# SMTP_SENDER_EMAIL: The email address from which system emails will be sent.
|
||||||
|
SMTP_HOST=localhost
|
||||||
|
SMTP_PORT=1025
|
||||||
|
SMTP_USERNAME=
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
SMTP_USE_TLS=False
|
||||||
|
SMTP_SENDER_EMAIL=no-reply@retoors.com
|
||||||
|
|
||||||
|
# IMAP (Email Receiving) Configuration (if applicable for future features)
|
||||||
|
# IMAP_HOST=
|
||||||
|
# IMAP_PORT=
|
||||||
|
# IMAP_USERNAME=
|
||||||
|
# IMAP_PASSWORD=
|
||||||
|
# IMAP_USE_SSL=True
|
||||||
|
"""
|
||||||
|
with open(env_path, "w") as f:
|
||||||
|
f.write(default_env_content)
|
||||||
|
print(f"Created default .env file at {env_path}")
|
||||||
|
else:
|
||||||
|
print(f".env file already exists at {env_path}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Example usage:
|
||||||
|
ensure_env_file_exists(Path(__file__).parent.parent.parent / ".env")
|
||||||
73
retoors/templates/emails/password_changed_confirmation.html
Normal file
73
retoors/templates/emails/password_changed_confirmation.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Password Changed - Retoor's Cloud Solutions</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h2>Your Password Has Been Changed</h2>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>Dear {{ user_name }},</p>
|
||||||
|
<p>This is a confirmation that the password for your Retoor's Cloud Solutions account has been successfully changed.</p>
|
||||||
|
<p>If you made this change, you can now log in with your new password:</p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="{{ login_url }}" class="button">Log In Now</a>
|
||||||
|
</p>
|
||||||
|
<p>If you did not authorize this change, please contact our support team immediately.</p>
|
||||||
|
<p>Best regards,</p>
|
||||||
|
<p>The Retoor's Cloud Solutions Team</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2025 Retoor's Cloud Solutions. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
73
retoors/templates/emails/password_reset_request.html
Normal file
73
retoors/templates/emails/password_reset_request.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Password Reset Request - Retoor's Cloud Solutions</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h2>Password Reset Request</h2>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>Dear {{ user_name }},</p>
|
||||||
|
<p>We received a request to reset the password for your Retoor's Cloud Solutions account.</p>
|
||||||
|
<p>To reset your password, please click on the link below:</p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="{{ reset_link }}" class="button">Reset My Password</a>
|
||||||
|
</p>
|
||||||
|
<p>This link will expire in 1 hour. If you did not request a password reset, please ignore this email.</p>
|
||||||
|
<p>Best regards,</p>
|
||||||
|
<p>The Retoor's Cloud Solutions Team</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2025 Retoor's Cloud Solutions. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
74
retoors/templates/emails/welcome.html
Normal file
74
retoors/templates/emails/welcome.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Welcome to Retoor's Cloud Solutions!</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h2>Welcome to Retoor's Cloud Solutions!</h2>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>Dear {{ user_name }},</p>
|
||||||
|
<p>Thank you for registering with Retoor's Cloud Solutions. We are excited to have you on board!</p>
|
||||||
|
<p>Our platform offers secure, reliable, and flexible cloud storage tailored to your needs. Whether you're a small business, a home office user, or an individual, we're here to help you manage your data with ease.</p>
|
||||||
|
<p>To get started, please log in to your dashboard:</p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="{{ login_url }}" class="button">Go to My Dashboard</a>
|
||||||
|
</p>
|
||||||
|
<p>If you have any questions, feel free to visit our <a href="{{ support_url }}">Support Page</a> or contact our team.</p>
|
||||||
|
<p>Best regards,</p>
|
||||||
|
<p>The Retoor's Cloud Solutions Team</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2025 Retoor's Cloud Solutions. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
retoors/templates/pages/forgot_password.html
Normal file
35
retoors/templates/pages/forgot_password.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{% extends "layouts/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Forgot Password - Retoor's Cloud Solutions{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="/static/css/components/form.css"> {# Reusing form.css for styling #}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="form-page-container">
|
||||||
|
<h1>Forgot Your Password?</h1>
|
||||||
|
<p class="subtitle">Enter your email address below and we'll send you a link to reset your password.</p>
|
||||||
|
|
||||||
|
<section class="form-container">
|
||||||
|
<h2>Reset Password</h2>
|
||||||
|
{% if error %}
|
||||||
|
<p class="error">{{ error }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if message %}
|
||||||
|
<p class="message">{{ message }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<form action="/forgot_password" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email Address</label>
|
||||||
|
<input type="email" id="email" name="email" required value="{{ data.email or '' }}">
|
||||||
|
{% if errors.email %}
|
||||||
|
<p class="error">{{ errors.email }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">Send Reset Link</button>
|
||||||
|
</form>
|
||||||
|
<p class="login-link">Remember your password? <a href="/login">Log In</a></p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
38
retoors/templates/pages/reset_password.html
Normal file
38
retoors/templates/pages/reset_password.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{% extends "layouts/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Reset Password - Retoor's Cloud Solutions{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="/static/css/components/form.css"> {# Reusing form.css for styling #}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="form-page-container">
|
||||||
|
<h1>Set Your New Password</h1>
|
||||||
|
<p class="subtitle">Please enter and confirm your new password below.</p>
|
||||||
|
|
||||||
|
<section class="form-container">
|
||||||
|
<h2>Reset Password</h2>
|
||||||
|
{% if error %}
|
||||||
|
<p class="error">{{ error }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<form action="/reset_password/{{ token }}" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">New Password</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
{% if errors.password %}
|
||||||
|
<p class="error">{{ errors.password }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirm_password">Confirm New Password</label>
|
||||||
|
<input type="password" id="confirm_password" name="confirm_password" required>
|
||||||
|
{% if errors.confirm_password %}
|
||||||
|
<p class="error">{{ errors.confirm_password }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">Reset Password</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue
Block a user