-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ generating emails for account verification and reset password (#81)
* 🎨 refactored project name to config * ✨ added smtp variables to config * ✨ added email template for reset password * ✨ added emails and jinja2 * upgraded uvicorn version because of logging bug - encode/uvicorn#1285 * 🎨 refactored utils folder structure * ✨ handling forget password bt sending an email * 💄 cosmetic * 🔥 removed unused code * ✨ handling account verification by sending an email * 🔥 removed unused parameters * added logs * renamed function * import utils from its __init__.py * added reset_password_token_lifetime_seconds and verification_token_secret to config * added the reset password and user verification lifetime to the email templates * added validator for EMAILS_FROM_NAME * modified docker-compose env variables * added env variables section in README.md * fixed mypy * fixed bandit * added required env variables to ci
- Loading branch information
1 parent
48376eb
commit 07d1fa0
Showing
14 changed files
with
228 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
POSTGRES_DB=todos | ||
POSTGRES_USER=username | ||
POSTGRES_PASSWORD=password |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>{{ project_name }} - Account Verification</title> | ||
</head> | ||
<body style="background-color: #f5f5f5; font-family: Arial, sans-serif;"> | ||
<div style="background-color: #fff; max-width: 600px; margin: 0 auto; padding: 20px;"> | ||
<div style="border-bottom: 1px solid #e1e1e1;"> | ||
<h1 style="font-size: 24px; color: #333; font-weight: 400;">{{ project_name }} - Account Verification</h1> | ||
</div> | ||
<div style="margin-top: 20px;"> | ||
<p style="font-size: 16px; color: #555;">Welcome to {{ project_name }}!</p> | ||
<p style="font-size: 16px; color: #555;">We received a request for account verification for email <strong>{{ email }}</strong>.</p> | ||
<p style="font-size: 16px; color: #555;">Please click the button below to verify your account:</p> | ||
<div style="margin-top: 20px; text-align: center;"> | ||
<a href="{{ link }}" style="display: inline-block; background-color: #007bff; color: #fff; text-decoration: none; font-size: 16px; font-weight: 600; padding: 12px 30px; border-radius: 5px;">Verify Account</a> | ||
</div> | ||
</div> | ||
<div style="border-top: 1px solid #e1e1e1; margin-top: 20px; padding-top: 20px;"> | ||
<p style="font-size: 14px; color: #555;">The account verification button will expire in {{ expire_hours }} hours</p> | ||
<p style="font-size: 14px; color: #555;">If you did not ask for an account verification on {{ project_name }}, you can disregard this email.</p> | ||
</div> | ||
</div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>{{ project_name }} - Password Recovery</title> | ||
</head> | ||
<body style="background-color: #f5f5f5; font-family: Arial, sans-serif;"> | ||
<div style="background-color: #fff; max-width: 600px; margin: 0 auto; padding: 20px;"> | ||
<div style="border-bottom: 1px solid #e1e1e1;"> | ||
<h1 style="font-size: 24px; color: #333; font-weight: 400;">{{ project_name }} - Password Recovery</h1> | ||
</div> | ||
<div style="margin-top: 20px;"> | ||
<p style="font-size: 16px; color: #555;">We received a request to recover the password for email <strong>{{ email }}</strong>.</p> | ||
<p style="font-size: 16px; color: #555;">Reset your password by clicking the button below:</p> | ||
<div style="margin-top: 20px; text-align: center;"> | ||
<a href="{{ link }}" style="display: inline-block; background-color: #007bff; color: #fff; text-decoration: none; font-size: 16px; font-weight: 600; padding: 12px 30px; border-radius: 5px;">Reset Password</a> | ||
</div> | ||
</div> | ||
<div style="border-top: 1px solid #e1e1e1; margin-top: 20px; padding-top: 20px;"> | ||
<p style="font-size: 14px; color: #555;">The reset password button will expire in {{ expire_hours }} hours</p> | ||
<p style="font-size: 14px; color: #555;">If you didn't request a password recovery, you can disregard this email.</p> | ||
</div> | ||
</div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,42 @@ | ||
import uuid | ||
from typing import Optional | ||
import logging | ||
|
||
from pydantic import SecretStr | ||
from fastapi import Request | ||
from fastapi_users import BaseUserManager, UUIDIDMixin | ||
|
||
from app.core.config import get_config | ||
from app.models.tables import User | ||
from app.utils import send_reset_password_email, send_account_verification_email | ||
|
||
|
||
config = get_config() | ||
|
||
logging.basicConfig(level=logging.INFO) | ||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): | ||
reset_password_token_secret: SecretStr = config.JWT_SECRET_KEY | ||
reset_password_token_lifetime_seconds: int = config.RESET_PASSWORD_TOKEN_LIFETIME_SECONDS | ||
verification_token_secret: SecretStr = config.JWT_SECRET_KEY | ||
verification_token_lifetime_seconds: int = config.VERIFY_TOKEN_LIFETIME_SECONDS | ||
|
||
async def on_after_forgot_password( | ||
self, | ||
user: User, | ||
token: str, | ||
request: Optional[Request] = None | ||
) -> None: | ||
send_reset_password_email(email_to=user.email, token=token) | ||
logger.info('sent reset password email to %s', user.email) | ||
|
||
async def on_after_request_verify( | ||
self, | ||
user: User, | ||
token: str, | ||
request: Optional[Request] = None | ||
) -> None: | ||
send_account_verification_email(email_to=user.email, token=token) | ||
logger.info('sent account verification email to %s', user.email) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .emails import send_reset_password_email, send_account_verification_email | ||
from .exceptions import exception_handler |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
from typing import Any, Optional | ||
import logging | ||
|
||
from emails import Message | ||
from emails.template import JinjaTemplate | ||
|
||
from app.core.config import get_config | ||
|
||
|
||
config = get_config() | ||
|
||
logging.basicConfig(level=logging.INFO) | ||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def send_email( | ||
*, | ||
email_to: str, | ||
environment: Optional[dict[str, Any]], | ||
subject_template: str = "", | ||
html_template: str = "", | ||
) -> None: | ||
if not config.EMAILS_ENABLED: | ||
raise RuntimeError('no configuration provided for email variables') | ||
if not environment: | ||
environment = {} | ||
message = Message( | ||
subject=JinjaTemplate(subject_template), | ||
html=JinjaTemplate(html_template), | ||
mail_from=(config.EMAILS_FROM_NAME, config.EMAILS_FROM_EMAIL), | ||
) | ||
smtp_options = {'host': config.SMTP_HOST, 'port': config.SMTP_PORT} | ||
if config.SMTP_TLS: | ||
smtp_options['tls'] = True | ||
if config.SMTP_USER: | ||
smtp_options['user'] = config.SMTP_USER | ||
if config.SMTP_PASSWORD: | ||
smtp_options['password'] = config.SMTP_PASSWORD.get_secret_value() | ||
res = message.send(to=email_to, render=environment, smtp=smtp_options) | ||
logger.info('send email result %s', res) | ||
|
||
|
||
def send_reset_password_email(*, email_to: str, token: str) -> None: | ||
subject = f'{config.PROJECT_NAME} - Password recovery for email {email_to}' | ||
with open(f'{config.EMAIL_TEMPLATES_DIR}/reset_password.html', 'r', encoding='utf-8') as f: | ||
template_str = f.read() | ||
link = f'{config.FRONT_END_BASE_URL}/reset-password?token={token}' | ||
send_email( | ||
email_to=email_to, | ||
subject_template=subject, | ||
html_template=template_str, | ||
environment={ | ||
'project_name': config.PROJECT_NAME, | ||
'email': email_to, | ||
'link': link, | ||
# dividing by 3600 to get the number of hours from the number of seconds | ||
'expire_hours': config.RESET_PASSWORD_TOKEN_LIFETIME_SECONDS / 3600, | ||
} | ||
) | ||
|
||
|
||
def send_account_verification_email(*, email_to: str, token: str) -> None: | ||
subject = f'{config.PROJECT_NAME} - Account verification for email {email_to}' | ||
with open(f'{config.EMAIL_TEMPLATES_DIR}/account_verification.html', 'r', encoding='utf-8') as f: | ||
template_str = f.read() | ||
link = f'{config.FRONT_END_BASE_URL}/verify-account?token={token}' | ||
send_email( | ||
email_to=email_to, | ||
subject_template=subject, | ||
html_template=template_str, | ||
environment={ | ||
'project_name': config.PROJECT_NAME, | ||
'email': email_to, | ||
'link': link, | ||
# dividing by 3600 to get the number of hours from the number of seconds | ||
'expire_hours': config.VERIFY_TOKEN_LIFETIME_SECONDS / 3600, | ||
} | ||
) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
JWT_SECRET_KEY=SECRET | ||
CORS_ORIGINS=http://localhost:3000;http://127.0.0.1:8000 | ||
POSTGRES_DB=todos | ||
POSTGRES_HOST=db:5432 | ||
POSTGRES_USER=username | ||
POSTGRES_PASSWORD=password | ||
CORS_ORIGINS=http://localhost:3000;http://127.0.0.1:8000 | ||
FRONT_END_BASE_URL=http://localhost:3000 | ||
SMTP_HOST= | ||
SMTP_PORT= | ||
SMTP_USER= | ||
SMTP_PASSWORD= | ||
EMAILS_FROM_EMAIL= | ||
EMAILS_FROM_NAME= |