mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-01 03:48:24 +00:00
Add share link hook (#351)
This commit is contained in:
@@ -3,8 +3,18 @@ import { IpLimit } from '@/service/common/ipLimit/schema';
|
||||
import { authBalanceByUid, AuthUserTypeEnum } from '@/service/utils/auth';
|
||||
import { OutLinkSchema } from '@/types/support/outLink';
|
||||
import { OutLink } from './schema';
|
||||
import axios from 'axios';
|
||||
|
||||
export async function authOutLinkChat({ shareId, ip }: { shareId: string; ip?: string | null }) {
|
||||
type AuthLinkProps = { ip?: string | null; authToken?: string; question: string };
|
||||
|
||||
export async function authOutLinkChat({
|
||||
shareId,
|
||||
ip,
|
||||
authToken,
|
||||
question
|
||||
}: AuthLinkProps & {
|
||||
shareId: string;
|
||||
}) {
|
||||
// get outLink
|
||||
const outLink = await OutLink.findOne({
|
||||
shareId
|
||||
@@ -18,7 +28,7 @@ export async function authOutLinkChat({ shareId, ip }: { shareId: string; ip?: s
|
||||
|
||||
const [user] = await Promise.all([
|
||||
authBalanceByUid(uid), // authBalance
|
||||
...(global.feConfigs?.isPlus ? [authOutLinkLimit({ outLink, ip })] : []) // limit auth
|
||||
...(global.feConfigs?.isPlus ? [authOutLinkLimit({ outLink, ip, authToken, question })] : []) // limit auth
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -32,10 +42,11 @@ export async function authOutLinkChat({ shareId, ip }: { shareId: string; ip?: s
|
||||
|
||||
export async function authOutLinkLimit({
|
||||
outLink,
|
||||
ip
|
||||
}: {
|
||||
ip,
|
||||
authToken,
|
||||
question
|
||||
}: AuthLinkProps & {
|
||||
outLink: OutLinkSchema;
|
||||
ip?: string | null;
|
||||
}) {
|
||||
if (!ip || !outLink.limit) {
|
||||
return;
|
||||
@@ -49,30 +60,97 @@ export async function authOutLinkLimit({
|
||||
return Promise.reject('链接超出使用限制');
|
||||
}
|
||||
|
||||
const ipLimit = await IpLimit.findOne({ ip, eventId: outLink._id });
|
||||
|
||||
try {
|
||||
if (!ipLimit) {
|
||||
await IpLimit.create({
|
||||
eventId: outLink._id,
|
||||
ip,
|
||||
account: outLink.limit.QPM - 1
|
||||
});
|
||||
// ip limit
|
||||
await (async () => {
|
||||
if (!outLink.limit) {
|
||||
return;
|
||||
}
|
||||
// over one minute
|
||||
const diffTime = Date.now() - ipLimit.lastMinute.getTime();
|
||||
if (diffTime >= 60 * 1000) {
|
||||
ipLimit.account = outLink.limit.QPM - 1;
|
||||
ipLimit.lastMinute = new Date();
|
||||
return await ipLimit.save();
|
||||
}
|
||||
if (ipLimit.account <= 0) {
|
||||
return Promise.reject(
|
||||
`每分钟仅能请求 ${outLink.limit.QPM} 次, ${60 - Math.round(diffTime / 1000)}s 后重试~`
|
||||
);
|
||||
}
|
||||
ipLimit.account = ipLimit.account - 1;
|
||||
await ipLimit.save();
|
||||
} catch (error) {}
|
||||
try {
|
||||
const ipLimit = await IpLimit.findOne({ ip, eventId: outLink._id });
|
||||
|
||||
// first request
|
||||
if (!ipLimit) {
|
||||
return await IpLimit.create({
|
||||
eventId: outLink._id,
|
||||
ip,
|
||||
account: outLink.limit.QPM - 1
|
||||
});
|
||||
}
|
||||
|
||||
// over one minute
|
||||
const diffTime = Date.now() - ipLimit.lastMinute.getTime();
|
||||
if (diffTime >= 60 * 1000) {
|
||||
ipLimit.account = outLink.limit.QPM - 1;
|
||||
ipLimit.lastMinute = new Date();
|
||||
return await ipLimit.save();
|
||||
}
|
||||
|
||||
// over limit
|
||||
if (ipLimit.account <= 0) {
|
||||
return Promise.reject(
|
||||
`每分钟仅能请求 ${outLink.limit.QPM} 次, ${60 - Math.round(diffTime / 1000)}s 后重试~`
|
||||
);
|
||||
}
|
||||
|
||||
// update limit
|
||||
ipLimit.account = ipLimit.account - 1;
|
||||
await ipLimit.save();
|
||||
} catch (error) {}
|
||||
})();
|
||||
|
||||
// url auth. send request
|
||||
await authShareStart({ authToken, tokenUrl: outLink.limit.hookUrl, question });
|
||||
}
|
||||
|
||||
type TokenAuthResponseType = {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export const authShareChatInit = async (authToken?: string, tokenUrl?: string) => {
|
||||
if (!tokenUrl || !global.feConfigs?.isPlus) return;
|
||||
try {
|
||||
const { data } = await axios<TokenAuthResponseType>({
|
||||
baseURL: tokenUrl,
|
||||
url: '/shareAuth/init',
|
||||
method: 'POST',
|
||||
data: {
|
||||
token: authToken
|
||||
}
|
||||
});
|
||||
if (data?.success !== true) {
|
||||
return Promise.reject(data?.message || '身份校验失败');
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject('身份校验失败');
|
||||
}
|
||||
};
|
||||
|
||||
export const authShareStart = async ({
|
||||
tokenUrl,
|
||||
authToken,
|
||||
question
|
||||
}: {
|
||||
authToken?: string;
|
||||
question: string;
|
||||
tokenUrl?: string;
|
||||
}) => {
|
||||
if (!tokenUrl || !global.feConfigs?.isPlus) return;
|
||||
try {
|
||||
const { data } = await axios<TokenAuthResponseType>({
|
||||
baseURL: tokenUrl,
|
||||
url: '/shareAuth/start',
|
||||
method: 'POST',
|
||||
data: {
|
||||
token: authToken,
|
||||
question
|
||||
}
|
||||
});
|
||||
|
||||
if (data?.success !== true) {
|
||||
return Promise.reject(data?.message || '身份校验失败');
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject('身份校验失败');
|
||||
}
|
||||
};
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { addLog } from '@/service/utils/tools';
|
||||
import { ChatHistoryItemResType } from '@/types/chat';
|
||||
import axios from 'axios';
|
||||
import { OutLink } from './schema';
|
||||
|
||||
export const updateOutLinkUsage = async ({
|
||||
@@ -20,3 +22,31 @@ export const updateOutLinkUsage = async ({
|
||||
addLog.error('update shareChat error', err);
|
||||
}
|
||||
};
|
||||
|
||||
export const pushResult2Remote = async ({
|
||||
authToken,
|
||||
shareId,
|
||||
responseData
|
||||
}: {
|
||||
authToken?: string;
|
||||
shareId?: string;
|
||||
responseData?: ChatHistoryItemResType[];
|
||||
}) => {
|
||||
if (!shareId || !authToken) return;
|
||||
try {
|
||||
const outLink = await OutLink.findOne({
|
||||
shareId
|
||||
});
|
||||
if (!outLink?.limit?.hookUrl) return;
|
||||
|
||||
axios({
|
||||
method: 'post',
|
||||
baseURL: outLink.limit.hookUrl,
|
||||
url: '/shareAuth/finish',
|
||||
data: {
|
||||
token: authToken,
|
||||
responseData
|
||||
}
|
||||
});
|
||||
} catch (error) {}
|
||||
};
|
||||
|
@@ -48,6 +48,9 @@ const OutLinkSchema = new Schema({
|
||||
credit: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
hookUrl: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -12,18 +12,6 @@ export enum AuthUserTypeEnum {
|
||||
apikey = 'apikey'
|
||||
}
|
||||
|
||||
export const authCookieToken = async (cookie?: string, token?: string): Promise<string> => {
|
||||
// 获取 cookie
|
||||
const cookies = Cookie.parse(cookie || '');
|
||||
const cookieToken = cookies.token || token;
|
||||
|
||||
if (!cookieToken) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
return await authJWT(cookieToken);
|
||||
};
|
||||
|
||||
/* auth balance */
|
||||
export const authBalanceByUid = async (uid: string) => {
|
||||
const user = await User.findById<UserModelSchema>(
|
||||
@@ -45,13 +33,27 @@ export const authUser = async ({
|
||||
req,
|
||||
authToken = false,
|
||||
authRoot = false,
|
||||
authApiKey = false,
|
||||
authBalance = false
|
||||
}: {
|
||||
req: NextApiRequest;
|
||||
authToken?: boolean;
|
||||
authRoot?: boolean;
|
||||
authApiKey?: boolean;
|
||||
authBalance?: boolean;
|
||||
}) => {
|
||||
const authCookieToken = async (cookie?: string, token?: string): Promise<string> => {
|
||||
// 获取 cookie
|
||||
const cookies = Cookie.parse(cookie || '');
|
||||
const cookieToken = cookies.token || token;
|
||||
|
||||
if (!cookieToken) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
return await authJWT(cookieToken);
|
||||
};
|
||||
// from authorization get apikey
|
||||
const parseAuthorization = async (authorization?: string) => {
|
||||
if (!authorization) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
@@ -89,6 +91,7 @@ export const authUser = async ({
|
||||
appId: apiKeyAppId || authorizationAppid
|
||||
};
|
||||
};
|
||||
// root user
|
||||
const parseRootKey = async (rootKey?: string, userId = '') => {
|
||||
if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
@@ -110,30 +113,31 @@ export const authUser = async ({
|
||||
let openApiKey = apikey;
|
||||
let authType: `${AuthUserTypeEnum}` = AuthUserTypeEnum.token;
|
||||
|
||||
if (authToken) {
|
||||
if (authToken && (cookie || token)) {
|
||||
// user token(from fastgpt web)
|
||||
uid = await authCookieToken(cookie, token);
|
||||
authType = AuthUserTypeEnum.token;
|
||||
} else if (authRoot) {
|
||||
} else if (authRoot && rootkey) {
|
||||
// root user
|
||||
uid = await parseRootKey(rootkey, userid);
|
||||
authType = AuthUserTypeEnum.root;
|
||||
} else if (cookie || token) {
|
||||
uid = await authCookieToken(cookie, token);
|
||||
authType = AuthUserTypeEnum.token;
|
||||
} else if (apikey) {
|
||||
} else if (authApiKey && apikey) {
|
||||
// apikey
|
||||
const parseResult = await authOpenApiKey({ apikey });
|
||||
uid = parseResult.userId;
|
||||
authType = AuthUserTypeEnum.apikey;
|
||||
openApiKey = parseResult.apikey;
|
||||
} else if (authorization) {
|
||||
} else if (authApiKey && authorization) {
|
||||
// apikey from authorization
|
||||
const authResponse = await parseAuthorization(authorization);
|
||||
uid = authResponse.uid;
|
||||
appId = authResponse.appId;
|
||||
openApiKey = authResponse.apikey;
|
||||
authType = AuthUserTypeEnum.apikey;
|
||||
} else if (rootkey) {
|
||||
uid = await parseRootKey(rootkey, userid);
|
||||
authType = AuthUserTypeEnum.root;
|
||||
} else {
|
||||
}
|
||||
|
||||
// not rootUser and no uid, reject request
|
||||
if (!rootkey && !uid) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
@@ -158,14 +162,12 @@ export const authApp = async ({
|
||||
appId,
|
||||
userId,
|
||||
authUser = true,
|
||||
authOwner = true,
|
||||
reserveDetail = false
|
||||
authOwner = true
|
||||
}: {
|
||||
appId: string;
|
||||
userId: string;
|
||||
authUser?: boolean;
|
||||
authOwner?: boolean;
|
||||
reserveDetail?: boolean; // focus reserve detail
|
||||
}) => {
|
||||
// 获取 app 数据
|
||||
const app = await App.findById<AppSchema>(appId);
|
||||
|
Reference in New Issue
Block a user