feat: login limit (#3369)

* feat: login limit

* feat: env template

* fix: ts
This commit is contained in:
Archer
2024-12-11 15:46:21 +08:00
committed by GitHub
parent d5752ddbaa
commit 69dc927a5a
9 changed files with 65 additions and 71 deletions

View File

@@ -72,7 +72,7 @@ export const ERROR_RESPONSE: Record<
[ERROR_ENUM.tooManyRequest]: { [ERROR_ENUM.tooManyRequest]: {
code: 429, code: 429,
statusText: ERROR_ENUM.tooManyRequest, statusText: ERROR_ENUM.tooManyRequest,
message: 'Too many request', message: i18nT('common:error.too_many_request'),
data: null data: null
}, },
[ERROR_ENUM.insufficientQuota]: { [ERROR_ENUM.insufficientQuota]: {

View File

@@ -24,7 +24,7 @@ export async function getTmpData<T extends TmpDataEnum>({
}).lean()) as TmpDataSchema<TmpDataType<T>> | null; }).lean()) as TmpDataSchema<TmpDataType<T>> | null;
} }
export async function setTmpData<T extends TmpDataEnum>({ export function setTmpData<T extends TmpDataEnum>({
type, type,
metadata, metadata,
data data
@@ -33,7 +33,7 @@ export async function setTmpData<T extends TmpDataEnum>({
metadata: TmpDataMetadata<T>; metadata: TmpDataMetadata<T>;
data: TmpDataType<T>; data: TmpDataType<T>;
}) { }) {
return await MongoTmpData.updateOne( return MongoTmpData.updateOne(
{ {
dataId: getDataId(type, metadata) dataId: getDataId(type, metadata)
}, },
@@ -43,7 +43,8 @@ export async function setTmpData<T extends TmpDataEnum>({
expireAt: addMilliseconds(Date.now(), TmpDataExpireTime[type]) expireAt: addMilliseconds(Date.now(), TmpDataExpireTime[type])
}, },
{ {
upsert: true upsert: true,
new: true
} }
); );
} }

View File

@@ -874,6 +874,7 @@
"error.fileNotFound": "File not found~", "error.fileNotFound": "File not found~",
"error.inheritPermissionError": "Inherit permission Error", "error.inheritPermissionError": "Inherit permission Error",
"error.missingParams": "Insufficient parameters", "error.missingParams": "Insufficient parameters",
"error.too_many_request": "Too many request",
"error.upload_file_error_filename": "{{name}} Upload Failed", "error.upload_file_error_filename": "{{name}} Upload Failed",
"error.username_empty": "Account cannot be empty", "error.username_empty": "Account cannot be empty",
"extraction_results": "Extraction Results", "extraction_results": "Extraction Results",

View File

@@ -873,6 +873,7 @@
"error.fileNotFound": "文件找不到了~", "error.fileNotFound": "文件找不到了~",
"error.inheritPermissionError": "权限继承错误", "error.inheritPermissionError": "权限继承错误",
"error.missingParams": "参数缺失", "error.missingParams": "参数缺失",
"error.too_many_request": "请求太频繁了,请稍后重试",
"error.upload_file_error_filename": "{{name}} 上传失败", "error.upload_file_error_filename": "{{name}} 上传失败",
"error.username_empty": "账号不能为空", "error.username_empty": "账号不能为空",
"extraction_results": "提取结果", "extraction_results": "提取结果",
@@ -1179,7 +1180,6 @@
"user.password_message": "密码最少 4 位最多 60 位", "user.password_message": "密码最少 4 位最多 60 位",
"user.team.Balance": "团队余额", "user.team.Balance": "团队余额",
"user.team.Check Team": "切换", "user.team.Check Team": "切换",
"user.team.Invite Member": "邀请成员", "user.team.Invite Member": "邀请成员",
"user.team.Invite Member Tips": "对方可查阅或使用团队内的其他资源", "user.team.Invite Member Tips": "对方可查阅或使用团队内的其他资源",
"user.team.Leave Team": "离开团队", "user.team.Leave Team": "离开团队",
@@ -1192,13 +1192,10 @@
"user.team.Processing invitations Tips": "你有 {{amount}} 个需要处理的团队邀请", "user.team.Processing invitations Tips": "你有 {{amount}} 个需要处理的团队邀请",
"user.team.Remove Member Confirm Tip": "确认将 {{username}} 移出团队?", "user.team.Remove Member Confirm Tip": "确认将 {{username}} 移出团队?",
"user.team.Select Team": "团队选择", "user.team.Select Team": "团队选择",
"user.team.Switch Team Failed": "切换团队异常", "user.team.Switch Team Failed": "切换团队异常",
"user.team.Tags Async": "保存", "user.team.Tags Async": "保存",
"user.team.Team Tags Async": "标签同步", "user.team.Team Tags Async": "标签同步",
"user.team.Team Tags Async Success": "链接报错成功,标签信息更新", "user.team.Team Tags Async Success": "链接报错成功,标签信息更新",
"user.team.invite.Accept Confirm": "确认加入该团队?", "user.team.invite.Accept Confirm": "确认加入该团队?",
"user.team.invite.Accepted": "已加入团队", "user.team.invite.Accepted": "已加入团队",
"user.team.invite.Deal Width Footer Tip": "处理完会自动关闭噢~", "user.team.invite.Deal Width Footer Tip": "处理完会自动关闭噢~",

View File

@@ -873,6 +873,7 @@
"error.fileNotFound": "找不到檔案", "error.fileNotFound": "找不到檔案",
"error.inheritPermissionError": "繼承權限錯誤", "error.inheritPermissionError": "繼承權限錯誤",
"error.missingParams": "參數不足", "error.missingParams": "參數不足",
"error.too_many_request": "請求太頻繁了,請稍後重試",
"error.upload_file_error_filename": "{{name}} 上傳失敗", "error.upload_file_error_filename": "{{name}} 上傳失敗",
"error.username_empty": "帳號不能為空", "error.username_empty": "帳號不能為空",
"extraction_results": "提取結果", "extraction_results": "提取結果",

View File

@@ -43,6 +43,8 @@ LOG_LEVEL=debug
STORE_LOG_LEVEL=warn STORE_LOG_LEVEL=warn
# 安全配置 # 安全配置
# 启动 IP 限流(true),部分接口增加了 ip 限流策略,防止非正常请求操作。
USE_IP_LIMIT=false
# 工作流最大运行次数,避免极端的死循环情况 # 工作流最大运行次数,避免极端的死循环情况
WORKFLOW_MAX_RUN_TIMES=500 WORKFLOW_MAX_RUN_TIMES=500
# 循环最大运行次数,避免极端的死循环情况 # 循环最大运行次数,避免极端的死循环情况

View File

@@ -99,4 +99,4 @@ async function handler(req: NextApiRequest) {
}; };
} }
export default NextAPI(useReqFrequencyLimit(1, 2), handler); export default NextAPI(useReqFrequencyLimit(1, 15), handler);

View File

@@ -1,71 +1,63 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { MongoUser } from '@fastgpt/service/support/user/schema'; import { MongoUser } from '@fastgpt/service/support/user/schema';
import { createJWT, setCookie } from '@fastgpt/service/support/permission/controller'; import { createJWT, setCookie } from '@fastgpt/service/support/permission/controller';
import { connectToDatabase } from '@/service/mongo';
import { getUserDetail } from '@fastgpt/service/support/user/controller'; import { getUserDetail } from '@fastgpt/service/support/user/controller';
import type { PostLoginProps } from '@fastgpt/global/support/user/api.d'; import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
import { UserStatusEnum } from '@fastgpt/global/support/user/constant'; import { UserStatusEnum } from '@fastgpt/global/support/user/constant';
import { NextAPI } from '@/service/middleware/entry';
import { useReqFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequencyLimit';
export default async function handler(req: NextApiRequest, res: NextApiResponse) { async function handler(req: NextApiRequest, res: NextApiResponse) {
try { const { username, password } = req.body as PostLoginProps;
await connectToDatabase();
const { username, password } = req.body as PostLoginProps;
if (!username || !password) { if (!username || !password) {
throw new Error('缺少参数'); throw new Error('缺少参数');
}
// 检测用户是否存在
const authCert = await MongoUser.findOne(
{
username
},
'status'
);
if (!authCert) {
throw new Error('用户未注册');
}
if (authCert.status === UserStatusEnum.forbidden) {
throw new Error('账号已停用,无法登录');
}
const user = await MongoUser.findOne({
username,
password
});
if (!user) {
throw new Error('密码错误');
}
const userDetail = await getUserDetail({
tmbId: user?.lastLoginTmbId,
userId: user._id
});
MongoUser.findByIdAndUpdate(user._id, {
lastLoginTmbId: userDetail.team.tmbId
});
const token = createJWT({
...userDetail,
isRoot: username === 'root'
});
setCookie(res, token);
jsonRes(res, {
data: {
user: userDetail,
token
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
} }
// 检测用户是否存在
const authCert = await MongoUser.findOne(
{
username
},
'status'
);
if (!authCert) {
throw new Error('用户未注册');
}
if (authCert.status === UserStatusEnum.forbidden) {
throw new Error('账号已停用,无法登录');
}
const user = await MongoUser.findOne({
username,
password
});
if (!user) {
throw new Error('密码错误');
}
const userDetail = await getUserDetail({
tmbId: user?.lastLoginTmbId,
userId: user._id
});
MongoUser.findByIdAndUpdate(user._id, {
lastLoginTmbId: userDetail.team.tmbId
});
const token = createJWT({
...userDetail,
isRoot: username === 'root'
});
setCookie(res, token);
return {
user: userDetail,
token
};
} }
export default NextAPI(useReqFrequencyLimit(120, 10), handler);

View File

@@ -89,7 +89,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
removeFilesByPaths(filePaths); removeFilesByPaths(filePaths);
} }
export default NextAPI(useReqFrequencyLimit(1, 2), handler); export default NextAPI(useReqFrequencyLimit(1, 1), handler);
export const config = { export const config = {
api: { api: {