adminforth 1.1.66 → 1.1.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth.ts +19 -4
- package/dataConnectors/mongo.ts +4 -4
- package/dataConnectors/postgres.ts +2 -2
- package/dataConnectors/sqlite.ts +2 -2
- package/dist/auth.js +14 -4
- package/dist/dataConnectors/mongo.js +4 -4
- package/dist/dataConnectors/postgres.js +2 -2
- package/dist/dataConnectors/sqlite.js +2 -2
- package/dist/index.js +3 -6
- package/dist/plugins/AuditLogPlugin/custom/AuditLogView.vue +2 -2
- package/dist/plugins/AuditLogPlugin/index.js +43 -11
- package/dist/servers/express.js +1 -1
- package/dist/spa/spa/src/components/ResourceListTable.vue +6 -1
- package/dist/spa/spa/src/views/ListView.vue +10 -1
- package/dist/types/AdminForthConfig.js +2 -2
- package/index.ts +3 -7
- package/package.json +1 -1
- package/plugins/AuditLogPlugin/custom/AuditLogView.vue +2 -2
- package/plugins/AuditLogPlugin/index.ts +45 -12
- package/servers/express.ts +1 -1
- package/spa/src/components/ResourceListTable.vue +6 -1
- package/spa/src/views/ListView.vue +10 -1
- package/types/AdminForthConfig.ts +8 -3
package/auth.ts
CHANGED
|
@@ -3,6 +3,7 @@ import jwt from 'jsonwebtoken';
|
|
|
3
3
|
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import AdminForth from './index.js';
|
|
6
|
+
import { th } from '@faker-js/faker';
|
|
6
7
|
|
|
7
8
|
// Function to generate a password hash using PBKDF2
|
|
8
9
|
function calcPasswordHash(password, salt, iterations = 100000, keyLength = 64, digest = 'sha512') {
|
|
@@ -26,7 +27,18 @@ class AdminForthAuth {
|
|
|
26
27
|
this.adminforth = adminforth;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
removeAuthCookie(response) {
|
|
31
|
+
response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setAuthCookie({ response, username, pk}: {
|
|
35
|
+
response: any, username: string, pk: string | null
|
|
36
|
+
}) {
|
|
37
|
+
const token = this.issueJWT({ username, pk, }, 'auth');
|
|
38
|
+
response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
issueJWT(payload: Object, type: string) {
|
|
30
42
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
31
43
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
32
44
|
if (!secret) {
|
|
@@ -35,10 +47,10 @@ class AdminForthAuth {
|
|
|
35
47
|
|
|
36
48
|
// issue JWT token
|
|
37
49
|
const expiresIn = process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h';
|
|
38
|
-
return jwt.sign(payload, secret, { expiresIn });
|
|
50
|
+
return jwt.sign({...payload, t: type}, secret, { expiresIn });
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
async verify(jwtToken) {
|
|
53
|
+
async verify(jwtToken: string, mustHaveType: string): Promise<Object> {
|
|
42
54
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
43
55
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
44
56
|
if (!secret) {
|
|
@@ -58,7 +70,10 @@ class AdminForthAuth {
|
|
|
58
70
|
}
|
|
59
71
|
return null;
|
|
60
72
|
}
|
|
61
|
-
const { pk } = decoded;
|
|
73
|
+
const { pk, t } = decoded;
|
|
74
|
+
if (t !== mustHaveType) {
|
|
75
|
+
throw new Error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
|
|
76
|
+
}
|
|
62
77
|
if (pk === null) {
|
|
63
78
|
decoded.isRoot = true;
|
|
64
79
|
} else {
|
package/dataConnectors/mongo.ts
CHANGED
|
@@ -34,8 +34,8 @@ class MongoConnector implements AdminForthDataSourceConnector {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
SortDirectionsMap = {
|
|
37
|
-
[AdminForthSortDirections.
|
|
38
|
-
[AdminForthSortDirections.
|
|
37
|
+
[AdminForthSortDirections.asc]: 1,
|
|
38
|
+
[AdminForthSortDirections.desc]: -1,
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
async discoverFields(resource) {
|
|
@@ -68,13 +68,13 @@ class MongoConnector implements AdminForthDataSourceConnector {
|
|
|
68
68
|
if (!value) {
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
|
-
return dayjs.
|
|
71
|
+
return dayjs(Date.parse(value)).toISOString();
|
|
72
72
|
|
|
73
73
|
} else if (field.type == AdminForthDataTypes.DATE) {
|
|
74
74
|
if (!value) {
|
|
75
75
|
return null;
|
|
76
76
|
}
|
|
77
|
-
return dayjs(value).toISOString().split('T')[0];
|
|
77
|
+
return dayjs(Date.parse(value)).toISOString().split('T')[0];
|
|
78
78
|
|
|
79
79
|
} else if (field.type == AdminForthDataTypes.BOOLEAN) {
|
|
80
80
|
return !!value;
|
|
@@ -37,8 +37,8 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
SortDirectionsMap = {
|
|
40
|
-
[AdminForthSortDirections.
|
|
41
|
-
[AdminForthSortDirections.
|
|
40
|
+
[AdminForthSortDirections.asc]: 'ASC',
|
|
41
|
+
[AdminForthSortDirections.desc]: 'DESC',
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
async discoverFields(resource) {
|
package/dataConnectors/sqlite.ts
CHANGED
|
@@ -141,8 +141,8 @@ class SQLiteConnector implements AdminForthDataSourceConnector {
|
|
|
141
141
|
};
|
|
142
142
|
|
|
143
143
|
SortDirectionsMap = {
|
|
144
|
-
[AdminForthSortDirections.
|
|
145
|
-
[AdminForthSortDirections.
|
|
144
|
+
[AdminForthSortDirections.asc]: 'ASC',
|
|
145
|
+
[AdminForthSortDirections.desc]: 'DESC',
|
|
146
146
|
};
|
|
147
147
|
|
|
148
148
|
|
package/dist/auth.js
CHANGED
|
@@ -27,7 +27,14 @@ class AdminForthAuth {
|
|
|
27
27
|
constructor(adminforth) {
|
|
28
28
|
this.adminforth = adminforth;
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
removeAuthCookie(response) {
|
|
31
|
+
response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
|
|
32
|
+
}
|
|
33
|
+
setAuthCookie({ response, username, pk }) {
|
|
34
|
+
const token = this.issueJWT({ username, pk, }, 'auth');
|
|
35
|
+
response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
|
|
36
|
+
}
|
|
37
|
+
issueJWT(payload, type) {
|
|
31
38
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
32
39
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
33
40
|
if (!secret) {
|
|
@@ -35,9 +42,9 @@ class AdminForthAuth {
|
|
|
35
42
|
}
|
|
36
43
|
// issue JWT token
|
|
37
44
|
const expiresIn = process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h';
|
|
38
|
-
return jwt.sign(payload, secret, { expiresIn });
|
|
45
|
+
return jwt.sign(Object.assign(Object.assign({}, payload), { t: type }), secret, { expiresIn });
|
|
39
46
|
}
|
|
40
|
-
verify(jwtToken) {
|
|
47
|
+
verify(jwtToken, mustHaveType) {
|
|
41
48
|
return __awaiter(this, void 0, void 0, function* () {
|
|
42
49
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
43
50
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
@@ -61,7 +68,10 @@ class AdminForthAuth {
|
|
|
61
68
|
}
|
|
62
69
|
return null;
|
|
63
70
|
}
|
|
64
|
-
const { pk } = decoded;
|
|
71
|
+
const { pk, t } = decoded;
|
|
72
|
+
if (t !== mustHaveType) {
|
|
73
|
+
throw new Error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
|
|
74
|
+
}
|
|
65
75
|
if (pk === null) {
|
|
66
76
|
decoded.isRoot = true;
|
|
67
77
|
}
|
|
@@ -25,8 +25,8 @@ class MongoConnector {
|
|
|
25
25
|
[AdminForthFilterOperators.NIN]: (value) => ({ $nin: value }),
|
|
26
26
|
};
|
|
27
27
|
this.SortDirectionsMap = {
|
|
28
|
-
[AdminForthSortDirections.
|
|
29
|
-
[AdminForthSortDirections.
|
|
28
|
+
[AdminForthSortDirections.asc]: 1,
|
|
29
|
+
[AdminForthSortDirections.desc]: -1,
|
|
30
30
|
};
|
|
31
31
|
this.db = new MongoClient(url);
|
|
32
32
|
(() => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -71,13 +71,13 @@ class MongoConnector {
|
|
|
71
71
|
if (!value) {
|
|
72
72
|
return null;
|
|
73
73
|
}
|
|
74
|
-
return dayjs.
|
|
74
|
+
return dayjs(Date.parse(value)).toISOString();
|
|
75
75
|
}
|
|
76
76
|
else if (field.type == AdminForthDataTypes.DATE) {
|
|
77
77
|
if (!value) {
|
|
78
78
|
return null;
|
|
79
79
|
}
|
|
80
|
-
return dayjs(value).toISOString().split('T')[0];
|
|
80
|
+
return dayjs(Date.parse(value)).toISOString().split('T')[0];
|
|
81
81
|
}
|
|
82
82
|
else if (field.type == AdminForthDataTypes.BOOLEAN) {
|
|
83
83
|
return !!value;
|
|
@@ -26,8 +26,8 @@ class PostgresConnector {
|
|
|
26
26
|
[AdminForthFilterOperators.NIN]: 'NOT IN',
|
|
27
27
|
};
|
|
28
28
|
this.SortDirectionsMap = {
|
|
29
|
-
[AdminForthSortDirections.
|
|
30
|
-
[AdminForthSortDirections.
|
|
29
|
+
[AdminForthSortDirections.asc]: 'ASC',
|
|
30
|
+
[AdminForthSortDirections.desc]: 'DESC',
|
|
31
31
|
};
|
|
32
32
|
this.db = new Client({
|
|
33
33
|
connectionString: url
|
|
@@ -26,8 +26,8 @@ class SQLiteConnector {
|
|
|
26
26
|
[AdminForthFilterOperators.NIN]: 'NOT IN',
|
|
27
27
|
};
|
|
28
28
|
this.SortDirectionsMap = {
|
|
29
|
-
[AdminForthSortDirections.
|
|
30
|
-
[AdminForthSortDirections.
|
|
29
|
+
[AdminForthSortDirections.asc]: 'ASC',
|
|
30
|
+
[AdminForthSortDirections.desc]: 'DESC',
|
|
31
31
|
};
|
|
32
32
|
this.db = betterSqlite3(url.replace('sqlite://', ''));
|
|
33
33
|
}
|
package/dist/index.js
CHANGED
|
@@ -541,7 +541,7 @@ class AdminForth {
|
|
|
541
541
|
const { username, password } = body;
|
|
542
542
|
let token;
|
|
543
543
|
if (username === this.config.rootUser.username && password === this.config.rootUser.password) {
|
|
544
|
-
|
|
544
|
+
this.auth.setAuthCookie({ response, username, pk: null });
|
|
545
545
|
}
|
|
546
546
|
else {
|
|
547
547
|
// get resource from db
|
|
@@ -575,15 +575,12 @@ class AdminForth {
|
|
|
575
575
|
console.log('User record', userRecord, passwordHash); // why does it has no hash?
|
|
576
576
|
const valid = yield AdminForthAuth.verifyPassword(password, passwordHash);
|
|
577
577
|
if (valid) {
|
|
578
|
-
|
|
579
|
-
username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
|
|
580
|
-
});
|
|
578
|
+
this.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
|
|
581
579
|
}
|
|
582
580
|
else {
|
|
583
581
|
return { error: INVALID_MESSAGE };
|
|
584
582
|
}
|
|
585
583
|
}
|
|
586
|
-
response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
|
|
587
584
|
return { ok: true };
|
|
588
585
|
}),
|
|
589
586
|
});
|
|
@@ -599,7 +596,7 @@ class AdminForth {
|
|
|
599
596
|
method: 'POST',
|
|
600
597
|
path: '/logout',
|
|
601
598
|
handler: (_e) => __awaiter(this, [_e], void 0, function* ({ response }) {
|
|
602
|
-
|
|
599
|
+
this.auth.removeAuthCookie({ response });
|
|
603
600
|
return { ok: true };
|
|
604
601
|
}),
|
|
605
602
|
});
|
|
@@ -14,8 +14,8 @@ const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser'])
|
|
|
14
14
|
:mode="'split'"
|
|
15
15
|
:theme="'light'"
|
|
16
16
|
:language="'JSON'"
|
|
17
|
-
:prev="props.record[props.meta.resourceColumns.resourceDataColumnName].oldRecord"
|
|
18
|
-
:current="props.record[props.meta.resourceColumns.resourceDataColumnName].newRecord"
|
|
17
|
+
:prev="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).oldRecord, null, 2)"
|
|
18
|
+
:current="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).newRecord, null, 2)"
|
|
19
19
|
/>
|
|
20
20
|
</template>
|
|
21
21
|
|
|
@@ -7,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
import { AllowedActionsEnum, AdminForthSortDirections } from "../../types/AdminForthConfig.js";
|
|
10
11
|
import AdminForthPlugin from "../base.js";
|
|
11
12
|
class AuditLogPlugin extends AdminForthPlugin {
|
|
12
13
|
constructor(options) {
|
|
@@ -16,16 +17,41 @@ class AuditLogPlugin extends AdminForthPlugin {
|
|
|
16
17
|
const recordIdFieldName = (_a = resource.columns.find((c) => c.primaryKey === true)) === null || _a === void 0 ? void 0 : _a.name;
|
|
17
18
|
const recordId = (data === null || data === void 0 ? void 0 : data[recordIdFieldName]) || (oldRecord === null || oldRecord === void 0 ? void 0 : oldRecord[recordIdFieldName]);
|
|
18
19
|
const connector = this.adminforth.connectors[resource.dataSource];
|
|
19
|
-
const newRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const newRecord = action == AllowedActionsEnum.delete ? {} : (yield connector.getRecordByPrimaryKey(resource, recordId)) || {};
|
|
21
|
+
if (action !== AllowedActionsEnum.delete) {
|
|
22
|
+
oldRecord = oldRecord ? JSON.parse(JSON.stringify(oldRecord)) : {};
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
oldRecord = data;
|
|
26
|
+
}
|
|
27
|
+
if (action !== AllowedActionsEnum.delete) {
|
|
28
|
+
const columnsNamesList = resource.columns.map((c) => c.name);
|
|
29
|
+
columnsNamesList.forEach((key) => {
|
|
30
|
+
if (JSON.stringify(oldRecord[key]) == JSON.stringify(newRecord[key])) {
|
|
31
|
+
delete oldRecord[key];
|
|
32
|
+
delete newRecord[key];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const backendOnlyColumns = resource.columns.filter((c) => c.backendOnly);
|
|
37
|
+
backendOnlyColumns.forEach((c) => {
|
|
38
|
+
if (JSON.stringify(oldRecord[c.name]) != JSON.stringify(newRecord[c.name])) {
|
|
39
|
+
if (action !== AllowedActionsEnum.delete) {
|
|
40
|
+
newRecord[c.name] = '<hidden value after>';
|
|
41
|
+
}
|
|
42
|
+
if (action !== AllowedActionsEnum.create) {
|
|
43
|
+
oldRecord[c.name] = '<hidden value before>';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
delete oldRecord[c.name];
|
|
48
|
+
delete newRecord[c.name];
|
|
49
|
+
}
|
|
50
|
+
});
|
|
25
51
|
const record = {
|
|
26
52
|
[this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
|
|
27
53
|
[this.options.resourceColumns.resourceActionColumnName]: action,
|
|
28
|
-
[this.options.resourceColumns.resourceDataColumnName]:
|
|
54
|
+
[this.options.resourceColumns.resourceDataColumnName]: { 'oldRecord': oldRecord || {}, 'newRecord': newRecord },
|
|
29
55
|
[this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
|
|
30
56
|
[this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
|
|
31
57
|
[this.options.resourceColumns.resourceCreatedColumnName]: new Date()
|
|
@@ -54,18 +80,23 @@ class AuditLogPlugin extends AdminForthPlugin {
|
|
|
54
80
|
// type: AdminForthDataTypes.STRING
|
|
55
81
|
};
|
|
56
82
|
}
|
|
83
|
+
diffColumn.showIn = ['show'];
|
|
57
84
|
diffColumn.components = {
|
|
58
85
|
show: {
|
|
59
86
|
file: this.componentPath('AuditLogView.vue'),
|
|
60
87
|
meta: Object.assign(Object.assign({}, this.options), { pluginInstanceId: this.pluginInstanceId })
|
|
61
88
|
}
|
|
62
89
|
};
|
|
90
|
+
resource.options.defaultSort = {
|
|
91
|
+
columnName: this.options.resourceColumns.resourceCreatedColumnName,
|
|
92
|
+
direction: AdminForthSortDirections.desc
|
|
93
|
+
};
|
|
63
94
|
return;
|
|
64
95
|
}
|
|
65
96
|
const defaultHooks = {
|
|
66
97
|
create: { afterSave: [] },
|
|
67
98
|
edit: { afterSave: [] },
|
|
68
|
-
delete: {
|
|
99
|
+
delete: { beforeSave: [] }
|
|
69
100
|
};
|
|
70
101
|
if (!resource.hooks) {
|
|
71
102
|
resource.hooks = defaultHooks;
|
|
@@ -74,10 +105,11 @@ class AuditLogPlugin extends AdminForthPlugin {
|
|
|
74
105
|
resource.hooks = Object.assign(Object.assign({}, defaultHooks), resource.hooks);
|
|
75
106
|
}
|
|
76
107
|
Object.keys(resource.hooks).forEach((hook) => {
|
|
77
|
-
|
|
78
|
-
|
|
108
|
+
const hookToUse = hook == AllowedActionsEnum.delete ? 'beforeSave' : 'afterSave';
|
|
109
|
+
if (!Array.isArray(resource.hooks[hook][hookToUse])) {
|
|
110
|
+
resource.hooks[hook][hookToUse] = [resource.hooks[hook][hookToUse]];
|
|
79
111
|
}
|
|
80
|
-
resource.hooks[hook].
|
|
112
|
+
resource.hooks[hook][hookToUse].push((_a) => __awaiter(this, [_a], void 0, function* ({ resource, record, adminUser, oldRecord }) {
|
|
81
113
|
return yield this.createLogRecord(resource, hook, record, adminUser, oldRecord);
|
|
82
114
|
}));
|
|
83
115
|
});
|
package/dist/servers/express.js
CHANGED
|
@@ -149,7 +149,7 @@ class ExpressServer {
|
|
|
149
149
|
res.status(401).send('Unauthorized by AdminForth');
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
|
-
const adminforthUser = yield this.adminforth.auth.verify(jwt);
|
|
152
|
+
const adminforthUser = yield this.adminforth.auth.verify(jwt, 'auth');
|
|
153
153
|
if (!adminforthUser) {
|
|
154
154
|
res.status(401).send('Unauthorized by AdminForth');
|
|
155
155
|
}
|
|
@@ -252,7 +252,8 @@ const props = defineProps([
|
|
|
252
252
|
'rows',
|
|
253
253
|
'totalRows',
|
|
254
254
|
'pageSize',
|
|
255
|
-
'checkboxes'
|
|
255
|
+
'checkboxes',
|
|
256
|
+
'sort'
|
|
256
257
|
])
|
|
257
258
|
|
|
258
259
|
// emits, update page
|
|
@@ -288,6 +289,10 @@ watch(() => props.checkboxes, (newCheckboxes) => {
|
|
|
288
289
|
checkboxesInternal.value = newCheckboxes;
|
|
289
290
|
});
|
|
290
291
|
|
|
292
|
+
watch(() => props.sort, (newSort) => {
|
|
293
|
+
sort.value = newSort;
|
|
294
|
+
});
|
|
295
|
+
|
|
291
296
|
function addToCheckedValues(id) {
|
|
292
297
|
if (checkboxesInternal.value.includes(id)) {
|
|
293
298
|
checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
@update:sort="sort = $event"
|
|
90
90
|
@update:checkboxes="checkboxes = $event"
|
|
91
91
|
@update:records="getList"
|
|
92
|
+
:sort="sort"
|
|
92
93
|
:pageSize="pageSize"
|
|
93
94
|
:totalRows="totalRows"
|
|
94
95
|
:checkboxes="checkboxes"
|
|
@@ -229,6 +230,15 @@ async function init() {
|
|
|
229
230
|
resourceId: route.params.resourceId
|
|
230
231
|
});
|
|
231
232
|
|
|
233
|
+
if (coreStore.resource.options?.defaultSort) {
|
|
234
|
+
sort.value = [{
|
|
235
|
+
field: coreStore.resource.options.defaultSort.columnName,
|
|
236
|
+
direction: coreStore.resource.options.defaultSort.direction
|
|
237
|
+
}];
|
|
238
|
+
} else {
|
|
239
|
+
sort.value = [];
|
|
240
|
+
}
|
|
241
|
+
|
|
232
242
|
await getList();
|
|
233
243
|
columnsMinMax.value = await callAdminForthApi({
|
|
234
244
|
path: '/get_min_max_for_columns',
|
|
@@ -250,7 +260,6 @@ onMounted(async () => {
|
|
|
250
260
|
watch(() => route.params.resourceId, async () => {
|
|
251
261
|
filtersStore.setFilters([]);
|
|
252
262
|
checkboxes.value = [];
|
|
253
|
-
sort.value = [];
|
|
254
263
|
await init();
|
|
255
264
|
});
|
|
256
265
|
|
|
@@ -99,7 +99,7 @@ export var AdminForthFilterOperators;
|
|
|
99
99
|
;
|
|
100
100
|
export var AdminForthSortDirections;
|
|
101
101
|
(function (AdminForthSortDirections) {
|
|
102
|
-
AdminForthSortDirections["
|
|
103
|
-
AdminForthSortDirections["
|
|
102
|
+
AdminForthSortDirections["asc"] = "asc";
|
|
103
|
+
AdminForthSortDirections["desc"] = "desc";
|
|
104
104
|
})(AdminForthSortDirections || (AdminForthSortDirections = {}));
|
|
105
105
|
;
|
package/index.ts
CHANGED
|
@@ -626,7 +626,7 @@ class AdminForth implements AdminForthClass {
|
|
|
626
626
|
const { username, password } = body;
|
|
627
627
|
let token;
|
|
628
628
|
if (username === this.config.rootUser.username && password === this.config.rootUser.password) {
|
|
629
|
-
|
|
629
|
+
this.auth.setAuthCookie({ response, username, pk: null });
|
|
630
630
|
} else {
|
|
631
631
|
// get resource from db
|
|
632
632
|
if (!this.config.auth) {
|
|
@@ -664,15 +664,11 @@ class AdminForth implements AdminForthClass {
|
|
|
664
664
|
console.log('User record', userRecord, passwordHash) // why does it has no hash?
|
|
665
665
|
const valid = await AdminForthAuth.verifyPassword(password, passwordHash);
|
|
666
666
|
if (valid) {
|
|
667
|
-
|
|
668
|
-
username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
|
|
669
|
-
});
|
|
667
|
+
this.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
|
|
670
668
|
} else {
|
|
671
669
|
return { error: INVALID_MESSAGE };
|
|
672
670
|
}
|
|
673
671
|
}
|
|
674
|
-
|
|
675
|
-
response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
|
|
676
672
|
return { ok: true };
|
|
677
673
|
},
|
|
678
674
|
});
|
|
@@ -690,7 +686,7 @@ class AdminForth implements AdminForthClass {
|
|
|
690
686
|
method: 'POST',
|
|
691
687
|
path: '/logout',
|
|
692
688
|
handler: async ({ response }) => {
|
|
693
|
-
|
|
689
|
+
this.auth.removeAuthCookie({ response });
|
|
694
690
|
return { ok: true };
|
|
695
691
|
},
|
|
696
692
|
})
|
package/package.json
CHANGED
|
@@ -14,8 +14,8 @@ const props = defineProps(['column', 'record', 'meta', 'resource', 'adminUser'])
|
|
|
14
14
|
:mode="'split'"
|
|
15
15
|
:theme="'light'"
|
|
16
16
|
:language="'JSON'"
|
|
17
|
-
:prev="props.record[props.meta.resourceColumns.resourceDataColumnName].oldRecord"
|
|
18
|
-
:current="props.record[props.meta.resourceColumns.resourceDataColumnName].newRecord"
|
|
17
|
+
:prev="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).oldRecord, null, 2)"
|
|
18
|
+
:current="JSON.stringify(JSON.parse(props.record[props.meta.resourceColumns.resourceDataColumnName]).newRecord, null, 2)"
|
|
19
19
|
/>
|
|
20
20
|
</template>
|
|
21
21
|
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
AdminUser,
|
|
7
7
|
AdminForthDataTypes,
|
|
8
8
|
AdminForthResourcePages,
|
|
9
|
-
AdminForthFilterOperators
|
|
9
|
+
AdminForthFilterOperators,
|
|
10
|
+
AdminForthSortDirections
|
|
10
11
|
} from "../../types/AdminForthConfig.js";
|
|
11
12
|
import AdminForthPlugin from "../base.js";
|
|
12
13
|
import { PluginOptions } from "./types.js";
|
|
@@ -30,17 +31,43 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
30
31
|
const recordIdFieldName = resource.columns.find((c) => c.primaryKey === true)?.name;
|
|
31
32
|
const recordId = data?.[recordIdFieldName] || oldRecord?.[recordIdFieldName];
|
|
32
33
|
const connector = this.adminforth.connectors[resource.dataSource];
|
|
33
|
-
const newRecord = await connector.getRecordByPrimaryKey(resource, recordId);
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const newRecord = action == AllowedActionsEnum.delete ? {} : (await connector.getRecordByPrimaryKey(resource, recordId)) || {};
|
|
36
|
+
if (action !== AllowedActionsEnum.delete) {
|
|
37
|
+
oldRecord = oldRecord ? JSON.parse(JSON.stringify(oldRecord)) : {};
|
|
38
|
+
} else {
|
|
39
|
+
oldRecord = data
|
|
38
40
|
}
|
|
39
|
-
|
|
41
|
+
|
|
42
|
+
if (action !== AllowedActionsEnum.delete) {
|
|
43
|
+
const columnsNamesList = resource.columns.map((c) => c.name);
|
|
44
|
+
columnsNamesList.forEach((key) => {
|
|
45
|
+
if (JSON.stringify(oldRecord[key]) == JSON.stringify(newRecord[key])) {
|
|
46
|
+
delete oldRecord[key];
|
|
47
|
+
delete newRecord[key];
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const backendOnlyColumns = resource.columns.filter((c) => c.backendOnly);
|
|
53
|
+
backendOnlyColumns.forEach((c) => {
|
|
54
|
+
if (JSON.stringify(oldRecord[c.name]) != JSON.stringify(newRecord[c.name])) {
|
|
55
|
+
if (action !== AllowedActionsEnum.delete) {
|
|
56
|
+
newRecord[c.name] = '<hidden value after>'
|
|
57
|
+
}
|
|
58
|
+
if (action !== AllowedActionsEnum.create) {
|
|
59
|
+
oldRecord[c.name] = '<hidden value before>'
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
delete oldRecord[c.name];
|
|
63
|
+
delete newRecord[c.name];
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
40
67
|
const record = {
|
|
41
68
|
[this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
|
|
42
69
|
[this.options.resourceColumns.resourceActionColumnName]: action,
|
|
43
|
-
[this.options.resourceColumns.resourceDataColumnName]:
|
|
70
|
+
[this.options.resourceColumns.resourceDataColumnName]: { 'oldRecord': oldRecord || {}, 'newRecord': newRecord },
|
|
44
71
|
[this.options.resourceColumns.resourceUserIdColumnName]: user.pk,
|
|
45
72
|
[this.options.resourceColumns.resourceRecordIdColumnName]: recordId,
|
|
46
73
|
[this.options.resourceColumns.resourceCreatedColumnName]: new Date()
|
|
@@ -69,7 +96,8 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
69
96
|
// type: AdminForthDataTypes.STRING
|
|
70
97
|
}
|
|
71
98
|
}
|
|
72
|
-
|
|
99
|
+
|
|
100
|
+
diffColumn.showIn = ['show']
|
|
73
101
|
diffColumn.components = {
|
|
74
102
|
show: {
|
|
75
103
|
file: this.componentPath('AuditLogView.vue'),
|
|
@@ -79,13 +107,17 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
79
107
|
}
|
|
80
108
|
}
|
|
81
109
|
}
|
|
110
|
+
resource.options.defaultSort = {
|
|
111
|
+
columnName: this.options.resourceColumns.resourceCreatedColumnName,
|
|
112
|
+
direction: AdminForthSortDirections.desc
|
|
113
|
+
}
|
|
82
114
|
return;
|
|
83
115
|
}
|
|
84
116
|
|
|
85
117
|
const defaultHooks = {
|
|
86
118
|
create: { afterSave: [] },
|
|
87
119
|
edit: { afterSave: [] },
|
|
88
|
-
delete: {
|
|
120
|
+
delete: { beforeSave: [] }
|
|
89
121
|
}
|
|
90
122
|
|
|
91
123
|
if ( !resource.hooks ) {
|
|
@@ -95,10 +127,11 @@ export default class AuditLogPlugin extends AdminForthPlugin {
|
|
|
95
127
|
}
|
|
96
128
|
|
|
97
129
|
Object.keys(resource.hooks).forEach((hook) => {
|
|
98
|
-
|
|
99
|
-
|
|
130
|
+
const hookToUse = hook == AllowedActionsEnum.delete ? 'beforeSave' : 'afterSave';
|
|
131
|
+
if(!Array.isArray(resource.hooks[hook][hookToUse])){
|
|
132
|
+
resource.hooks[hook][hookToUse] = [resource.hooks[hook][hookToUse]]
|
|
100
133
|
}
|
|
101
|
-
resource.hooks[hook].
|
|
134
|
+
resource.hooks[hook][hookToUse].push(async ({resource, record, adminUser, oldRecord}) => {
|
|
102
135
|
return await this.createLogRecord(resource, hook as AllowedActionsEnum, record, adminUser, oldRecord)
|
|
103
136
|
})
|
|
104
137
|
})
|
package/servers/express.ts
CHANGED
|
@@ -157,7 +157,7 @@ class ExpressServer implements ExpressHttpServer {
|
|
|
157
157
|
res.status(401).send('Unauthorized by AdminForth');
|
|
158
158
|
return
|
|
159
159
|
}
|
|
160
|
-
const adminforthUser = await this.adminforth.auth.verify(jwt);
|
|
160
|
+
const adminforthUser = await this.adminforth.auth.verify(jwt, 'auth');
|
|
161
161
|
if (!adminforthUser) {
|
|
162
162
|
res.status(401).send('Unauthorized by AdminForth');
|
|
163
163
|
} else {
|
|
@@ -252,7 +252,8 @@ const props = defineProps([
|
|
|
252
252
|
'rows',
|
|
253
253
|
'totalRows',
|
|
254
254
|
'pageSize',
|
|
255
|
-
'checkboxes'
|
|
255
|
+
'checkboxes',
|
|
256
|
+
'sort'
|
|
256
257
|
])
|
|
257
258
|
|
|
258
259
|
// emits, update page
|
|
@@ -288,6 +289,10 @@ watch(() => props.checkboxes, (newCheckboxes) => {
|
|
|
288
289
|
checkboxesInternal.value = newCheckboxes;
|
|
289
290
|
});
|
|
290
291
|
|
|
292
|
+
watch(() => props.sort, (newSort) => {
|
|
293
|
+
sort.value = newSort;
|
|
294
|
+
});
|
|
295
|
+
|
|
291
296
|
function addToCheckedValues(id) {
|
|
292
297
|
if (checkboxesInternal.value.includes(id)) {
|
|
293
298
|
checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
@update:sort="sort = $event"
|
|
90
90
|
@update:checkboxes="checkboxes = $event"
|
|
91
91
|
@update:records="getList"
|
|
92
|
+
:sort="sort"
|
|
92
93
|
:pageSize="pageSize"
|
|
93
94
|
:totalRows="totalRows"
|
|
94
95
|
:checkboxes="checkboxes"
|
|
@@ -229,6 +230,15 @@ async function init() {
|
|
|
229
230
|
resourceId: route.params.resourceId
|
|
230
231
|
});
|
|
231
232
|
|
|
233
|
+
if (coreStore.resource.options?.defaultSort) {
|
|
234
|
+
sort.value = [{
|
|
235
|
+
field: coreStore.resource.options.defaultSort.columnName,
|
|
236
|
+
direction: coreStore.resource.options.defaultSort.direction
|
|
237
|
+
}];
|
|
238
|
+
} else {
|
|
239
|
+
sort.value = [];
|
|
240
|
+
}
|
|
241
|
+
|
|
232
242
|
await getList();
|
|
233
243
|
columnsMinMax.value = await callAdminForthApi({
|
|
234
244
|
path: '/get_min_max_for_columns',
|
|
@@ -250,7 +260,6 @@ onMounted(async () => {
|
|
|
250
260
|
watch(() => route.params.resourceId, async () => {
|
|
251
261
|
filtersStore.setFilters([]);
|
|
252
262
|
checkboxes.value = [];
|
|
253
|
-
sort.value = [];
|
|
254
263
|
await init();
|
|
255
264
|
});
|
|
256
265
|
|
|
@@ -101,8 +101,9 @@ export interface AdminForthClass {
|
|
|
101
101
|
createResourceRecord(params: { resource: AdminForthResource, record: any, adminUser: AdminUser }): Promise<any>;
|
|
102
102
|
|
|
103
103
|
auth: {
|
|
104
|
+
verify(jwt : string, mustHaveType: string): Promise<any>;
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
issueJWT(payload: Object, type: string): string;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
/**
|
|
@@ -544,6 +545,10 @@ export type AdminForthResource = {
|
|
|
544
545
|
},
|
|
545
546
|
},
|
|
546
547
|
options?: {
|
|
548
|
+
defaultSort?: {
|
|
549
|
+
columnName: string,
|
|
550
|
+
direction: AdminForthSortDirections | string,
|
|
551
|
+
}
|
|
547
552
|
bulkActions?: Array<{
|
|
548
553
|
id?: string,
|
|
549
554
|
label: string,
|
|
@@ -1072,8 +1077,8 @@ export enum AdminForthFilterOperators {
|
|
|
1072
1077
|
};
|
|
1073
1078
|
|
|
1074
1079
|
export enum AdminForthSortDirections {
|
|
1075
|
-
|
|
1076
|
-
|
|
1080
|
+
asc = 'asc',
|
|
1081
|
+
desc = 'desc',
|
|
1077
1082
|
};
|
|
1078
1083
|
|
|
1079
1084
|
|