mirror of
https://github.com/koreanbots/core.git
synced 2025-12-15 14:10:22 +00:00
feat: added owner transfer and edit
This commit is contained in:
parent
d4b55eae60
commit
4e979831f3
42
pages/api/v2/bots/[id]/owners.ts
Normal file
42
pages/api/v2/bots/[id]/owners.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { NextApiRequest } from 'next'
|
||||
|
||||
import RequestHandler from '@utils/RequestHandler'
|
||||
import { CaptchaVerify, get, update } from '@utils/Query'
|
||||
import ResponseWrapper from '@utils/ResponseWrapper'
|
||||
import { checkToken } from '@utils/Csrf'
|
||||
import { EditBotOwner, EditBotOwnerSchema } from '@utils/Yup'
|
||||
import { User } from '@types'
|
||||
|
||||
const BotOwners = RequestHandler()
|
||||
.patch(async (req: PostApiRequest, res) => {
|
||||
const user = await get.Authorization(req.cookies.token)
|
||||
if (!user) return ResponseWrapper(res, { code: 401 })
|
||||
const bot = await get.bot.load(req.query.id)
|
||||
if(!bot) return ResponseWrapper(res, { code: 404 })
|
||||
if((bot.owners as User[])[0].id !== user) return ResponseWrapper(res, { code: 403 })
|
||||
const validated = await EditBotOwnerSchema.validate(req.body, { abortEarly: false })
|
||||
.then(el => el)
|
||||
.catch(e => {
|
||||
ResponseWrapper(res, { code: 400, errors: e.errors })
|
||||
return null
|
||||
})
|
||||
if(!validated) return
|
||||
const csrfValidated = checkToken(req, res, validated._csrf)
|
||||
if (!csrfValidated) return
|
||||
const captcha = await CaptchaVerify(validated._captcha)
|
||||
if(!captcha) return
|
||||
const userFetched: User[] = await Promise.all(validated.owners.map((u: string) => get.user.load(u)))
|
||||
if(userFetched.indexOf(null) !== -1) return ResponseWrapper(res, { code: 400, message: '올바르지 않은 유저 ID를 포함하고 있습니다.' })
|
||||
if(userFetched.length > 1 && userFetched[0].id !== (bot.owners as User[])[0].id) return ResponseWrapper(res, { code: 400, errors: ['소유자를 이전할 때는 다른 관리자를 포함할 수 없습니다.'] })
|
||||
await update.botOwners(bot.id, validated.owners)
|
||||
return ResponseWrapper(res, { code: 200 })
|
||||
})
|
||||
|
||||
interface PostApiRequest extends NextApiRequest {
|
||||
query: {
|
||||
id: string
|
||||
},
|
||||
body: EditBotOwner
|
||||
}
|
||||
|
||||
export default BotOwners
|
||||
@ -177,7 +177,20 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
|
||||
</div>
|
||||
<Button onClick={() => setAdminModal(true)} className='h-10 bg-red-500 hover:opacity-80 text-white lg:w-1/8'><i className='fas fa-user-cog' /> 관리자 수정</Button>
|
||||
<Modal full header='관리자 수정' isOpen={adminModal} dark={theme === 'dark'} onClose={() => setAdminModal(false)} closeIcon>
|
||||
<Formik initialValues={{ owners: (bot.owners as User[]), id: '', _captcha: '' }} onSubmit={(v) => alert(JSON.stringify(v.owners.map(el => el.id)))}>
|
||||
<Formik initialValues={{ owners: (bot.owners as User[]), id: '', _captcha: '' }} onSubmit={async (v) => {
|
||||
const res = await Fetch(`/bots/${bot.id}/owners`, { method: 'PATCH', body: JSON.stringify({
|
||||
_captcha: v._captcha,
|
||||
_csrf: csrfToken,
|
||||
owners: v.owners.map(el => el.id)
|
||||
}) })
|
||||
if(res.code === 200) {
|
||||
alert('성공적으로 수정했습니다.')
|
||||
setAdminModal(false)
|
||||
} else {
|
||||
alert(res.message)
|
||||
setAdminModal(false)
|
||||
}
|
||||
}}>
|
||||
{
|
||||
({ values, setFieldValue }) => <Form>
|
||||
<Message type='warning'>
|
||||
@ -223,7 +236,7 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
|
||||
</div>
|
||||
</div>
|
||||
<Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} />
|
||||
<Button disabled={!values._captcha} className='mt-2 bg-red-500 text-white hover:opacity-80' type='submit'><i className='fas fa-save text-sm' /> 저장</Button>
|
||||
<Button disabled={!values._captcha} className={`mt-2 bg-red-500 text-white ${!values._captcha ? 'opacity-80' : 'hover:opacity-80'}`} type='submit'><i className='fas fa-save text-sm' /> 저장</Button>
|
||||
</Form>
|
||||
}
|
||||
</Formik>
|
||||
@ -237,11 +250,25 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
|
||||
</div>
|
||||
<Button onClick={() => setTransferModal(true)} className='h-10 bg-red-500 hover:opacity-80 text-white lg:w-1/8'><i className='fas fa-exchange-alt' /> 소유권 이전</Button>
|
||||
<Modal full header={`${bot.name} 소유권 이전하기`} isOpen={transferModal} dark={theme === 'dark'} onClose={() => setTransferModal(false)} closeIcon>
|
||||
<Formik initialValues={{ ownerID: '', name: '', _captcha: '' }} onSubmit={(v) => alert(JSON.stringify(v))}>
|
||||
<Formik initialValues={{ ownerID: '', name: '', _captcha: '' }} onSubmit={async (v) => {
|
||||
const res = await Fetch(`/bots/${bot.id}/owners`, { method: 'PATCH', body: JSON.stringify({
|
||||
_captcha: v._captcha,
|
||||
_csrf: csrfToken,
|
||||
owners: [ v.ownerID ]
|
||||
}) })
|
||||
if(res.code === 200) {
|
||||
alert('성공적으로 소유권을 이전했습니다.')
|
||||
router.push('/')
|
||||
} else {
|
||||
alert(res.message)
|
||||
setTransferModal(false)
|
||||
}
|
||||
}}>
|
||||
{
|
||||
({ values, setFieldValue }) => <Form>
|
||||
<Message type='warning'>
|
||||
<p>봇의 소유권을 이전하게 되면 봇의 소유자 권한을 이전하게 됩니다.</p>
|
||||
<h2 className='text-2xl font-bold'>주의해주세요!</h2>
|
||||
<p>봇의 소유권을 이전하게 되면 봇의 소유자 권한을 이전하게 되며, 본인을 포함한 모든 관리자가 해당 봇에 대한 권한을 잃게됩니다.</p>
|
||||
</Message>
|
||||
<div className='py-4'>
|
||||
<h2 className='text-md my-1'>이전하실 유저 ID를 입력해주세요.</h2>
|
||||
@ -267,11 +294,11 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
|
||||
<Modal full header={`${bot.name} 삭제하기`} isOpen={deleteModal} dark={theme === 'dark'} onClose={() => setDeleteModal(false)} closeIcon>
|
||||
<Formik initialValues={{ name: '', _captcha: '', _csrf: csrfToken }} onSubmit={async (v) => {
|
||||
const res = await Fetch(`/bots/${bot.id}`, { method: 'DELETE', body: JSON.stringify(v) })
|
||||
if(res.code !== 200) alert(res.message)
|
||||
else {
|
||||
if(res.code === 200) {
|
||||
alert('성공적으로 삭제하였습니다.')
|
||||
redirectTo(router, '/')
|
||||
}
|
||||
else alert(res.message)
|
||||
}}>
|
||||
{
|
||||
({ values, setFieldValue }) => <Form>
|
||||
|
||||
@ -21,9 +21,9 @@ export const getToken = (req: IncomingMessage, res: ServerResponse) => {
|
||||
res.setHeader(
|
||||
'set-cookie',
|
||||
serialize(csrfKey, key, {
|
||||
expires: new Date(+new Date() + 24 * 60 * 60 * 1000),
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
path: '/'
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -15,7 +15,8 @@ export function sign(payload: string | Record<string, unknown>, options?: JWTSig
|
||||
export function verify(token: string): any | null {
|
||||
try {
|
||||
return jwt.verify(token, publicPem)
|
||||
} catch {
|
||||
} catch(e) {
|
||||
console.log(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,6 +295,11 @@ async function updateBotApplication(id: string, value: { webhook: string }) {
|
||||
return true
|
||||
}
|
||||
|
||||
async function updateOwner(id: string, owners: string[]): Promise<void> {
|
||||
await knex('bots').where({ id }).update({ owners: JSON.stringify(owners) })
|
||||
get.bot.clear(id)
|
||||
}
|
||||
|
||||
async function resetBotToken(id: string, beforeToken: string) {
|
||||
const token = sign({ id })
|
||||
const bot = await knex('bots').update({ token }).where({ id, token: beforeToken })
|
||||
@ -461,7 +466,8 @@ export const update = {
|
||||
resetBotToken,
|
||||
updateServer,
|
||||
Github,
|
||||
bot: updateBot
|
||||
bot: updateBot,
|
||||
botOwners: updateOwner
|
||||
}
|
||||
|
||||
export const put = {
|
||||
|
||||
23
utils/Yup.ts
23
utils/Yup.ts
@ -246,11 +246,6 @@ export const ManageBotSchema: Yup.SchemaOf<ManageBot> = Yup.object({
|
||||
.min(100, '봇 설명은 최소 100자여야합니다.')
|
||||
.max(1500, '봇 설명은 최대 1500자여야합니다.')
|
||||
.required('봇 설명은 필수 항목입니다.'),
|
||||
// owners: Yup.array(Yup.string())
|
||||
// .min(1, '최소 한 명의 소유자는 입력해주세요.')
|
||||
// .max(10, '소유자는 최대 10명까지만 가능합니다.')
|
||||
// .unique('소유자 아이디는 중복될 수 없습니다.')
|
||||
// .required('소유자는 필수 항목입니다.'),
|
||||
_csrf: Yup.string().required(),
|
||||
})
|
||||
|
||||
@ -290,7 +285,7 @@ export interface DeveloperBot {
|
||||
_csrf: string
|
||||
}
|
||||
|
||||
export const ResetBotTokenSchema = Yup.object({
|
||||
export const ResetBotTokenSchema: Yup.SchemaOf<ResetBotToken> = Yup.object({
|
||||
token: Yup.string().required(),
|
||||
_csrf: Yup.string().required(),
|
||||
})
|
||||
@ -300,4 +295,20 @@ export interface ResetBotToken {
|
||||
_csrf: string
|
||||
}
|
||||
|
||||
export const EditBotOwnerSchema: Yup.SchemaOf<EditBotOwner> = Yup.object({
|
||||
owners: Yup.array(Yup.string())
|
||||
.min(1, '최소 한 명의 소유자는 입력해주세요.')
|
||||
.max(10, '소유자는 최대 10명까지만 가능합니다.')
|
||||
.unique('소유자 아이디는 중복될 수 없습니다.')
|
||||
.required('소유자는 필수 항목입니다.'),
|
||||
_csrf: Yup.string().required(),
|
||||
_captcha: Yup.string().required()
|
||||
})
|
||||
|
||||
export interface EditBotOwner {
|
||||
owners: string[]
|
||||
_csrf: string
|
||||
_captcha: string
|
||||
}
|
||||
|
||||
export default Yup
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user