This commit is contained in:
retoor 2025-11-08 19:39:39 +01:00
parent ed4cd5c14f
commit 83ac1eb001
7 changed files with 392 additions and 0 deletions

View 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}")

View 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")

View 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>&copy; 2025 Retoor's Cloud Solutions. All rights reserved.</p>
</div>
</div>
</body>
</html>

View 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>&copy; 2025 Retoor's Cloud Solutions. All rights reserved.</p>
</div>
</div>
</body>
</html>

View 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>&copy; 2025 Retoor's Cloud Solutions. All rights reserved.</p>
</div>
</div>
</body>
</html>

View 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 %}

View 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 %}