diff --git a/data/static/codefixes/resetPasswordMortyChallenge_3.ts b/data/static/codefixes/resetPasswordMortyChallenge_3.ts index d5e589ec9b2..669617efc07 100644 --- a/data/static/codefixes/resetPasswordMortyChallenge_3.ts +++ b/data/static/codefixes/resetPasswordMortyChallenge_3.ts @@ -1,7 +1,68 @@ -/* Rate limiting */ +import crypto from 'crypto'; + +// Security service stubs +async function verifyCaptcha(response: string): Promise { /* integrate with CAPTCHA service */ return true; } +async function validateToken(token: string): Promise { /* check token validity and expiration */ return true; } +async function getUserIdFromToken(token: string): Promise { /* decode token to user id */ return ''; } +async function isAccountLocked(userId: string): Promise { /* check lockout status */ return false; } +async function recordLockoutAttempt(userId: string): Promise { /* record failed attempt */ } +async function verifySecurityAnswer(userId: string, answer: string): Promise { /* compare answer */ return true; } +async function resetUserPassword(userId: string, newPassword: string): Promise { /* update hash */ } +async function terminateSessions(userId: string): Promise { /* kill sessions */ } +async function notifyUser(userId: string): Promise { /* 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 } - })) \ No newline at end of file + })) + + // 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' }); + });