Skip to content

Commit 813d7f6

Browse files
committed
feat: implement forgot password functionality with email integration
1 parent 14f94ef commit 813d7f6

8 files changed

Lines changed: 146 additions & 14 deletions

File tree

client/src/modules/auth/v1/reset-password/index.tsx

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,32 +43,44 @@ const ResetPassword = () => {
4343
const [email, setEmail] = useState('');
4444
const [loading, setLoading] = useState(false);
4545
const [error, setError] = useState('');
46+
const [success, setSuccess] = useState(false);
4647
const navigate = useNavigate();
4748

4849
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
4950
e.preventDefault();
5051
setLoading(true);
5152
setError('');
53+
setSuccess(false);
5254

5355
try {
54-
// TODO: Implement password reset API call here
55-
console.log('Reset password for:', email);
56-
57-
// Simulate API call
58-
setTimeout(() => {
59-
setLoading(false);
60-
alert('Password reset link sent to your email!');
56+
const response = await fetch('http://localhost:8000/api/auth/forgot-password', {
57+
method: 'POST',
58+
headers: {
59+
'Content-Type': 'application/json',
60+
},
61+
body: JSON.stringify({ email }),
62+
});
63+
64+
const data = await response.json();
65+
66+
if (response.ok) {
67+
setSuccess(true);
6168
setEmail(''); // Clear email after success
62-
}, 2000);
69+
} else {
70+
setError(data.message || 'Failed to send reset link. Please try again.');
71+
}
6372
} catch (err) {
64-
setError('Something went wrong. Please try again.');
73+
setError('Something went wrong. Please try again later.');
6574
console.error('Error:', err);
75+
} finally {
6676
setLoading(false);
6777
}
6878
};
6979

7080
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
7181
setEmail(e.target.value);
82+
setError(''); // Clear error when user types
83+
setSuccess(false); // Clear success when user types
7284
};
7385

7486
return (
@@ -95,6 +107,23 @@ const ResetPassword = () => {
95107
Enter your email address to receive a link to reset your password.
96108
</Typography>
97109

110+
{success && (
111+
<Typography
112+
sx={{
113+
color: 'success.main',
114+
fontSize: '0.9rem',
115+
mb: 1,
116+
width: '100%',
117+
textAlign: 'center',
118+
backgroundColor: 'success.light',
119+
padding: '8px 12px',
120+
borderRadius: '4px',
121+
}}
122+
>
123+
Password reset link sent successfully! Check your email.
124+
</Typography>
125+
)}
126+
98127
{error && (
99128
<Typography
100129
sx={{
@@ -103,6 +132,9 @@ const ResetPassword = () => {
103132
mb: 1,
104133
width: '100%',
105134
textAlign: 'center',
135+
backgroundColor: 'error.light',
136+
padding: '8px 12px',
137+
borderRadius: '4px',
106138
}}
107139
>
108140
{error}

server/.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,10 @@ CLOUDINARY_API_SECRET=
2727
# Admin Credentials (for sending emails)
2828
ADMIN_EMAIL=
2929
RESEND_API_KEY=
30+
31+
# Email Configuration (for password reset)
32+
EMAIL_HOST=smtp.gmail.com
33+
EMAIL_PORT=587
34+
EMAIL_USER=youremailid
35+
EMAIL_PASSWORD=your-16-char-app-password-here
36+
FRONTEND_URL=http://localhost:5173

server/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
""
2+
"# Environment variables"
3+
".env"

server/package-lock.json

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

server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"morgan": "^1.10.1",
4646
"multer": "^1.4.5-lts.1",
4747
"nanoid": "^5.1.2",
48-
"nodemailer": "^7.0.5",
48+
"nodemailer": "^7.0.13",
4949
"rate-limiter-flexible": "^7.3.0",
5050
"resend": "^6.1.2",
5151
"sanitize-html": "^2.17.0",
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { sendResponse } from '../../utils/response.js';
2+
import nodemailer from 'nodemailer';
3+
import crypto from 'crypto';
4+
5+
const forgotPassword = async (req, res) => {
6+
try {
7+
const { email } = req.body;
8+
9+
// Validate email
10+
if (!email) {
11+
return sendResponse(res, 400, 'Email is required');
12+
}
13+
14+
// TODO: Check if user exists in database
15+
// const user = await User.findOne({ email });
16+
// if (!user) {
17+
// return sendResponse(res, 404, 'User not found');
18+
// }
19+
20+
// Generate reset token
21+
const resetToken = crypto.randomBytes(32).toString('hex');
22+
const resetTokenExpiry = Date.now() + 3600000; // 1 hour
23+
24+
// TODO: Save token to database
25+
// user.passwordResetToken = resetToken;
26+
// user.passwordResetExpires = resetTokenExpiry;
27+
// await user.save();
28+
29+
// Create reset URL
30+
const resetUrl = `${process.env.FRONTEND_URL || 'http://localhost:5173'}/reset-password/${resetToken}`;
31+
32+
// Configure email transporter
33+
const transporter = nodemailer.createTransport({
34+
host: process.env.EMAIL_HOST || 'smtp.gmail.com',
35+
port: process.env.EMAIL_PORT || 587,
36+
secure: false,
37+
auth: {
38+
user: process.env.EMAIL_USER,
39+
pass: process.env.EMAIL_PASSWORD,
40+
},
41+
});
42+
43+
// Email content
44+
const mailOptions = {
45+
from: `"Code A2Z" <${process.env.EMAIL_USER}>`,
46+
to: email,
47+
subject: 'Password Reset Request',
48+
html: `
49+
<div style="font-family: Arial, sans-serif; padding: 20px;">
50+
<h2>Password Reset Request</h2>
51+
<p>You requested a password reset for your Code A2Z account.</p>
52+
<p>Click the link below to reset your password:</p>
53+
<a href="${resetUrl}" style="display: inline-block; padding: 10px 20px; background-color: #4CAF50; color: white; text-decoration: none; border-radius: 5px;">Reset Password</a>
54+
<p style="margin-top: 20px;">This link will expire in 1 hour.</p>
55+
<p>If you didn't request this, please ignore this email.</p>
56+
</div>
57+
`,
58+
};
59+
60+
// Send email
61+
await transporter.sendMail(mailOptions);
62+
63+
console.log('Password reset email sent to:', email);
64+
65+
return sendResponse(
66+
res,
67+
200,
68+
'Password reset link sent successfully to your email'
69+
);
70+
} catch (err) {
71+
console.error('Forgot password error:', err);
72+
return sendResponse(
73+
res,
74+
500,
75+
err.message || 'Failed to send reset link. Please try again later.'
76+
);
77+
}
78+
};
79+
80+
export default forgotPassword;

server/src/routes/api/auth.routes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import login from '../../controllers/auth/login.js';
77
import changePassword from '../../controllers/auth/change-password.js';
88
import refresh from '../../controllers/auth/refresh.js';
99
import logout from '../../controllers/auth/logout.js';
10+
import forgotPassword from '../../controllers/auth/forgot-password.js';
1011

1112
const authRoutes = express.Router();
1213

@@ -15,5 +16,6 @@ authRoutes.post('/login', login);
1516
authRoutes.post('/refresh', refresh);
1617
authRoutes.post('/logout', logout);
1718
authRoutes.patch('/change-password', authenticateUser, changePassword);
19+
authRoutes.post('/forgot-password', forgotPassword);
1820

1921
export default authRoutes;

server/src/schemas/user.schema.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ const USER_SCHEMA = Schema(
4444
return `https://api.dicebear.com/6.x/${profile_imgs_collections_list[Math.floor(Math.random() * profile_imgs_collections_list.length)]}/svg?seed=${profile_imgs_name_list[Math.floor(Math.random() * profile_imgs_name_list.length)]}`;
4545
},
4646
},
47+
resetToken: {
48+
type: String,
49+
default: null,
50+
},
51+
resetTokenExpiry: {
52+
type: Date,
53+
default: null,
54+
},
4755
},
4856
social_links: {
4957
youtube: {

0 commit comments

Comments
 (0)