From 161659de5cd7633161b0788799b641ff6b7e55f9 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 28 May 2022 05:06:47 +0200 Subject: enhance: replace signin CAPTCHA with rate limit (#8740) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance: rate limit works without signed in user * fix: make limit key required for limiter As before the fallback limiter key will be set from the endpoint name. * enhance: use limiter for signin * Revert "CAPTCHA求めるのは2fa認証が無効になっているときだけにした" This reverts commit 02a43a310f6ad0cc9e9beccc26e51ab5b339e15f. * Revert "feat: make captcha required when signin to improve security" This reverts commit b21b0580058c14532ff3f4033e2a9147643bfca6. * fix undefined reference * fix: better error message * enhance: only handle prefix of IPv6 --- packages/backend/src/server/api/private/signin.ts | 41 +++++++++++------------ 1 file changed, 20 insertions(+), 21 deletions(-) (limited to 'packages/backend/src/server/api/private') diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index 0024b8ce3e..b304550e29 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -1,25 +1,21 @@ -import { randomBytes } from 'node:crypto'; import Koa from 'koa'; import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; -import { IsNull } from 'typeorm'; +import signin from '../common/signin.js'; import config from '@/config/index.js'; import { Users, Signins, UserProfiles, UserSecurityKeys, AttestationChallenges } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; import { genId } from '@/misc/gen-id.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { verifyHcaptcha, verifyRecaptcha } from '@/misc/captcha.js'; import { verifyLogin, hash } from '../2fa.js'; -import signin from '../common/signin.js'; +import { randomBytes } from 'node:crypto'; +import { IsNull } from 'typeorm'; +import { limiter } from '../limiter.js'; export default async (ctx: Koa.Context) => { ctx.set('Access-Control-Allow-Origin', config.url); ctx.set('Access-Control-Allow-Credentials', 'true'); const body = ctx.request.body as any; - - const instance = await fetchMeta(true); - const username = body['username']; const password = body['password']; const token = body['token']; @@ -29,6 +25,21 @@ export default async (ctx: Koa.Context) => { ctx.body = { error }; } + try { + // not more than 1 attempt per second and not more than 10 attempts per hour + await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, ctx.ip); + } catch (err) { + ctx.status = 429; + ctx.body = { + error: { + message: 'Too many failed attempts to sign in. Try again later.', + code: 'TOO_MANY_AUTHENTICATION_FAILURES', + id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', + }, + }; + return; + } + if (typeof username !== 'string') { ctx.status = 400; return; @@ -84,18 +95,6 @@ export default async (ctx: Koa.Context) => { } if (!profile.twoFactorEnabled) { - if (instance.enableHcaptcha && instance.hcaptchaSecretKey) { - await verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => { - ctx.throw(400, e); - }); - } - - if (instance.enableRecaptcha && instance.recaptchaSecretKey) { - await verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => { - ctx.throw(400, e); - }); - } - if (same) { signin(ctx, user); return; @@ -172,7 +171,7 @@ export default async (ctx: Koa.Context) => { body.credentialId .replace(/-/g, '+') .replace(/_/g, '/'), - 'base64', + 'base64' ).toString('hex'), }); -- cgit v1.2.3-freya