Skip to content

Commit

Permalink
refactor openapikey and outlink apis (#2134)
Browse files Browse the repository at this point in the history
* refactor: OpenAPIKey refactor

* refactor: outlink api refactor

fix: list return wrong data

* chore: remove deprecated type definition

* chore: remove throw Error. instead of Promise.reject

* fix: auth openapikey's owner

* fix: manager could read all keys
  • Loading branch information
FinleyGe authored Jul 24, 2024
1 parent a233ab9 commit a478621
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 243 deletions.
7 changes: 6 additions & 1 deletion packages/global/common/error/code/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { ErrType } from '../errorCode';
/* dataset: 506000 */
export enum OpenApiErrEnum {
unExist = 'openapiUnExist',
unAuth = 'openapiUnAuth'
unAuth = 'openapiUnAuth',
exceedLimit = 'openapiExceedLimit'
}
const errList = [
{
Expand All @@ -13,6 +14,10 @@ const errList = [
{
statusText: OpenApiErrEnum.unAuth,
message: '无权操作该 Api Key'
},
{
statusText: OpenApiErrEnum.exceedLimit,
message: '最多 10 组 API 密钥'
}
];
export default errList.reduce((acc, cur, index) => {
Expand Down
10 changes: 0 additions & 10 deletions packages/global/support/permission/type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@ export type PermissionListType<T = {}> = Record<
}
>;

export type AuthResponseType = {
teamId: string;
tmbId: string;
isOwner: boolean;
canWrite: boolean;
authType?: `${AuthUserTypeEnum}`;
appId?: string;
apikey?: string;
};

export type ResourcePermissionType = {
teamId: string;
tmbId: string;
Expand Down
45 changes: 20 additions & 25 deletions packages/service/support/permission/auth/openapi.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { AuthModeType } from '../type';
import { AuthModeType, AuthResponseType } from '../type';
import { OpenApiSchema } from '@fastgpt/global/support/openapi/type';
import { parseHeaderCert } from '../controller';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { MongoOpenApi } from '../../openapi/schema';
import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import {
OwnerPermissionVal,
ReadPermissionVal,
WritePermissionVal
} from '@fastgpt/global/support/permission/constant';
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
import { authAppByTmbId } from '../app/auth';
import { Permission } from '@fastgpt/global/support/permission/controller';

export async function authOpenApiKeyCrud({
id,
Expand All @@ -26,39 +22,38 @@ export async function authOpenApiKeyCrud({
const result = await parseHeaderCert(props);
const { tmbId, teamId } = result;

const { role, permission: tmbPer } = await getTmbInfoByTmbId({ tmbId });

const { openapi, isOwner, canWrite } = await (async () => {
const { openapi, permission } = await (async () => {
const openapi = await MongoOpenApi.findOne({ _id: id, teamId });

if (!openapi) {
throw new Error(OpenApiErrEnum.unExist);
}

const isOwner = String(openapi.tmbId) === tmbId || role === TeamMemberRoleEnum.owner;
const canWrite = isOwner || (String(openapi.tmbId) === tmbId && tmbPer.hasWritePer);

if (per === ReadPermissionVal && !canWrite) {
return Promise.reject(OpenApiErrEnum.unAuth);
if (!!openapi.appId) {
// if is not global openapi, then auth app
const { app } = await authAppByTmbId({ appId: openapi.appId!, tmbId, per });
return {
permission: app.permission,
openapi
};
}
if (per === WritePermissionVal && !canWrite) {
return Promise.reject(OpenApiErrEnum.unAuth);
}
if (per === OwnerPermissionVal && !isOwner) {
// if is global openapi, then auth openapi
const { permission: tmbPer } = await getTmbInfoByTmbId({ tmbId });

if (!tmbPer.checkPer(per) && tmbId !== String(openapi.tmbId)) {
return Promise.reject(OpenApiErrEnum.unAuth);
}

return {
openapi,
isOwner,
canWrite
permission: new Permission({
per
})
};
})();

return {
...result,
openapi,
isOwner,
canWrite
permission
};
}
88 changes: 88 additions & 0 deletions projects/app/src/pages/api/core/dataset/folder/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import {
PerResourceTypeEnum,
WritePermissionVal
} from '@fastgpt/global/support/permission/constant';
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
import { FolderImgUrl } from '@fastgpt/global/common/file/image/constants';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
import { getResourceAllClbs } from '@fastgpt/service/support/permission/controller';
import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission';
export type DatasetFolderCreateQuery = {};
export type DatasetFolderCreateBody = {
parentId?: string;
name: string;
intro: string;
};
export type DatasetFolderCreateResponse = {};
async function handler(
req: ApiRequestProps<DatasetFolderCreateBody, DatasetFolderCreateQuery>,
_res: ApiResponseType<any>
): Promise<DatasetFolderCreateResponse> {
const { parentId, name, intro } = req.body;

if (!name) {
return Promise.reject(CommonErrEnum.missingParams);
}

const { tmbId, teamId } = await authUserPer({
req,
per: WritePermissionVal,
authToken: true
});

const parentFolder = await (async () => {
if (parentId) {
return (
await authDataset({
datasetId: parentId,
per: WritePermissionVal,
req,
authToken: true
})
).dataset;
}
})();

await mongoSessionRun(async (session) => {
const app = await MongoDataset.create({
...parseParentIdInMongo(parentId),
avatar: FolderImgUrl,
name,
intro,
teamId,
tmbId,
type: DatasetTypeEnum.folder,
defaultPermission: !!parentFolder
? parentFolder.defaultPermission
: DatasetDefaultPermissionVal
});

if (parentId) {
const parentClbs = await getResourceAllClbs({
teamId,
resourceId: parentId,
resourceType: PerResourceTypeEnum.dataset,
session
});

await syncCollaborators({
resourceType: PerResourceTypeEnum.dataset,
teamId,
resourceId: app._id,
collaborators: parentClbs,
session
});
}
});

return {};
}
export default NextAPI(handler);
80 changes: 46 additions & 34 deletions projects/app/src/pages/api/support/openapi/create.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,56 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema';
import { customAlphabet } from 'nanoid';
import type { EditApiKeyProps } from '@/global/support/openapi/api';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import {
ManagePermissionVal,
WritePermissionVal
} from '@fastgpt/global/support/permission/constant';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, name, limit } = req.body as EditApiKeyProps;
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });

const count = await MongoOpenApi.find({ tmbId, appId }).countDocuments();

if (count >= 10) {
throw new Error('最多 10 组 API 秘钥');
async function handler(req: ApiRequestProps<EditApiKeyProps>): Promise<string> {
const { appId, name, limit } = req.body;
const { tmbId, teamId } = await (async () => {
if (!appId) {
// global apikey is being created, auth the tmb
const { teamId, tmbId } = await authUserPer({
req,
authToken: true,
per: WritePermissionVal
});
return { teamId, tmbId };
} else {
const { teamId, tmbId } = await authApp({
req,
per: ManagePermissionVal,
appId,
authToken: true
});
return { teamId, tmbId };
}
})();

const nanoid = getNanoid(Math.floor(Math.random() * 14) + 52);
const apiKey = `${global.systemEnv?.openapiPrefix || 'fastgpt'}-${nanoid}`;

await MongoOpenApi.create({
teamId,
tmbId,
apiKey,
appId,
name,
limit
});
const count = await MongoOpenApi.find({ tmbId, appId }).countDocuments();

jsonRes(res, {
data: apiKey
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
if (count >= 10) {
return Promise.reject(OpenApiErrEnum.exceedLimit);
}

const nanoid = getNanoid(Math.floor(Math.random() * 14) + 52);
const apiKey = `${global.systemEnv?.openapiPrefix || 'fastgpt'}-${nanoid}`;

await MongoOpenApi.create({
teamId,
tmbId,
apiKey,
appId,
name,
limit
});
return apiKey;
}

export default NextAPI(handler);
3 changes: 2 additions & 1 deletion projects/app/src/pages/api/support/openapi/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { connectToDatabase } from '@/service/mongo';
import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema';
import { authOpenApiKeyCrud } from '@fastgpt/service/support/permission/auth/openapi';
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { id } = req.query as { id: string };

if (!id) {
throw new Error('缺少参数');
return Promise.reject(CommonErrEnum.missingParams);
}

await authOpenApiKeyCrud({ req, authToken: true, id, per: OwnerPermissionVal });
Expand Down
61 changes: 25 additions & 36 deletions projects/app/src/pages/api/support/openapi/list.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,42 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema';
import type { GetApiKeyProps } from '@/global/support/openapi/api';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId } = req.query as GetApiKeyProps;
async function handler(req: ApiRequestProps<any, GetApiKeyProps>) {
const { appId } = req.query;

if (appId) {
await authApp({
req,
authToken: true,
appId,
per: ManagePermissionVal
});

const findResponse = await MongoOpenApi.find({
appId
}).sort({ _id: -1 });

return jsonRes(res, {
data: findResponse.map((item) => item.toObject())
});
}

const { teamId, tmbId, permission } = await authUserPer({
if (appId) {
// app-level apikey
await authApp({
req,
authToken: true,
appId,
per: ManagePermissionVal
});

const findResponse = await MongoOpenApi.find({
appId,
teamId,
...(!permission.isOwner && { tmbId })
appId
}).sort({ _id: -1 });

return jsonRes(res, {
data: findResponse.map((item) => item.toObject())
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
return findResponse.map((item) => item.toObject());
}
// global apikey
const { teamId, tmbId, permission } = await authUserPer({
req,
authToken: true
});

const findResponse = await MongoOpenApi.find({
appId,
teamId,
...(!permission.hasManagePer && { tmbId }) // if not manager, read own key
}).sort({ _id: -1 });

return findResponse.map((item) => item.toObject());
}

export default NextAPI(handler);
Loading

0 comments on commit a478621

Please sign in to comment.