From b4a46cfcdc668709a765f3ea2a2555da6ab31d1a Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Sun, 19 Apr 2026 00:46:59 +0800 Subject: [PATCH] feat(auth): require captcha on login and register Made-with: Cursor --- server/api/auth/login.post.ts | 28 ++++++++++++++++++++-------- server/api/auth/register.post.ts | 27 ++++++++++++++++++++------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/server/api/auth/login.post.ts b/server/api/auth/login.post.ts index 80dd6bd..8e5343d 100644 --- a/server/api/auth/login.post.ts +++ b/server/api/auth/login.post.ts @@ -1,18 +1,30 @@ +import { getRequestIP } from "h3"; import { loginUser } from "#server/service/auth"; import { toPublicAuthError } from "#server/service/auth/errors"; import { setSessionCookie } from "#server/service/auth/cookie"; - -type LoginBody = { - username: string; - password: string; -}; +import { captchaConsume } from "#server/service/captcha/store"; +import { assertLoginRegisterCaptchaFieldsPresent } from "#server/service/captcha/validate-body"; +import { assertUnderRateLimit } from "#server/utils/simple-rate-limit"; export default defineWrappedResponseHandler(async (event) => { + const ip = getRequestIP(event, { xForwardedFor: true }) ?? "unknown"; + assertUnderRateLimit(`auth-login:${ip}`, 30, 60_000); + + const body = await readBody(event); + assertLoginRegisterCaptchaFieldsPresent(body); + if (!captchaConsume(body.captchaId, body.captchaAnswer)) { + throw createError({ + statusCode: 400, + statusMessage: "验证码错误或已过期,请重试", + }); + } + try { - const body = await readBody(event); - const result = await loginUser(body); + const result = await loginUser({ + username: body.username, + password: body.password, + }); setSessionCookie(event, result.sessionId); - return R.success({ user: result.user, }); diff --git a/server/api/auth/register.post.ts b/server/api/auth/register.post.ts index 490dafb..dbde34a 100644 --- a/server/api/auth/register.post.ts +++ b/server/api/auth/register.post.ts @@ -1,12 +1,23 @@ +import { getRequestIP } from "h3"; import { registerUser } from "#server/service/auth"; import { toPublicAuthError } from "#server/service/auth/errors"; - -type RegisterBody = { - username: string; - password: string; -}; +import { captchaConsume } from "#server/service/captcha/store"; +import { assertLoginRegisterCaptchaFieldsPresent } from "#server/service/captcha/validate-body"; +import { assertUnderRateLimit } from "#server/utils/simple-rate-limit"; export default defineWrappedResponseHandler(async (event) => { + const ip = getRequestIP(event, { xForwardedFor: true }) ?? "unknown"; + assertUnderRateLimit(`auth-register:${ip}`, 20, 60_000); + + const body = await readBody(event); + assertLoginRegisterCaptchaFieldsPresent(body); + if (!captchaConsume(body.captchaId, body.captchaAnswer)) { + throw createError({ + statusCode: 400, + statusMessage: "验证码错误或已过期,请重试", + }); + } + const allowRegister = await event.context.config.getGlobal("allowRegister"); if (!allowRegister) { throw createError({ @@ -16,8 +27,10 @@ export default defineWrappedResponseHandler(async (event) => { } try { - const body = await readBody(event); - const user = await registerUser(body); + const user = await registerUser({ + username: body.username, + password: body.password, + }); return R.success({ user, });