feat: add password reset functionality and email notifications
- Implemented forgot password and reset password routes in the backend. - Added email sending capabilities using Nodemailer for password reset requests. - Created ResetPassword page in the frontend for users to reset their passwords. - Updated user model to include reset token and expiry fields. - Integrated hiscores API with caching mechanism for improved performance. - Enhanced authentication modal to include forgot password option. - Updated environment configuration for SMTP settings.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { Hono } from 'hono';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { prisma } from '../db';
|
||||
import { hashPassword, verifyPassword } from '../utils/password';
|
||||
import { createSession, destroySession, requireAuth } from '../middleware/session';
|
||||
import { sendPasswordResetEmail, isEmailConfigured } from '../utils/email';
|
||||
|
||||
const auth = new Hono();
|
||||
|
||||
@@ -132,4 +134,100 @@ auth.get('/me', requireAuth, (c) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/forgot-password
|
||||
* Request a password reset
|
||||
*/
|
||||
auth.post('/forgot-password', async (c) => {
|
||||
const body = await c.req.json();
|
||||
const { email } = body;
|
||||
|
||||
if (!email) {
|
||||
return c.json({ error: 'Email is required' }, 400);
|
||||
}
|
||||
|
||||
// Find user by email
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
// Always return success to prevent email enumeration
|
||||
if (!user) {
|
||||
return c.json({ message: 'If an account with that email exists, a reset link has been sent.' });
|
||||
}
|
||||
|
||||
// Generate secure token
|
||||
const resetToken = randomBytes(32).toString('hex');
|
||||
const resetTokenExpiry = new Date(Date.now() + 60 * 60 * 1000); // 1 hour from now
|
||||
|
||||
// Store token in database
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
resetToken,
|
||||
resetTokenExpiry,
|
||||
},
|
||||
});
|
||||
|
||||
// Send email with reset link
|
||||
const baseUrl = process.env.APP_URL || 'https://leagues.tools';
|
||||
|
||||
if (isEmailConfigured()) {
|
||||
const emailResult = await sendPasswordResetEmail(email, resetToken, baseUrl);
|
||||
if (!emailResult.success) {
|
||||
console.error(`Failed to send password reset email to ${email}:`, emailResult.error);
|
||||
}
|
||||
} else {
|
||||
// Log token in development when email is not configured
|
||||
console.log(`Password reset requested for ${email}. Token: ${resetToken}`);
|
||||
console.log(`Reset URL: ${baseUrl}/reset-password?token=${resetToken}`);
|
||||
}
|
||||
|
||||
return c.json({ message: 'If an account with that email exists, a reset link has been sent.' });
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/reset-password
|
||||
* Reset password using token
|
||||
*/
|
||||
auth.post('/reset-password', async (c) => {
|
||||
const body = await c.req.json();
|
||||
const { token, password } = body;
|
||||
|
||||
if (!token || !password) {
|
||||
return c.json({ error: 'Token and password are required' }, 400);
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
return c.json({ error: 'Password must be at least 8 characters' }, 400);
|
||||
}
|
||||
|
||||
// Find user with this token
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
resetToken: token,
|
||||
resetTokenExpiry: {
|
||||
gt: new Date(), // Token must not be expired
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return c.json({ error: 'Invalid or expired reset token' }, 400);
|
||||
}
|
||||
|
||||
// Update password and clear token
|
||||
const passwordHash = await hashPassword(password);
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
passwordHash,
|
||||
resetToken: null,
|
||||
resetTokenExpiry: null,
|
||||
},
|
||||
});
|
||||
|
||||
return c.json({ message: 'Password has been reset successfully. You can now log in.' });
|
||||
});
|
||||
|
||||
export default auth;
|
||||
|
||||
Reference in New Issue
Block a user