From f791d43716921b1e3e04dceb49a43e2b36606a80 Mon Sep 17 00:00:00 2001 From: chenos Date: Sun, 10 Apr 2022 19:22:39 +0800 Subject: [PATCH] feat: acl provider (#279) * feat: acl provider * feat: menu server hook * feat: improve code * fix: fields filter --- packages/acl/src/acl-role.ts | 4 +- packages/acl/src/acl.ts | 2 +- packages/app/src/pages/index.tsx | 2 + packages/client/src/acl/ACLProvider.tsx | 100 +++++++++++++++++- .../Configuration/RolesResourcesActions.tsx | 28 +++-- .../src/block-provider/BlockProvider.tsx | 12 ++- .../route-switch/antd/admin-layout/index.tsx | 39 ++++--- .../antd/menu/Menu.Designer.tsx | 21 ++++ .../antd/menu/MenuItemInitializers/index.tsx | 21 ++++ .../antd/record-picker/InputRecordPicker.tsx | 12 ++- .../core/RemoteSchemaComponent.tsx | 5 +- .../buttons/CalendarActionInitializers.tsx | 1 + .../buttons/TableActionColumnInitializers.tsx | 3 + .../buttons/TableActionInitializers.tsx | 2 + packages/client/src/user/SwitchRole.tsx | 13 ++- packages/plugin-acl/src/actions/role-check.ts | 5 +- packages/plugin-acl/src/collections/roles.ts | 5 + packages/plugin-acl/src/server.ts | 43 +++++++- ...nd-menu-to-row.ts => bind-menu-to-role.ts} | 0 .../src/server-hooks/hooks/index.ts | 4 +- .../plugin-users/src/__tests__/role.test.ts | 6 +- packages/plugin-users/src/actions/users.ts | 11 +- .../src/middlewares/parseToken.ts | 7 ++ packages/plugin-users/src/server.ts | 2 +- 24 files changed, 294 insertions(+), 54 deletions(-) rename packages/plugin-ui-schema-storage/src/server-hooks/hooks/{bind-menu-to-row.ts => bind-menu-to-role.ts} (100%) diff --git a/packages/acl/src/acl-role.ts b/packages/acl/src/acl-role.ts index 003cc4c83..46f377851 100644 --- a/packages/acl/src/acl-role.ts +++ b/packages/acl/src/acl-role.ts @@ -1,6 +1,6 @@ -import { ACLResource } from './acl-resource'; -import { AvailableStrategyOptions } from './acl-available-strategy'; import { ACL, DefineOptions } from './acl'; +import { AvailableStrategyOptions } from './acl-available-strategy'; +import { ACLResource } from './acl-resource'; export interface RoleActionParams { fields?: string[]; diff --git a/packages/acl/src/acl.ts b/packages/acl/src/acl.ts index 9369146a9..5e78402f2 100644 --- a/packages/acl/src/acl.ts +++ b/packages/acl/src/acl.ts @@ -241,7 +241,7 @@ export class ACL extends EventEmitter { } if (!permission.can || typeof permission.can !== 'object') { - ctx.throw(403, 'no permission'); + ctx.throw(403, 'No permissions'); return; } diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx index 9249b9524..a4dceee9e 100644 --- a/packages/app/src/pages/index.tsx +++ b/packages/app/src/pages/index.tsx @@ -1,4 +1,5 @@ import { + ACLProvider, ACLShortcut, AdminLayout, AntdConfigProvider, @@ -99,6 +100,7 @@ const providers = [ ], BlockSchemaComponentProvider, AntdSchemaComponentProvider, + ACLProvider, ChinaRegionProvider, WorkflowRouteProvider, [DocumentTitleProvider, { addonAfter: 'NocoBase' }], diff --git a/packages/client/src/acl/ACLProvider.tsx b/packages/client/src/acl/ACLProvider.tsx index 8d6a58951..aba3d634e 100644 --- a/packages/client/src/acl/ACLProvider.tsx +++ b/packages/client/src/acl/ACLProvider.tsx @@ -1,13 +1,103 @@ +import { useFieldSchema } from '@formily/react'; +import { Spin } from 'antd'; import React, { createContext, useContext } from 'react'; +import { Redirect } from 'react-router-dom'; +import { useRequest } from '../api-client'; +import { useCollection } from '../collection-manager'; +import { SchemaComponentOptions, useDesignable } from '../schema-component'; export const ACLContext = createContext(null); -export function ACLProvider() { +export const ACLProvider = (props) => { return ( - + + {props.children} + + ); +}; - - ) -} +export const ACLRolesCheckProvider = (props) => { + const { setDesignable } = useDesignable(); + const result = useRequest( + { + url: 'roles:check', + }, + { + onSuccess(data) { + if (!data?.data?.allowConfigure) { + setDesignable(false); + } + }, + }, + ); + if (result.loading) { + return ; + } + if (result.error) { + return ; + } + return {props.children}; +}; + +export const useACLRoleContext = () => { + const ctx = useContext(ACLContext); + const data = ctx.data?.data; + return { + ...data, + getActionParams(path) { + return data?.actions?.[path]; + }, + }; +}; + +export const ACLAllowConfigure = (props) => { + const { allowAll, allowConfigure } = useACLRoleContext(); + if (allowAll || allowConfigure) { + return <>{props.children}; + } + return null; +}; + +export const ACLCollectionProvider = (props) => { + const { name } = useCollection(); + + return <>{props.children}; +}; + +export const ACLActionProvider = (props) => { + const { name } = useCollection(); + const fieldSchema = useFieldSchema(); + const { allowAll, getActionParams } = useACLRoleContext(); + if (!name || allowAll) { + return <>{props.children}; + } + const actionName = fieldSchema['x-action']; + const params = getActionParams([`${name}:${actionName}`]); + if (!params) { + return null; + } + return <>{props.children}; +}; + +export const ACLCollectionFieldProvider = (props) => { + return <>{props.children}; +}; + +export const ACLMenuItemProvider = (props) => { + const { allowAll, allowMenuItemIds = [] } = useACLRoleContext(); + const fieldSchema = useFieldSchema(); + if (allowAll) { + return <>{props.children}; + } + if (!fieldSchema['x-uid']) { + return <>{props.children}; + } + if (allowMenuItemIds.includes(fieldSchema['x-uid'])) { + return <>{props.children}; + } + return null; +}; export default ACLProvider; diff --git a/packages/client/src/acl/Configuration/RolesResourcesActions.tsx b/packages/client/src/acl/Configuration/RolesResourcesActions.tsx index e5d58ab5e..af1681e7e 100644 --- a/packages/client/src/acl/Configuration/RolesResourcesActions.tsx +++ b/packages/client/src/acl/Configuration/RolesResourcesActions.tsx @@ -2,6 +2,7 @@ import { FormItem, FormLayout } from '@formily/antd'; import { ArrayField } from '@formily/core'; import { connect, useField, useForm } from '@formily/react'; import { Checkbox, Table, Tag } from 'antd'; +import { isEmpty } from 'lodash'; import React, { createContext } from 'react'; import { useTranslation } from 'react-i18next'; import { useCollectionManager, useCompile, useRecord } from '../..'; @@ -13,6 +14,7 @@ const toActionMap = (arr: any[]) => { arr?.forEach?.((action) => { if (action.name) { obj[action.name] = action; + obj[action.name]['scope'] = isEmpty(action.scope) ? null : action.scope; } }); return obj; @@ -21,7 +23,16 @@ const toActionMap = (arr: any[]) => { export const RoleResourceCollectionContext = createContext({}); export const RolesResourcesActions = connect((props) => { - const { onChange } = props; + // const { onChange } = props; + const onChange = (values) => { + const items = values.map((item) => { + return { + ...item, + scope: isEmpty(item.scope) ? null : item.scope, + }; + }); + props.onChange(items); + }; const form = useForm(); const roleCollection = useRecord(); const availableActions = useAvailableActions(); @@ -39,7 +50,7 @@ export const RolesResourcesActions = connect((props) => { return action?.fields?.includes(fieldName); }; const availableActionsWithFields = availableActions.filter((action) => action.allowConfigureFields); - const fieldPermissions = collection?.fields?.map((field) => { + const fieldPermissions = collection?.fields?.filter(field => field.interface)?.map((field) => { const permission = { ...field }; for (const action of availableActionsWithFields) { permission[action.name] = inAction(action.name, field.name); @@ -52,7 +63,7 @@ export const RolesResourcesActions = connect((props) => { } else { actionMap[actionName] = { name: actionName, - fields: collection?.fields?.map?.((item) => item.name), + fields: collection?.fields?.filter(field => field.interface)?.map?.((item) => item.name), }; } onChange(Object.values(actionMap)); @@ -60,12 +71,15 @@ export const RolesResourcesActions = connect((props) => { const setScope = (actionName, scope) => { if (!actionMap[actionName]) { toggleAction(actionName); + actionMap[actionName]['scope'] = scope; + } else { + actionMap[actionName]['scope'] = scope; + onChange(Object.values(actionMap)); } - actionMap[actionName]['scope'] = scope; }; const allChecked = {}; for (const action of availableActionsWithFields) { - allChecked[action.name] = collection?.fields?.length === actionMap?.[action.name]?.fields?.length; + allChecked[action.name] = collection?.fields?.filter(field => field.interface)?.length === actionMap?.[action.name]?.fields?.length; } return (
@@ -122,7 +136,9 @@ export const RolesResourcesActions = connect((props) => { let scope = null; if (actionMap[item.name]) { enabled = true; - scope = actionMap[item.name]['scope']; + if (!item.onNewRecord) { + scope = actionMap[item.name]['scope']; + } } return { ...item, diff --git a/packages/client/src/block-provider/BlockProvider.tsx b/packages/client/src/block-provider/BlockProvider.tsx index 1ba482776..4b8b8e1c4 100644 --- a/packages/client/src/block-provider/BlockProvider.tsx +++ b/packages/client/src/block-provider/BlockProvider.tsx @@ -4,7 +4,7 @@ import { useRequest } from 'ahooks'; import template from 'lodash/template'; import React, { createContext, useContext } from 'react'; import { Link } from 'react-router-dom'; -import { TableFieldResource, useAPIClient, useRecord } from '../'; +import { ACLCollectionProvider, TableFieldResource, useAPIClient, useRecord } from '../'; import { CollectionProvider, useCollection, useCollectionManager } from '../collection-manager'; import { useRecordIndex } from '../record-provider'; @@ -89,7 +89,9 @@ export const useResourceAction = (props, opts = {}) => { const MaybeCollectionProvider = (props) => { const { collection } = props; return collection ? ( - {props.children} + + {props.children} + ) : ( <>{props.children} ); @@ -183,5 +185,9 @@ export const RecordLink = (props) => { const record = useRecord(); const { title, to, ...others } = props; const compiled = template(to || ''); - return {field.title}; + return ( + + {field.title} + + ); }; diff --git a/packages/client/src/route-switch/antd/admin-layout/index.tsx b/packages/client/src/route-switch/antd/admin-layout/index.tsx index 0c104b9e3..b8773acc7 100644 --- a/packages/client/src/route-switch/antd/admin-layout/index.tsx +++ b/packages/client/src/route-switch/antd/admin-layout/index.tsx @@ -3,6 +3,7 @@ import { Layout } from 'antd'; import React, { useRef, useState } from 'react'; import { useHistory, useRouteMatch } from 'react-router-dom'; import { + ACLAllowConfigure, ACLRolesCheckProvider, CurrentUser, CurrentUserProvider, findByUid, @@ -97,16 +98,18 @@ const InternalAdminLayout = (props: any) => {
- + + +
@@ -137,13 +140,15 @@ const InternalAdminLayout = (props: any) => { export const AdminLayout = (props) => { return ( - - - - - - - + + + + + + + + + ); }; diff --git a/packages/client/src/schema-component/antd/menu/Menu.Designer.tsx b/packages/client/src/schema-component/antd/menu/Menu.Designer.tsx index 772a28652..6868fce66 100644 --- a/packages/client/src/schema-component/antd/menu/Menu.Designer.tsx +++ b/packages/client/src/schema-component/antd/menu/Menu.Designer.tsx @@ -78,9 +78,16 @@ const InsertMenuItems = (props) => { type: 'void', title, 'x-component': 'Menu.SubMenu', + 'x-decorator': 'ACLMenuItemProvider', 'x-component-props': { icon, }, + 'x-server-hooks': [ + { + type: 'onSelfCreate', + method: 'bindMenuToRole', + }, + ], }); }} /> @@ -112,9 +119,16 @@ const InsertMenuItems = (props) => { type: 'void', title, 'x-component': 'Menu.Item', + 'x-decorator': 'ACLMenuItemProvider', 'x-component-props': { icon, }, + 'x-server-hooks': [ + { + type: 'onSelfCreate', + method: 'bindMenuToRole', + }, + ], properties: { page: { type: 'void', @@ -165,10 +179,17 @@ const InsertMenuItems = (props) => { type: 'void', title, 'x-component': 'Menu.URL', + 'x-decorator': 'ACLMenuItemProvider', 'x-component-props': { icon, href, }, + 'x-server-hooks': [ + { + type: 'onSelfCreate', + method: 'bindMenuToRole', + }, + ], }); }} /> diff --git a/packages/client/src/schema-component/antd/menu/MenuItemInitializers/index.tsx b/packages/client/src/schema-component/antd/menu/MenuItemInitializers/index.tsx index 80a3767fb..b35d976f9 100644 --- a/packages/client/src/schema-component/antd/menu/MenuItemInitializers/index.tsx +++ b/packages/client/src/schema-component/antd/menu/MenuItemInitializers/index.tsx @@ -79,9 +79,16 @@ export const GroupItem = itemWrap((props) => { type: 'void', title, 'x-component': 'Menu.SubMenu', + 'x-decorator': 'ACLMenuItemProvider', 'x-component-props': { icon, }, + 'x-server-hooks': [ + { + type: 'onSelfCreate', + method: 'bindMenuToRole', + }, + ], }); }} /> @@ -128,9 +135,16 @@ export const PageMenuItem = itemWrap((props) => { type: 'void', title, 'x-component': 'Menu.Item', + 'x-decorator': 'ACLMenuItemProvider', 'x-component-props': { icon, }, + 'x-server-hooks': [ + { + type: 'onSelfCreate', + method: 'bindMenuToRole', + }, + ], properties: { page: { type: 'void', @@ -197,10 +211,17 @@ export const LinkMenuItem = itemWrap((props) => { type: 'void', title, 'x-component': 'Menu.URL', + 'x-decorator': 'ACLMenuItemProvider', 'x-component-props': { icon, href, }, + 'x-server-hooks': [ + { + type: 'onSelfCreate', + method: 'bindMenuToRole', + }, + ], }); }} /> diff --git a/packages/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx b/packages/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx index 4c9359456..0a073b8d7 100644 --- a/packages/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx +++ b/packages/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx @@ -4,6 +4,7 @@ import React, { createContext, useContext, useState } from 'react'; import { useTableSelectorProps as useTsp } from '../../../block-provider/TableSelectorProvider'; import { CollectionProvider, useCollection } from '../../../collection-manager'; import { FormProvider, SchemaComponentOptions } from '../../core'; +import { useCompile } from '../../hooks'; import { ActionContext, useActionContext } from '../action'; import { useFieldNames } from './useFieldNames'; @@ -49,7 +50,7 @@ const useAssociation = (props) => { return association; } return getField(fieldSchema.name); -} +}; export const InputRecordPicker: React.FC = (props) => { const { value, multiple, onChange } = props; @@ -57,7 +58,14 @@ export const InputRecordPicker: React.FC = (props) => { const [visible, setVisible] = useState(false); const fieldSchema = useFieldSchema(); const collectionField = useAssociation(props); - const options = Array.isArray(value) ? value : value ? [value] : []; + const compile = useCompile(); + const options = (Array.isArray(value) ? value : value ? [value] : []).map(option => { + const label = option[fieldNames.label]; + return { + ...option, + [fieldNames.label]: compile(label), + }; + }); const [selectedRows, setSelectedRows] = useState(options); const values = options?.map((option) => option[fieldNames.value]); return ( diff --git a/packages/client/src/schema-component/core/RemoteSchemaComponent.tsx b/packages/client/src/schema-component/core/RemoteSchemaComponent.tsx index fbfb8e300..ab5d890bb 100644 --- a/packages/client/src/schema-component/core/RemoteSchemaComponent.tsx +++ b/packages/client/src/schema-component/core/RemoteSchemaComponent.tsx @@ -11,6 +11,7 @@ export interface RemoteSchemaComponentProps { scope?: any; uid?: string; onSuccess?: any; + components?: any; schemaTransform?: (schema: Schema) => Schema; render?: any; hidden?: any; @@ -20,7 +21,7 @@ export interface RemoteSchemaComponentProps { const defaultTransform = (s: Schema) => s; const RequestSchemaComponent: React.FC = (props) => { - const { onlyRenderProperties, hidden, scope, uid, onSuccess, schemaTransform = defaultTransform } = props; + const { onlyRenderProperties, hidden, scope, uid, components, onSuccess, schemaTransform = defaultTransform } = props; const { reset } = useSchemaComponentContext(); const conf = { url: `/uiSchemas:${onlyRenderProperties ? 'getProperties' : 'getJsonSchema'}/${uid}`, @@ -41,7 +42,7 @@ const RequestSchemaComponent: React.FC = (props) => } return ( - + ); }; diff --git a/packages/client/src/schema-initializer/buttons/CalendarActionInitializers.tsx b/packages/client/src/schema-initializer/buttons/CalendarActionInitializers.tsx index 3a81143e5..5e9a7bf4c 100644 --- a/packages/client/src/schema-initializer/buttons/CalendarActionInitializers.tsx +++ b/packages/client/src/schema-initializer/buttons/CalendarActionInitializers.tsx @@ -67,6 +67,7 @@ export const CalendarActionInitializers = { component: 'CreateActionInitializer', schema: { 'x-align': 'right', + 'x-decorator': 'ACLActionProvider', }, }, ], diff --git a/packages/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx b/packages/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx index 181100762..340730733 100644 --- a/packages/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx +++ b/packages/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx @@ -44,6 +44,7 @@ export const TableActionColumnInitializers = (props: any) => { schema: { 'x-component': 'Action.Link', 'x-action': 'view', + 'x-decorator': 'ACLActionProvider', }, }, { @@ -53,6 +54,7 @@ export const TableActionColumnInitializers = (props: any) => { schema: { 'x-component': 'Action.Link', 'x-action': 'update', + 'x-decorator': 'ACLActionProvider', }, }, { @@ -62,6 +64,7 @@ export const TableActionColumnInitializers = (props: any) => { schema: { 'x-component': 'Action.Link', 'x-action': 'destroy', + 'x-decorator': 'ACLActionProvider', }, }, ], diff --git a/packages/client/src/schema-initializer/buttons/TableActionInitializers.tsx b/packages/client/src/schema-initializer/buttons/TableActionInitializers.tsx index 5afc24aae..d2c68954f 100644 --- a/packages/client/src/schema-initializer/buttons/TableActionInitializers.tsx +++ b/packages/client/src/schema-initializer/buttons/TableActionInitializers.tsx @@ -24,6 +24,7 @@ export const TableActionInitializers = { component: 'CreateActionInitializer', schema: { 'x-align': 'right', + 'x-decorator': 'ACLActionProvider', }, }, { @@ -32,6 +33,7 @@ export const TableActionInitializers = { component: 'BulkDestroyActionInitializer', schema: { 'x-align': 'right', + 'x-decorator': 'ACLActionProvider', }, }, ], diff --git a/packages/client/src/user/SwitchRole.tsx b/packages/client/src/user/SwitchRole.tsx index 7d2ee6780..8d851e998 100644 --- a/packages/client/src/user/SwitchRole.tsx +++ b/packages/client/src/user/SwitchRole.tsx @@ -2,14 +2,22 @@ import { useCookieState } from 'ahooks'; import { Menu, Select } from 'antd'; import React from 'react'; import { useTranslation } from 'react-i18next'; +import { useAPIClient } from '../api-client'; import { useCurrentUserContext } from './CurrentUserProvider'; const useCurrentRoles = () => { const { data } = useCurrentUserContext(); - return data?.data?.roles || []; + return [ + ...(data?.data?.roles || []), + { + title: 'Anonymous', + name: 'anonymous', + }, + ]; }; export const SwitchRole = () => { + const api = useAPIClient(); const roles = useCurrentRoles(); const { t } = useTranslation(); const [roleName, setRoleName] = useCookieState('currentRoleName', { @@ -30,8 +38,9 @@ export const SwitchRole = () => { }} options={roles} value={roleName} - onChange={(roleName) => { + onChange={async (roleName) => { setRoleName(roleName); + await api.resource('users').setDefaultRole({ values: { roleName } }); window.location.reload(); }} /> diff --git a/packages/plugin-acl/src/actions/role-check.ts b/packages/plugin-acl/src/actions/role-check.ts index 772cac1bd..6b71e47a3 100644 --- a/packages/plugin-acl/src/actions/role-check.ts +++ b/packages/plugin-acl/src/actions/role-check.ts @@ -9,9 +9,10 @@ export async function checkAction(ctx, next) { }); ctx.body = { - role: ctx.app.acl.getRole(currentRole).toJSON(), + ...ctx.app.acl.getRole(currentRole).toJSON(), + allowAll: currentRole === 'root', allowConfigure: roleInstance.get('allowConfigure'), - roleMenuItemIds: roleInstance.get('menuUiSchemas').map((uiSchema) => uiSchema.get('x-uid')), + allowMenuItemIds: roleInstance.get('menuUiSchemas').map((uiSchema) => uiSchema.get('x-uid')), }; } diff --git a/packages/plugin-acl/src/collections/roles.ts b/packages/plugin-acl/src/collections/roles.ts index 22f1330c7..dd72107c2 100644 --- a/packages/plugin-acl/src/collections/roles.ts +++ b/packages/plugin-acl/src/collections/roles.ts @@ -48,6 +48,11 @@ export default { name: 'default', defaultValue: false, }, + { + type: 'boolean', + name: 'hidden', + defaultValue: false, + }, { type: 'boolean', name: 'allowConfigure', diff --git a/packages/plugin-acl/src/server.ts b/packages/plugin-acl/src/server.ts index 892e3b851..7ba340783 100644 --- a/packages/plugin-acl/src/server.ts +++ b/packages/plugin-acl/src/server.ts @@ -245,9 +245,14 @@ export class PluginACL extends Plugin { if (plugin.constructor.name !== 'UsersPlugin') { return; } - const repository = this.app.db.getRepository('roles'); - await repository.createMany({ + const roles = this.app.db.getRepository('roles'); + await roles.createMany({ records: [ + { + name: 'root', + title: 'Root', + hidden: true, + }, { name: 'admin', title: 'Admin', @@ -263,8 +268,42 @@ export class PluginACL extends Plugin { }, ], }); + const rolesResourcesScopes = this.app.db.getRepository('rolesResourcesScopes'); + await rolesResourcesScopes.createMany({ + records: [ + { + name: '{{t("All records")}}', + scope: {}, + }, + { + name: '{{t("Own records")}}', + scope: { + createdById: '{{ ctx.state.currentUser.id }}', + }, + }, + ], + }); }); + this.app.acl.skip('roles.menuUiSchemas', 'set', 'logged-in'); + this.app.acl.skip('roles.menuUiSchemas', 'toggle', 'logged-in'); + this.app.acl.skip('roles.menuUiSchemas', 'list', 'logged-in'); this.app.acl.skip('roles', 'check', 'logged-in'); + this.app.acl.skip('*', '*', (ctx) => { + return ctx.state.currentRole === 'root'; + }); + + // root role + this.app.resourcer.use(async (ctx, next) => { + const { actionName, resourceName } = ctx.action.params; + if (actionName === 'list' && resourceName === 'roles') { + ctx.action.mergeParams({ + filter: { + 'name.$ne': 'root', + }, + }); + } + await next(); + }); } async install() { diff --git a/packages/plugin-ui-schema-storage/src/server-hooks/hooks/bind-menu-to-row.ts b/packages/plugin-ui-schema-storage/src/server-hooks/hooks/bind-menu-to-role.ts similarity index 100% rename from packages/plugin-ui-schema-storage/src/server-hooks/hooks/bind-menu-to-row.ts rename to packages/plugin-ui-schema-storage/src/server-hooks/hooks/bind-menu-to-role.ts diff --git a/packages/plugin-ui-schema-storage/src/server-hooks/hooks/index.ts b/packages/plugin-ui-schema-storage/src/server-hooks/hooks/index.ts index 6f359c590..432295eb0 100644 --- a/packages/plugin-ui-schema-storage/src/server-hooks/hooks/index.ts +++ b/packages/plugin-ui-schema-storage/src/server-hooks/hooks/index.ts @@ -1,7 +1,7 @@ +import { bindMenuToRole } from './bind-menu-to-role'; import { hookFactory } from './factory'; -import { removeSchema } from './remove-schema'; -import { bindMenuToRole } from './bind-menu-to-row'; import { removeParentsIfNoChildren } from './remove-parents-if-no-children'; +import { removeSchema } from './remove-schema'; const hooks = [ hookFactory('onCollectionDestroy', 'removeSchema', removeSchema), diff --git a/packages/plugin-users/src/__tests__/role.test.ts b/packages/plugin-users/src/__tests__/role.test.ts index 79d6bf9c0..6426b4350 100644 --- a/packages/plugin-users/src/__tests__/role.test.ts +++ b/packages/plugin-users/src/__tests__/role.test.ts @@ -1,8 +1,8 @@ import Database, { BelongsToManyRepository } from '@nocobase/database'; import PluginACL from '@nocobase/plugin-acl'; +import UsersPlugin from '@nocobase/plugin-users'; import { MockServer, mockServer } from '@nocobase/test'; import { userPluginConfig } from './utils'; -import UsersPlugin from '@nocobase/plugin-users'; describe('role', () => { let api: MockServer; @@ -108,12 +108,12 @@ describe('role', () => { .agent() .post('/users:setDefaultRole') .send({ - defaultRole: 'test2', + roleName: 'test2', }) .set({ Authorization: `Bearer ${userToken}`, }); - + expect(response.statusCode).toEqual(200); const userRoles = await userRolesRepo.find(); diff --git a/packages/plugin-users/src/actions/users.ts b/packages/plugin-users/src/actions/users.ts index b406584f7..d08668213 100644 --- a/packages/plugin-users/src/actions/users.ts +++ b/packages/plugin-users/src/actions/users.ts @@ -1,7 +1,5 @@ import { Context, Next } from '@nocobase/actions'; import { PasswordField } from '@nocobase/database'; -import UsersPlugin from '../server'; - import crypto from 'crypto'; export async function check(ctx: Context, next: Next) { @@ -154,9 +152,14 @@ export async function changePassword(ctx: Context, next: Next) { export async function setDefaultRole(ctx: Context, next: Next) { const { - values: { defaultRole }, + values: { roleName }, } = ctx.action.params; + if (roleName == 'anonymous') { + ctx.body = 'ok'; + return next(); + } + const currentUserId = ctx.state.currentUser.id; await ctx.db.getRepository('rolesUsers').update({ @@ -171,7 +174,7 @@ export async function setDefaultRole(ctx: Context, next: Next) { await ctx.db.getRepository('rolesUsers').update({ filter: { userId: currentUserId, - roleName: defaultRole, + roleName, }, values: { default: true, diff --git a/packages/plugin-users/src/middlewares/parseToken.ts b/packages/plugin-users/src/middlewares/parseToken.ts index fb19d3a42..091ffd61f 100644 --- a/packages/plugin-users/src/middlewares/parseToken.ts +++ b/packages/plugin-users/src/middlewares/parseToken.ts @@ -13,6 +13,13 @@ export function parseToken(options?: { plugin: UsersPlugin }) { } function setCurrentRole(ctx, user) { + const roleName = ctx.get('X-Role'); + + if (roleName === 'anonymous') { + ctx.state.currentRole = roleName; + return; + } + const userRoles = user.get('roles'); let userRole; diff --git a/packages/plugin-users/src/server.ts b/packages/plugin-users/src/server.ts index 2869eea7a..8c0312644 100644 --- a/packages/plugin-users/src/server.ts +++ b/packages/plugin-users/src/server.ts @@ -118,7 +118,7 @@ export default class UsersPlugin extends Plugin { nickname: adminNickname, email: adminEmail, password: adminPassword, - roles: ['admin'], + roles: ['root', 'admin'], }, });