Permission (#1687)

Co-authored-by: Archer <545436317@qq.com>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
Archer
2024-06-04 17:52:00 +08:00
committed by GitHub
parent fcb915c988
commit 19c8a06d51
109 changed files with 2291 additions and 1091 deletions

View File

@@ -31,7 +31,6 @@ export type FastGPTFeConfigsType = {
show_openai_account?: boolean;
show_promotion?: boolean;
show_team_chat?: boolean;
hide_app_flow?: boolean;
concatMd?: string;
docUrl?: string;
chatbotUrl?: string;

View File

@@ -0,0 +1,12 @@
import { PermissionValueType } from '../../support/permission/type';
export type UpdateAppCollaboratorBody = {
appId: string;
tmbIds: string[];
permission: PermissionValueType;
};
export type AppCollaboratorDeleteParams = {
appId: string;
tmbId: string;
};

View File

@@ -7,6 +7,8 @@ import { SelectedDatasetType } from '../workflow/api';
import { DatasetSearchModeEnum } from '../dataset/constants';
import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d';
import { StoreEdgeItemType } from '../workflow/type/edge';
import { PermissionValueType } from '../../support/permission/type';
import { AppPermission } from '../../support/permission/app/controller';
export type AppSchema = {
_id: string;
@@ -27,9 +29,9 @@ export type AppSchema = {
scheduledTriggerConfig?: AppScheduledTriggerConfigType | null;
scheduledTriggerNextTime?: Date;
permission: `${PermissionTypeEnum}`;
inited?: boolean;
teamTags: string[];
defaultPermission: PermissionValueType;
};
export type AppListItemType = {
@@ -37,13 +39,12 @@ export type AppListItemType = {
name: string;
avatar: string;
intro: string;
isOwner: boolean;
permission: `${PermissionTypeEnum}`;
defaultPermission: PermissionValueType;
permission: AppPermission;
};
export type AppDetailType = AppSchema & {
isOwner: boolean;
canWrite: boolean;
permission: AppPermission;
};
export type AppSimpleEditFormType = {

View File

@@ -0,0 +1,20 @@
import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant';
import { PermissionListType } from '../type';
export enum AppPermissionKeyEnum {}
export const AppPermissionList: PermissionListType = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read],
description: '可使用该应用进行对话'
},
[PermissionKeyEnum.write]: {
...PermissionList[PermissionKeyEnum.write],
description: '可查看和编辑应用'
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
description: '写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限'
}
};
export const AppDefaultPermission = NullPermission;

View File

@@ -0,0 +1,15 @@
import { PerConstructPros, Permission } from '../controller';
import { AppDefaultPermission } from './constant';
export class AppPermission extends Permission {
constructor(props?: PerConstructPros) {
if (!props) {
props = {
per: AppDefaultPermission
};
} else if (!props?.per) {
props.per = AppDefaultPermission;
}
super(props);
}
}

View File

View File

@@ -0,0 +1,9 @@
import { PermissionValueType } from './type';
export type CollaboratorItemType = {
teamId: string;
tmbId: string;
permission: PermissionValueType;
name: string;
avatar: string;
};

View File

@@ -1,3 +1,6 @@
import { Permission } from './controller';
import { PermissionListType } from './type';
export enum AuthUserTypeEnum {
token = 'token',
root = 'root',
@@ -21,8 +24,41 @@ export const PermissionTypeMap = {
}
};
export enum ResourceTypeEnum {
export enum PerResourceTypeEnum {
team = 'team',
app = 'app',
dataset = 'dataset'
}
/* new permission */
export enum PermissionKeyEnum {
read = 'read',
write = 'write',
manage = 'manage'
}
export const PermissionList: PermissionListType = {
[PermissionKeyEnum.read]: {
name: '读权限',
description: '',
value: 0b100,
checkBoxType: 'single'
},
[PermissionKeyEnum.write]: {
name: '写权限',
description: '',
value: 0b110, // 如果某个资源有特殊要求,再重写这个值
checkBoxType: 'single'
},
[PermissionKeyEnum.manage]: {
name: '管理员',
description: '',
value: 0b111,
checkBoxType: 'single'
}
};
export const NullPermission = 0;
export const OwnerPermissionVal = ~0 >>> 0;
export const ReadPermissionVal = PermissionList['read'].value;
export const WritePermissionVal = PermissionList['write'].value;
export const ManagePermissionVal = PermissionList['manage'].value;

View File

@@ -0,0 +1,71 @@
import { PermissionValueType } from './type';
import { PermissionList, NullPermission, OwnerPermissionVal } from './constant';
export type PerConstructPros = {
per?: PermissionValueType;
isOwner?: boolean;
};
// the Permission helper class
export class Permission {
value: PermissionValueType;
isOwner: boolean;
hasManagePer: boolean;
hasWritePer: boolean;
hasReadPer: boolean;
constructor(props?: PerConstructPros) {
const { per = NullPermission, isOwner = false } = props || {};
if (isOwner) {
this.value = OwnerPermissionVal;
} else {
this.value = per;
}
this.isOwner = isOwner;
this.hasManagePer = this.checkPer(PermissionList['manage'].value);
this.hasWritePer = this.checkPer(PermissionList['write'].value);
this.hasReadPer = this.checkPer(PermissionList['read'].value);
}
// add permission(s)
// it can be chaining called.
// @example
// const perm = new Permission(permission)
// perm.add(PermissionList['read'])
// perm.add(PermissionList['read'], PermissionList['write'])
// perm.add(PermissionList['read']).add(PermissionList['write'])
addPer(...perList: PermissionValueType[]) {
for (let oer of perList) {
this.value = this.value | oer;
}
this.updatePermissions();
return this.value;
}
removePer(...perList: PermissionValueType[]) {
for (let per of perList) {
this.value = this.value & ~per;
}
this.updatePermissions();
return this.value;
}
checkPer(perm: PermissionValueType): boolean {
// if the permission is owner permission, only owner has this permission.
if (perm === OwnerPermissionVal) {
return this.value === OwnerPermissionVal;
} else if (this.hasManagePer) {
// The manager has all permissions except the owner permission
return true;
}
return (this.value & perm) === perm;
}
private updatePermissions() {
this.isOwner = this.value === OwnerPermissionVal;
this.hasManagePer = this.checkPer(PermissionList['manage'].value);
this.hasWritePer = this.checkPer(PermissionList['write'].value);
this.hasReadPer = this.checkPer(PermissionList['read'].value);
}
}

View File

@@ -1,6 +1,20 @@
import { AuthUserTypeEnum } from './constant';
import { TeamMemberWithUserSchema } from '../user/team/type';
import { AuthUserTypeEnum, PermissionKeyEnum } from './constant';
// PermissionValueType, the type of permission's value is a number, which is a bit field actually.
// It is spired by the permission system in Linux.
// The lowest 3 bits present the permission of reading, writing and managing.
// The higher bits are advanced permissions or extended permissions, which could be customized.
export type PermissionValueType = number;
export type PermissionListType<T = {}> = Record<
T | PermissionKeyEnum,
{
name: string;
description: string;
value: PermissionValueType;
checkBoxType: 'single' | 'multiple';
}
>;
export type AuthResponseType = {
teamId: string;
@@ -17,4 +31,9 @@ export type ResourcePermissionType = {
tmbId: string;
resourceType: ResourceType;
permission: PermissionValueType;
resourceId: string;
};
export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & {
tmbId: TeamMemberWithUserSchema;
};

View File

@@ -0,0 +1,16 @@
import { PermissionKeyEnum, PermissionList, ReadPermissionVal } from '../constant';
export const TeamPermissionList = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read]
},
[PermissionKeyEnum.write]: {
...PermissionList[PermissionKeyEnum.write]
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
description: '可邀请, 删除成员'
}
};
export const TeamDefaultPermissionVal = ReadPermissionVal;

View File

@@ -0,0 +1,15 @@
import { PerConstructPros, Permission } from '../controller';
import { TeamDefaultPermissionVal } from './constant';
export class TeamPermission extends Permission {
constructor(props?: PerConstructPros) {
if (!props) {
props = {
per: TeamDefaultPermissionVal
};
} else if (!props?.per) {
props.per = TeamDefaultPermissionVal;
}
super(props);
}
}

View File

@@ -1,22 +1,25 @@
import { TeamMemberRoleEnum } from '../user/team/constant';
import { PermissionTypeEnum } from './constant';
import { Permission } from './controller';
/* team public source, or owner source in team */
export function mongoRPermission({
teamId,
tmbId,
role
permission
}: {
teamId: string;
tmbId: string;
role: `${TeamMemberRoleEnum}`;
permission: Permission;
}) {
if (permission.isOwner) {
return {
teamId
};
}
return {
teamId,
...(role === TeamMemberRoleEnum.visitor && { permission: PermissionTypeEnum.public }),
...(role === TeamMemberRoleEnum.admin && {
$or: [{ permission: PermissionTypeEnum.public }, { tmbId }]
})
$or: [{ permission: PermissionTypeEnum.public }, { tmbId }]
};
}
export function mongoOwnerPermission({ teamId, tmbId }: { teamId: string; tmbId: string }) {

View File

@@ -1,4 +1,4 @@
import { PermissionValueType } from 'support/permission/type';
import { PermissionValueType } from '../../permission/type';
import { TeamMemberRoleEnum } from './constant';
import { LafAccountType, TeamMemberSchema } from './type';
@@ -22,7 +22,6 @@ export type UpdateTeamProps = {
/* ------------- member ----------- */
export type DelMemberProps = {
teamId: string;
memberId: string;
};
export type UpdateTeamMemberProps = {
@@ -46,7 +45,6 @@ export type InviteMemberResponse = Record<
>;
export type UpdateTeamMemberPermissionProps = {
teamId: string;
memberIds: string[];
permission: PermissionValueType;
};

View File

@@ -2,6 +2,7 @@ import type { UserModelSchema } from '../type';
import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant';
import { LafAccountType } from './type';
import { PermissionValueType, ResourcePermissionType } from '../../permission/type';
import { TeamPermission } from '../../permission/user/controller';
export type TeamSchema = {
_id: string;
@@ -49,7 +50,7 @@ export type TeamMemberWithTeamAndUserSchema = Omit<TeamMemberWithTeamSchema, 'us
userId: UserModelSchema;
};
export type TeamItemType = {
export type TeamTmbItemType = {
userId: string;
teamId: string;
teamName: string;
@@ -61,9 +62,8 @@ export type TeamItemType = {
defaultTeam: boolean;
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
canWrite: boolean;
lafAccount?: LafAccountType;
defaultPermission: PermissionValueType;
permission: TeamPermission;
};
export type TeamMemberItemType = {
@@ -75,7 +75,7 @@ export type TeamMemberItemType = {
// TODO: this should be deprecated.
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
permission: PermissionValueType;
permission: TeamPermission;
};
export type TeamTagItemType = {

View File

@@ -1,5 +1,5 @@
import { UserStatusEnum } from './constant';
import { TeamItemType } from './team/type';
import { TeamTmbItemType } from './team/type';
export type UserModelSchema = {
_id: string;
@@ -29,6 +29,6 @@ export type UserType = {
timezone: string;
promotionRate: UserModelSchema['promotionRate'];
openaiAccount: UserModelSchema['openaiAccount'];
team: TeamItemType;
team: TeamTmbItemType;
standardInfo?: standardInfoType;
};

View File

@@ -7,6 +7,7 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { AppDefaultPermission } from '@fastgpt/global/support/permission/app/constant';
export const AppCollectionName = 'apps';
@@ -98,6 +99,12 @@ const AppSchema = new Schema({
inited: {
type: Boolean
},
// the default permission of a app
defaultPermission: {
type: Number,
default: AppDefaultPermission
}
});

View File

@@ -0,0 +1,85 @@
/* Auth app permission */
import { MongoApp } from '../../../core/app/schema';
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
import { AuthPropsType } from '../type/auth.d';
import { parseHeaderCert } from '../controller';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { getResourcePermission } from '../controller';
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
import { AuthResponseType } from '../type/auth.d';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
export const authAppByTmbId = async ({
teamId,
tmbId,
appId,
per
}: {
teamId: string;
tmbId: string;
appId: string;
per: PermissionValueType;
}) => {
const { permission: tmbPer } = await getTmbInfoByTmbId({ tmbId });
const app = await (async () => {
// get app and per
const [app, rp] = await Promise.all([
MongoApp.findOne({ _id: appId, teamId }).lean(),
getResourcePermission({
teamId,
tmbId,
resourceId: appId,
resourceType: PerResourceTypeEnum.app
}) // this could be null
]);
if (!app) {
return Promise.reject(AppErrEnum.unExist);
}
const isOwner = tmbPer.isOwner || String(app.tmbId) === tmbId;
const Per = new AppPermission({ per: rp?.permission ?? app.defaultPermission, isOwner });
if (!Per.checkPer(per)) {
return Promise.reject(AppErrEnum.unAuthApp);
}
return {
...app,
permission: Per
};
})();
return { app };
};
export const authApp = async ({
appId,
per,
...props
}: AuthPropsType & {
appId: string;
}): Promise<
AuthResponseType & {
app: AppDetailType;
}
> => {
const result = await parseHeaderCert(props);
const { teamId, tmbId } = result;
const { app } = await authAppByTmbId({
teamId,
tmbId,
appId,
per
});
return {
...result,
permission: app.permission,
app
};
};

View File

@@ -1,72 +0,0 @@
import { MongoApp } from '../../../core/app/schema';
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
import { AuthModeType } from '../type';
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { parseHeaderCert } from '../controller';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { getTmbInfoByTmbId } from '../../user/team/controller';
// 模型使用权校验
export async function authApp({
appId,
per = 'owner',
...props
}: AuthModeType & {
appId: string;
}): Promise<
AuthResponseType & {
teamOwner: boolean;
app: AppDetailType;
role: `${TeamMemberRoleEnum}`;
}
> {
const result = await parseHeaderCert(props);
const { teamId, tmbId } = result;
const { role } = await getTmbInfoByTmbId({ tmbId });
const { app, isOwner, canWrite } = await (async () => {
// get app
const app = await MongoApp.findOne({ _id: appId, teamId }).lean();
if (!app) {
return Promise.reject(AppErrEnum.unExist);
}
const isOwner = String(app.tmbId) === tmbId || role === TeamMemberRoleEnum.owner;
const canWrite =
isOwner ||
(app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor);
if (per === 'r') {
if (!isOwner && app.permission !== PermissionTypeEnum.public) {
return Promise.reject(AppErrEnum.unAuthApp);
}
}
if (per === 'w' && !canWrite) {
return Promise.reject(AppErrEnum.unAuthApp);
}
if (per === 'owner' && !isOwner) {
return Promise.reject(AppErrEnum.unAuthApp);
}
return {
app: {
...app,
isOwner,
canWrite
},
isOwner,
canWrite
};
})();
return {
...result,
app,
role,
isOwner,
canWrite,
teamOwner: role === TeamMemberRoleEnum.owner
};
}

View File

@@ -1,96 +0,0 @@
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { AuthModeType } from '../type';
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { AppDetailType } from '@fastgpt/global/core/app/type';
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
import { parseHeaderCert } from '../controller';
import { MongoOutLink } from '../../outLink/schema';
import { MongoApp } from '../../../core/app/schema';
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { getTmbInfoByTmbId } from '../../user/team/controller';
/* crud outlink permission */
export async function authOutLinkCrud({
outLinkId,
per = 'owner',
...props
}: AuthModeType & {
outLinkId: string;
}): Promise<
AuthResponseType & {
app: AppDetailType;
outLink: OutLinkSchema;
}
> {
const result = await parseHeaderCert(props);
const { tmbId, teamId } = result;
const { role } = await getTmbInfoByTmbId({ tmbId });
const { app, outLink, isOwner, canWrite } = await (async () => {
const outLink = await MongoOutLink.findOne({ _id: outLinkId, teamId });
if (!outLink) {
throw new Error(OutLinkErrEnum.unExist);
}
const app = await MongoApp.findById(outLink.appId);
if (!app) {
return Promise.reject(AppErrEnum.unExist);
}
const isOwner = String(outLink.tmbId) === tmbId || role === TeamMemberRoleEnum.owner;
const canWrite =
isOwner ||
(app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor);
if (per === 'r' && !isOwner && app.permission !== PermissionTypeEnum.public) {
return Promise.reject(OutLinkErrEnum.unAuthLink);
}
if (per === 'w' && !canWrite) {
return Promise.reject(OutLinkErrEnum.unAuthLink);
}
if (per === 'owner' && !isOwner) {
return Promise.reject(OutLinkErrEnum.unAuthLink);
}
return {
app: {
...app,
isOwner: String(app.tmbId) === tmbId,
canWrite
},
outLink,
isOwner,
canWrite
};
})();
return {
...result,
app,
outLink,
isOwner,
canWrite
};
}
/* outLink exist and it app exist */
export async function authOutLinkValid({ shareId }: { shareId?: string }) {
if (!shareId) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
const shareChat = await MongoOutLink.findOne({ shareId });
if (!shareChat) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
return {
appId: shareChat.appId,
shareChat
};
}

View File

@@ -1,66 +0,0 @@
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { AuthModeType } from '../type';
import { TeamItemType } from '@fastgpt/global/support/user/team/type';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { parseHeaderCert } from '../controller';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { UserErrEnum } from '../../../../global/common/error/code/user';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
export async function authUserNotVisitor(props: AuthModeType): Promise<
AuthResponseType & {
team: TeamItemType;
role: `${TeamMemberRoleEnum}`;
}
> {
const { teamId, tmbId } = await parseHeaderCert(props);
const team = await getTmbInfoByTmbId({ tmbId });
if (team.role === TeamMemberRoleEnum.visitor) {
return Promise.reject(UserErrEnum.binVisitor);
}
return {
teamId,
tmbId,
team,
role: team.role,
isOwner: team.role === TeamMemberRoleEnum.owner, // teamOwner
canWrite: true
};
}
/* auth user role */
export async function authUserRole(props: AuthModeType): Promise<
AuthResponseType & {
role: `${TeamMemberRoleEnum}`;
teamOwner: boolean;
}
> {
const result = await parseHeaderCert(props);
const { role: userRole, canWrite } = await getTmbInfoByTmbId({ tmbId: result.tmbId });
return {
...result,
isOwner: true,
role: userRole,
teamOwner: userRole === TeamMemberRoleEnum.owner,
canWrite
};
}
/* auth teamMember in team role */
export async function authTeamOwner(props: AuthModeType): Promise<
AuthResponseType & {
role: `${TeamMemberRoleEnum}`;
teamOwner: boolean;
}
> {
const authRes = await authUserRole(props);
if (authRes.role !== TeamMemberRoleEnum.owner) {
return Promise.reject(TeamErrEnum.unAuthTeam);
}
return authRes;
}

View File

@@ -3,10 +3,76 @@ import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import jwt from 'jsonwebtoken';
import { NextApiResponse } from 'next';
import type { AuthModeType, ReqHeaderAuthType } from './type.d';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { AuthUserTypeEnum, PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { authOpenApiKey } from '../openapi/auth';
import { FileTokenQuery } from '@fastgpt/global/common/file/type';
import { MongoResourcePermission } from './schema';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { mongoSessionRun } from '../../common/mongo/sessionRun';
export const getResourcePermission = async ({
resourceType,
teamId,
tmbId,
resourceId
}: {
resourceType: PerResourceTypeEnum;
teamId: string;
tmbId: string;
resourceId?: string;
}) => {
const per = await MongoResourcePermission.findOne({
tmbId,
teamId,
resourceType,
resourceId
});
if (!per) {
return null;
}
return per;
};
export const delResourcePermissionById = (id: string) => {
return MongoResourcePermission.findByIdAndRemove(id);
};
export const updateResourcePermission = async ({
resourceId,
resourceType,
teamId,
tmbIdList,
permission
}: {
resourceId?: string;
resourceType: PerResourceTypeEnum;
teamId: string;
tmbIdList: string[];
permission: PermissionValueType;
}) => {
await mongoSessionRun((session) => {
return Promise.all(
tmbIdList.map((tmbId) =>
MongoResourcePermission.findOneAndUpdate(
{
resourceType,
teamId,
tmbId,
resourceId
},
{
permission
},
{
session,
upsert: true
}
)
)
);
});
};
/* 下面代码等迁移 */
/* create token */
export function createJWT(user: { _id?: string; team?: { teamId?: string; tmbId: string } }) {
const key = process.env.TOKEN_KEY as string;

View File

@@ -0,0 +1,69 @@
import { AppDetailType } from '@fastgpt/global/core/app/type';
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
import { parseHeaderCert } from '../controller';
import { MongoOutLink } from '../../outLink/schema';
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
import { AuthPropsType } from '../type/auth';
import { AuthResponseType } from '../type/auth';
import { authAppByTmbId } from '../app/auth';
/* crud outlink permission */
export async function authOutLinkCrud({
outLinkId,
per,
...props
}: AuthPropsType & {
outLinkId: string;
}): Promise<
AuthResponseType & {
app: AppDetailType;
outLink: OutLinkSchema;
}
> {
const result = await parseHeaderCert(props);
const { tmbId, teamId } = result;
const { app, outLink } = await (async () => {
const outLink = await MongoOutLink.findOne({ _id: outLinkId, teamId });
if (!outLink) {
throw new Error(OutLinkErrEnum.unExist);
}
const { app } = await authAppByTmbId({
teamId,
tmbId,
appId: outLink.appId,
per: ManagePermissionVal
});
return {
outLink,
app
};
})();
return {
...result,
permission: app.permission,
app,
outLink
};
}
/* outLink exist and it app exist */
export async function authOutLinkValid({ shareId }: { shareId?: string }) {
if (!shareId) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
const shareChat = await MongoOutLink.findOne({ shareId });
if (!shareChat) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
return {
appId: shareChat.appId,
shareChat
};
}

View File

@@ -1,16 +0,0 @@
import { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
import { MongoResourcePermission } from './schema';
import { ResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
export async function getResourcePermission({
tmbId,
resourceType
}: {
tmbId: string;
resourceType: ResourceTypeEnum;
}) {
return (await MongoResourcePermission.findOne({
tmbId,
resourceType
})) as ResourcePermissionType;
}

View File

@@ -1,127 +0,0 @@
// PermissionValueType, the type of permission's value is a number, which is a bit field actually.
// It is spired by the permission system in Linux.
// The lowest 3 bits present the permission of reading, writing and managing.
// The higher bits are advanced permissions or extended permissions, which could be customized.
export type PermissionValueType = number;
export type PermissionListType = { [key: string]: PermissionValueType };
export const NullPermission: PermissionValueType = 0;
// the Permission helper class
export class Permission {
value: PermissionValueType;
constructor(value: PermissionValueType) {
this.value = value;
}
// add permission(s)
// it can be chaining called.
// @example
// const perm = new Permission(permission)
// perm.add(PermissionList['read'])
// perm.add(PermissionList['read'], PermissionList['write'])
// perm.add(PermissionList['read']).add(PermissionList['write'])
add(...perm: PermissionValueType[]): Permission {
for (let p of perm) {
this.value = addPermission(this.value, p);
}
return this;
}
remove(...perm: PermissionValueType[]): Permission {
for (let p of perm) {
this.value = removePermission(this.value, p);
}
return this;
}
check(perm: PermissionValueType): Permission | boolean {
if (checkPermission(this.value, perm)) {
return this;
} else {
return false;
}
}
}
export function constructPermission(permList: PermissionValueType[]) {
return new Permission(NullPermission).add(...permList);
}
// The base Permissions List
// It can be extended, for example:
// export const UserPermissionList: PermissionListType = {
// ...PermissionList,
// 'Invite': 0b1000
// }
export const PermissionList: PermissionListType = {
Read: 0b100,
Write: 0b010,
Manage: 0b001
};
// list of permissions. could be customized.
// ! removal of the basic permissions is not recommended.
// const PermList: Array<PermissionType> = [ReadPerm, WritePerm, ManagePerm];
// return the list of permissions
// @param Perm(optional): the list of permissions to be added
// export function getPermList(Perm?: PermissionType[]): Array<PermissionType> {
// if (Perm === undefined) {
// return PermList;
// } else {
// return PermList.concat(Perm);
// }
// }
// check the permission
// @param [val]: The permission value to be checked
// @parma [perm]: Which Permission value will be checked
// @returns [booean]: if the [val] has the [perm]
// example:
// const perm = user.permission // get this permisiion from db or somewhere else
// const ok = checkPermission(perm, PermissionList['Read'])
export function checkPermission(val: PermissionValueType, perm: PermissionValueType): boolean {
return (val & perm) === perm;
}
// add the permission
// it can be chaining called.
// return the new permission value based on [val] added with [perm]
// @param val: PermissionValueType
// @param perm: PermissionValueType
// example:
// const basePerm = 0b001; // Manage only
export function addPermission(
val: PermissionValueType,
perm: PermissionValueType
): PermissionValueType {
return val | perm;
}
// remove the permission
export function removePermission(
val: PermissionValueType,
perm: PermissionValueType
): PermissionValueType {
return val & ~perm;
}
// export function parsePermission(val: PermissionValueType, list: PermissionValueType[]) {
// const result: [[string, boolean]] = [] as any;
// list.forEach((perm) => {
// result.push([perm[0], checkPermission(val, perm)]);
// });
// return result;
// }
export function hasManage(val: PermissionValueType) {
return checkPermission(val, PermissionList['Manage']);
}
export function hasWrite(val: PermissionValueType) {
return checkPermission(val, PermissionList['Write']);
}
export function hasRead(val: PermissionValueType) {
return checkPermission(val, PermissionList['Read']);
}

View File

@@ -2,11 +2,13 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { Model, connectionMongo } from '../../../common/mongo';
import { Model, connectionMongo } from '../../common/mongo';
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
import { ResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
const { Schema, model, models } = connectionMongo;
export const ResourcePermissionCollectionName = 'resource_permission';
export const ResourcePermissionSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
@@ -17,30 +19,36 @@ export const ResourcePermissionSchema = new Schema({
ref: TeamMemberCollectionName
},
resourceType: {
type: Object.values(ResourceTypeEnum),
type: Object.values(PerResourceTypeEnum),
required: true
},
permission: {
type: Number,
required: true
},
// Resrouce ID: App or DataSet or any other resource type.
// It is null if the resourceType is team.
resourceId: {
type: Schema.Types.ObjectId
}
});
try {
ResourcePermissionSchema.index({
teamId: 1,
resourceType: 1
});
ResourcePermissionSchema.index({
tmbId: 1,
resourceType: 1
});
ResourcePermissionSchema.index(
{
resourceType: 1,
teamId: 1,
tmbId: 1,
resourceId: 1
},
{
unique: true
}
);
} catch (error) {
console.log(error);
}
export const ResourcePermissionCollectionName = 'resource_permission';
export const MongoResourcePermission: Model<ResourcePermissionType> =
models[ResourcePermissionCollectionName] ||
model(ResourcePermissionCollectionName, ResourcePermissionSchema);

View File

@@ -1,4 +1,3 @@
import { getVectorCountByTeamId } from '../../common/vectorStore/controller';
import { getTeamPlanStatus, getTeamStandPlan } from '../../support/wallet/sub/utils';
import { MongoApp } from '../../core/app/schema';
import { MongoPlugin } from '../../core/plugin/schema';

View File

@@ -1,4 +1,5 @@
import { ApiRequestProps } from '../../type/next';
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
export type ReqHeaderAuthType = {
cookie?: string;
@@ -8,10 +9,11 @@ export type ReqHeaderAuthType = {
userid?: string;
authorization?: string;
};
export type AuthModeType = {
req: ApiRequestProps;
authToken?: boolean;
authRoot?: boolean;
authApiKey?: boolean;
per?: 'r' | 'w' | 'owner';
per?: PermissionValueType | 'r' | 'w' | 'owner'; // this is for compatibility
};

View File

@@ -0,0 +1,21 @@
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { Permission } from '@fastgpt/global/support/permission/controller';
import { ApiRequestProps } from '../../../type/next';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
export type AuthPropsType = {
req: ApiRequestProps;
authToken?: boolean;
authRoot?: boolean;
authApiKey?: boolean;
per: PermissionValueType;
};
export type AuthResponseType = {
teamId: string;
tmbId: string;
authType?: `${AuthUserTypeEnum}`;
appId?: string;
apikey?: string;
permission: Permission;
};

View File

@@ -0,0 +1,26 @@
import { AuthResponseType } from '../type/auth.d';
import { AuthPropsType } from '../type/auth.d';
import { TeamTmbItemType } from '@fastgpt/global/support/user/team/type';
import { parseHeaderCert } from '../controller';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
/* auth user role */
export async function authUserPer(props: AuthPropsType): Promise<
AuthResponseType & {
tmb: TeamTmbItemType;
}
> {
const result = await parseHeaderCert(props);
const tmb = await getTmbInfoByTmbId({ tmbId: result.tmbId });
if (!tmb.permission.checkPer(props.per)) {
return Promise.reject(TeamErrEnum.unAuthTeam);
}
return {
...result,
permission: tmb.permission,
tmb
};
}

View File

@@ -1,4 +1,4 @@
import { TeamItemType, TeamMemberWithTeamSchema } from '@fastgpt/global/support/user/team/type';
import { TeamTmbItemType, TeamMemberWithTeamSchema } from '@fastgpt/global/support/user/team/type';
import { ClientSession, Types } from '../../../common/mongo';
import {
TeamMemberRoleEnum,
@@ -8,13 +8,25 @@ import {
import { MongoTeamMember } from './teamMemberSchema';
import { MongoTeam } from './teamSchema';
import { UpdateTeamProps } from '@fastgpt/global/support/user/team/controller';
import { getResourcePermission } from '../../permission/controller';
import {
PerResourceTypeEnum,
ReadPermissionVal
} from '@fastgpt/global/support/permission/constant';
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
async function getTeamMember(match: Record<string, any>): Promise<TeamItemType> {
async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemType> {
const tmb = (await MongoTeamMember.findOne(match).populate('teamId')) as TeamMemberWithTeamSchema;
if (!tmb) {
return Promise.reject('member not exist');
}
const tmbPer = await getResourcePermission({
resourceType: PerResourceTypeEnum.team,
teamId: tmb.teamId._id,
tmbId: tmb._id
});
return {
userId: String(tmb.userId),
teamId: String(tmb.teamId._id),
@@ -27,9 +39,11 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamItemType>
role: tmb.role,
status: tmb.status,
defaultTeam: tmb.defaultTeam,
canWrite: tmb.role !== TeamMemberRoleEnum.visitor,
lafAccount: tmb.teamId.lafAccount,
defaultPermission: tmb.teamId.defaultPermission
permission: new TeamPermission({
per: tmbPer?.permission ?? tmb.teamId.defaultPermission,
isOwner: tmb.role === TeamMemberRoleEnum.owner
})
};
}

View File

@@ -3,7 +3,7 @@ const { Schema, model, models } = connectionMongo;
import { TeamSchema as TeamType } from '@fastgpt/global/support/user/team/type.d';
import { userCollectionName } from '../../user/schema';
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { NullPermission } from '../../permission/resourcePermission/permisson';
import { TeamDefaultPermissionVal } from '@fastgpt/global/support/permission/user/constant';
const TeamSchema = new Schema({
name: {
@@ -16,7 +16,7 @@ const TeamSchema = new Schema({
},
defaultPermission: {
type: Number,
default: NullPermission
default: TeamDefaultPermissionVal
},
avatar: {
type: String,

View File

@@ -180,6 +180,7 @@ export const iconPaths = {
kbTest: () => import('./icons/kbTest.svg'),
menu: () => import('./icons/menu.svg'),
minus: () => import('./icons/minus.svg'),
'modal/AddClb': () => import('./icons/modal/AddClb.svg'),
'modal/concat': () => import('./icons/modal/concat.svg'),
'modal/confirmPay': () => import('./icons/modal/confirmPay.svg'),
'modal/edit': () => import('./icons/modal/edit.svg'),

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8.00303 3.64414C6.69861 3.64414 5.64117 4.70158 5.64117 6.006C5.64117 7.31042 6.69861 8.36786 8.00303 8.36786C9.30744 8.36786 10.3649 7.31042 10.3649 6.006C10.3649 4.70158 9.30744 3.64414 8.00303 3.64414ZM3.9745 6.006C3.9745 3.78111 5.77813 1.97748 8.00303 1.97748C10.2279 1.97748 12.0315 3.78111 12.0315 6.006C12.0315 8.23089 10.2279 10.0345 8.00303 10.0345C5.77813 10.0345 3.9745 8.23089 3.9745 6.006ZM12.0234 2.73039C12.1961 2.30378 12.6819 2.09793 13.1085 2.27062C14.5833 2.86762 15.6261 4.31403 15.6261 6.006C15.6261 7.69797 14.5833 9.14439 13.1085 9.74138C12.6819 9.91407 12.1961 9.70823 12.0234 9.28161C11.8507 8.855 12.0565 8.36917 12.4831 8.19649C13.3502 7.84549 13.9595 6.9959 13.9595 6.006C13.9595 5.01611 13.3502 4.16651 12.4831 3.81552C12.0565 3.64283 11.8507 3.157 12.0234 2.73039ZM6.77537 11.563H10C10.4603 11.563 10.8334 11.9361 10.8334 12.3964C10.8334 12.8566 10.4603 13.2297 10 13.2297H6.80483C6.04904 13.2297 5.52394 13.2302 5.11329 13.2582C4.71013 13.2857 4.47852 13.337 4.30339 13.4095C3.72467 13.6492 3.26488 14.109 3.02516 14.6877C2.95262 14.8629 2.90135 15.0945 2.87385 15.4976C2.84583 15.9083 2.84538 16.4334 2.84538 17.1892C2.84538 17.6494 2.47228 18.0225 2.01204 18.0225C1.55181 18.0225 1.17871 17.6494 1.17871 17.1892L1.17871 17.1597C1.17871 16.4403 1.1787 15.8583 1.21105 15.3842C1.24434 14.8962 1.31469 14.462 1.48536 14.0499C1.89424 13.0628 2.67848 12.2786 3.66559 11.8697C4.07764 11.699 4.51182 11.6287 4.99984 11.5954C5.47392 11.563 6.05597 11.563 6.77537 11.563ZM15.5916 11.563C16.0518 11.563 16.4249 11.9361 16.4249 12.3964V13.9594H17.988C18.4482 13.9594 18.8213 14.3325 18.8213 14.7928C18.8213 15.253 18.4482 15.6261 17.988 15.6261H16.4249V17.1892C16.4249 17.6494 16.0518 18.0225 15.5916 18.0225C15.1314 18.0225 14.7583 17.6494 14.7583 17.1892V15.6261H13.1952C12.735 15.6261 12.3619 15.253 12.3619 14.7928C12.3619 14.3325 12.735 13.9594 13.1952 13.9594H14.7583V12.3964C14.7583 11.9361 15.1314 11.563 15.5916 11.563Z"
fill="#3370FF" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M7.00303 2.64414C5.69861 2.64414 4.64117 3.70158 4.64117 5.006C4.64117 6.31042 5.69861 7.36786 7.00303 7.36786C8.30744 7.36786 9.36488 6.31042 9.36488 5.006C9.36488 3.70158 8.30744 2.64414 7.00303 2.64414ZM2.9745 5.006C2.9745 2.78111 4.77813 0.977478 7.00303 0.977478C9.22792 0.977478 11.0315 2.78111 11.0315 5.006C11.0315 7.23089 9.22792 9.03453 7.00303 9.03453C4.77813 9.03453 2.9745 7.23089 2.9745 5.006ZM11.0234 1.73039C11.1961 1.30378 11.6819 1.09793 12.1085 1.27062C13.5833 1.86762 14.6261 3.31403 14.6261 5.006C14.6261 6.69797 13.5833 8.14439 12.1085 8.74138C11.6819 8.91407 11.1961 8.70823 11.0234 8.28161C10.8507 7.855 11.0565 7.36917 11.4831 7.19649C12.3502 6.84549 12.9595 5.9959 12.9595 5.006C12.9595 4.01611 12.3502 3.16651 11.4831 2.81552C11.0565 2.64283 10.8507 2.157 11.0234 1.73039ZM5.77537 10.563H9.00002C9.46026 10.563 9.83335 10.9361 9.83335 11.3964C9.83335 11.8566 9.46026 12.2297 9.00002 12.2297H5.80483C5.04904 12.2297 4.52394 12.2302 4.11329 12.2582C3.71013 12.2857 3.47852 12.337 3.30339 12.4095C2.72467 12.6492 2.26488 13.109 2.02516 13.6877C1.95262 13.8629 1.90135 14.0945 1.87385 14.4976C1.84583 14.9083 1.84538 15.4334 1.84538 16.1892C1.84538 16.6494 1.47228 17.0225 1.01204 17.0225C0.551807 17.0225 0.178711 16.6494 0.178711 16.1892L0.178711 16.1597C0.178705 15.4403 0.1787 14.8583 0.211047 14.3842C0.244344 13.8962 0.314685 13.462 0.485364 13.0499C0.894235 12.0628 1.67848 11.2786 2.66559 10.8697C3.07764 10.699 3.51182 10.6287 3.99984 10.5954C4.47392 10.563 5.05597 10.563 5.77537 10.563ZM14.5916 10.563C15.0518 10.563 15.4249 10.9361 15.4249 11.3964V12.9594H16.988C17.4482 12.9594 17.8213 13.3325 17.8213 13.7928C17.8213 14.253 17.4482 14.6261 16.988 14.6261H15.4249V16.1892C15.4249 16.6494 15.0518 17.0225 14.5916 17.0225C14.1314 17.0225 13.7583 16.6494 13.7583 16.1892V14.6261H12.1952C11.735 14.6261 11.3619 14.253 11.3619 13.7928C11.3619 13.3325 11.735 12.9594 12.1952 12.9594H13.7583V11.3964C13.7583 10.9361 14.1314 10.563 14.5916 10.563Z"
fill="#3370FF" />
</svg>
d="M7.00303 2.64414C5.69861 2.64414 4.64117 3.70158 4.64117 5.006C4.64117 6.31042 5.69861 7.36786 7.00303 7.36786C8.30744 7.36786 9.36488 6.31042 9.36488 5.006C9.36488 3.70158 8.30744 2.64414 7.00303 2.64414ZM2.9745 5.006C2.9745 2.78111 4.77813 0.977478 7.00303 0.977478C9.22792 0.977478 11.0315 2.78111 11.0315 5.006C11.0315 7.23089 9.22792 9.03453 7.00303 9.03453C4.77813 9.03453 2.9745 7.23089 2.9745 5.006ZM11.0234 1.73039C11.1961 1.30378 11.6819 1.09793 12.1085 1.27062C13.5833 1.86762 14.6261 3.31403 14.6261 5.006C14.6261 6.69797 13.5833 8.14439 12.1085 8.74138C11.6819 8.91407 11.1961 8.70823 11.0234 8.28161C10.8507 7.855 11.0565 7.36917 11.4831 7.19649C12.3502 6.84549 12.9595 5.9959 12.9595 5.006C12.9595 4.01611 12.3502 3.16651 11.4831 2.81552C11.0565 2.64283 10.8507 2.157 11.0234 1.73039ZM5.77537 10.563H9.00002C9.46026 10.563 9.83335 10.9361 9.83335 11.3964C9.83335 11.8566 9.46026 12.2297 9.00002 12.2297H5.80483C5.04904 12.2297 4.52394 12.2302 4.11329 12.2582C3.71013 12.2857 3.47852 12.337 3.30339 12.4095C2.72467 12.6492 2.26488 13.109 2.02516 13.6877C1.95262 13.8629 1.90135 14.0945 1.87385 14.4976C1.84583 14.9083 1.84538 15.4334 1.84538 16.1892C1.84538 16.6494 1.47228 17.0225 1.01204 17.0225C0.551807 17.0225 0.178711 16.6494 0.178711 16.1892L0.178711 16.1597C0.178705 15.4403 0.1787 14.8583 0.211047 14.3842C0.244344 13.8962 0.314685 13.462 0.485364 13.0499C0.894235 12.0628 1.67848 11.2786 2.66559 10.8697C3.07764 10.699 3.51182 10.6287 3.99984 10.5954C4.47392 10.563 5.05597 10.563 5.77537 10.563ZM14.5916 10.563C15.0518 10.563 15.4249 10.9361 15.4249 11.3964V12.9594H16.988C17.4482 12.9594 17.8213 13.3325 17.8213 13.7928C17.8213 14.253 17.4482 14.6261 16.988 14.6261H15.4249V16.1892C15.4249 16.6494 15.0518 17.0225 14.5916 17.0225C14.1314 17.0225 13.7583 16.6494 13.7583 16.1892V14.6261H12.1952C11.735 14.6261 11.3619 14.253 11.3619 13.7928C11.3619 13.3325 11.735 12.9594 12.1952 12.9594H13.7583V11.3964C13.7583 10.9361 14.1314 10.563 14.5916 10.563Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,16 +1,17 @@
import React, { forwardRef } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import { Box, BoxProps, SpinnerProps } from '@chakra-ui/react';
import Loading from '../MyLoading';
type Props = BoxProps & {
isLoading?: boolean;
text?: string;
size?: SpinnerProps['size'];
};
const MyBox = ({ text, isLoading, children, ...props }: Props, ref: any) => {
const MyBox = ({ text, isLoading, children, size, ...props }: Props, ref: any) => {
return (
<Box ref={ref} position={isLoading ? 'relative' : 'unset'} {...props}>
{isLoading && <Loading fixed={false} text={text} />}
{isLoading && <Loading fixed={false} text={text} size={size} />}
{children}
</Box>
);

View File

@@ -1,16 +1,18 @@
import React from 'react';
import { Spinner, Flex, Box } from '@chakra-ui/react';
import { Spinner, Flex, Box, SpinnerProps } from '@chakra-ui/react';
const Loading = ({
fixed = true,
text = '',
bg = 'rgba(255,255,255,0.5)',
zIndex = 1000
zIndex = 1000,
size = 'xl'
}: {
fixed?: boolean;
text?: string;
bg?: string;
zIndex?: number;
size?: SpinnerProps['size'];
}) => {
return (
<Flex
@@ -31,7 +33,7 @@ const Loading = ({
speed="0.65s"
emptyColor="myGray.100"
color="primary.500"
size="xl"
size={size}
/>
{text && (
<Box mt={2} color="primary.600" fontWeight={'bold'}>

View File

@@ -16,12 +16,12 @@ import { useLoading } from '../../../hooks/useLoading';
import MyIcon from '../Icon';
export type SelectProps = ButtonProps & {
value?: string;
value?: string | number;
placeholder?: string;
list: {
alias?: string;
label: string | React.ReactNode;
value: string;
value: string | number;
}[];
isLoading?: boolean;
onchange?: (val: any) => void;