diff --git a/lib/verifyCaptcha.ts b/lib/verifyCaptcha.ts new file mode 100644 index 00000000000..e1cfeaa1a62 --- /dev/null +++ b/lib/verifyCaptcha.ts @@ -0,0 +1,18 @@ +export async function verifyCaptcha(token: string): Promise { + const secret = process.env.CAPTCHA_SECRET + if (!secret) { + throw new Error('Missing CAPTCHA secret configuration') + } + const response = await fetch( + 'https://www.google.com/recaptcha/api/siteverify', + { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `secret=${secret}&response=${token}` + } + ) + const data: { success: boolean } = await response.json() + if (!data.success) { + throw new Error('Captcha verification failed') + } +} diff --git a/routes/securityQuestion.ts b/routes/securityQuestion.ts index f780e8acf18..514b6897c2c 100644 --- a/routes/securityQuestion.ts +++ b/routes/securityQuestion.ts @@ -7,11 +7,17 @@ import { type Request, type Response, type NextFunction } from 'express' import { SecurityAnswerModel } from '../models/securityAnswer' import { UserModel } from '../models/user' import { SecurityQuestionModel } from '../models/securityQuestion' +import { verifyCaptcha } from '../lib/verifyCaptcha' module.exports = function securityQuestion () { - return ({ query }: Request, res: Response, next: NextFunction) => { - const email = query.email - SecurityAnswerModel.findOne({ + return (req: Request, res: Response, next: NextFunction) => { + const email = req.query.email + const responseToken = req.query.captchaToken?.toString() + if (!responseToken) { + return res.status(400).json({ error: 'Missing CAPTCHA token' }) + } + verifyCaptcha(responseToken).then(() => { + SecurityAnswerModel.findOne({ include: [{ model: UserModel, where: { email: email?.toString() } @@ -28,6 +34,8 @@ module.exports = function securityQuestion () { } }).catch((error: unknown) => { next(error) + }).catch((error: Error) => { + res.status(403).json({ error: 'Invalid CAPTCHA token' }) }) } }