Skip to content

Commit 5772499

Browse files
Used Jinja2 templates to generate the reset email
1 parent 2ff7be3 commit 5772499

File tree

3 files changed

+71
-7
lines changed

3 files changed

+71
-7
lines changed

templates/emails/base_email.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{% from 'components/logo.html' import render_logo %}
2+
3+
<!DOCTYPE html>
4+
<html>
5+
<head>
6+
<meta charset="utf-8">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<title>{% block email_title %}{% endblock %}</title>
9+
</head>
10+
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
11+
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
12+
<!-- Header with branding -->
13+
<div style="display: inline-block; vertical-align: middle;">
14+
{{ render_logo(width=40, height=40) }}
15+
<span style="display: inline-block; vertical-align: middle; font-size: 24px; margin-left: 10px;">FastAPI-Jinja2-Postgres Webapp</span>
16+
</div>
17+
18+
<hr style="margin: 30px 0; border: none; border-top: 1px solid #eee;">
19+
20+
{% block email_content %}{% endblock %}
21+
22+
<hr style="margin: 30px 0; border: none; border-top: 1px solid #eee;">
23+
24+
<p style="font-size: 12px; color: #666;">
25+
This is an automated message, please do not reply directly to this email.
26+
{% block email_footer %}
27+
{% endblock %}
28+
</p>
29+
</div>
30+
</body>
31+
</html>

templates/emails/reset_email.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{% extends "emails/base_email.html" %}
2+
3+
{% block email_title %}Password Reset{% endblock %}
4+
5+
{% block email_content %}
6+
<h2>Password Reset Request</h2>
7+
<p>Hello,</p>
8+
<p>We received a request to reset your password. If you didn't make this request, you can safely ignore this email.</p>
9+
<p style="margin: 25px 0;">
10+
<a href="{{ reset_url }}" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">Reset Your Password</a>
11+
</p>
12+
<p>Or copy and paste this link into your browser:</p>
13+
<p style="word-break: break-all;">{{ reset_url }}</p>
14+
<p>This link will expire in 24 hours.</p>
15+
{% endblock %}
16+
17+
{% block email_footer %}
18+
If you didn't request this password reset, please ignore this email or contact support if you have concerns.
19+
{% endblock %}

utils/auth.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,24 @@
1212
from bcrypt import gensalt, hashpw, checkpw
1313
from datetime import UTC, datetime, timedelta
1414
from typing import Optional
15+
from jinja2.environment import Template
16+
from fastapi.templating import Jinja2Templates
1517
from fastapi import Depends, Cookie, HTTPException, status
1618
from utils.db import get_session
1719
from utils.models import User, Role, PasswordResetToken
1820

1921
load_dotenv()
20-
logger = logging.getLogger("uvicorn.error")
22+
resend.api_key = os.environ["RESEND_API_KEY"]
23+
24+
logger = logging.getLogger(__name__)
25+
logger.setLevel(logging.DEBUG)
26+
logger.addHandler(logging.StreamHandler())
2127

2228

2329
# --- Constants ---
2430

2531

32+
templates = Jinja2Templates(directory="templates")
2633
SECRET_KEY = os.getenv("SECRET_KEY")
2734
ALGORITHM = "HS256"
2835
ACCESS_TOKEN_EXPIRE_MINUTES = 30
@@ -294,9 +301,9 @@ def generate_password_reset_url(email: str, token: str) -> str:
294301
return f"{base_url}/auth/reset_password?email={email}&token={token}"
295302

296303

297-
def send_reset_email(email: str, session: Session):
304+
def send_reset_email(email: str, session: Session) -> None:
298305
# Check for an existing unexpired token
299-
user = session.exec(select(User).where(
306+
user: Optional[User] = session.exec(select(User).where(
300307
User.email == email
301308
)).first()
302309
if user:
@@ -314,17 +321,24 @@ def send_reset_email(email: str, session: Session):
314321
return
315322

316323
# Generate a new token
317-
token = str(uuid.uuid4())
318-
reset_token = PasswordResetToken(user_id=user.id, token=token)
324+
token: str = str(uuid.uuid4())
325+
reset_token: PasswordResetToken = PasswordResetToken(
326+
user_id=user.id, token=token)
319327
session.add(reset_token)
320328

321329
try:
322-
reset_url = generate_password_reset_url(email, token)
330+
reset_url: str = generate_password_reset_url(email, token)
331+
332+
# Render the email template
333+
template: Template = templates.get_template(
334+
"emails/reset_email.html")
335+
html_content: str = template.render({"reset_url": reset_url})
336+
323337
params: resend.Emails.SendParams = {
324338
"from": "[email protected]",
325339
"to": [email],
326340
"subject": "Password Reset Request",
327-
"html": f"<p>Click <a href='{reset_url}'>here</a> to reset your password.</p>",
341+
"html": html_content,
328342
}
329343

330344
sent_email: resend.Email = resend.Emails.send(params)

0 commit comments

Comments
 (0)