Skip to content

Commit 3a3d637

Browse files
committed
Added the otp sending and verification logic
1 parent 9711f59 commit 3a3d637

File tree

4 files changed

+72
-4
lines changed

4 files changed

+72
-4
lines changed

app/db/crud.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,16 @@ def get_leaderboard_data(db: Session, track_id: int):
5757
.group_by(models.User.id)
5858
.order_by(func.sum(models.Task.points).desc())
5959
.all()
60-
)
60+
)
61+
def get_otp_by_email(db, email):
62+
return db.query(models.OTP).filter(models.OTP.email == email).first()
63+
64+
def create_or_update_otp(db, email, otp, expires_at):
65+
entry = get_otp_by_email(db, email)
66+
if entry:
67+
entry.otp = otp
68+
entry.expires_at = expires_at
69+
else:
70+
entry = models.OTP(email=email, otp=otp, expires_at=expires_at)
71+
db.add(entry)
72+
db.commit()

app/routes/auth.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
from fastapi import APIRouter, Depends, HTTPException
1+
from fastapi import APIRouter, Depends, HTTPException, Query
22
from sqlalchemy.orm import Session
3+
from datetime import datetime, timedelta
4+
import random, string
5+
from app.schemas.user import UserCreate, UserOut
6+
37
from app.db import models, crud
48
from app.db.db import get_db
5-
from app.schemas.user import UserCreate, UserOut
9+
from app.schemas.user import UserOut
10+
from app.utils.mail import send_email
611

712
router = APIRouter()
813

@@ -23,8 +28,32 @@ def register_user(user: UserCreate, db: Session = Depends(get_db)):
2328
return new_user
2429

2530
@router.get("/user/{email}", response_model=UserOut)
26-
def get_user(email: str, db: Session = Depends(get_db)):
31+
def get_user(email: str, otp: str = Query(None), db: Session = Depends(get_db)):
2732
user = crud.get_user_by_email(db, email)
2833
if not user:
2934
raise HTTPException(status_code=404, detail="User not found")
35+
36+
# OTP phase 1: if not submitted, generate and send
37+
if otp is None:
38+
otp_code = ''.join(random.choices(string.digits, k=6))
39+
expiry = datetime.utcnow() + timedelta(minutes=5)
40+
41+
crud.create_or_update_otp(db, email, otp_code, expiry)
42+
send_email(email, otp_code)
43+
44+
raise HTTPException(
45+
status_code=202,
46+
detail="OTP sent to email. Re-call with ?otp=XXXX"
47+
)
48+
49+
# OTP phase 2: validate
50+
otp_entry = crud.get_otp_by_email(db, email)
51+
if not otp_entry or otp_entry.otp != otp:
52+
raise HTTPException(status_code=400, detail="Invalid OTP")
53+
if otp_entry.expires_at < datetime.utcnow():
54+
raise HTTPException(status_code=400, detail="OTP expired")
55+
56+
db.delete(otp_entry)
57+
db.commit()
58+
3059
return user

app/utils/mail.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import smtplib
2+
from email.message import EmailMessage
3+
import os
4+
5+
6+
from dotenv import load_dotenv
7+
load_dotenv()
8+
9+
EMAIL_ADDRESS = os.getenv("SMTP_EMAIL")
10+
EMAIL_PASSWORD = os.getenv("SMTP_PASSWORD")
11+
12+
def send_email(to_email: str, otp: str):
13+
msg = EmailMessage()
14+
msg["Subject"] = "Your OTP Verification Code"
15+
msg["From"] = EMAIL_ADDRESS
16+
msg["To"] = to_email
17+
msg.set_content(f"Your OTP is: {otp}\n\nThis OTP is valid for 5 minutes.")
18+
19+
try:
20+
with smtplib.SMTP("smtp.gmail.com", 587) as smtp:
21+
smtp.starttls()
22+
smtp.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
23+
smtp.send_message(msg)
24+
print(f"OTP sent to {to_email}")
25+
except Exception as e:
26+
print(f"Failed to send email to {to_email}: {e}")

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ services:
2020
POSTGRES_DB: ammentor
2121
POSTGRES_USER: ammentor_user
2222
POSTGRES_PASSWORD: strongpassword123
23+
2324
volumes:
2425
- postgres_data:/var/lib/postgresql/data
2526
ports:

0 commit comments

Comments
 (0)