test8 / src /app /api /auth /reset-password /route.ts
simikkk's picture
Upload 93 files
eaab0a9 verified
Raw
History Blame Contribute Delete
3.96 kB
import { NextRequest, NextResponse } from 'next/server';
import { withDb } from '@/lib/api-handler';
import { db } from '@/lib/db';
import { z } from 'zod';
import { hashPassword } from '@/lib/auth';
import { getClientIp } from '@/lib/validation';
import { rateLimit } from '@/lib/rate-limit';
const resetPasswordSchema = z.object({
email: z.string().email('Please enter a valid email address'),
code: z.string().length(6, 'Code must be exactly 6 digits'),
newPassword: z
.string()
.min(8, 'Password must be at least 8 characters')
.refine((pw) => /[A-Z]/.test(pw), 'Password must contain at least one uppercase letter')
.refine((pw) => /[a-z]/.test(pw), 'Password must contain at least one lowercase letter')
.refine((pw) => /[0-9]/.test(pw), 'Password must contain at least one number'),
});
async function handler(request: NextRequest) {
try {
// ── Rate Limit: 5 per 15 minutes ─────────────────────────────────
const ip = getClientIp(request);
const { allowed, retryAfterMs } = await rateLimit(`reset-pw:${ip}`, 5, 15 * 60_000);
if (!allowed) {
return NextResponse.json(
{ error: 'Too many reset attempts. Please try again later.' },
{
status: 429,
headers: { 'Retry-After': String(Math.ceil(retryAfterMs / 1000)) },
}
);
}
// ── Parse & Validate Body ─────────────────────────────────────────
const body = await request.json();
const parsed = resetPasswordSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: parsed.error.issues[0].message },
{ status: 400 }
);
}
const { email, code, newPassword } = parsed.data;
// ── Find valid reset token ────────────────────────────────────────
const resetToken = await db.resetToken.findFirst({
where: {
email,
token: code,
usedAt: null,
expiresAt: { gt: new Date() },
},
});
if (!resetToken) {
return NextResponse.json(
{ error: 'Invalid or expired reset code. Please request a new one.' },
{ status: 400 }
);
}
// ── Find user ─────────────────────────────────────────────────────
const user = await db.user.findUnique({ where: { email } });
if (!user) {
return NextResponse.json(
{ error: 'No account found with this email address.' },
{ status: 404 }
);
}
// ── Hash new password and update user ─────────────────────────────
const hashedPassword = await hashPassword(newPassword);
await db.user.update({
where: { id: user.id },
data: { password: hashedPassword },
});
// ── Mark token as used ────────────────────────────────────────────
await db.resetToken.update({
where: { id: resetToken.id },
data: { usedAt: new Date() },
});
// ── Delete all sessions for the user ──────────────────────────────
await db.session.deleteMany({
where: { userId: user.id },
});
return NextResponse.json({
success: true,
message: 'Password has been reset successfully. Please log in with your new password.',
});
} catch (error) {
console.error('[RESET_PASSWORD_ERROR]', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
export const POST = withDb(handler);