Add share link hook (#351)

This commit is contained in:
Archer
2023-09-25 23:12:42 +08:00
committed by GitHub
parent 9136c9306a
commit 63cd379682
29 changed files with 430 additions and 98 deletions

View File

@@ -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('身份校验失败');
}
};

View File

@@ -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) {}
};

View File

@@ -48,6 +48,9 @@ const OutLinkSchema = new Schema({
credit: {
type: Number,
default: -1
},
hookUrl: {
type: String
}
}
});

View File

@@ -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);