From 12d6948ba727190111aab81707f27a50c26606b0 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Thu, 8 May 2025 22:09:02 +0800 Subject: [PATCH] Feat: prelogin (#4773) * add prelogin api (#4762) * add prelogin api * move type.d.ts * perf: prelogin code * doc * fix: ts --------- Co-authored-by: dreamer6680 <1468683855@qq.com> --- .../zh-cn/docs/development/upgrading/498.md | 1 + packages/global/support/user/api.d.ts | 1 + .../global/support/user/auth/constants.ts | 6 +- packages/global/support/user/auth/type.d.ts | 10 +++ .../service/support/user/auth/controller.ts | 63 +++++++++++++++++++ packages/service/support/user/auth/schema.ts | 41 ++++++++++++ .../login/LoginForm/LoginForm.tsx | 6 +- .../support/user/account/loginByPassword.ts | 13 +++- .../api/support/user/account/preLogin.ts | 40 ++++++++++++ projects/app/src/web/support/user/api.ts | 4 ++ 10 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 packages/global/support/user/auth/type.d.ts create mode 100644 packages/service/support/user/auth/controller.ts create mode 100644 packages/service/support/user/auth/schema.ts create mode 100644 projects/app/src/pages/api/support/user/account/preLogin.ts diff --git a/docSite/content/zh-cn/docs/development/upgrading/498.md b/docSite/content/zh-cn/docs/development/upgrading/498.md index f807f13e0..72d85313a 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/498.md +++ b/docSite/content/zh-cn/docs/development/upgrading/498.md @@ -15,6 +15,7 @@ weight: 792 3. qwen3 模型预设 4. 语雀知识库支持设置根目录。 5. 可配置密码过期时间,过期后下次登录会强制要求修改密码。 +6. 密码登录增加 preLogin 临时密钥校验。 ## ⚙️ 优化 diff --git a/packages/global/support/user/api.d.ts b/packages/global/support/user/api.d.ts index c75b5dee4..3b3da316b 100644 --- a/packages/global/support/user/api.d.ts +++ b/packages/global/support/user/api.d.ts @@ -9,6 +9,7 @@ import type { TeamMemberItemType } from './team/type'; export type PostLoginProps = { username: string; password: string; + code: string; }; export type OauthLoginProps = { diff --git a/packages/global/support/user/auth/constants.ts b/packages/global/support/user/auth/constants.ts index 74812b519..7d909b716 100644 --- a/packages/global/support/user/auth/constants.ts +++ b/packages/global/support/user/auth/constants.ts @@ -3,7 +3,8 @@ export enum UserAuthTypeEnum { findPassword = 'findPassword', wxLogin = 'wxLogin', bindNotification = 'bindNotification', - captcha = 'captcha' + captcha = 'captcha', + login = 'login' } export const userAuthTypeMap = { @@ -11,5 +12,6 @@ export const userAuthTypeMap = { [UserAuthTypeEnum.findPassword]: 'findPassword', [UserAuthTypeEnum.wxLogin]: 'wxLogin', [UserAuthTypeEnum.bindNotification]: 'bindNotification', - [UserAuthTypeEnum.captcha]: 'captcha' + [UserAuthTypeEnum.captcha]: 'captcha', + [UserAuthTypeEnum.login]: 'login' }; diff --git a/packages/global/support/user/auth/type.d.ts b/packages/global/support/user/auth/type.d.ts new file mode 100644 index 000000000..3dfc60562 --- /dev/null +++ b/packages/global/support/user/auth/type.d.ts @@ -0,0 +1,10 @@ +import type { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants'; + +export type UserAuthSchemaType = { + key: string; + type: `${UserAuthTypeEnum}`; + code?: string; + openid?: string; + createTime: Date; + expiredTime: Date; +}; diff --git a/packages/service/support/user/auth/controller.ts b/packages/service/support/user/auth/controller.ts new file mode 100644 index 000000000..48d876298 --- /dev/null +++ b/packages/service/support/user/auth/controller.ts @@ -0,0 +1,63 @@ +import type { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants'; +import { MongoUserAuth } from './schema'; +import { i18nT } from '../../../../web/i18n/utils'; +import { mongoSessionRun } from '../../../common/mongo/sessionRun'; + +export const addAuthCode = async ({ + key, + code, + openid, + type, + expiredTime +}: { + key: string; + code?: string; + openid?: string; + type: `${UserAuthTypeEnum}`; + expiredTime?: Date; +}) => { + return MongoUserAuth.updateOne( + { + key, + type + }, + { + code, + openid, + expiredTime + }, + { + upsert: true + } + ); +}; + +export const authCode = async ({ + key, + type, + code +}: { + key: string; + type: `${UserAuthTypeEnum}`; + code: string; +}) => { + return mongoSessionRun(async (session) => { + const result = await MongoUserAuth.findOne( + { + key, + type, + code: { $regex: new RegExp(`^${code}$`, 'i') } + }, + undefined, + { session } + ); + + if (!result) { + return Promise.reject(i18nT('common:error.code_error')); + } + + await result.deleteOne({ session }); + + return 'SUCCESS'; + }); +}; diff --git a/packages/service/support/user/auth/schema.ts b/packages/service/support/user/auth/schema.ts new file mode 100644 index 000000000..1e0a6c3bc --- /dev/null +++ b/packages/service/support/user/auth/schema.ts @@ -0,0 +1,41 @@ +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +const { Schema } = connectionMongo; +import type { UserAuthSchemaType } from '@fastgpt/global/support/user/auth/type'; +import { userAuthTypeMap } from '@fastgpt/global/support/user/auth/constants'; +import { addMinutes } from 'date-fns'; + +const UserAuthSchema = new Schema({ + key: { + type: String, + required: true + }, + code: { + // auth code + type: String, + length: 6 + }, + // wx openid + openid: String, + type: { + type: String, + enum: Object.keys(userAuthTypeMap), + required: true + }, + createTime: { + type: Date, + default: () => new Date() + }, + expiredTime: { + type: Date, + default: () => addMinutes(new Date(), 5) + } +}); + +try { + UserAuthSchema.index({ key: 1, type: 1 }); + UserAuthSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 0 }); +} catch (error) { + console.log(error); +} + +export const MongoUserAuth = getMongoModel('auth_codes', UserAuthSchema); diff --git a/projects/app/src/pageComponents/login/LoginForm/LoginForm.tsx b/projects/app/src/pageComponents/login/LoginForm/LoginForm.tsx index 876fd5d7b..430d028d2 100644 --- a/projects/app/src/pageComponents/login/LoginForm/LoginForm.tsx +++ b/projects/app/src/pageComponents/login/LoginForm/LoginForm.tsx @@ -2,7 +2,7 @@ import React, { type Dispatch } from 'react'; import { FormControl, Flex, Input, Button, Box, Link } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; import { LoginPageTypeEnum } from '@/web/support/user/login/constants'; -import { postLogin } from '@/web/support/user/api'; +import { postLogin, getPreLogin } from '@/web/support/user/api'; import type { ResLogin } from '@/global/support/api/userRes'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { useSystemStore } from '@/web/common/system/useSystemStore'; @@ -33,10 +33,12 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => { const { runAsync: onclickLogin, loading: requesting } = useRequest2( async ({ username, password }: LoginFormType) => { + const { code } = await getPreLogin(username); loginSuccess( await postLogin({ username, - password + password, + code }) ); toast({ diff --git a/projects/app/src/pages/api/support/user/account/loginByPassword.ts b/projects/app/src/pages/api/support/user/account/loginByPassword.ts index a3a2991d0..97fb338e4 100644 --- a/projects/app/src/pages/api/support/user/account/loginByPassword.ts +++ b/projects/app/src/pages/api/support/user/account/loginByPassword.ts @@ -11,14 +11,23 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { UserErrEnum } from '@fastgpt/global/common/error/code/user'; import { addOperationLog } from '@fastgpt/service/support/operationLog/addOperationLog'; import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants'; +import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants'; +import { authCode } from '@fastgpt/service/support/user/auth/controller'; async function handler(req: NextApiRequest, res: NextApiResponse) { - const { username, password } = req.body as PostLoginProps; + const { username, password, code } = req.body as PostLoginProps; - if (!username || !password) { + if (!username || !password || !code) { return Promise.reject(CommonErrEnum.invalidParams); } + // Auth prelogin code + await authCode({ + key: username, + code, + type: UserAuthTypeEnum.login + }); + // 检测用户是否存在 const authCert = await MongoUser.findOne( { diff --git a/projects/app/src/pages/api/support/user/account/preLogin.ts b/projects/app/src/pages/api/support/user/account/preLogin.ts new file mode 100644 index 000000000..c8c7125a6 --- /dev/null +++ b/projects/app/src/pages/api/support/user/account/preLogin.ts @@ -0,0 +1,40 @@ +import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; +import { NextAPI } from '@/service/middleware/entry'; +import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants'; +import { getNanoid } from '@fastgpt/global/common/string/tools'; +import { addSeconds } from 'date-fns'; +import { addAuthCode } from '@fastgpt/service/support/user/auth/controller'; + +export type preLoginQuery = { + username: string; +}; + +export type preLoginBody = {}; + +export type preLoginResponse = { code: string }; + +async function handler( + req: ApiRequestProps, + res: ApiResponseType +): Promise { + const { username } = req.query; + + if (!username) { + return Promise.reject('username is required'); + } + + const code = getNanoid(6); + + await addAuthCode({ + type: UserAuthTypeEnum.login, + key: username, + code, + expiredTime: addSeconds(new Date(), 30) + }); + + return { + code + }; +} + +export default NextAPI(handler); diff --git a/projects/app/src/web/support/user/api.ts b/projects/app/src/web/support/user/api.ts index a634cf690..a856e14ce 100644 --- a/projects/app/src/web/support/user/api.ts +++ b/projects/app/src/web/support/user/api.ts @@ -14,6 +14,7 @@ import type { AccountRegisterBody, GetWXLoginQRResponse } from '@fastgpt/global/support/user/login/api.d'; +import type { preLoginResponse } from '@/pages/api/support/user/account/preLogin'; export const sendAuthCode = (data: { username: string; @@ -104,6 +105,9 @@ export const getCaptchaPic = (username: string) => captchaImage: string; }>('/proApi/support/user/account/captcha/getImgCaptcha', { username }); +export const getPreLogin = (username: string) => + GET('/support/user/account/preLogin', { username }); + export const postSyncMembers = () => POST('/proApi/support/user/sync'); export const GetSearchUserGroupOrg = (