-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
171 lines (148 loc) · 7.39 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import type {
AdminForthResource,
IAdminForth,
AdminUser,
} from "adminforth";
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc.js';
import { AdminForthPlugin, AllowedActionsEnum, AdminForthSortDirections, AdminForthDataTypes, HttpExtra } from "adminforth";
import { PluginOptions } from "./types.js";
dayjs.extend(utc);
export default class AuditLogPlugin extends AdminForthPlugin {
options: PluginOptions;
adminforth: IAdminForth;
auditLogResource: string;
constructor(options: PluginOptions) {
super(options, import.meta.url);
this.options = options;
}
instanceUniqueRepresentation(pluginOptions: any) : string {
return `single`;
}
static defaultError = 'Sorry, you do not have access to this resource.'
createLogRecord = async (resource: AdminForthResource, action: AllowedActionsEnum | string, data: Object, user: AdminUser, oldRecord?: Object, extra?: HttpExtra) => {
const recordIdFieldName = resource.columns.find((c) => c.primaryKey === true)?.name;
const recordId = data?.[recordIdFieldName] || oldRecord?.[recordIdFieldName];
const connector = this.adminforth.connectors[resource.dataSource];
const newRecord = action == AllowedActionsEnum.delete ? {} : (await connector.getRecordByPrimaryKey(resource, recordId)) || {};
if (action !== AllowedActionsEnum.delete) {
oldRecord = oldRecord ? JSON.parse(JSON.stringify(oldRecord)) : {};
} else {
oldRecord = data
}
if (action !== AllowedActionsEnum.delete) {
const columnsNamesList = resource.columns.map((c) => c.name);
columnsNamesList.forEach((key) => {
if (JSON.stringify(oldRecord[key]) == JSON.stringify(newRecord[key])) {
delete oldRecord[key];
delete newRecord[key];
}
});
}
const backendOnlyColumns = resource.columns.filter((c) => c.backendOnly);
backendOnlyColumns.forEach((c) => {
if (JSON.stringify(oldRecord[c.name]) != JSON.stringify(newRecord[c.name])) {
if (action !== AllowedActionsEnum.delete) {
newRecord[c.name] = '<hidden value after>'
}
if (action !== AllowedActionsEnum.create) {
oldRecord[c.name] = '<hidden value before>'
}
} else {
delete oldRecord[c.name];
delete newRecord[c.name];
}
});
const record = {
[this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
[this.options.resourceColumns.resourceActionColumnName]: action,
[this.options.resourceColumns.resourceDataColumnName]: { 'oldRecord': oldRecord || {}, 'newRecord': newRecord },
[this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
[this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
// utc iso string
[this.options.resourceColumns.resourceCreatedColumnName]: dayjs.utc().format(),
...(this.options.resourceColumns.resourceIpColumnName && extra?.headers ? {[this.options.resourceColumns.resourceIpColumnName]: this.adminforth.auth.getClientIp(extra.headers)} : {}),
}
const auditLogResource = this.adminforth.config.resources.find((r) => r.resourceId === this.auditLogResource);
await this.adminforth.createResourceRecord({ resource: auditLogResource, record, adminUser: user});
return {ok: true};
}
/**
* Create a custom action in the audit log resource
* @param resourceId - The resourceId of the resource that the action is being performed on. Can be null if the action is not related to a specific resource.
* @param recordId - The recordId of the record that the action is being performed on. Can be null if the action is not related to a specific record.
* @param actionId - The id of the action being performed, can be random string
* @param data - The data to be stored in the audit log
* @param user - The adminUser user performing the action
*/
logCustomAction = async (resourceId: string | null=null, recordId: string | null=null, actionId: string, data: Object, user: AdminUser, headers?: Record<string, string>) => {
if (resourceId) {
const resource = this.adminforth.config.resources.find((r) => r.resourceId === resourceId);
if (!resource) {
const similarResource = this.adminforth.config.resources.find((r) => r.resourceId.includes(resourceId));
throw new Error(`Resource ${resourceId} not found. Did you mean ${similarResource.resourceId}?`)
}
}
const record = {
[this.options.resourceColumns.resourceIdColumnName]: resourceId,
[this.options.resourceColumns.resourceActionColumnName]: actionId,
[this.options.resourceColumns.resourceDataColumnName]: { 'oldRecord': {}, 'newRecord': data },
[this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
[this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
[this.options.resourceColumns.resourceCreatedColumnName]: dayjs.utc().format(),
...(this.options.resourceColumns.resourceIpColumnName && headers ? {[this.options.resourceColumns.resourceIpColumnName]: this.adminforth.auth.getClientIp(headers)} : {}),
}
const auditLogResource = this.adminforth.config.resources.find((r) => r.resourceId === this.auditLogResource);
await this.adminforth.createResourceRecord({ resource: auditLogResource, record, adminUser: user});
}
modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
super.modifyResourceConfig(adminforth, resourceConfig);
this.adminforth = adminforth;
this.auditLogResource = resourceConfig.resourceId;
this.adminforth.config.resources.forEach((resource) => {
if (this.options.excludeResourceIds?.includes(resource.resourceId)) {
return;
}
if (this.auditLogResource === resource.resourceId) {
let diffColumn = resource.columns.find((c) => c.name === this.options.resourceColumns.resourceDataColumnName);
if (!diffColumn) {
throw new Error(`Column ${this.options.resourceColumns.resourceDataColumnName} not found in ${resource.label}`)
}
if (diffColumn.type !== AdminForthDataTypes.JSON) {
throw new Error(`Column ${this.options.resourceColumns.resourceDataColumnName} must be of type 'json'`)
}
diffColumn.showIn = {
show: true,
list: false,
edit: false,
create: false,
filter: false,
};
diffColumn.components = {
show: {
file: this.componentPath('AuditLogView.vue'),
meta: {
...this.options,
pluginInstanceId: this.pluginInstanceId
}
}
}
resource.options.defaultSort = {
columnName: this.options.resourceColumns.resourceCreatedColumnName,
direction: AdminForthSortDirections.desc
}
return;
};
['edit', 'delete'].forEach((hook) => {
resource.hooks[hook].afterSave.push(async ({resource, updates, adminUser, oldRecord, extra}) => {
return await this.createLogRecord(resource, hook as AllowedActionsEnum, updates, adminUser, oldRecord, extra)
})
});
['create'].forEach((hook) => {
resource.hooks[hook].afterSave.push(async ({resource, record, adminUser, extra}) => {
return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser, undefined, extra)
})
});
})
}
}