diff --git a/components/Captcha.tsx b/components/Captcha.tsx index 91b2687..60a3ad9 100644 --- a/components/Captcha.tsx +++ b/components/Captcha.tsx @@ -1,4 +1,5 @@ import HCaptcha from '@hcaptcha/react-hcaptcha' +import { Ref } from 'react' const Captcha = ({ dark, onVerify }:CaptchaProps):JSX.Element => { return @@ -7,6 +8,7 @@ const Captcha = ({ dark, onVerify }:CaptchaProps):JSX.Element => { interface CaptchaProps { dark: boolean onVerify(token: string, eKey?: string): void + ref?: Ref } export default Captcha \ No newline at end of file diff --git a/pages/addbot.tsx b/pages/addbot.tsx index 4d6c204..5512b0f 100644 --- a/pages/addbot.tsx +++ b/pages/addbot.tsx @@ -1,9 +1,10 @@ import { NextPage, NextPageContext } from 'next' -import { useState } from 'react' +import { useRef, useState } from 'react' import { useRouter } from 'next/router' import dynamic from 'next/dynamic' import Link from 'next/link' import { Form, Formik } from 'formik' +import HCaptcha from '@hcaptcha/react-hcaptcha' import { get } from '@utils/Query' import { cleanObject, parseCookie, redirectTo } from '@utils/Tools' @@ -25,18 +26,44 @@ const Selects = dynamic(() => import('@components/Form/Selects')) const Button = dynamic(() => import('@components/Button')) const Container = dynamic(() => import('@components/Container')) const Message = dynamic(() => import('@components/Message')) +const Captcha = dynamic(() => import('@components/Captcha')) const SEO = dynamic(() => import('@components/SEO')) const AddBot:NextPage = ({ logged, user, csrfToken, theme }) => { const [ data, setData ] = useState>(null) + const [ captcha, setCaptcha ] = useState(false) + const captchaRef = useRef() const router = useRouter() + const initialValues: AddBotSubmit = { + agree: false, + id: '', + prefix: '', + library: '', + category: [], + intro: '', + desc: ` +# 봇이름 +자신의 봇을 자유롭게 표현해보세요! + +## ✏️ 소개 + +무엇이 목적인 봇인가요? + +## 🛠️ 기능 + +- 어떤 +- 기능 +- 있나요?`, + _csrf: csrfToken, + _captcha: 'captcha' + } function toLogin() { localStorage.redirectTo = window.location.href redirectTo(router, 'login') } - async function submitBot(value: AddBotSubmit) { - const res = await Fetch(`/bots/${value.id}`, { method: 'POST', body: JSON.stringify(cleanObject(value)) }) + async function submitBot(value: AddBotSubmit, token: string) { + const res = await Fetch(`/bots/${value.id}`, { method: 'POST', body: JSON.stringify(cleanObject({ ...value, _captcha: token})) }) setData(res) } if(!logged) { @@ -61,31 +88,10 @@ const AddBot:NextPage = ({ logged, user, csrfToken, theme }) => { : <> } - -# 봇이름 -자신의 봇을 자유롭게 표현해보세요! - -## ✏️ 소개 - -무엇이 목적인 봇인가요? - -## 🛠️ 기능 - -- 어떤 -- 기능 -- 있나요?`, - _csrf: csrfToken - }} - validationSchema={AddBotSubmitSchema} - onSubmit={submitBot}> - {({ errors, touched, values, setFieldTouched, setFieldValue }) => ( + setCaptcha(true)}> + {({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => (
@@ -113,7 +119,6 @@ const AddBot:NextPage = ({ logged, user, csrfToken, theme }) => {
- @@ -169,11 +174,20 @@ const AddBot:NextPage = ({ logged, user, csrfToken, theme }) => { - + { + captcha ? { + submitBot(values, token) + window.scrollTo({ top: 0 }) + setCaptcha(false) + captchaRef?.current?.resetCaptcha() + }} /> : + } )}
diff --git a/pages/api/v2/bots/[id]/index.ts b/pages/api/v2/bots/[id]/index.ts index 66feca4..7ade532 100644 --- a/pages/api/v2/bots/[id]/index.ts +++ b/pages/api/v2/bots/[id]/index.ts @@ -1,7 +1,7 @@ import { NextApiRequest } from 'next' import rateLimit from 'express-rate-limit' -import { get, put, update } from '@utils/Query' +import { CaptchaVerify, get, put, update } from '@utils/Query' import ResponseWrapper from '@utils/ResponseWrapper' import { checkToken } from '@utils/Csrf' import { AddBotSubmit, AddBotSubmitSchema, ManageBot, ManageBotSchema } from '@utils/Yup' @@ -41,6 +41,8 @@ const Bots = RequestHandler() if (!validated) return if (validated.id !== req.query.id) return ResponseWrapper(res, { code: 400, errors: ['요청 주소와 Body의 정보가 다릅니다.'] }) + const captcha = await CaptchaVerify(validated._captcha) + if(!captcha) return ResponseWrapper(res, { code: 400, message: '캡챠 검증에 실패하였습니다.' }) const result = await put.submitBot(user, validated) if (result === 1) return ResponseWrapper(res, { diff --git a/utils/Constants.ts b/utils/Constants.ts index 2713897..569b1af 100644 --- a/utils/Constants.ts +++ b/utils/Constants.ts @@ -185,6 +185,9 @@ export const SpecialEndPoints = { Github: { Token: (clientID: string, clientSecret: string, code: string) => `https://github.com/login/oauth/access_token?client_id=${clientID}&client_secret=${clientSecret}&code=${code}`, Me: 'https://api.github.com/user' + }, + HCaptcha: { + Verify: 'https://hcaptcha.com/siteverify' } } diff --git a/utils/Query.ts b/utils/Query.ts index 6281dac..ccedf2e 100644 --- a/utils/Query.ts +++ b/utils/Query.ts @@ -4,12 +4,12 @@ import DataLoader from 'dataloader' import { User as DiscordUser } from 'discord.js' import { Bot, User, ListType, BotList, TokenRegister, BotFlags, DiscordUserFlags, SubmittedBot } from '@types' -import { categories } from './Constants' +import { categories, SpecialEndPoints } from './Constants' import knex from './Knex' import { DiscordBot, getMainGuild } from './DiscordBot' import { sign, verify } from './Jwt' -import { serialize } from './Tools' +import { formData, serialize } from './Tools' import { AddBotSubmit, ManageBot } from './Yup' export const imageRateLimit = new TLRU({ maxAgeMs: 60000 }) @@ -321,6 +321,23 @@ async function addRequest(ip: string, map: TLRU) { map.set(ip, map.get(ip) + 1) } +export async function CaptchaVerify(response: string): Promise { + const res:{ success: boolean } = await fetch(SpecialEndPoints.HCaptcha.Verify, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: formData({ + response, + secret: process.env.HCAPTCHA_KEY + }) + }).then(r=> r.json()) + + console.log(res) + + return res.success +} + export const get = { discord: { user: new DataLoader( diff --git a/utils/Yup.ts b/utils/Yup.ts index d02aae9..5fb28ea 100644 --- a/utils/Yup.ts +++ b/utils/Yup.ts @@ -167,6 +167,7 @@ export const AddBotSubmitSchema: Yup.SchemaOf = Yup.object({ .max(1500, '봇 설명은 최대 1500자여야합니다.') .required('봇 설명은 필수 항목입니다.'), _csrf: Yup.string().required(), + _captcha: Yup.string().required() }) export interface AddBotSubmit { @@ -182,6 +183,7 @@ export interface AddBotSubmit { intro: string desc: string _csrf: string + _captcha?: string } export const BotStatUpdateSchema: Yup.SchemaOf = Yup.object({