Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 63 additions & 2 deletions data/static/codefixes/resetPasswordMortyChallenge_3.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,68 @@
/* Rate limiting */
import crypto from 'crypto';

// Security service stubs
async function verifyCaptcha(response: string): Promise<boolean> { /* integrate with CAPTCHA service */ return true; }
async function validateToken(token: string): Promise<boolean> { /* check token validity and expiration */ return true; }
async function getUserIdFromToken(token: string): Promise<string> { /* decode token to user id */ return ''; }
async function isAccountLocked(userId: string): Promise<boolean> { /* check lockout status */ return false; }
async function recordLockoutAttempt(userId: string): Promise<void> { /* record failed attempt */ }
async function verifySecurityAnswer(userId: string, answer: string): Promise<boolean> { /* compare answer */ return true; }
async function resetUserPassword(userId: string, newPassword: string): Promise<void> { /* update hash */ }
async function terminateSessions(userId: string): Promise<void> { /* kill sessions */ }
async function notifyUser(userId: string): Promise<void> { /* send email */ }

// Rate limiting
app.enable('trust proxy')
app.use('/rest/user/reset-password', new RateLimit({
windowMs: 3 * 60 * 1000,
max: 10,
keyGenerator ({ headers, ip }) { return headers['X-Forwarded-For'] ?? ip }
}))
}))

// CAPTCHA verification
app.use('/rest/user/reset-password', async (req, res, next) => {
const captchaResponse = req.body.captcha;
if (!captchaResponse || !(await verifyCaptcha(captchaResponse))) {
res.cookie('captchaFail', '1', { httpOnly: true, sameSite: 'Strict' });
return res.status(400).json({ message: 'CAPTCHA verification failed' });
}
next();
});

// Security: validate reset tokens, expiration, prevent enumeration & lockout
app.use('/rest/user/reset-password', async (req, res, next) => {
const token = req.body.token || req.query.token;
const tokenRegex = /^[A-Za-z0-9\-_]{32,128}$/;
if (!token || typeof token !== 'string' || !tokenRegex.test(token) || !(await validateToken(token))) {
// Dummy cookies to prevent enumeration
res.cookie('resetToken', 'invalid', { httpOnly: true, sameSite: 'Strict' });
res.cookie('username', 'hidden', { httpOnly: true, sameSite: 'Strict' });
return res.status(400).json({ message: 'Invalid request' });
}
const userId = await getUserIdFromToken(token);
if (await isAccountLocked(userId)) {
return res.status(423).json({ message: 'Account locked due to multiple failed attempts' });
}
req.userId = userId;
next();
});

// Security questions verification
app.use('/rest/user/reset-password', async (req, res, next) => {
const { securityAnswer } = req.body;
if (!securityAnswer || !(await verifySecurityAnswer(req.userId, securityAnswer))) {
await recordLockoutAttempt(req.userId);
return res.status(400).json({ message: 'Security question answer incorrect' });
}
next();
});

// Post-reset handler: reset password, terminate sessions, notify user
app.post('/rest/user/reset-password', async (req, res) => {
const { newPassword } = req.body;
await resetUserPassword(req.userId, newPassword);
await terminateSessions(req.userId);
await notifyUser(req.userId);
res.clearCookie('resetToken');
res.json({ message: 'Password reset successful' });
});