diff options
| author | Adam Soltys <[email protected]> | 2026-01-21 21:33:55 +0000 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-01-21 21:33:56 +0000 |
| commit | 5567d850d784b0a40dc16a0d95d17193626c971c (patch) | |
| tree | 370f07f9db4b8203e4cdd96c789587d2d8747b55 | |
| parent | e5e5f2acf46ce6a86b668407a67fce3f8edac4bc (diff) | |
| download | coinos-server-5567d850d784b0a40dc16a0d95d17193626c971c.tar.gz coinos-server-5567d850d784b0a40dc16a0d95d17193626c971c.zip | |
captcha thresholds
| -rw-r--r-- | routes/users.ts | 29 |
1 files changed, 18 insertions, 11 deletions
diff --git a/routes/users.ts b/routes/users.ts index ed638d9e..f704da03 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -26,7 +26,7 @@ import type { ProfilePointer } from "nostr-tools/nip19"; const { host } = new URL(process.env.URL); const relay = encodeURIComponent(config.publicRelay); -const verifyRecaptcha = async (response) => { +const verifyRecaptcha = async (response, ip) => { const { recaptcha: secret } = config; if (!secret) return true; if (!response) return false; @@ -37,6 +37,7 @@ const verifyRecaptcha = async (response) => { form: { secret, response, + remoteip: ip, }, }) .json(); @@ -347,9 +348,15 @@ export default { async login(req, res) { try { - let { challenge, username, password, token: twofa, recaptcha } = req.body; + let { username, password, token: twofa, recaptcha } = req.body; + const ip = req.headers["cf-connecting-ip"] || req.socket.remoteAddress; - const recaptchaOk = await verifyRecaptcha(recaptcha); + const ipKey = `ip:${ip}:login`; + const ipCount = await db.incr(ipKey); + if (ipCount === 1) await db.expire(ipKey, 10); + if (ipCount > 30) return res.code(429).send({}); + + const recaptchaOk = await verifyRecaptcha(recaptcha, ip); if (!recaptchaOk) { return res.code(401).send("failed captcha"); } @@ -358,13 +365,9 @@ export default { const fk = `${username}:failures`; const failures = await g(fk); - - if (failures > 3) { - const verified = await db.exists(`challenge:${challenge}`); - if (!verified) { - return res.code(401).send({}); - } - } + const ipFailKey = `ip:${ip}:login:fail`; + const ipFailures = await g(ipFailKey); + if (ipFailures > 20) return res.code(429).send({}); let user = await getUser(username); @@ -374,6 +377,8 @@ export default { !user.password || !(await Bun.password.verify(password, user.password)) ) { + await db.incrBy(ipFailKey, 1); + if (!(await db.ttl(ipFailKey))) await db.expire(ipFailKey, 600); await db.incrBy(fk, 1); setTimeout(() => db.decrBy(fk, 1), 120000); return res.code(401).send({}); @@ -384,6 +389,8 @@ export default { (typeof twofa === "undefined" || !authenticator.check(twofa, user.otpsecret)) ) { + await db.incrBy(ipFailKey, 1); + if (!(await db.ttl(ipFailKey))) await db.expire(ipFailKey, 600); return res.code(401).send("2fa required"); } } @@ -413,7 +420,7 @@ export default { try { const { event, challenge, twofa, recaptcha } = req.body; const ip = req.headers["cf-connecting-ip"]; - const recaptchaOk = await verifyRecaptcha(recaptcha); + const recaptchaOk = await verifyRecaptcha(recaptcha, ip); if (!recaptchaOk) { return res.code(401).send("failed captcha"); } |
