feat: add audit manager (#5601)

* feat: 部分代码

* feat: auditmanager

* feat: remove plugin and update Audit test case

* fix: build error

* feat: update audit value

* fix: some action like create remove can not work correctly

* fix: metadata data

* feat: update some dataSource

* fix: filterKeys error

* style: update code format

* feat: add getMetaData test & add audit log in specific module

* fix: bug

* refactor: registerAction and getAction

* refactor: registerAction and getAction

* fix: log

* fix: fix middleware

* chore: add getResourceUk

* chore: use response code as status

* chore: log error

* chore: get role from response header

* chore: auth signIn getUserInfo

* chore: add X-Forwarded-For

* chore: adjust IP acquisition and add log user updateprofile

* fix: getAction bug

* fix: get ip from header

* chore: register uiSchemas actions

* chore: register uiSchemas:insertAdjacent

* chore: record source and target

* chore:  record auth:changePassword

* chore: add getSourceAndTarget

* chore: auditManager tests

* fix: delete submodule

* chore: delete debug port

* fix: module not found

* chore: save path and swap the values of source and target

---------

Co-authored-by: yujian.sun <yujian.sun@dmall.com>
Co-authored-by: sunyujian <565974029@qq.com>
Co-authored-by: xilesun <2013xile@gmail.com>
This commit is contained in:
Zhi Chen 2024-11-28 12:32:47 +08:00 committed by GitHub
parent ac7098d0c5
commit 3d1e856d55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 933 additions and 4 deletions

View File

@ -34,6 +34,7 @@ server {
location ^~ /api/ {
proxy_pass http://127.0.0.1:13000/api/;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;

View File

@ -59,6 +59,7 @@ server {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 600;
proxy_send_timeout 600;

View File

@ -2,9 +2,7 @@
"version": "1.5.0-alpha.5",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": [
"--ignore-engines"
],
"npmClientArgs": ["--ignore-engines"],
"command": {
"version": {
"forcePublish": true,

View File

@ -70,6 +70,7 @@ server {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
add_header Cache-Control 'no-cache, no-store';
proxy_cache_bypass $http_upgrade;

View File

@ -0,0 +1,347 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { MockServer, mockServer } from '@nocobase/test';
import { SourceAndTarget, UserInfo } from '../audit-manager';
async function createApp(options: any = {}) {
const app = mockServer({
...options,
// 还会有些其他参数配置
});
// 这里可以补充一些需要特殊处理的逻辑,比如导入测试需要的数据表
return app;
}
async function startApp() {
const app = createApp();
return app;
}
describe('audit manager', () => {
let app: MockServer;
beforeEach(async () => {
app = await startApp();
});
afterEach(async () => {
// 运行测试后,清空数据库
await app.destroy();
// 只停止不清空数据库
await app.stop();
});
it('register action', () => {
app.auditManager.registerAction('create');
expect(app.auditManager.resources).toMatchInlineSnapshot(`
Map {
"__default__" => Map {
"create" => {
"name": "create",
},
},
}
`);
});
it('register actions with getMetaData and getUserInfo and getresourceUk', () => {
const getMetaData = () => {
return new Promise((resolve, reject) => {
resolve({
request: {
params: {
testData: 'testParamData',
},
body: {
testBody: 'testBodyData',
},
},
response: {
body: {
testBody: 'testBody',
},
},
});
});
};
const getUserInfo = () => {
return new Promise<UserInfo>((resolve, reject) => {
resolve({
userId: '1',
});
});
};
const getSourceAndTarget = () => {
return new Promise<SourceAndTarget>((resolve, reject) => {
resolve({
sourceCollection: 'users',
sourceRecordUK: '1',
targetCollection: 'roles',
targetRecordUK: '2',
});
});
};
app.auditManager.registerAction({ name: 'create', getMetaData, getUserInfo, getSourceAndTarget });
expect(app.auditManager.resources).toMatchInlineSnapshot(`
Map {
"__default__" => Map {
"create" => {
"getMetaData": [Function],
"getSourceAndTarget": [Function],
"getUserInfo": [Function],
"name": "create",
},
},
}
`);
});
it('register actions', () => {
app.auditManager.registerActions(['create', 'update', 'user:create', 'user:*', 'user:destory', 'role:*']);
expect(app.auditManager.resources).toMatchInlineSnapshot(`
Map {
"__default__" => Map {
"create" => {
"name": "create",
},
"update" => {
"name": "update",
},
},
"user" => Map {
"create" => {
"name": "user:create",
},
"__default__" => {
"name": "user:*",
},
"destory" => {
"name": "user:destory",
},
},
"role" => Map {
"__default__" => {
"name": "role:*",
},
},
}
`);
});
it('register actions with getMetaData', async () => {
const getMetaData = () => {
return new Promise((resolve, reject) => {
resolve({
request: {
params: {
testData: 'testParamData',
},
body: {
testBody: 'testBodyData',
},
},
response: {
body: {
testBody: 'testBody',
},
},
});
});
};
app.auditManager.registerActions(['pm:enable', { name: 'pm:enable', getMetaData }, 'pm:add']);
expect(app.auditManager.resources).toMatchInlineSnapshot(`
Map {
"pm" => Map {
"enable" => {
"getMetaData": [Function],
"name": "pm:enable",
},
"add" => {
"name": "pm:add",
},
},
}
`);
});
it('register actions with getUserInfo', async () => {
const getUserInfo = () => {
return new Promise<UserInfo>((resolve, reject) => {
resolve({
userId: '1',
roleName: 'test',
});
});
};
app.auditManager.registerActions(['pm:enable', { name: 'pm:enable', getUserInfo }, 'pm:add']);
expect(app.auditManager.resources).toMatchInlineSnapshot(`
Map {
"pm" => Map {
"enable" => {
"getUserInfo": [Function],
"name": "pm:enable",
},
"add" => {
"name": "pm:add",
},
},
}
`);
});
it('register actions with getSourceAndTarget', async () => {
const getSourceAndTarget = () => {
return new Promise<SourceAndTarget>((resolve, reject) => {
resolve({
sourceCollection: 'users',
sourceRecordUK: '1',
targetCollection: 'roles',
targetRecordUK: '2',
});
});
};
app.auditManager.registerActions(['pm:enable', { name: 'pm:enable', getSourceAndTarget }, 'pm:add']);
expect(app.auditManager.resources).toMatchInlineSnapshot(`
Map {
"pm" => Map {
"enable" => {
"getSourceAndTarget": [Function],
"name": "pm:enable",
},
"add" => {
"name": "pm:add",
},
},
}
`);
});
it('getAction', () => {
app.auditManager.registerActions(['create', 'update', 'user:create', 'user:*', 'user:destory', 'role:*']);
expect(app.auditManager.resources).toMatchInlineSnapshot(`
Map {
"__default__" => Map {
"create" => {
"name": "create",
},
"update" => {
"name": "update",
},
},
"user" => Map {
"create" => {
"name": "user:create",
},
"__default__" => {
"name": "user:*",
},
"destory" => {
"name": "user:destory",
},
},
"role" => Map {
"__default__" => {
"name": "role:*",
},
},
}
`);
const action = app.auditManager.getAction('update');
expect(action).toMatchInlineSnapshot(`
{
"name": "update",
}
`);
const action2 = app.auditManager.getAction('user:update');
expect(action2).toEqual(null);
const action3 = app.auditManager.getAction('update', 'app');
expect(action3).toMatchInlineSnapshot(`
{
"name": "update",
}
`);
const action4 = app.auditManager.getAction('update', 'role');
expect(action4).toMatchInlineSnapshot(`
{
"name": "role:*",
}
`);
const action5 = app.auditManager.getAction('update', 'user');
expect(action5).toMatchInlineSnapshot(`
{
"name": "user:*",
}
`);
const action6 = app.auditManager.getAction('update', 'department');
expect(action6).toMatchInlineSnapshot(`
{
"name": "update",
}
`);
const action7 = app.auditManager.getAction('destory');
expect(action7).toEqual(null);
});
it('getAction priority', () => {
app.auditManager.registerActions(['list']);
const action8 = app.auditManager.getAction('list', 'department');
expect(action8).toMatchInlineSnapshot(`
{
"name": "list",
}
`);
app.auditManager.registerActions(['department:*']);
const action9 = app.auditManager.getAction('list', 'department');
expect(action9).toMatchInlineSnapshot(`
{
"name": "department:*",
}
`);
app.auditManager.registerActions(['department:list']);
const action10 = app.auditManager.getAction('list', 'department');
expect(action10).toMatchInlineSnapshot(`
{
"name": "department:list",
}
`);
});
it('getAction default', () => {
app.auditManager.registerActions(['users:updateProfile', 'update']);
expect(app.auditManager.resources).toMatchInlineSnapshot(`
Map {
"users" => Map {
"updateProfile" => {
"name": "users:updateProfile",
},
},
"__default__" => Map {
"update" => {
"name": "update",
},
},
}
`);
const action = app.auditManager.getAction('update', 'users');
expect(action).toMatchInlineSnapshot(`
{
"name": "update",
}
`);
});
});

View File

@ -73,6 +73,7 @@ import { createPubSubManager, PubSubManager, PubSubManagerOptions } from './pub-
import { SyncMessageManager } from './sync-message-manager';
import packageJson from '../package.json';
import { AuditManager } from './audit-manager';
export type PluginType = string | typeof Plugin;
export type PluginConfiguration = PluginType | [PluginType, any];
@ -124,7 +125,9 @@ export interface ApplicationOptions {
pmSock?: string;
name?: string;
authManager?: AuthManagerOptions;
auditManager?: AuditManager;
lockManager?: LockManagerOptions;
/**
* @internal
*/
@ -382,6 +385,11 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
return this._authManager;
}
protected _auditManager: AuditManager;
get auditManager() {
return this._auditManager;
}
protected _locales: Locale;
/**
@ -1207,6 +1215,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
...(this.options.authManager || {}),
});
this._auditManager = new AuditManager();
this.resourceManager.define({
name: 'auth',
actions: authActions,

View File

@ -0,0 +1,346 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Context } from '@nocobase/actions';
import { head } from 'lodash';
import Stream from 'stream';
function isStream(obj) {
return (
obj instanceof Stream.Readable ||
obj instanceof Stream.Writable ||
obj instanceof Stream.Duplex ||
obj instanceof Stream.Transform
);
}
export interface AuditLog {
uuid: string;
dataSource: string;
resource: string;
action: string;
sourceCollection?: string;
sourceRecordUK?: string;
targetCollection?: string;
targetRecordUK?: string;
userId: string;
roleName: string;
ip: string;
ua: string;
status: number;
metadata?: Record<string, any>;
}
export interface UserInfo {
userId?: string;
roleName?: string;
}
export interface SourceAndTarget {
sourceCollection?: string;
sourceRecordUK?: string;
targetCollection?: string;
targetRecordUK?: string;
}
export interface AuditLogger {
log(auditLog: AuditLog): Promise<void>;
}
type Action =
| string
| {
name: string;
getMetaData?: (ctx: Context) => Promise<Record<string, any>>;
getUserInfo?: (ctx: Context) => Promise<UserInfo>;
getSourceAndTarget?: (ctx: Context) => Promise<SourceAndTarget>;
};
export class AuditManager {
logger: AuditLogger;
resources: Map<string, Map<string, Action>>;
constructor() {
this.resources = new Map();
}
public setLogger(logger: AuditLogger) {
this.logger = logger;
}
/**
*
*
*
* registerActions(['create'])
*
* resource:*
* registerActions(['app:*'])
*
* resouce:action
* registerAction(['pm:update'])
*
* getMetaData方法
*
* registerActions([
* 'create',
* { name: 'auth:signIn', getMetaData}
* ])
*
* getUserInfo方法
*
* registerActions([
* 'create',
* { name: 'auth:signIn', getUserInfo }
* ])
*
*
*
* Action1: registerActions(['create']);
*
* Action2: registerAction([{ name: 'user:*', getMetaData }]);
*
* Action3: registerAction([{ name: 'user:create', getMetaData }]);
*
* 对于user:create接口 Action3 > Action2 > Action1
*
* @param actions
*/
registerActions(actions: Action[]) {
actions.forEach((action) => {
this.registerAction(action);
});
}
/**
* registerActions
* @param action
*/
registerAction(action: Action) {
let originAction = '';
let getMetaData = null;
let getUserInfo = null;
let getSourceAndTarget = null;
if (typeof action === 'string') {
originAction = action;
} else {
originAction = action.name;
getMetaData = action.getMetaData;
getUserInfo = action.getUserInfo;
getSourceAndTarget = action.getSourceAndTarget;
}
// 解析originAction, 获取actionName, resourceName
const nameRegex = /^[a-zA-Z0-9_-]+$/;
const resourceWildcardRegex = /^([a-zA-Z0-9_-]+):\*$/;
const resourceAndActionRegex = /^([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)$/;
let resourceName = '';
let actionName = '';
if (nameRegex.test(originAction)) {
actionName = originAction;
resourceName = '__default__';
}
if (resourceWildcardRegex.test(originAction)) {
const match = originAction.match(resourceWildcardRegex);
resourceName = match[1];
actionName = '__default__';
}
if (resourceAndActionRegex.test(originAction)) {
const match = originAction.match(resourceAndActionRegex);
resourceName = match[1];
actionName = match[2];
}
if (!resourceName && !actionName) {
return;
}
let resource = this.resources.get(resourceName);
if (!resource) {
resource = new Map();
this.resources.set(resourceName, resource);
}
const saveAction: Action = {
name: originAction,
};
if (getMetaData) {
saveAction.getMetaData = getMetaData;
}
if (getUserInfo) {
saveAction.getUserInfo = getUserInfo;
}
if (getSourceAndTarget) {
saveAction.getSourceAndTarget = getSourceAndTarget;
}
resource.set(actionName, saveAction);
}
getAction(action: string, resource?: string) {
let resourceName = resource;
if (!resource) {
resourceName = '__default__';
}
if (resourceName === '__default__') {
const resourceActions = this.resources.get(resourceName);
if (resourceActions) {
const resourceAction = resourceActions.get(action);
if (resourceAction) {
return resourceAction;
}
}
} else {
const resourceActions = this.resources.get(resourceName);
if (resourceActions) {
let resourceAction = resourceActions.get(action);
if (resourceAction) {
return resourceAction;
} else {
resourceAction = resourceActions.get('__default__');
if (resourceAction) {
return resourceAction;
} else {
const defaultResourceActions = this.resources.get('__default__');
if (defaultResourceActions) {
const defaultResourceAction = defaultResourceActions.get(action);
if (defaultResourceAction) {
return defaultResourceAction;
}
}
}
}
} else {
const resourceActions = this.resources.get('__default__');
if (resourceActions) {
const resourceAction = resourceActions.get(action);
if (resourceAction) {
return resourceAction;
}
}
}
}
return null;
}
async getDefaultMetaData(ctx: any) {
let body: any = null;
if (ctx.body) {
if (!Buffer.isBuffer(ctx.body) && !isStream(ctx.body)) {
body = ctx.body;
}
}
return {
request: {
params: ctx.request.params,
query: ctx.request.query,
body: ctx.request.body,
path: ctx.request.path,
headers: {
'x-authenticator': ctx.request?.headers['x-authenticator'],
'x-locale': ctx.request?.headers['x-locale'],
'x-timezone': ctx.request?.headers['x-timezone'],
},
},
response: {
body,
},
};
}
formatAuditData(ctx: Context) {
const { resourceName } = ctx.action;
// 获取ip优先使用X-Forwarded-For如果没有则使用ctx.request.ip
const ipvalues = ctx.request.header['x-forwarded-for'];
let ipvalue = '';
if (ipvalues instanceof Array) {
ipvalue = ipvalues[0];
} else {
ipvalue = ipvalues;
}
const ips = ipvalue ? ipvalue?.split(/\s*,\s*/) : [];
const auditLog: AuditLog = {
uuid: ctx.reqId,
dataSource: (ctx.request.header['x-data-source'] || 'main') as string,
resource: resourceName,
action: ctx.action.actionName,
userId: ctx.state?.currentUser?.id,
roleName: ctx.state?.currentRole,
ip: ips.length > 0 ? ips[0] : ctx.request.ip,
ua: ctx.request.header['user-agent'],
status: ctx.response.status,
};
return auditLog;
}
async output(ctx: any, reqId: any, metadata?: Record<string, any>) {
try {
const { resourceName, actionName } = ctx.action;
const action: Action = this.getAction(actionName, resourceName);
if (!action) {
return;
}
const auditLog: AuditLog = this.formatAuditData(ctx);
auditLog.uuid = reqId;
auditLog.status = ctx.status;
if (typeof action !== 'string') {
if (action.getUserInfo) {
const userInfo = await action.getUserInfo(ctx);
if (userInfo) {
if (userInfo.userId) {
auditLog.userId = userInfo.userId;
}
if (userInfo.roleName) {
auditLog.roleName = userInfo.roleName;
}
}
}
if (action.getMetaData) {
const extra = await action.getMetaData(ctx);
auditLog.metadata = { ...metadata, ...extra };
} else {
const defaultMetaData = await this.getDefaultMetaData(ctx);
auditLog.metadata = { ...metadata, ...defaultMetaData };
}
if (action.getSourceAndTarget) {
const sourceAndTarget = await action.getSourceAndTarget(ctx);
if (sourceAndTarget) {
auditLog.sourceCollection = sourceAndTarget.sourceCollection;
auditLog.sourceRecordUK = sourceAndTarget.sourceRecordUK;
auditLog.targetCollection = sourceAndTarget.targetCollection;
auditLog.targetRecordUK = sourceAndTarget.targetRecordUK;
}
}
} else {
const defaultMetaData = await this.getDefaultMetaData(ctx);
auditLog.metadata = { ...metadata, ...defaultMetaData };
}
this.logger.log(auditLog);
} catch (err) {
ctx.log?.error(err);
}
}
// 中间件
middleware() {
return async (ctx: any, next: any) => {
const reqId = ctx.reqId;
let metadata = {};
try {
await next();
} catch (err) {
// 操作失败的时候
// HTTP相应状态码和error message 放到 metadata
metadata = {
status: ctx.status,
errMsg: err.message,
};
throw err;
} finally {
if (this.logger) {
this.output(ctx, reqId, metadata);
}
}
};
}
}

View File

@ -47,6 +47,8 @@ export function registerMiddlewares(app: Application, options: ApplicationOption
{ tag: 'generateReqId' },
);
app.use(app.auditManager.middleware(), { tag: 'audit', after: 'generateReqId' });
app.use(requestLogger(app.name, app.requestLogger, options.logger?.request), { tag: 'logger' });
app.use(

View File

@ -15,6 +15,7 @@ export * as middlewares from './middlewares';
export * from './migration';
export * from './plugin';
export * from './plugin-manager';
export * from './audit-manager';
export * from './pub-sub-manager';
export const OFFICIAL_PLUGIN_PREFIX = '@nocobase/plugin-';

View File

@ -455,6 +455,41 @@ export class PluginManager {
await this.app.emitAsync('afterLoadPlugin', plugin, options);
}
const getSourceAndTargetForAddAction = async (ctx: any) => {
const { packageName } = ctx.action.params;
return {
targetCollection: 'applicationPlugins',
targetRecordUK: packageName,
};
};
const getSourceAndTargetForUpdateAction = async (ctx: any) => {
let { packageName } = ctx.action.params;
if (ctx.file) {
packageName = ctx.request.body.packageName;
}
return {
targetCollection: 'applicationPlugins',
targetRecordUK: packageName,
};
};
const getSourceAndTargetForOtherActions = async (ctx: any) => {
const { filterByTk } = ctx.action.params;
return {
targetCollection: 'applicationPlugins',
targetRecordUK: filterByTk,
};
};
this.app.auditManager.registerActions([
{ name: 'pm:add', getSourceAndTarget: getSourceAndTargetForAddAction },
{ name: 'pm:update', getSourceAndTarget: getSourceAndTargetForUpdateAction },
{ name: 'pm:enable', getSourceAndTarget: getSourceAndTargetForOtherActions },
{ name: 'pm:disable', getSourceAndTarget: getSourceAndTargetForOtherActions },
{ name: 'pm:remove', getSourceAndTarget: getSourceAndTargetForOtherActions },
]);
this.app.log.debug('plugins loaded');
this.app.setMaintainingMessage('plugins loaded');
}

View File

@ -10,7 +10,6 @@
import { Cache } from '@nocobase/cache';
import { Model } from '@nocobase/database';
import { InstallOptions, Plugin } from '@nocobase/server';
import { resolve } from 'path';
import { namespace, presetAuthType, presetAuthenticator } from '../preset';
import authActions from './actions/auth';
import authenticatorsActions from './actions/authenticators';
@ -112,6 +111,126 @@ export class PluginAuthServer extends Plugin {
this.app.on('cache:del:auth', async ({ userId }) => {
await this.cache.del(`auth:${userId}`);
});
this.app.auditManager.registerActions([
{
name: 'auth:signIn',
getMetaData: async (ctx: any) => {
let body = {};
if (ctx.status === 200) {
body = {
data: {
...ctx.body.data,
token: undefined,
},
};
} else {
body = ctx.body;
}
return {
request: {
params: ctx.request?.params,
body: {
...ctx.request?.body,
password: undefined,
},
path: ctx.request?.path,
headers: {
'x-authenticator': ctx.request?.headers['x-authenticator'],
'x-locale': ctx.request?.headers['x-locale'],
'x-timezone': ctx.request?.headers['x-timezone'],
},
},
response: {
body,
},
};
},
getUserInfo: async (ctx: any) => {
if (!ctx.body?.data?.user) {
return null;
}
// 查询用户角色
const userId = ctx.body.data.user.id;
const user = await ctx.db.getRepository('users').findOne({
filterByTk: userId,
});
const roles = await user?.getRoles();
if (!roles) {
return {
userId,
};
} else {
if (roles.length === 1) {
return {
userId,
roleName: roles[0].name,
};
} else {
// 多角色的情况下暂时不返回角色名
return {
userId,
};
}
}
},
},
{
name: 'auth:signUp',
getMetaData: async (ctx: any) => {
return {
request: {
params: ctx.request?.params,
body: {
...ctx.request?.body,
password: undefined,
confirm_password: undefined,
},
path: ctx.request?.path,
headers: {
'x-authenticator': ctx.request?.headers['x-authenticator'],
'x-locale': ctx.request?.headers['x-locale'],
'x-timezone': ctx.request?.headers['x-timezone'],
},
},
response: {
body: {
...ctx.response?.body,
token: undefined,
},
},
};
},
},
{
name: 'auth:changePassword',
getMetaData: async (ctx: any) => {
return {
request: {
params: ctx.request.params,
query: ctx.request.query,
body: {},
path: ctx.request.path,
headers: {
'x-authenticator': ctx.request?.headers['x-authenticator'],
'x-locale': ctx.request?.headers['x-locale'],
'x-timezone': ctx.request?.headers['x-timezone'],
},
},
response: {
body: {},
},
};
},
getSourceAndTarget: async (ctx: any) => {
return {
targetCollection: 'users',
targetRecordUK: ctx.auth.user.id,
};
},
},
'auth:signOut',
]);
}
async install(options?: InstallOptions) {

View File

@ -118,6 +118,8 @@ export class PluginClientServer extends Plugin {
},
},
});
this.app.auditManager.registerActions(['app:restart', 'app:clearCache']);
}
}

View File

@ -97,6 +97,33 @@ export class PluginUISchemaStorageServer extends Plugin {
},
});
const getSourceAndTargetForRemoveAction = async (ctx: any) => {
const { filterByTk } = ctx.action.params;
return {
targetCollection: 'uiSchemas',
targetRecordUK: filterByTk,
};
};
const getSourceAndTargetForInsertAdjacentAction = async (ctx: any) => {
return {
targetCollection: 'uiSchemas',
targetRecordUK: ctx.request.body?.schema?.['x-uid'],
};
};
const getSourceAndTargetForPatchAction = async (ctx: any) => {
return {
targetCollection: 'uiSchemas',
targetRecordUK: ctx.request.body?.['x-uid'],
};
};
this.app.auditManager.registerActions([
{ name: 'uiSchemas:remove', getSourceAndTarget: getSourceAndTargetForRemoveAction },
{ name: 'uiSchemas:insertAdjacent', getSourceAndTarget: getSourceAndTargetForInsertAdjacentAction },
{ name: 'uiSchemas:patch', getSourceAndTarget: getSourceAndTargetForPatchAction },
]);
await this.importCollections(resolve(__dirname, 'collections'));
}
}

View File

@ -150,6 +150,45 @@ export default class PluginUsersServer extends Plugin {
name: `pm.${this.name}`,
actions: ['users:*'],
});
const getMetaDataForUpdateProfileAction = async (ctx: any) => {
return {
request: {
params: ctx.request.params,
query: ctx.request.query,
body: {
...ctx.request.body,
password: '******',
},
path: ctx.request.path,
},
response: {
body: ctx.body,
},
};
};
const getSourceAndTargetForUpdateProfileAction = async (ctx: any) => {
const { id } = ctx.state.currentUser;
let idStr = '';
if (typeof id === 'number') {
idStr = id.toString();
} else if (typeof id === 'string') {
idStr = id;
}
return {
targetCollection: 'users',
targetRecordUK: idStr,
};
};
this.app.auditManager.registerActions([
{
name: 'users:updateProfile',
getMetaData: getMetaDataForUpdateProfileAction,
getSourceAndTarget: getSourceAndTargetForUpdateProfileAction,
},
]);
}
async load() {