Commit 8d67db1e by Archer Committed by GitHub

fix: stt usage (#6855)

* fix: stt usage

* perf: skil ssrf with some api

* fix:test
parent 073bd591
...@@ -43,6 +43,7 @@ export function createProxyAxios(config?: AxiosRequestConfig, ssrfCheck = true) ...@@ -43,6 +43,7 @@ export function createProxyAxios(config?: AxiosRequestConfig, ssrfCheck = true)
/** @see https://github.com/axios/axios/issues/4531 */ /** @see https://github.com/axios/axios/issues/4531 */
export const axios = createProxyAxios(); export const axios = createProxyAxios();
export const axiosWithoutSSRF = createProxyAxios(undefined, false);
/** /**
* 内部相对路径请求专用的 axios 实例: * 内部相对路径请求专用的 axios 实例:
......
import type fs from 'fs'; import type fs from 'fs';
import { getAxiosConfig } from '../config'; import { getAxiosConfig } from '../config';
import { axios } from '../../../common/api/axios'; import { axiosWithoutSSRF } from '../../../common/api/axios';
import FormData from 'form-data'; import FormData from 'form-data';
import { type STTModelType } from '@fastgpt/global/core/ai/model.schema'; import { type STTModelType } from '@fastgpt/global/core/ai/model.schema';
import { UserError } from '@fastgpt/global/common/error/utils'; import { UserError } from '@fastgpt/global/common/error/utils';
...@@ -24,22 +24,19 @@ export const aiTranscriptions = async ({ ...@@ -24,22 +24,19 @@ export const aiTranscriptions = async ({
const aiAxiosConfig = getAxiosConfig(); const aiAxiosConfig = getAxiosConfig();
const { data: result } = await axios<{ text: string; usage?: { total_tokens: number } }>({ // 管理员配置的 url,允许是内网
method: 'post', const { data: result } = await axiosWithoutSSRF.post<{
...(modelData.requestUrl text: string;
? { url: modelData.requestUrl } usage?: { total_tokens: number };
: { }>(modelData.requestUrl ? modelData.requestUrl : '/audio/transcriptions', data, {
baseURL: aiAxiosConfig.baseUrl, ...(modelData.requestUrl ? {} : { baseURL: aiAxiosConfig.baseUrl }),
url: '/audio/transcriptions'
}),
headers: { headers: {
Authorization: modelData.requestAuth Authorization: modelData.requestAuth
? `Bearer ${modelData.requestAuth}` ? `Bearer ${modelData.requestAuth}`
: aiAxiosConfig.authorization, : aiAxiosConfig.authorization,
...data.getHeaders(), ...data.getHeaders(),
...headers ...headers
}, }
data: data
}); });
return result; return result;
......
import { axios } from '../../../common/api/axios'; import { axiosWithoutSSRF } from '../../../common/api/axios';
import { getDefaultRerankModel } from '../model'; import { getDefaultRerankModel } from '../model';
import { getAxiosConfig } from '../config'; import { getAxiosConfig } from '../config';
import { type RerankModelItemType } from '@fastgpt/global/core/ai/model.schema'; import { type RerankModelItemType } from '@fastgpt/global/core/ai/model.schema';
...@@ -93,8 +93,9 @@ export async function reRankRecall({ ...@@ -93,8 +93,9 @@ export async function reRankRecall({
const { baseUrl, authorization } = getAxiosConfig(); const { baseUrl, authorization } = getAxiosConfig();
const start = Date.now(); const start = Date.now();
// 模型的请求 url,允许是内网
const requestUrl = model.requestUrl ? model.requestUrl : `${baseUrl}/rerank`; const requestUrl = model.requestUrl ? model.requestUrl : `${baseUrl}/rerank`;
const apiResult = await axios const apiResult = await axiosWithoutSSRF
.post<PostReRankResponse>( .post<PostReRankResponse>(
requestUrl, requestUrl,
{ {
......
...@@ -24,7 +24,6 @@ import { JSONPath } from 'jsonpath-plus'; ...@@ -24,7 +24,6 @@ import { JSONPath } from 'jsonpath-plus';
import { getSecretValue } from '../../../../common/secret/utils'; import { getSecretValue } from '../../../../common/secret/utils';
import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type'; import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type';
import { getLogger, LogCategories } from '../../../../common/logger'; import { getLogger, LogCategories } from '../../../../common/logger';
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { formatHttpError } from '../utils'; import { formatHttpError } from '../utils';
import { isInternalAddress, PRIVATE_URL_TEXT } from '../../../../common/system/utils'; import { isInternalAddress, PRIVATE_URL_TEXT } from '../../../../common/system/utils';
import { serviceRequestMaxContentLength } from '../../../../common/system/constants'; import { serviceRequestMaxContentLength } from '../../../../common/system/constants';
...@@ -504,10 +503,10 @@ async function fetchData({ ...@@ -504,10 +503,10 @@ async function fetchData({
return Promise.reject(PRIVATE_URL_TEXT); return Promise.reject(PRIVATE_URL_TEXT);
} }
// 都认为是用户的请求,强制 SSRF 检查
const { data: response } = await axios({ const { data: response } = await axios({
method, method,
maxContentLength: serviceRequestMaxContentLength, maxContentLength: serviceRequestMaxContentLength,
baseURL: `http://${SERVICE_LOCAL_HOST}`,
url, url,
headers: { headers: {
...headers ...headers
......
...@@ -3,7 +3,6 @@ import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workfl ...@@ -3,7 +3,6 @@ import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workfl
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { axios } from '../../../../common/api/axios'; import { axios } from '../../../../common/api/axios';
import { valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils'; import { valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils';
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { getLogger, LogCategories } from '../../../../common/logger'; import { getLogger, LogCategories } from '../../../../common/logger';
...@@ -122,7 +121,6 @@ async function fetchData({ ...@@ -122,7 +121,6 @@ async function fetchData({
}): Promise<Record<string, any>> { }): Promise<Record<string, any>> {
const { data: response } = await axios({ const { data: response } = await axios({
method, method,
baseURL: `http://${SERVICE_LOCAL_HOST}`,
url, url,
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
......
...@@ -13,9 +13,9 @@ vi.mock('@fastgpt/service/common/string/tiktoken', () => ({ ...@@ -13,9 +13,9 @@ vi.mock('@fastgpt/service/common/string/tiktoken', () => ({
countPromptTokens: mockCountPromptTokens countPromptTokens: mockCountPromptTokens
})); }));
// rerank 现在改用统一 axios(带 SSRF 拦截),mock axios.post 返回 axios 风格的 { data, ... } // rerank 走 axiosWithoutSSRF(管理员配置的 url 允许内网),mock 它的 .post 返回 axios 风格的 { data, ... }
vi.mock('@fastgpt/service/common/api/axios', () => ({ vi.mock('@fastgpt/service/common/api/axios', () => ({
axios: { axiosWithoutSSRF: {
post: (...args: any[]) => Promise.resolve(mockAxiosPost(...args)).then((data) => ({ data })) post: (...args: any[]) => Promise.resolve(mockAxiosPost(...args)).then((data) => ({ data }))
} }
})); }));
......
import { SandboxCodeTypeEnum } from '@fastgpt/global/core/workflow/template/system/sandbox/constants'; import { SandboxCodeTypeEnum } from '@fastgpt/global/core/workflow/template/system/sandbox/constants';
import type { AxiosInstance } from 'axios'; import type { AxiosInstance } from 'axios';
import axios from 'axios'; import { createProxyAxios } from '../../common/api/axios';
import { getLogger, LogCategories } from '../../common/logger'; import { getLogger, LogCategories } from '../../common/logger';
const logger = getLogger(LogCategories.MODULE.WORKFLOW.CODE_SANDBOX); const logger = getLogger(LogCategories.MODULE.WORKFLOW.CODE_SANDBOX);
...@@ -17,14 +17,17 @@ export class CodeSandbox { ...@@ -17,14 +17,17 @@ export class CodeSandbox {
const baseUrl = process.env.CODE_SANDBOX_URL || ''; const baseUrl = process.env.CODE_SANDBOX_URL || '';
const token = process.env.CODE_SANDBOX_TOKEN || ''; const token = process.env.CODE_SANDBOX_TOKEN || '';
this.client = axios.create({ this.client = createProxyAxios(
{
baseURL: `${baseUrl.replace(/\/$/, '')}/sandbox`, baseURL: `${baseUrl.replace(/\/$/, '')}/sandbox`,
timeout: 180000, timeout: 180000,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: token ? `Bearer ${token}` : undefined Authorization: token ? `Bearer ${token}` : undefined
} }
}); },
false
);
this.client.interceptors.response.use( this.client.interceptors.response.use(
(response) => { (response) => {
......
import { matchMdImg } from '@fastgpt/global/common/string/markdown'; import { matchMdImg } from '@fastgpt/global/common/string/markdown';
import axios from 'axios'; import { createProxyAxios } from '../../common/api/axios';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { getLogger, LogCategories } from '../../common/logger'; import { getLogger, LogCategories } from '../../common/logger';
export const useTextinServer = ({ appId, secretCode }: { appId: string; secretCode: string }) => { export const useTextinServer = ({ appId, secretCode }: { appId: string; secretCode: string }) => {
const logger = getLogger(LogCategories.MODULE.DATASET.FILE); const logger = getLogger(LogCategories.MODULE.DATASET.FILE);
// Init request // Init request
const instance = axios.create({ const instance = createProxyAxios({
baseURL: 'https://api.textin.com/ai/service/v1', baseURL: 'https://api.textin.com/ai/service/v1',
timeout: 300000, timeout: 300000,
headers: { headers: {
......
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth'; import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth';
import { axios } from '@fastgpt/service/common/api/axios'; import { axiosWithoutSSRF } from '@fastgpt/service/common/api/axios';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
const baseUrl = process.env.AIPROXY_API_ENDPOINT; const baseUrl = process.env.AIPROXY_API_ENDPOINT;
...@@ -14,7 +14,7 @@ async function handler(req: ApiRequestProps, res: ApiResponseType<any>) { ...@@ -14,7 +14,7 @@ async function handler(req: ApiRequestProps, res: ApiResponseType<any>) {
return Promise.reject('AIPROXY_API_ENDPOINT or AIPROXY_API_TOKEN is not set'); return Promise.reject('AIPROXY_API_ENDPOINT or AIPROXY_API_TOKEN is not set');
} }
const { data } = await axios.post(`${baseUrl}/api/channel/`, req.body, { const { data } = await axiosWithoutSSRF.post(`${baseUrl}/api/channel/`, req.body, {
headers: { headers: {
Authorization: `Bearer ${token}` Authorization: `Bearer ${token}`
} }
......
import { getLogger, LogCategories } from '@fastgpt/service/common/logger';
import { type Method } from 'axios';
import { createProxyAxios } from '@fastgpt/service/common/api/axios';
const logger = getLogger(LogCategories.NETWORK);
const url = process.env.API_PROXY_URL;
const token = process.env.API_PROXY_TOKEN;
const instance = createProxyAxios({
baseURL: url,
timeout: 60000, // 超时时间
headers: {
Authorization: `Bearer ${token}`
}
});
/**
* 响应数据检查
*/
const checkRes = (data: any) => {
if (data === undefined) {
logger.info('api proxy data is empty');
return Promise.reject('服务器异常');
}
return data.data;
};
const responseError = (err: any) => {
logger.error('API proxy request failed', { error: err });
if (!err) {
return Promise.reject({ message: '未知错误' });
}
if (typeof err === 'string') {
return Promise.reject({ message: err });
}
if (typeof err.message === 'string') {
return Promise.reject({ message: err.message });
}
if (typeof err.data === 'string') {
return Promise.reject({ message: err.data });
}
if (err?.response?.data) {
return Promise.reject(err?.response?.data);
}
return Promise.reject(err);
};
const request = <T>(url: string, data: any, method: Method): Promise<T> => {
/* 去空 */
for (const key in data) {
if (data[key] === undefined) {
delete data[key];
}
}
return instance
.request({
url,
method,
data: ['POST', 'PUT'].includes(method) ? data : undefined,
params: !['POST', 'PUT'].includes(method) ? data : undefined
})
.then((res) => checkRes(res.data))
.catch((err) => responseError(err));
};
// TODO: channel crud
export const ApiProxy = {};
...@@ -2,9 +2,8 @@ import { UsageItemTypeEnum, UsageSourceEnum } from '@fastgpt/global/support/wall ...@@ -2,9 +2,8 @@ import { UsageItemTypeEnum, UsageSourceEnum } from '@fastgpt/global/support/wall
import { createUsage, concatUsage } from '@fastgpt/service/support/wallet/usage/controller'; import { createUsage, concatUsage } from '@fastgpt/service/support/wallet/usage/controller';
import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils'; import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils';
import { i18nT } from '@fastgpt/web/i18n/utils'; import { i18nT } from '@fastgpt/web/i18n/utils';
import { getDefaultTTSModel } from '@fastgpt/service/core/ai/model'; import { getDefaultSTTModel } from '@fastgpt/service/core/ai/model';
import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type'; import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
import type { HelperBotTypeEnumType } from '@fastgpt/global/core/chat/helperBot/type';
export const pushHelperBotUsage = ({ export const pushHelperBotUsage = ({
teamId, teamId,
...@@ -246,7 +245,7 @@ export const pushWhisperUsage = ({ ...@@ -246,7 +245,7 @@ export const pushWhisperUsage = ({
tmbId: string; tmbId: string;
duration: number; duration: number;
}) => { }) => {
const whisperModel = getDefaultTTSModel(); const whisperModel = getDefaultSTTModel();
if (!whisperModel) return; if (!whisperModel) return;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment