feat: added captcha at addbot

This commit is contained in:
wonderlandpark 2021-03-25 22:10:47 +09:00
parent c90afdb75d
commit 0a7edca2d8
6 changed files with 77 additions and 37 deletions

View File

@ -1,4 +1,5 @@
import HCaptcha from '@hcaptcha/react-hcaptcha'
import { Ref } from 'react'
const Captcha = ({ dark, onVerify }:CaptchaProps):JSX.Element => {
return <HCaptcha sitekey='43e556b4-cc90-494f-b100-378b906bb736' theme={dark ? 'dark' : 'light'} onVerify={onVerify}/>
@ -7,6 +8,7 @@ const Captcha = ({ dark, onVerify }:CaptchaProps):JSX.Element => {
interface CaptchaProps {
dark: boolean
onVerify(token: string, eKey?: string): void
ref?: Ref<HCaptcha>
}
export default Captcha

View File

@ -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<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
const [ data, setData ] = useState<ResponseProps<SubmittedBot>>(null)
const [ captcha, setCaptcha ] = useState(false)
const captchaRef = useRef<HCaptcha>()
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<SubmittedBot>(`/bots/${value.id}`, { method: 'POST', body: JSON.stringify(cleanObject<AddBotSubmit>(value)) })
async function submitBot(value: AddBotSubmit, token: string) {
const res = await Fetch<SubmittedBot>(`/bots/${value.id}`, { method: 'POST', body: JSON.stringify(cleanObject<AddBotSubmit>({ ...value, _captcha: token})) })
setData(res)
}
if(!logged) {
@ -61,31 +88,10 @@ const AddBot:NextPage<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
</Message> : <></>
}
<Formik initialValues={{
agree: false,
id: '',
prefix: '',
library: '',
category: [],
intro: '',
desc: `<!-- 이 설명을 지우시고 원하시는 설명을 적으셔도 좋습니다! -->
#
!
##
?
## 🛠
-
-
- ?`,
_csrf: csrfToken
}}
validationSchema={AddBotSubmitSchema}
onSubmit={submitBot}>
{({ errors, touched, values, setFieldTouched, setFieldValue }) => (
<Formik initialValues={initialValues}
validationSchema={AddBotSubmitSchema}
onSubmit={() => setCaptcha(true)}>
{({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => (
<Form>
<div className='py-3'>
<Message type='warning'>
@ -113,7 +119,6 @@ const AddBot:NextPage<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
</div>
</Label>
<Divider />
<Label For='id' label='봇 ID' labelDesc='봇의 클라이언트 ID를 의미합니다.' error={errors.id && touched.id ? errors.id : null} short required>
<Input name='id' placeholder='653534001742741552' />
</Label>
@ -169,11 +174,20 @@ const AddBot:NextPage<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
</Segment>
</Label>
<Divider />
<Button type='submit' onClick={() => window.scrollTo({ top: 0 })}>
<>
<i className='far fa-paper-plane'/>
</>
</Button>
{
captcha ? <Captcha ref={captchaRef} dark={theme === 'dark'} onVerify={(token) => {
submitBot(values, token)
window.scrollTo({ top: 0 })
setCaptcha(false)
captchaRef?.current?.resetCaptcha()
}} /> : <Button type='submit' onClick={() => {
if(!isValid) window.scrollTo({ top: 0 })
} }>
<>
<i className='far fa-paper-plane'/>
</>
</Button>
}
</Form>
)}
</Formik>

View File

@ -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, {

View File

@ -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'
}
}

View File

@ -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<unknown, number>({ maxAgeMs: 60000 })
@ -321,6 +321,23 @@ async function addRequest(ip: string, map: TLRU<unknown, number>) {
map.set(ip, map.get(ip) + 1)
}
export async function CaptchaVerify(response: string): Promise<boolean> {
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(

View File

@ -167,6 +167,7 @@ export const AddBotSubmitSchema: Yup.SchemaOf<AddBotSubmit> = 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<BotStatUpdate> = Yup.object({