Skip to content

Commit

Permalink
feat: instance permissions (#1044)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sky-FE authored Oct 31, 2024
1 parent d897e50 commit 977d01b
Show file tree
Hide file tree
Showing 14 changed files with 105 additions and 56 deletions.
25 changes: 25 additions & 0 deletions apps/nestjs-backend/src/features/auth/guard/permission.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,24 @@ export class PermissionGuard {
return true;
}

protected async instancePermissionChecker(action: Action) {
const isAdmin = this.cls.get('user.isAdmin');

if (!isAdmin) {
throw new ForbiddenException('User is not an admin');
}

const accessTokenId = this.cls.get('accessTokenId');
if (accessTokenId) {
const { scopes } = await this.permissionService.getAccessToken(accessTokenId);
const allowConfig = scopes.includes(action);
if (!allowConfig) {
throw new ForbiddenException(`Access token does not have ${action} permission`);
}
}
return true;
}

protected async permissionCheck(context: ExecutionContext) {
const permissions = this.reflector.getAllAndOverride<Action[] | undefined>(PERMISSIONS_KEY, [
context.getHandler(),
Expand All @@ -81,6 +99,13 @@ export class PermissionGuard {
if (!permissions?.length) {
return true;
}
// instance permission check
if (permissions?.includes('instance|update')) {
return this.instancePermissionChecker('instance|update');
}
if (permissions?.includes('instance|read')) {
return this.instancePermissionChecker('instance|read');
}
// space create permission check
if (permissions?.includes('space|create')) {
return await this.permissionCreateSpace();
Expand Down
6 changes: 3 additions & 3 deletions apps/nestjs-backend/src/features/setting/admin.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Controller, Param, Patch, UseGuards } from '@nestjs/common';
import { AdminGuard } from './admin.guard';
import { Controller, Param, Patch } from '@nestjs/common';
import { Permissions } from '../auth/decorators/permissions.decorator';
import { AdminService } from './admin.service';

@Controller('api/admin')
@Permissions('instance|update')
export class AdminController {
constructor(private readonly adminService: AdminService) {}

@UseGuards(AdminGuard)
@Patch('/plugin/:pluginId/publish')
async publishPlugin(@Param('pluginId') pluginId: string): Promise<void> {
await this.adminService.publishPlugin(pluginId);
Expand Down
27 changes: 0 additions & 27 deletions apps/nestjs-backend/src/features/setting/admin.guard.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Body, Controller, Get, Patch, UseGuards } from '@nestjs/common';
import { Body, Controller, Get, Patch } from '@nestjs/common';
import { IUpdateSettingRo, updateSettingRoSchema } from '@teable/openapi';
import type { ISettingVo } from '@teable/openapi';
import { ZodValidationPipe } from '../../zod.validation.pipe';
import { Permissions } from '../auth/decorators/permissions.decorator';
import { Public } from '../auth/decorators/public.decorator';
import { AdminGuard } from './admin.guard';
import { SettingService } from './setting.service';

@Controller('api/admin/setting')
Expand All @@ -16,8 +16,8 @@ export class SettingController {
return await this.settingService.getSetting();
}

@UseGuards(AdminGuard)
@Patch()
@Permissions('instance|update')
async updateSetting(
@Body(new ZodValidationPipe(updateSettingRoSchema))
updateSettingRo: IUpdateSettingRo
Expand Down
3 changes: 1 addition & 2 deletions apps/nestjs-backend/src/features/setting/setting.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
import { AdminGuard } from './admin.guard';
import { AdminService } from './admin.service';
import { SettingController } from './setting.controller';
import { SettingService } from './setting.service';

@Module({
controllers: [SettingController, AdminController],
exports: [SettingService],
providers: [SettingService, AdminGuard, AdminService],
providers: [SettingService, AdminService],
})
export class SettingModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type UpdateAccessTokenRo,
updateAccessTokenRoSchema,
} from '@teable/openapi';
import { useSession } from '@teable/sdk/hooks';
import { Spin } from '@teable/ui-lib/base';
import { Button, Input, Label, Separator } from '@teable/ui-lib/shadcn';
import { useTranslation } from 'next-i18next';
Expand All @@ -18,16 +19,6 @@ import { RefreshToken } from './RefreshToken';

export type IFormType = 'new' | 'edit';

const actionsPrefixes = [
ActionPrefix.Space,
ActionPrefix.Base,
ActionPrefix.Table,
ActionPrefix.View,
ActionPrefix.Field,
ActionPrefix.Record,
ActionPrefix.Automation,
];

type ISubmitData = {
new: CreateAccessTokenRo;
edit: UpdateAccessTokenRo;
Expand All @@ -54,6 +45,8 @@ export const AccessTokenForm = <T extends IFormType>(props: IAccessTokenForm<T>)
const { type, isLoading, onCancel, onSubmit, onRefresh, defaultData, id } = props;
const { t } = useTranslation(personalAccessTokenConfig.i18nNamespaces);

const { user } = useSession();

const [spaceIds, setSpaceIds] = useState<string[] | undefined | null>(defaultData?.spaceIds);
const [baseIds, setBaseIds] = useState<string[] | undefined | null>(defaultData?.baseIds);
const [expiredTime, setExpiredTime] = useState<string | undefined>(defaultData?.expiredTime);
Expand All @@ -63,6 +56,23 @@ export const AccessTokenForm = <T extends IFormType>(props: IAccessTokenForm<T>)
);
const [scopes, setScopes] = useState<string[]>(defaultData?.scopes || []);

const actionsPrefixes = useMemo(() => {
const prefixes = [
ActionPrefix.Space,
ActionPrefix.Base,
ActionPrefix.Table,
ActionPrefix.View,
ActionPrefix.Field,
ActionPrefix.Record,
ActionPrefix.Automation,
];

if (user.isAdmin) {
prefixes.push(ActionPrefix.Instance);
}
return prefixes;
}, [user.isAdmin]);

const disableSubmit = useMemo(() => {
if (type === 'new') {
return !createAccessTokenRoSchema.safeParse({
Expand Down
7 changes: 5 additions & 2 deletions packages/common-i18n/src/locales/en/sdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,9 @@
"automationUpdate": "Update automation",
"userEmailRead": "Read user email",
"recordHistoryRead": "Read record history",
"baseQuery": "Query base"
"baseQuery": "Query base",
"instanceRead": "Read instance",
"instanceUpdate": "Update instance"
}
},
"noun": {
Expand All @@ -292,7 +294,8 @@
"automation": "Automation",
"user": "User",
"recordHistory": "Record History",
"you": "You"
"you": "You",
"instance": "Instance"
},
"formula": {
"SUM": {
Expand Down
7 changes: 5 additions & 2 deletions packages/common-i18n/src/locales/fr/sdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,9 @@
"automationUpdate": "Mettre à jour une automatisation",
"userEmailRead": "Lire l'email de l'utilisateur",
"recordHistoryRead": "Lire l'historique des enregistrements",
"baseQuery": "Interroger la base"
"baseQuery": "Interroger la base",
"instanceRead": "Lire l'instance",
"instanceUpdate": "Mettre à jour l'instance"
}
},
"noun": {
Expand All @@ -286,7 +288,8 @@
"record": "Enregistrement",
"automation": "Automatisation",
"user": "Utilisateur",
"recordHistory": "Historique des enregistrements"
"recordHistory": "Historique des enregistrements",
"instance": "Instance"
},
"formula": {
"SUM": {
Expand Down
7 changes: 5 additions & 2 deletions packages/common-i18n/src/locales/ja/sdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,9 @@
"automationUpdate": "オートメーションの更新",
"userEmailRead": "ユーザーメールの読み取り",
"recordHistoryRead": "レコード履歴の読み取り",
"baseQuery": "クエリベース"
"baseQuery": "クエリベース",
"instanceRead": "インスタンスの読み取り",
"instanceUpdate": "インスタンスの更新"
}
},
"noun": {
Expand All @@ -273,7 +275,8 @@
"record": "レコード",
"automation": "オートメーション",
"user": "ユーザー",
"recordHistory": "レコード履歴"
"recordHistory": "レコード履歴",
"instance": "インスタンス"
},
"formula": {
"SUM": {
Expand Down
7 changes: 5 additions & 2 deletions packages/common-i18n/src/locales/ru/sdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,9 @@
"automationUpdate": "Обновить автоматизацию",
"userEmailRead": "Читать электронную почту пользователя",
"recordHistoryRead": "Читать историю записей",
"baseQuery": "Запрос базы"
"baseQuery": "Запрос базы",
"instanceRead": "Читать экземпляр",
"instanceUpdate": "Обновить экземпляр"
}
},
"noun": {
Expand All @@ -289,7 +291,8 @@
"automation": "Автоматизация",
"user": "Пользователь",
"recordHistory": "История записи",
"you": "Вы"
"you": "Вы",
"instance": "Экземпляр"
},
"formula": {
"SUM": {
Expand Down
7 changes: 5 additions & 2 deletions packages/common-i18n/src/locales/zh/sdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"automation": "自动化",
"user": "用户",
"recordHistory": "记录历史",
"you": ""
"you": "",
"instance": "实例"
},
"preview": {
"previewFileLimit": "预览暂不支持{{size}}MB以上的附件, 请下载后预览",
Expand Down Expand Up @@ -292,7 +293,9 @@
"automationUpdate": "更新自动化",
"userEmailRead": "查看用户电子邮件",
"recordHistoryRead": "查看记录历史",
"baseQuery": "查询数据库"
"baseQuery": "查询数据库",
"instanceRead": "查看实例",
"instanceUpdate": "更新实例"
}
},
"formula": {
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/auth/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum ActionPrefix {
Automation = 'automation',
User = 'user',
TableRecordHistory = 'table_record_history',
Instance = 'instance',
}

export const spaceActions = [
Expand Down Expand Up @@ -93,6 +94,10 @@ export const tableRecordHistoryActions = ['table_record_history|read'] as const;
export const tableRecordHistoryActionSchema = z.enum(tableRecordHistoryActions);
export type TableRecordHistoryAction = z.infer<typeof tableRecordHistoryActionSchema>;

export const instanceActions = ['instance|read', 'instance|update'] as const;
export const instanceActionSchema = z.enum(instanceActions);
export type InstanceAction = z.infer<typeof instanceActionSchema>;

export type Action =
| SpaceAction
| BaseAction
Expand All @@ -102,7 +107,8 @@ export type Action =
| RecordAction
| AutomationAction
| UserAction
| TableRecordHistoryAction;
| TableRecordHistoryAction
| InstanceAction;

export type ActionPrefixMap = {
[ActionPrefix.Space]: SpaceAction[];
Expand All @@ -114,6 +120,7 @@ export type ActionPrefixMap = {
[ActionPrefix.Automation]: AutomationAction[];
[ActionPrefix.User]: UserAction[];
[ActionPrefix.TableRecordHistory]: TableRecordHistoryAction[];
[ActionPrefix.Instance]: InstanceAction[];
};
export const actionPrefixMap: ActionPrefixMap = {
[ActionPrefix.Space]: [...spaceActions],
Expand All @@ -125,4 +132,5 @@ export const actionPrefixMap: ActionPrefixMap = {
[ActionPrefix.Automation]: [...automationActions],
[ActionPrefix.TableRecordHistory]: [...tableRecordHistoryActions],
[ActionPrefix.User]: [...userActions],
[ActionPrefix.Instance]: [...instanceActions],
};
10 changes: 10 additions & 0 deletions packages/core/src/auth/role/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export const RolePermission: Record<IRole, Record<Action, boolean>> = {
'automation|read': true,
'automation|update': true,
'user|email_read': true,
'instance|read': false,
'instance|update': false,
},
[Role.Creator]: {
'space|create': false,
Expand Down Expand Up @@ -94,6 +96,8 @@ export const RolePermission: Record<IRole, Record<Action, boolean>> = {
'automation|read': true,
'automation|update': true,
'user|email_read': true,
'instance|read': false,
'instance|update': false,
},
[Role.Editor]: {
'space|create': false,
Expand Down Expand Up @@ -140,6 +144,8 @@ export const RolePermission: Record<IRole, Record<Action, boolean>> = {
'automation|read': true,
'automation|update': false,
'user|email_read': true,
'instance|read': false,
'instance|update': false,
},
[Role.Commenter]: {
'space|create': false,
Expand Down Expand Up @@ -186,6 +192,8 @@ export const RolePermission: Record<IRole, Record<Action, boolean>> = {
'automation|read': true,
'automation|update': false,
'user|email_read': true,
'instance|read': false,
'instance|update': false,
},
[Role.Viewer]: {
'space|create': false,
Expand Down Expand Up @@ -232,5 +240,7 @@ export const RolePermission: Record<IRole, Record<Action, boolean>> = {
'automation|read': true,
'automation|update': false,
'user|email_read': true,
'instance|read': false,
'instance|update': false,
},
};
9 changes: 9 additions & 0 deletions packages/sdk/src/hooks/use-permission-actions-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ const actionsI18nMap: Record<
'base|query_data': {
description: 'permission.actionDescription.baseQuery',
},
'instance|read': {
description: 'permission.actionDescription.instanceRead',
},
'instance|update': {
description: 'permission.actionDescription.instanceUpdate',
},
};

const actionPrefixI18nMap: Record<ActionPrefix, { title: TKey }> = {
Expand Down Expand Up @@ -173,6 +179,9 @@ const actionPrefixI18nMap: Record<ActionPrefix, { title: TKey }> = {
[ActionPrefix.TableRecordHistory]: {
title: 'noun.recordHistory',
},
[ActionPrefix.Instance]: {
title: 'noun.instance',
},
};

export const usePermissionActionsStatic = () => {
Expand Down

0 comments on commit 977d01b

Please sign in to comment.