feat: google auth
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { GET, POST, PUT } from './request';
|
||||
import type { ChatModelItemType } from '@/constants/model';
|
||||
import type { InitDateResponse } from '@/pages/api/system/getInitData';
|
||||
|
||||
export const getFilling = () => GET<{ beianText: string }>('/system/getFiling');
|
||||
export const getInitData = () => GET<InitDateResponse>('/system/getInitData');
|
||||
|
||||
export const getSystemModelList = () => GET<ChatModelItemType[]>('/system/getModels');
|
||||
|
||||
@@ -6,13 +6,11 @@ import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
|
||||
import type { PagingData, RequestPaging } from '@/types';
|
||||
import { PaySchema } from '@/types/mongoSchema';
|
||||
|
||||
export const sendAuthCode = ({
|
||||
username,
|
||||
type
|
||||
}: {
|
||||
export const sendAuthCode = (data: {
|
||||
username: string;
|
||||
type: `${UserAuthTypeEnum}`;
|
||||
}) => GET('/user/sendAuthCode', { username, type });
|
||||
googleToken: string;
|
||||
}) => POST('/user/sendAuthCode', data);
|
||||
|
||||
export const getTokenLogin = () => GET<UserType>('/user/tokenLogin');
|
||||
|
||||
|
||||
@@ -3,8 +3,13 @@ import { sendAuthCode } from '@/api/user';
|
||||
import { UserAuthTypeEnum } from '@/constants/common';
|
||||
let timer: any;
|
||||
import { useToast } from './useToast';
|
||||
import { getClientToken } from '@/utils/plugin/google';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
export const useSendCode = () => {
|
||||
const {
|
||||
initData: { googleVerKey }
|
||||
} = useGlobalStore();
|
||||
const { toast } = useToast();
|
||||
const [codeSending, setCodeSending] = useState(false);
|
||||
const [codeCountDown, setCodeCountDown] = useState(0);
|
||||
@@ -24,7 +29,8 @@ export const useSendCode = () => {
|
||||
try {
|
||||
await sendAuthCode({
|
||||
username,
|
||||
type
|
||||
type,
|
||||
googleToken: await getClientToken(googleVerKey)
|
||||
});
|
||||
setCodeCountDown(60);
|
||||
timer = setInterval(() => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import NProgress from 'nprogress'; //nprogress module
|
||||
import Router from 'next/router';
|
||||
import 'nprogress/nprogress.css';
|
||||
import '../styles/reset.scss';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
//Binding events.
|
||||
Router.events.on('routeChangeStart', () => NProgress.start());
|
||||
@@ -29,23 +29,20 @@ const queryClient = new QueryClient({
|
||||
});
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const { toast } = useToast();
|
||||
// 校验是否支持 click 事件
|
||||
const {
|
||||
loadInitData,
|
||||
initData: { googleVerKey }
|
||||
} = useGlobalStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document.createElement('div').click !== 'function') {
|
||||
toast({
|
||||
title: '你的浏览器版本过低',
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
}, [toast]);
|
||||
loadInitData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Fast GPT</title>
|
||||
<meta name="description" content="Generated by Fast GPT" />
|
||||
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no, viewport-fit=cover"
|
||||
@@ -55,6 +52,12 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
<Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script>
|
||||
<Script src="/js/pdf.js" strategy="lazyOnload"></Script>
|
||||
<Script src="/js/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>
|
||||
{googleVerKey && (
|
||||
<Script
|
||||
src={`https://www.recaptcha.net/recaptcha/api.js?render=${googleVerKey}`}
|
||||
strategy="lazyOnload"
|
||||
></Script>
|
||||
)}
|
||||
<Script src="/js/particles.js"></Script>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ChakraProvider theme={theme}>
|
||||
|
||||
@@ -62,5 +62,5 @@ export function gpt_chatItemTokenSlice({
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return result.length === 0 && messages[0] ? [messages[0]] : result;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
|
||||
export type InitDateResponse = {
|
||||
beianText: string;
|
||||
googleVerKey: string;
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
jsonRes(res, {
|
||||
jsonRes<InitDateResponse>(res, {
|
||||
data: {
|
||||
beianText: process.env.SAFE_BEIAN_TEXT || ''
|
||||
beianText: process.env.SAFE_BEIAN_TEXT || '',
|
||||
googleVerKey: process.env.CLIENT_GOOGLE_VER_TOKEN || ''
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -7,15 +7,29 @@ import { sendPhoneCode, sendEmailCode } from '@/service/utils/sendNote';
|
||||
import { UserAuthTypeEnum } from '@/constants/common';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('123456789', 6);
|
||||
import { authGoogleToken } from '@/utils/plugin/google';
|
||||
import requestIp from 'request-ip';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { username, type } = req.query as { username: string; type: `${UserAuthTypeEnum}` };
|
||||
const { username, type, googleToken } = req.body as {
|
||||
username: string;
|
||||
type: `${UserAuthTypeEnum}`;
|
||||
googleToken: string;
|
||||
};
|
||||
|
||||
if (!username || !type) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// google auth
|
||||
process.env.SERVICE_GOOGLE_VER_TOKEN &&
|
||||
(await authGoogleToken({
|
||||
secret: process.env.SERVICE_GOOGLE_VER_TOKEN,
|
||||
response: googleToken,
|
||||
remoteip: requestIp.getClientIp(req) || undefined
|
||||
}));
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
const code = nanoid();
|
||||
|
||||
@@ -291,7 +291,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
|
||||
}, 100);
|
||||
|
||||
try {
|
||||
await gptChatPrompt(newChatList.slice(-2));
|
||||
await gptChatPrompt(newChatList.slice(newChatList.length - 2));
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: typeof err === 'string' ? err : err?.message || '聊天出错了~',
|
||||
|
||||
@@ -2,8 +2,6 @@ import React, { useEffect } from 'react';
|
||||
import { Card, Box, Link, Flex, Image, Button } from '@chakra-ui/react';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import { useMarkdown } from '@/hooks/useMarkdown';
|
||||
import { getFilling } from '@/api/system';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
@@ -13,7 +11,10 @@ const Home = () => {
|
||||
const router = useRouter();
|
||||
const { inviterId } = router.query as { inviterId: string };
|
||||
const { data } = useMarkdown({ url: '/intro.md' });
|
||||
const { isPc } = useGlobalStore();
|
||||
const {
|
||||
isPc,
|
||||
initData: { beianText }
|
||||
} = useGlobalStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (inviterId) {
|
||||
@@ -21,8 +22,6 @@ const Home = () => {
|
||||
}
|
||||
}, [inviterId]);
|
||||
|
||||
const { data: { beianText = '' } = {} } = useQuery(['init'], getFilling);
|
||||
|
||||
/* 加载动画 */
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -124,7 +124,8 @@ const InputDataModal = ({
|
||||
<ModalCloseButton />
|
||||
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
display={'flex'}
|
||||
flexDirection={['column', 'row']}
|
||||
flex={'1 0 0'}
|
||||
h={['100%', 0]}
|
||||
overflow={'overlay'}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import type { InitDateResponse } from '@/pages/api/system/getInitData';
|
||||
import { getInitData } from '@/api/system';
|
||||
|
||||
type State = {
|
||||
initData: InitDateResponse;
|
||||
loadInitData: () => Promise<void>;
|
||||
loading: boolean;
|
||||
setLoading: (val: boolean) => null;
|
||||
screenWidth: number;
|
||||
@@ -13,6 +17,18 @@ type State = {
|
||||
export const useGlobalStore = create<State>()(
|
||||
devtools(
|
||||
immer((set, get) => ({
|
||||
initData: {
|
||||
beianText: '',
|
||||
googleVerKey: ''
|
||||
},
|
||||
async loadInitData() {
|
||||
try {
|
||||
const res = await getInitData();
|
||||
set((state) => {
|
||||
state.initData = res;
|
||||
});
|
||||
} catch (error) {}
|
||||
},
|
||||
loading: false,
|
||||
setLoading: (val: boolean) => {
|
||||
set((state) => {
|
||||
|
||||
@@ -25,10 +25,6 @@ svg {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#__next {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
@@ -65,16 +61,26 @@ textarea::placeholder {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-webkit-focus-ring-color: rgba(0, 0, 0, 0);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#__next {
|
||||
height: 100%;
|
||||
}
|
||||
#nprogress .bar {
|
||||
background: '#85b1ff' !important; //自定义颜色
|
||||
}
|
||||
|
||||
.textEllipsis {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-webkit-focus-ring-color: rgba(0, 0, 0, 0);
|
||||
outline: none;
|
||||
.grecaptcha-badge {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
@@ -93,7 +99,3 @@ textarea::placeholder {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
|
||||
#nprogress .bar {
|
||||
background: '#85b1ff' !important; //自定义颜色
|
||||
}
|
||||
|
||||
6
src/types/index.d.ts
vendored
6
src/types/index.d.ts
vendored
@@ -1,18 +1,16 @@
|
||||
import type { Mongoose } from 'mongoose';
|
||||
import type { RedisClientType } from 'redis';
|
||||
import type { Agent } from 'http';
|
||||
import type { Pool } from 'pg';
|
||||
|
||||
declare global {
|
||||
var mongodb: Mongoose | string | null;
|
||||
var redisClient: RedisClientType | null;
|
||||
var pgClient: Pool | null;
|
||||
var generatingQA: boolean;
|
||||
var generatingAbstract: boolean;
|
||||
var generatingVector: boolean;
|
||||
var QRCode: any;
|
||||
var httpsAgent: Agent;
|
||||
var particlesJS: any;
|
||||
var grecaptcha: any;
|
||||
var QRCode: any;
|
||||
|
||||
interface Window {
|
||||
['pdfjs-dist/build/pdf']: any;
|
||||
|
||||
33
src/utils/plugin/google.ts
Normal file
33
src/utils/plugin/google.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import axios from 'axios';
|
||||
import { Obj2Query } from '../tools';
|
||||
|
||||
export const getClientToken = (googleVerKey: string) => {
|
||||
if (!grecaptcha?.ready) return '';
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
grecaptcha.ready(async () => {
|
||||
try {
|
||||
const token = await grecaptcha.execute(googleVerKey, {
|
||||
action: 'submit'
|
||||
});
|
||||
resolve(token);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// service run
|
||||
export const authGoogleToken = async (data: {
|
||||
secret: string;
|
||||
response: string;
|
||||
remoteip?: string;
|
||||
}) => {
|
||||
const res = await axios.post<{ score?: number }>(
|
||||
`https://www.recaptcha.net/recaptcha/api/siteverify?${Obj2Query(data)}`
|
||||
);
|
||||
if (res.data.score && res.data.score >= 0.9) {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
return Promise.reject('非法环境');
|
||||
};
|
||||
Reference in New Issue
Block a user