refactor(auth, email_service, user_service, templates): update email handling and improve base email template styling
This commit is contained in:
parent
47307a1045
commit
a46402fd08
@ -54,8 +54,7 @@ async def register_user(user_data: UserCreate, db: Session = Depends(get_db)):
|
|||||||
Raises:
|
Raises:
|
||||||
HTTPException: If there is an error in registration
|
HTTPException: If there is an error in registration
|
||||||
"""
|
"""
|
||||||
# TODO: remover o auto_verify temporariamente para teste
|
user, message = create_user(db, user_data, is_admin=False, auto_verify=False)
|
||||||
user, message = create_user(db, user_data, is_admin=False, auto_verify=True)
|
|
||||||
if not user:
|
if not user:
|
||||||
logger.error(f"Error registering user: {message}")
|
logger.error(f"Error registering user: {message}")
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import sendgrid
|
import sendgrid
|
||||||
from sendgrid.helpers.mail import Mail, Email, To, Content
|
from sendgrid.helpers.mail import Mail, Email, To, Content
|
||||||
from src.config.settings import settings
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
@ -51,12 +50,12 @@ def send_verification_email(email: str, token: str) -> bool:
|
|||||||
bool: True if the email was sent successfully, False otherwise
|
bool: True if the email was sent successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sg = sendgrid.SendGridAPIClient(api_key=settings.SENDGRID_API_KEY)
|
sg = sendgrid.SendGridAPIClient(api_key=os.getenv("SENDGRID_API_KEY"))
|
||||||
from_email = Email(settings.EMAIL_FROM)
|
from_email = Email(os.getenv("EMAIL_FROM"))
|
||||||
to_email = To(email)
|
to_email = To(email)
|
||||||
subject = "Email Verification - Evo AI"
|
subject = "Email Verification - Evo AI"
|
||||||
|
|
||||||
verification_link = f"{settings.APP_URL}/api/v1/auth/verify-email/{token}"
|
verification_link = f"{os.getenv('APP_URL')}/security/verify-email?code={token}"
|
||||||
|
|
||||||
html_content = _render_template(
|
html_content = _render_template(
|
||||||
"verification_email",
|
"verification_email",
|
||||||
@ -100,12 +99,12 @@ def send_password_reset_email(email: str, token: str) -> bool:
|
|||||||
bool: True if the email was sent successfully, False otherwise
|
bool: True if the email was sent successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sg = sendgrid.SendGridAPIClient(api_key=settings.SENDGRID_API_KEY)
|
sg = sendgrid.SendGridAPIClient(api_key=os.getenv("SENDGRID_API_KEY"))
|
||||||
from_email = Email(settings.EMAIL_FROM)
|
from_email = Email(os.getenv("EMAIL_FROM"))
|
||||||
to_email = To(email)
|
to_email = To(email)
|
||||||
subject = "Password Reset - Evo AI"
|
subject = "Password Reset - Evo AI"
|
||||||
|
|
||||||
reset_link = f"{settings.APP_URL}/reset-password?token={token}"
|
reset_link = f"{os.getenv('APP_URL')}/security/reset-password?token={token}"
|
||||||
|
|
||||||
html_content = _render_template(
|
html_content = _render_template(
|
||||||
"password_reset",
|
"password_reset",
|
||||||
@ -149,12 +148,12 @@ def send_welcome_email(email: str, user_name: str = None) -> bool:
|
|||||||
bool: True if the email was sent successfully, False otherwise
|
bool: True if the email was sent successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sg = sendgrid.SendGridAPIClient(api_key=settings.SENDGRID_API_KEY)
|
sg = sendgrid.SendGridAPIClient(api_key=os.getenv("SENDGRID_API_KEY"))
|
||||||
from_email = Email(settings.EMAIL_FROM)
|
from_email = Email(os.getenv("EMAIL_FROM"))
|
||||||
to_email = To(email)
|
to_email = To(email)
|
||||||
subject = "Welcome to Evo AI"
|
subject = "Welcome to Evo AI"
|
||||||
|
|
||||||
dashboard_link = f"{settings.APP_URL}/dashboard"
|
dashboard_link = f"{os.getenv('APP_URL')}/dashboard"
|
||||||
|
|
||||||
html_content = _render_template(
|
html_content = _render_template(
|
||||||
"welcome_email",
|
"welcome_email",
|
||||||
@ -200,12 +199,14 @@ def send_account_locked_email(
|
|||||||
bool: True if the email was sent successfully, False otherwise
|
bool: True if the email was sent successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sg = sendgrid.SendGridAPIClient(api_key=settings.SENDGRID_API_KEY)
|
sg = sendgrid.SendGridAPIClient(api_key=os.getenv("SENDGRID_API_KEY"))
|
||||||
from_email = Email(settings.EMAIL_FROM)
|
from_email = Email(os.getenv("EMAIL_FROM"))
|
||||||
to_email = To(email)
|
to_email = To(email)
|
||||||
subject = "Security Alert - Account Locked"
|
subject = "Security Alert - Account Locked"
|
||||||
|
|
||||||
reset_link = f"{settings.APP_URL}/reset-password?token={reset_token}"
|
reset_link = (
|
||||||
|
f"{os.getenv('APP_URL')}/security/reset-password?token={reset_token}"
|
||||||
|
)
|
||||||
|
|
||||||
html_content = _render_template(
|
html_content = _render_template(
|
||||||
"account_locked",
|
"account_locked",
|
||||||
|
@ -7,7 +7,7 @@ from src.services.email_service import (
|
|||||||
send_verification_email,
|
send_verification_email,
|
||||||
send_password_reset_email,
|
send_password_reset_email,
|
||||||
)
|
)
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
import uuid
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
@ -295,7 +295,11 @@ def reset_password(db: Session, token: str, new_password: str) -> Tuple[bool, st
|
|||||||
return False, "Invalid password reset token"
|
return False, "Invalid password reset token"
|
||||||
|
|
||||||
# Check if the token has expired
|
# Check if the token has expired
|
||||||
if user.password_reset_expiry < datetime.utcnow():
|
now = datetime.now(timezone.utc)
|
||||||
|
expiry = user.password_reset_expiry
|
||||||
|
if expiry is not None and expiry.tzinfo is None:
|
||||||
|
expiry = expiry.replace(tzinfo=timezone.utc)
|
||||||
|
if expiry is None or expiry < now:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Attempt to reset password with expired token for user: {user.email}"
|
f"Attempt to reset password with expired token for user: {user.email}"
|
||||||
)
|
)
|
||||||
|
@ -1,83 +1,103 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{% block title %}Evo AI{% endblock %}</title>
|
<title>{% block title %}Evo AI{% endblock %}</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: "Segoe UI", Arial, sans-serif;
|
||||||
line-height: 1.6;
|
background-color: #181a20;
|
||||||
margin: 0;
|
color: #f1f1f1;
|
||||||
padding: 0;
|
margin: 0;
|
||||||
background-color: #f7f7f7;
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 480px;
|
||||||
|
margin: 32px auto;
|
||||||
|
background: #23262f;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: #23262f;
|
||||||
|
border-bottom: 1px solid #222;
|
||||||
|
padding: 32px 0 16px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
color: #00ff9a;
|
||||||
|
font-size: 2rem;
|
||||||
|
margin: 0;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 32px 24px 24px 24px;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
background: linear-gradient(90deg, #00ff9a 0%, #00e0ff 100%);
|
||||||
|
color: #181a20 !important;
|
||||||
|
padding: 14px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 32px 0 0 0;
|
||||||
|
transition: filter 0.2s;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 255, 154, 0.1);
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
color: #888;
|
||||||
|
background: #181a20;
|
||||||
|
border-top: 1px solid #222;
|
||||||
|
padding: 20px 0 10px 0;
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
color: #00ff9a;
|
||||||
|
text-decoration: underline;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: #e74c3c;
|
||||||
|
background: #2c1b1b;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.container {
|
||||||
|
max-width: 98vw;
|
||||||
|
margin: 8px;
|
||||||
}
|
}
|
||||||
.container {
|
.content {
|
||||||
max-width: 600px;
|
padding: 18px 8px 16px 8px;
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background-color: #4A90E2;
|
|
||||||
color: white;
|
|
||||||
padding: 15px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.button {
|
|
||||||
background-color: #4A90E2;
|
|
||||||
color: white !important;
|
|
||||||
padding: 12px 24px;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
.button:hover {
|
|
||||||
background-color: #3a7bc8;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 30px;
|
|
||||||
color: #888;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
padding-top: 20px;
|
|
||||||
}
|
|
||||||
.link {
|
|
||||||
word-break: break-all;
|
|
||||||
color: #4A90E2;
|
|
||||||
}
|
|
||||||
.warning {
|
|
||||||
color: #E74C3C;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #FADBD8;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% block additional_styles %}{% endblock %}
|
{% block additional_styles %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>{% block header %}Evo AI{% endblock %}</h1>
|
<h1>{% block header %}Evo AI{% endblock %}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">{% block content %}{% endblock %}</div>
|
||||||
{% block content %}{% endblock %}
|
<div class="footer">
|
||||||
</div>
|
<p>
|
||||||
<div class="footer">
|
{% block footer_message %}This is an automated email, please do not
|
||||||
<p>{% block footer_message %}This is an automated email, please do not reply.{% endblock %}</p>
|
reply.{% endblock %}
|
||||||
<p>© {{ current_year }} Evo AI. All rights reserved.</p>
|
</p>
|
||||||
</div>
|
<p>© {{ current_year }} Evo AI. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user