Skip to content

Commit 5606659

Browse files
author
Uttam Singh
committed
Added Reset Password screen, fixed Forgot Password flow and App routes
1 parent fa69d09 commit 5606659

File tree

4 files changed

+323
-94
lines changed

4 files changed

+323
-94
lines changed
Lines changed: 23 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,30 @@
1-
from fastapi import APIRouter, Form, HTTPException, Depends
2-
from sqlalchemy.orm import Session
3-
from app.database import get_db
4-
from app.models.user import User
5-
import random
6-
import smtplib
7-
from email.mime.text import MIMEText
8-
from email.mime.multipart import MIMEMultipart
9-
import os
10-
11-
router = APIRouter()
12-
13-
# ✅ Generate random 6-digit OTP
14-
def generate_otp():
15-
return str(random.randint(100000, 999999))
16-
17-
# ✅ Send email via company mail (SMTP)
18-
def send_otp_email(recipient_email: str, otp: str):
19-
sender_email = os.getenv("SMTP_SENDER_EMAIL", "[email protected]")
20-
sender_password = os.getenv("SMTP_PASSWORD", "your-app-password")
21-
smtp_server = os.getenv("SMTP_SERVER", "smtp.office365.com")
22-
smtp_port = int(os.getenv("SMTP_PORT", 587))
23-
24-
subject = "FAT-EIBL Password Reset OTP"
25-
body = f"""
26-
<html>
27-
<body>
28-
<p>Dear User,</p>
29-
<p>Your OTP for password reset is: <b>{otp}</b></p>
30-
<p>This OTP will expire in 10 minutes.</p>
31-
<p>Regards,<br>Finance Audit Tracker - Edme Insurance Brokers Ltd</p>
32-
</body>
33-
</html>
34-
"""
35-
36-
msg = MIMEMultipart()
37-
msg["From"] = sender_email
38-
msg["To"] = recipient_email
39-
msg["Subject"] = subject
40-
msg.attach(MIMEText(body, "html"))
41-
42-
try:
43-
with smtplib.SMTP(smtp_server, smtp_port) as server:
44-
server.starttls()
45-
server.login(sender_email, sender_password)
46-
server.send_message(msg)
47-
print(f"✅ OTP sent to {recipient_email}")
48-
except Exception as e:
49-
print(f"❌ Error sending OTP: {e}")
50-
raise HTTPException(status_code=500, detail="Failed to send OTP email")
51-
52-
# ✅ Store OTP temporarily (in memory)
53-
otp_storage = {}
54-
55-
@router.post("/forgot-password")
56-
def forgot_password(email: str = Form(...), db: Session = Depends(get_db)):
57-
# Check if user exists
1+
from passlib.hash import bcrypt
2+
from datetime import datetime
3+
from app.models.otp import OtpModel
4+
5+
@router.post("/reset-password")
6+
def reset_password(
7+
email: str = Form(...),
8+
otp: str = Form(...),
9+
new_password: str = Form(...),
10+
db: Session = Depends(get_db),
11+
):
12+
# Check user exists
5813
user = db.query(User).filter(User.email == email).first()
5914
if not user:
6015
raise HTTPException(status_code=404, detail="Email not registered")
6116

62-
# Generate OTP
63-
otp = generate_otp()
17+
# Verify OTP
18+
otp_entry = db.query(OtpModel).filter(OtpModel.email == email, OtpModel.otp == otp).first()
19+
if not otp_entry:
20+
raise HTTPException(status_code=400, detail="Invalid OTP")
6421

65-
# Save OTP temporarily (could use Redis or DB for production)
66-
otp_storage[email] = otp
22+
if datetime.utcnow() > otp_entry.expires_at:
23+
raise HTTPException(status_code=400, detail="OTP expired")
6724

68-
# Send email
69-
send_otp_email(email, otp)
25+
# Update password
26+
user.hashed_password = bcrypt.hash(new_password)
27+
db.delete(otp_entry) # Remove OTP after successful use
28+
db.commit()
7029

71-
return {"ok": True, "message": f"OTP sent to {email}. Please check your inbox."}
30+
return {"ok": True, "message": "Password reset successfully"}

frontend/src/pages/App.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
33
import Login from "./Login.jsx";
44
import Dashboard from "./Dashboard.jsx";
55
import AdminDashboard from "./AdminDashboard.jsx";
6-
import ForgotPassword from "./ForgotPassword.jsx"; // ✅ added
6+
import ResetPassword from "./ResetPassword.jsx"; // ✅ import added
77

88
export default function App() {
99
return (
@@ -12,9 +12,8 @@ export default function App() {
1212
<Route path="/" element={<Login />} />
1313
<Route path="/dashboard" element={<Dashboard />} />
1414
<Route path="/admin-dashboard" element={<AdminDashboard />} />
15-
<Route path="/forgot-password" element={<ForgotPassword />} /> {/* ✅ added */}
15+
<Route path="/reset-password" element={<ResetPassword />} /> {/* ✅ added */}
1616
</Routes>
1717
</BrowserRouter>
1818
);
1919
}
20-
Lines changed: 127 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,144 @@
11
import React, { useState } from "react";
2+
import logo from "../assets/logo.png";
23

34
export default function ForgotPassword() {
45
const [email, setEmail] = useState("");
56
const [message, setMessage] = useState("");
7+
const [loading, setLoading] = useState(false);
68

7-
const handleSubmit = async (e) => {
9+
const handleSendOTP = async (e) => {
810
e.preventDefault();
11+
setMessage("");
12+
setLoading(true);
13+
914
try {
10-
const res = await fetch("https://fat-eibl-backend-x1sp.onrender.com/users/forgot-password", {
11-
method: "POST",
12-
headers: { "Content-Type": "application/json" },
13-
body: JSON.stringify({ email }),
14-
});
15-
const data = await res.json();
16-
setMessage(data.message || "If the email exists, OTP has been sent.");
17-
} catch (error) {
18-
setMessage("Something went wrong. Please try again.");
15+
const response = await fetch(
16+
"https://fat-eibl-backend-x1sp.onrender.com/users/forgot-password",
17+
{
18+
method: "POST",
19+
body: new URLSearchParams({ email }),
20+
}
21+
);
22+
23+
const data = await response.json();
24+
if (response.ok && data.ok) {
25+
setMessage(`✅ OTP sent to ${email}. Please check your inbox.`);
26+
} else {
27+
setMessage(`❌ ${data.detail || data.error || "Email not registered."}`);
28+
}
29+
} catch {
30+
setMessage("⚠️ Unable to reach the server. Please try again later.");
31+
} finally {
32+
setLoading(false);
1933
}
2034
};
2135

2236
return (
23-
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50">
24-
<h1 className="text-2xl font-semibold mb-4">Forgot Password</h1>
25-
<form onSubmit={handleSubmit} className="bg-white p-6 rounded shadow w-80">
26-
<input
27-
type="email"
28-
placeholder="Enter registered email"
29-
value={email}
30-
onChange={(e) => setEmail(e.target.value)}
31-
required
32-
className="border w-full p-2 mb-4 rounded"
37+
<div
38+
style={{
39+
height: "100vh",
40+
background: "linear-gradient(180deg, #f6f9ff 0%, #e7efff 100%)",
41+
display: "flex",
42+
justifyContent: "center",
43+
alignItems: "center",
44+
fontFamily: "Segoe UI, sans-serif",
45+
}}
46+
>
47+
<div
48+
style={{
49+
background: "#fff",
50+
padding: "40px",
51+
borderRadius: "16px",
52+
boxShadow: "0 6px 16px rgba(0, 0, 0, 0.1)",
53+
width: "380px",
54+
textAlign: "center",
55+
}}
56+
>
57+
<img
58+
src={logo}
59+
alt="Edme Logo"
60+
style={{ width: "120px", marginBottom: "20px" }}
3361
/>
34-
<button
35-
type="submit"
36-
className="bg-blue-600 text-white px-4 py-2 rounded w-full hover:bg-blue-700"
62+
<h2 style={{ color: "#004aad", marginBottom: "10px" }}>
63+
Forgot Password
64+
</h2>
65+
<p
66+
style={{
67+
fontSize: "14px",
68+
color: "#555",
69+
marginBottom: "25px",
70+
}}
71+
>
72+
Enter your registered email and we’ll send an OTP to reset your
73+
password.
74+
</p>
75+
76+
<form onSubmit={handleSendOTP}>
77+
<input
78+
type="email"
79+
placeholder="Enter registered email"
80+
value={email}
81+
onChange={(e) => setEmail(e.target.value)}
82+
required
83+
style={{
84+
width: "100%",
85+
padding: "12px",
86+
borderRadius: "8px",
87+
border: "1px solid #ccc",
88+
outlineColor: "#004aad",
89+
marginBottom: "15px",
90+
fontSize: "14px",
91+
}}
92+
/>
93+
<button
94+
type="submit"
95+
disabled={loading}
96+
style={{
97+
width: "100%",
98+
padding: "12px",
99+
borderRadius: "8px",
100+
backgroundColor: loading ? "#7a9be6" : "#004aad",
101+
color: "white",
102+
fontWeight: 500,
103+
border: "none",
104+
cursor: "pointer",
105+
fontSize: "15px",
106+
}}
107+
>
108+
{loading ? "Sending..." : "Send OTP"}
109+
</button>
110+
</form>
111+
112+
{message && (
113+
<p
114+
style={{
115+
color: message.includes("✅")
116+
? "green"
117+
: message.includes("⚠️")
118+
? "#e67e22"
119+
: "red",
120+
fontSize: "14px",
121+
marginTop: "15px",
122+
lineHeight: "1.5",
123+
}}
124+
>
125+
{message}
126+
</p>
127+
)}
128+
129+
<p
130+
onClick={() => (window.location.href = "/")}
131+
style={{
132+
color: "#004aad",
133+
marginTop: "25px",
134+
fontSize: "14px",
135+
textDecoration: "underline",
136+
cursor: "pointer",
137+
}}
37138
>
38-
Send OTP
39-
</button>
40-
</form>
41-
{message && <p className="mt-4 text-green-600">{message}</p>}
139+
← Back to Login
140+
</p>
141+
</div>
42142
</div>
43143
);
44144
}

0 commit comments

Comments
 (0)