Skip to content

Commit 9c295ff

Browse files
authored
Merge pull request #1155 from EmmyAnieDev/feature/login-email-notification
feat: Email Notification for Account Login Events
2 parents f36b512 + 15ebc91 commit 9c295ff

16 files changed

Lines changed: 411 additions & 47 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@ jobs:
6565
6666
- name: Run tests
6767
run: |
68-
PYTHONPATH=. pytest
68+
PYTHONPATH=. pytest

.idea/.gitignore

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/hng_boilerplate_python_fastapi.iml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/profiles_settings.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/material_theme_project_new.xml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{% extends 'base.html' %}
2+
3+
{% block title %}Account Login Detected{% endblock %}
4+
5+
{% block content %}
6+
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
7+
<h2 style="text-align: left; margin-bottom: 20px;">Account Login Detected</h2>
8+
9+
<p style="margin-bottom: 15px;"><strong>Hi {{ first_name }} {{ last_name }},</strong></p>
10+
11+
<p style="margin-bottom: 25px;">We detected a new login to your account. If this was you, no action is needed. If you didn't log in, please take action immediately.</p>
12+
13+
<div style="margin-bottom: 25px;">
14+
<p style="margin-bottom: 5px;"><strong>Date & Time: </strong>{{ login_time }}</p>
15+
<p style="margin-bottom: 5px;"><strong>IP Address: </strong>{{ ip_address }}</p>
16+
<p style="margin-bottom: 5px;"><strong>Location: </strong>{{ location }}</p>
17+
<p style="margin-bottom: 15px;"><strong>Device: </strong>{{ device }}</p>
18+
</div>
19+
20+
<div style="margin-top: 30px; margin-bottom: 30px; padding-left: 10px; border-left: 4px solid #ffc107; background-color: #fff8e1; padding: 15px;">
21+
<h3 style="margin-top: 0; margin-bottom: 15px;">Was this not you?</h3>
22+
23+
<p>If you didn't log in to your account at this time, someone else may have access to your account. Please take the following steps immediately:</p>
24+
25+
<ol style="padding-left: 25px; margin-bottom: 20px;">
26+
<li style="margin-bottom: 8px;">Change your password immediately</li>
27+
<li style="margin-bottom: 8px;">Enable two-factor authentication if available</li>
28+
<li style="margin-bottom: 8px;">Contact our customer support at <a href="{{ help_center_link }}" style="color: blue; text-decoration: underline;">Help Center</a></li>
29+
</ol>
30+
31+
<a href="{{ change_password_link }}" style="display: inline-block; background-color: #F97414; color: white; padding: 12px 25px; text-decoration: none; border-radius: 4px; font-weight: bold;">Change Password</a>
32+
</div>
33+
34+
</div>
35+
{% endblock %}

api/v1/routes/auth.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from datetime import timedelta
23
from slowapi import Limiter
34
from slowapi.util import get_remote_address
@@ -23,6 +24,7 @@
2324
from api.v1.schemas.user import (MagicLinkRequest,
2425
ChangePasswordSchema,
2526
AuthMeResponse)
27+
from api.v1.services.login_notification import send_login_notification
2628
from api.v1.services.organisation import organisation_service
2729
from api.v1.schemas.organisation import CreateUpdateOrganisation
2830
from api.db.database import get_db
@@ -37,6 +39,10 @@
3739

3840
# Initialize rate limiter
3941
limiter = Limiter(key_func=get_remote_address)
42+
43+
# Setup logging
44+
logging.basicConfig(level=logging.INFO)
45+
logger = logging.getLogger(__name__)
4046

4147
@auth.post("/register", status_code=status.HTTP_201_CREATED, response_model=auth_response)
4248
@limiter.limit("5/minute") # Limit to 5 requests per minute per IP
@@ -140,7 +146,8 @@ def register_as_super_admin(request: Request, user: UserCreate, db: Session = De
140146

141147
@auth.post("/login", status_code=status.HTTP_200_OK, response_model=auth_response)
142148
@limiter.limit("5/minute") # Limit to 5 requests per minute per IP
143-
def login(request: Request, login_request: LoginRequest, db: Session = Depends(get_db)):
149+
def login(request: Request, login_request: LoginRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
150+
144151
"""Endpoint to log in a user"""
145152

146153
# Authenticate the user
@@ -154,6 +161,10 @@ def login(request: Request, login_request: LoginRequest, db: Session = Depends(g
154161
access_token = user_service.create_access_token(user_id=user.id)
155162
refresh_token = user_service.create_refresh_token(user_id=user.id)
156163

164+
# Background task for email notification
165+
logger.info(f"Queueing login notification for {user.email} in the background...")
166+
background_tasks.add_task(send_login_notification, user, request)
167+
157168
response = auth_response(
158169
status_code=200,
159170
message="Login successful",

0 commit comments

Comments
 (0)