perf: captcha code (#2620)

* perf:  captcha code

* perf: dockerfile
This commit is contained in:
Archer
2024-09-05 13:41:11 +08:00
committed by GitHub
parent 5ed89130ef
commit 7fed4d697f
9 changed files with 179 additions and 226 deletions

View File

@@ -647,7 +647,8 @@
"success": "Start syncing"
}
},
"training": {}
"training": {
}
},
"data": {
"Auxiliary Data": "Auxiliary data",
@@ -1084,13 +1085,15 @@
"default_reply": "Default reply",
"error": {
"Create failed": "Create failed",
"code_error": "Code error",
"fileNotFound": "File not found~",
"inheritPermissionError": "Inherit permission Error",
"missingParams": "Insufficient parameters",
"team": {
"overSize": "Team members exceed the limit"
},
"upload_file_error_filename": "{{name}} upload failed"
"upload_file_error_filename": "{{name}} upload failed",
"username_empty": "Account cannot be empty"
},
"extraction_results": "Extract results",
"field_name": "Name",

View File

@@ -647,7 +647,8 @@
"success": "开始同步"
}
},
"training": {}
"training": {
}
},
"data": {
"Auxiliary Data": "辅助数据",
@@ -1084,13 +1085,15 @@
"default_reply": "默认回复",
"error": {
"Create failed": "创建失败",
"code_error": "验证码错误",
"fileNotFound": "文件找不到了~",
"inheritPermissionError": "权限继承错误",
"missingParams": "参数缺失",
"team": {
"overSize": "团队成员超出上限"
},
"upload_file_error_filename": "{{name}} 上传失败"
"upload_file_error_filename": "{{name}} 上传失败",
"username_empty": "账号不能为空"
},
"extraction_results": "提取结果",
"field_name": "字段名",
@@ -1247,7 +1250,6 @@
},
"user": {
"Avatar": "头像",
"captcha_placeholder": "请输入验证码",
"Go laf env": "点击前往 {{env}} 获取 PAT 凭证。",
"Laf account course": "查看绑定 laf 账号教程。",
"Laf account intro": "绑定你的 laf 账号后,你将可以在工作流中使用 laf 模块,实现在线编写代码。",
@@ -1257,6 +1259,7 @@
"auth": {
"Sending Code": "正在发送"
},
"captcha_placeholder": "请输入验证码",
"inform": {
"System message": "系统消息"
},

View File

@@ -75,8 +75,8 @@ COPY ./projects/app/data /app/data
RUN chown -R nextjs:nodejs /app/data
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
EXPOSE 3000

View File

@@ -1,58 +1,72 @@
import { getCaptchaPic } from '@/web/support/user/api';
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import { Box, Button, Input, Image, ModalBody, ModalFooter } from '@chakra-ui/react';
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
import { Button, Input, Image, ModalBody, ModalFooter, Skeleton } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
const SendCodeAuthModal = ({
username,
type,
onClose
onClose,
onSending,
onSendCode
}: {
username: string;
type: UserAuthTypeEnum;
onClose: () => void;
onSending: boolean;
onSendCode: (params_0: { username: string; captcha: string }) => Promise<void>;
}) => {
const { t } = useTranslation();
const [captchaInput, setCaptchaInput] = useState('');
const { codeSending, sendCode } = useSendCode();
const { register, handleSubmit } = useForm({
defaultValues: {
code: ''
}
});
const {
data,
loading,
runAsync: getCaptcha
} = useRequest2(() => getCaptchaPic(username), { manual: false });
return (
<MyModal isOpen={true} isLoading={loading}>
<MyModal isOpen={true}>
<ModalBody pt={8}>
<Image
borderRadius={'md'}
w={'100%'}
h={'200px'}
_hover={{ cursor: 'pointer' }}
mb={8}
onClick={getCaptcha}
src={data?.captchaImage}
alt="captcha"
/>
<Input
placeholder={t('common:support.user.captcha_placeholder')}
value={captchaInput}
onChange={(e) => setCaptchaInput(e.target.value)}
/>
<Skeleton
minH="200px"
isLoaded={!loading}
fadeDuration={2}
display={'flex'}
justifyContent={'center'}
my={1}
>
<Image
borderRadius={'md'}
w={'100%'}
h={'200px'}
_hover={{ cursor: 'pointer' }}
mb={8}
onClick={getCaptcha}
src={data?.captchaImage}
alt=""
/>
</Skeleton>
<Input placeholder={t('common:support.user.captcha_placeholder')} {...register('code')} />
</ModalBody>
<ModalFooter gap={2}>
<Button isLoading={codeSending} variant={'whiteBase'} onClick={onClose}>
<Button isLoading={onSending} variant={'whiteBase'} onClick={onClose}>
{t('common:common.Cancel')}
</Button>
<Button
isLoading={codeSending}
onClick={async () => {
await sendCode({ username, type, captcha: captchaInput });
onClose();
}}
isLoading={onSending}
onClick={handleSubmit(({ code }) => {
return onSendCode({ username, captcha: code }).then(() => {
onClose();
});
})}
>
{t('common:common.Confirm')}
</Button>

View File

@@ -18,8 +18,6 @@ import Icon from '@fastgpt/web/components/common/Icon';
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SendCodeAuthModal from '@/components/support/user/safe/SendCodeAuthModal';
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
type FormType = {
account: string;
@@ -30,7 +28,8 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { initUserInfo } = useUserStore();
const { feConfigs } = useSystemStore();
const { register, handleSubmit, trigger, getValues, watch } = useForm<FormType>({
const { register, handleSubmit, watch } = useForm<FormType>({
defaultValues: {
account: '',
verifyCode: ''
@@ -38,11 +37,7 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
});
const account = watch('account');
const verifyCode = watch('verifyCode');
const {
isOpen: openCodeAuthModal,
onOpen: onOpenCodeAuthModal,
onClose: onCloseCodeAuthModal
} = useDisclosure();
const { runAsync: onSubmit, loading: isLoading } = useRequest2(
(data: FormType) => {
return updateNotificationAccount(data);
@@ -57,13 +52,7 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
}
);
const { sendCodeText, codeCountDown } = useSendCode();
const onclickSendCode = useCallback(async () => {
const check = await trigger('account');
if (!check) return;
onOpenCodeAuthModal();
}, [onOpenCodeAuthModal, trigger]);
const { SendCodeBox } = useSendCode({ type: 'bindNotification' });
const placeholder = feConfigs?.bind_notification_method
?.map((item) => {
@@ -107,23 +96,7 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
{...register('verifyCode', { required: true })}
placeholder={t('user:password.code_required')}
></Input>
<Box
position={'absolute'}
right={2}
zIndex={1}
fontSize={'sm'}
{...(codeCountDown > 0
? {
color: 'myGray.500'
}
: {
color: 'primary.700',
cursor: 'pointer',
onClick: onclickSendCode
})}
>
{sendCodeText}
</Box>
<SendCodeBox username={account} />
</Flex>
</Flex>
</ModalBody>
@@ -140,13 +113,6 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
</Button>
</ModalFooter>
</MyModal>
{openCodeAuthModal && (
<SendCodeAuthModal
onClose={onCloseCodeAuthModal}
username={getValues('account')}
type={UserAuthTypeEnum.bindNotification}
/>
)}
</>
);
};

View File

@@ -8,8 +8,8 @@ import type { ResLogin } from '@/global/support/api/userRes.d';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
import SendCodeAuthModal from '@/components/support/user/safe/SendCodeAuthModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
interface Props {
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
loginSuccess: (e: ResLogin) => void;
@@ -30,25 +30,15 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
register,
handleSubmit,
getValues,
trigger,
watch,
formState: { errors }
} = useForm<RegisterType>({
mode: 'onBlur'
});
const username = watch('username');
const { sendCodeText, codeCountDown } = useSendCode();
const {
isOpen: openCodeAuthModal,
onOpen: onOpenCodeAuthModal,
onClose: onCloseCodeAuthModal
} = useDisclosure();
const onclickSendCode = useCallback(async () => {
const check = await trigger('username');
if (!check) return;
onOpenCodeAuthModal();
}, [onOpenCodeAuthModal, trigger]);
const { SendCodeBox } = useSendCode({ type: 'findPassword' });
const [requesting, setRequesting] = useState(false);
const placeholder = feConfigs?.find_password_method
?.map((item) => {
switch (item) {
@@ -62,30 +52,23 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
})
.join('/');
const onclickFindPassword = useCallback(
const { runAsync: onclickFindPassword, loading: requesting } = useRequest2(
async ({ username, code, password }: RegisterType) => {
setRequesting(true);
try {
loginSuccess(
await postFindPassword({
username,
code,
password
})
);
toast({
title: t('user:password.retrieved'),
status: 'success'
});
} catch (error: any) {
toast({
title: error.message || t('user:password.change_error'),
status: 'error'
});
}
setRequesting(false);
loginSuccess(
await postFindPassword({
username,
code,
password
})
);
toast({
status: 'success',
title: t('user:password.retrieved')
});
},
[loginSuccess, toast]
{
refreshDeps: [loginSuccess, t, toast]
}
);
return (
@@ -131,23 +114,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
required: t('user:password.code_required')
})}
></Input>
<Box
position={'absolute'}
right={3}
zIndex={1}
fontSize={'sm'}
{...(codeCountDown > 0
? {
color: 'myGray.500'
}
: {
color: 'primary.700',
cursor: 'pointer',
onClick: onclickSendCode
})}
>
{sendCodeText}
</Box>
<SendCodeBox username={username} />
</FormControl>
<FormControl mt={6} isInvalid={!!errors.password}>
<Input
@@ -203,13 +170,6 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
{t('user:password.to_login')}
</Box>
</Box>
{openCodeAuthModal && (
<SendCodeAuthModal
onClose={onCloseCodeAuthModal}
username={getValues('username')}
type={UserAuthTypeEnum.findPassword}
/>
)}
</>
);
};

View File

@@ -1,5 +1,5 @@
import React, { useState, Dispatch, useCallback } from 'react';
import { FormControl, Box, Input, Button, useDisclosure } from '@chakra-ui/react';
import React, { Dispatch } from 'react';
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { LoginPageTypeEnum } from '@/web/support/user/login/constants';
import { postRegister } from '@/web/support/user/api';
@@ -12,8 +12,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import SendCodeAuthModal from '@/components/support/user/safe/SendCodeAuthModal';
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
interface Props {
loginSuccess: (e: ResLogin) => void;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
@@ -35,63 +34,47 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
register,
handleSubmit,
getValues,
trigger,
watch,
formState: { errors }
} = useForm<RegisterType>({
mode: 'onBlur'
});
const {
isOpen: openCodeAuthModal,
onOpen: onOpenCodeAuthModal,
onClose: onCloseCodeAuthModal
} = useDisclosure();
const { sendCodeText, codeCountDown } = useSendCode();
const username = watch('username');
const onclickSendCode = useCallback(async () => {
const check = await trigger('username');
if (!check) return;
onOpenCodeAuthModal();
}, [onOpenCodeAuthModal, trigger]);
const { SendCodeBox } = useSendCode({ type: 'register' });
const [requesting, setRequesting] = useState(false);
const onclickRegister = useCallback(
const { runAsync: onclickRegister, loading: requesting } = useRequest2(
async ({ username, password, code }: RegisterType) => {
setRequesting(true);
try {
loginSuccess(
await postRegister({
username,
code,
password,
inviterId: localStorage.getItem('inviterId') || undefined
})
);
toast({
title: t('user:register.success'),
status: 'success'
});
// auto register template app
setTimeout(() => {
Object.entries(emptyTemplates).map(([type, emptyTemplate]) => {
postCreateApp({
avatar: emptyTemplate.avatar,
name: t(emptyTemplate.name as any),
modules: emptyTemplate.nodes,
edges: emptyTemplate.edges,
type: type as AppTypeEnum
});
loginSuccess(
await postRegister({
username,
code,
password,
inviterId: localStorage.getItem('inviterId') || undefined
})
);
toast({
status: 'success',
title: t('user:register.success')
});
// auto register template app
setTimeout(() => {
Object.entries(emptyTemplates).map(([type, emptyTemplate]) => {
postCreateApp({
avatar: emptyTemplate.avatar,
name: t(emptyTemplate.name as any),
modules: emptyTemplate.nodes,
edges: emptyTemplate.edges,
type: type as AppTypeEnum
});
}, 100);
} catch (error: any) {
toast({
title: error.message || t('user:register.error'),
status: 'error'
});
}
setRequesting(false);
}, 100);
},
[loginSuccess, t, toast]
{
refreshDeps: [loginSuccess, t, toast]
}
);
const placeholder = feConfigs?.register_method
@@ -148,23 +131,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
required: t('user:password.code_required')
})}
></Input>
<Box
position={'absolute'}
right={3}
zIndex={1}
fontSize={'sm'}
{...(codeCountDown > 0
? {
color: 'myGray.500'
}
: {
color: 'primary.700',
cursor: 'pointer',
onClick: onclickSendCode
})}
>
{sendCodeText}
</Box>
<SendCodeBox username={username} />
</FormControl>
<FormControl mt={6} isInvalid={!!errors.password}>
<Input
@@ -219,13 +186,6 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
{t('user:register.to_login')}
</Box>
</Box>
{openCodeAuthModal && (
<SendCodeAuthModal
onClose={onCloseCodeAuthModal}
username={getValues('username')}
type={UserAuthTypeEnum.register}
/>
)}
</>
);
};

View File

@@ -87,4 +87,4 @@ export const getWXLoginResult = (code: string) =>
export const getCaptchaPic = (username: string) =>
GET<{
captchaImage: string;
}>('/proApi/support/user/account/captcha', { username });
}>('/proApi/support/user/account/captcha/getImgCaptcha', { username });

View File

@@ -4,26 +4,24 @@ import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { Box, BoxProps, useDisclosure } from '@chakra-ui/react';
import SendCodeAuthModal from '@/components/support/user/safe/SendCodeAuthModal';
import { useMemoizedFn } from 'ahooks';
import { useToast } from '@fastgpt/web/hooks/useToast';
let timer: NodeJS.Timeout;
export const useSendCode = () => {
export const useSendCode = ({ type }: { type: `${UserAuthTypeEnum}` }) => {
const { t } = useTranslation();
const { feConfigs } = useSystemStore();
const { toast } = useToast();
const [codeCountDown, setCodeCountDown] = useState(0);
const { runAsync: sendCode, loading: codeSending } = useRequest2(
async ({
username,
type,
captcha
}: {
username: string;
type: `${UserAuthTypeEnum}`;
captcha: string;
}) => {
async ({ username, captcha }: { username: string; captcha: string }) => {
if (codeCountDown > 0) return;
const googleToken = await getClientToken(feConfigs.googleClientVerKey);
await sendAuthCode({ username, type, googleToken, captcha });
setCodeCountDown(60);
timer = setInterval(() => {
@@ -38,7 +36,7 @@ export const useSendCode = () => {
{
successToast: t('user:password.code_sended'),
errorToast: t('user:password.code_send_error'),
refreshDeps: [codeCountDown, feConfigs?.googleClientVerKey]
refreshDeps: [codeCountDown, type, feConfigs?.googleClientVerKey]
}
);
@@ -53,11 +51,60 @@ export const useSendCode = () => {
return t('user:password.get_code');
}, [codeCountDown, codeSending, t]);
const {
isOpen: openCodeAuthModal,
onOpen: onOpenCodeAuthModal,
onClose: onCloseCodeAuthModal
} = useDisclosure();
const SendCodeBox = useMemoizedFn(({ username, ...styles }: BoxProps & { username: string }) => {
return (
<>
<Box
position={'absolute'}
right={3}
zIndex={1}
fontSize={'sm'}
{...styles}
{...(codeCountDown > 0
? {
color: 'myGray.500'
}
: {
color: 'primary.700',
cursor: 'pointer',
onClick: () => {
if (!username) {
toast({
status: 'warning',
title: t('common:error.username_empty')
});
} else {
onOpenCodeAuthModal();
}
}
})}
>
{sendCodeText}
</Box>
{openCodeAuthModal && (
<SendCodeAuthModal
onClose={onCloseCodeAuthModal}
username={username}
onSending={codeSending}
onSendCode={sendCode}
/>
)}
</>
);
});
return {
codeSending,
sendCode,
sendCodeText,
codeCountDown
codeCountDown,
SendCodeBox
};
};