adminforth 1.1.12 → 1.1.14
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 +17 -3
- package/dist/auth.js +32 -18
- package/dist/index.js +107 -63
- package/dist/modules/codeInjector.js +9 -3
- package/dist/modules/utils.js +2 -12
- package/dist/plugins/AccessControl/index.js +66 -0
- package/dist/plugins/AccessControl/types.js +1 -0
- package/dist/plugins/ForeignInlineListPlugin/index.js +1 -1
- package/dist/servers/express.js +2 -2
- package/dist/spa/spa/src/App.vue +7 -3
- package/dist/spa/spa/src/components/Toast.vue +5 -4
- package/dist/spa/spa/src/components/ValueRenderer.vue +1 -1
- package/dist/spa/spa/src/composables/useStores.ts +2 -1
- package/dist/spa/spa/src/router/index.ts +13 -1
- package/dist/spa/spa/src/stores/core.ts +13 -11
- package/dist/spa/spa/src/stores/user.ts +50 -0
- package/dist/spa/spa/src/utils.ts +8 -3
- package/dist/spa/spa/src/views/CreateView.vue +7 -0
- package/dist/spa/spa/src/views/EditView.vue +8 -1
- package/dist/spa/spa/src/views/ListView.vue +10 -1
- package/dist/types/AdminForthConfig.js +9 -0
- package/dist/types/FrontendAPI.js +4 -4
- package/index.ts +103 -51
- package/modules/codeInjector.ts +9 -3
- package/modules/utils.ts +2 -10
- package/package.json +1 -1
- package/plugins/AccessControl/index.ts +83 -0
- package/plugins/AccessControl/types.ts +14 -0
- package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +13 -1
- package/plugins/ForeignInlineListPlugin/index.ts +2 -1
- package/servers/express.ts +2 -2
- package/spa/src/App.vue +7 -3
- package/spa/src/components/Toast.vue +5 -4
- package/spa/src/components/ValueRenderer.vue +1 -1
- package/spa/src/composables/useStores.ts +2 -1
- package/spa/src/router/index.ts +13 -1
- package/spa/src/stores/core.ts +13 -11
- package/spa/src/stores/user.ts +50 -0
- package/spa/src/utils.ts +8 -3
- package/spa/src/views/CreateView.vue +7 -0
- package/spa/src/views/EditView.vue +8 -1
- package/spa/src/views/ListView.vue +10 -1
- package/types/AdminForthConfig.ts +73 -21
- package/types/FrontendAPI.ts +11 -5
package/auth.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import jwt from 'jsonwebtoken';
|
|
3
3
|
|
|
4
4
|
import crypto from 'crypto';
|
|
5
|
+
import AdminForth from './index.js';
|
|
5
6
|
|
|
6
7
|
// Function to generate a password hash using PBKDF2
|
|
7
8
|
function calcPasswordHash(password, salt, iterations = 100000, keyLength = 64, digest = 'sha512') {
|
|
@@ -19,6 +20,11 @@ function generateSalt(length = 16) {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
class AdminForthAuth {
|
|
23
|
+
adminforth: AdminForth;
|
|
24
|
+
|
|
25
|
+
constructor(adminforth) {
|
|
26
|
+
this.adminforth = adminforth;
|
|
27
|
+
}
|
|
22
28
|
|
|
23
29
|
issueJWT(payload) {
|
|
24
30
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
@@ -32,16 +38,16 @@ class AdminForthAuth {
|
|
|
32
38
|
return jwt.sign(payload, secret, { expiresIn });
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
verify(jwtToken) {
|
|
41
|
+
async verify(jwtToken) {
|
|
36
42
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
37
43
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
38
44
|
if (!secret) {
|
|
39
45
|
throw new Error('ADMINFORTH_SECRET environment not set');
|
|
40
46
|
}
|
|
47
|
+
let decoded;
|
|
41
48
|
try {
|
|
42
49
|
// verify JWT token
|
|
43
|
-
|
|
44
|
-
return decoded;
|
|
50
|
+
decoded = jwt.verify(jwtToken, secret);
|
|
45
51
|
} catch (err) {
|
|
46
52
|
if (err.name === 'TokenExpiredError') {
|
|
47
53
|
console.error('Token expired:', err.message);
|
|
@@ -52,6 +58,14 @@ class AdminForthAuth {
|
|
|
52
58
|
}
|
|
53
59
|
return null;
|
|
54
60
|
}
|
|
61
|
+
const { pk } = decoded;
|
|
62
|
+
if (pk === null) {
|
|
63
|
+
decoded.isRoot = true;
|
|
64
|
+
} else {
|
|
65
|
+
const dbUser = await this.adminforth.getUserByPk(pk);
|
|
66
|
+
decoded.dbUser = dbUser;
|
|
67
|
+
}
|
|
68
|
+
return decoded;
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
static async generatePasswordHash(password) {
|
package/dist/auth.js
CHANGED
|
@@ -24,6 +24,9 @@ function generateSalt(length = 16) {
|
|
|
24
24
|
return crypto.randomBytes(length).toString('hex');
|
|
25
25
|
}
|
|
26
26
|
class AdminForthAuth {
|
|
27
|
+
constructor(adminforth) {
|
|
28
|
+
this.adminforth = adminforth;
|
|
29
|
+
}
|
|
27
30
|
issueJWT(payload) {
|
|
28
31
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
29
32
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
@@ -35,28 +38,39 @@ class AdminForthAuth {
|
|
|
35
38
|
return jwt.sign(payload, secret, { expiresIn });
|
|
36
39
|
}
|
|
37
40
|
verify(jwtToken) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
// read ADMINFORH_SECRET from environment if not drop error
|
|
43
|
+
const secret = process.env.ADMINFORTH_SECRET;
|
|
44
|
+
if (!secret) {
|
|
45
|
+
throw new Error('ADMINFORTH_SECRET environment not set');
|
|
46
|
+
}
|
|
47
|
+
let decoded;
|
|
48
|
+
try {
|
|
49
|
+
// verify JWT token
|
|
50
|
+
decoded = jwt.verify(jwtToken, secret);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err.name === 'TokenExpiredError') {
|
|
54
|
+
console.error('Token expired:', err.message);
|
|
55
|
+
}
|
|
56
|
+
else if (err.name === 'JsonWebTokenError') {
|
|
57
|
+
console.error('Token error:', err.message);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.error('Failed to verify JWT token', err);
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
51
63
|
}
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
const { pk } = decoded;
|
|
65
|
+
if (pk === null) {
|
|
66
|
+
decoded.isRoot = true;
|
|
54
67
|
}
|
|
55
68
|
else {
|
|
56
|
-
|
|
69
|
+
const dbUser = yield this.adminforth.getUserByPk(pk);
|
|
70
|
+
decoded.dbUser = dbUser;
|
|
57
71
|
}
|
|
58
|
-
return
|
|
59
|
-
}
|
|
72
|
+
return decoded;
|
|
73
|
+
});
|
|
60
74
|
}
|
|
61
75
|
static generatePasswordHash(password) {
|
|
62
76
|
return __awaiter(this, void 0, void 0, function* () {
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
13
13
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
14
14
|
};
|
|
15
15
|
var _a, _AdminForth_defaultConfig;
|
|
16
|
-
import
|
|
16
|
+
import AdminForthAuth from './auth.js';
|
|
17
17
|
import MongoConnector from './dataConnectors/mongo.js';
|
|
18
18
|
import PostgresConnector from './dataConnectors/postgres.js';
|
|
19
19
|
import SQLiteConnector from './dataConnectors/sqlite.js';
|
|
@@ -22,9 +22,8 @@ import { guessLabelFromName } from './modules/utils.js';
|
|
|
22
22
|
import ExpressServer from './servers/express.js';
|
|
23
23
|
import { v1 as uuid } from 'uuid';
|
|
24
24
|
import fs from 'fs';
|
|
25
|
-
import { ADMINFORTH_VERSION } from './modules/utils.js';
|
|
25
|
+
import { ADMINFORTH_VERSION, listify } from './modules/utils.js';
|
|
26
26
|
import { AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages } from './types/AdminForthConfig.js';
|
|
27
|
-
import { getFunctionList } from './modules/utils.js';
|
|
28
27
|
import path from 'path';
|
|
29
28
|
//get array from enum AdminForthResourcePages
|
|
30
29
|
const DEFAULT_ALLOWED_ACTIONS = { create: true, edit: true, show: true, delete: true };
|
|
@@ -40,7 +39,7 @@ class AdminForth {
|
|
|
40
39
|
this.activatePlugins();
|
|
41
40
|
this.validateConfig(); // revalidate after plugins
|
|
42
41
|
this.express = new ExpressServer(this);
|
|
43
|
-
this.auth = new
|
|
42
|
+
this.auth = new AdminForthAuth(this);
|
|
44
43
|
this.connectors = {};
|
|
45
44
|
this.statuses = {};
|
|
46
45
|
console.log(`🚀 AdminForth v${ADMINFORTH_VERSION} starting up`);
|
|
@@ -63,7 +62,7 @@ class AdminForth {
|
|
|
63
62
|
}
|
|
64
63
|
return [];
|
|
65
64
|
}
|
|
66
|
-
validateComponent(component, errors) {
|
|
65
|
+
validateComponent(component, errors, ignoreExistsCheck = false) {
|
|
67
66
|
if (!component) {
|
|
68
67
|
return component;
|
|
69
68
|
}
|
|
@@ -74,7 +73,9 @@ class AdminForth {
|
|
|
74
73
|
else {
|
|
75
74
|
obj = component;
|
|
76
75
|
}
|
|
77
|
-
|
|
76
|
+
if (!ignoreExistsCheck) {
|
|
77
|
+
errors.push(...this.checkCustomFileExists(obj.file));
|
|
78
|
+
}
|
|
78
79
|
return obj;
|
|
79
80
|
}
|
|
80
81
|
validateConfig() {
|
|
@@ -252,6 +253,37 @@ class AdminForth {
|
|
|
252
253
|
else {
|
|
253
254
|
res.options.allowedActions = DEFAULT_ALLOWED_ACTIONS;
|
|
254
255
|
}
|
|
256
|
+
// transform all hooks Functions to array of functions
|
|
257
|
+
if (res.hooks) {
|
|
258
|
+
for (const value of [res.hooks.show, res.hooks.list]) {
|
|
259
|
+
if (value) {
|
|
260
|
+
if (value.beforeDatasourceRequest) {
|
|
261
|
+
if (!Array.isArray(value.beforeDatasourceRequest)) {
|
|
262
|
+
value.beforeDatasourceRequest = [value.beforeDatasourceRequest];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (value.afterDatasourceResponse) {
|
|
266
|
+
if (!Array.isArray(value.afterDatasourceResponse)) {
|
|
267
|
+
value.afterDatasourceResponse = [value.afterDatasourceResponse];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
for (const value of [res.hooks.create, res.hooks.edit, res.hooks.delete]) {
|
|
273
|
+
if (value) {
|
|
274
|
+
if (value.beforeSave) {
|
|
275
|
+
if (!Array.isArray(value.beforeSave)) {
|
|
276
|
+
value.beforeSave = [value.beforeSave];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (value.afterSave) {
|
|
280
|
+
if (!Array.isArray(value.afterSave)) {
|
|
281
|
+
value.afterSave = [value.afterSave];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
255
287
|
});
|
|
256
288
|
if (!this.config.menu) {
|
|
257
289
|
errors.push('No config.menu defined');
|
|
@@ -313,14 +345,14 @@ class AdminForth {
|
|
|
313
345
|
for (const resource of this.config.resources) {
|
|
314
346
|
for (const column of resource.columns) {
|
|
315
347
|
if (column.components) {
|
|
316
|
-
|
|
317
|
-
|
|
348
|
+
for (const [key, comp] of Object.entries(column.components)) {
|
|
349
|
+
let ignoreExistsCheck = false;
|
|
318
350
|
if (this.codeInjector.allComponentNames[comp.file]) {
|
|
319
351
|
// not obvious, but if we are in this if, it means that this is plugin component
|
|
320
352
|
// and there is no sense to check if it exists in users folder
|
|
321
|
-
|
|
353
|
+
ignoreExistsCheck = true;
|
|
322
354
|
}
|
|
323
|
-
this.validateComponent(comp, errors);
|
|
355
|
+
column.components[key] = this.validateComponent(comp, errors, ignoreExistsCheck);
|
|
324
356
|
}
|
|
325
357
|
}
|
|
326
358
|
}
|
|
@@ -390,6 +422,24 @@ class AdminForth {
|
|
|
390
422
|
this.codeInjector.bundleNow({ hotReload, verbose });
|
|
391
423
|
});
|
|
392
424
|
}
|
|
425
|
+
getUserByPk(pk) {
|
|
426
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
427
|
+
const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
|
|
428
|
+
if (!resource) {
|
|
429
|
+
throw new Error('No auth resource found');
|
|
430
|
+
}
|
|
431
|
+
const users = yield this.connectors[resource.dataSource].getData({
|
|
432
|
+
resource,
|
|
433
|
+
filters: [
|
|
434
|
+
{ field: resource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: pk },
|
|
435
|
+
],
|
|
436
|
+
limit: 1,
|
|
437
|
+
offset: 0,
|
|
438
|
+
sort: [],
|
|
439
|
+
});
|
|
440
|
+
return users.data[0] || null;
|
|
441
|
+
});
|
|
442
|
+
}
|
|
393
443
|
setupEndpoints(server) {
|
|
394
444
|
server.endpoint({
|
|
395
445
|
noAuth: true,
|
|
@@ -433,7 +483,7 @@ class AdminForth {
|
|
|
433
483
|
}
|
|
434
484
|
const passwordHash = userRecord[this.config.auth.passwordHashField];
|
|
435
485
|
console.log('User record', userRecord, passwordHash); // why does it has no hash?
|
|
436
|
-
const valid = yield
|
|
486
|
+
const valid = yield AdminForthAuth.verifyPassword(password, passwordHash);
|
|
437
487
|
if (valid) {
|
|
438
488
|
token = this.auth.issueJWT({
|
|
439
489
|
username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
|
|
@@ -447,11 +497,18 @@ class AdminForth {
|
|
|
447
497
|
return { ok: true };
|
|
448
498
|
}),
|
|
449
499
|
});
|
|
500
|
+
server.endpoint({
|
|
501
|
+
method: 'POST',
|
|
502
|
+
path: '/check_auth',
|
|
503
|
+
handler: (_d) => __awaiter(this, [_d], void 0, function* ({ adminUser }) {
|
|
504
|
+
return { ok: true };
|
|
505
|
+
}),
|
|
506
|
+
});
|
|
450
507
|
server.endpoint({
|
|
451
508
|
noAuth: true,
|
|
452
509
|
method: 'POST',
|
|
453
510
|
path: '/logout',
|
|
454
|
-
handler: (
|
|
511
|
+
handler: (_e) => __awaiter(this, [_e], void 0, function* ({ response }) {
|
|
455
512
|
response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
|
|
456
513
|
return { ok: true };
|
|
457
514
|
}),
|
|
@@ -460,8 +517,8 @@ class AdminForth {
|
|
|
460
517
|
noAuth: true,
|
|
461
518
|
method: 'GET',
|
|
462
519
|
path: '/get_public_config',
|
|
463
|
-
handler: (
|
|
464
|
-
var
|
|
520
|
+
handler: (_f) => __awaiter(this, [_f], void 0, function* ({ body }) {
|
|
521
|
+
var _g;
|
|
465
522
|
// find resource
|
|
466
523
|
if (!this.config.auth) {
|
|
467
524
|
throw new Error('No config.auth defined');
|
|
@@ -473,37 +530,24 @@ class AdminForth {
|
|
|
473
530
|
brandName: this.config.customization.brandName,
|
|
474
531
|
usernameFieldName: usernameColumn.label,
|
|
475
532
|
loginBackgroundImage: this.config.auth.loginBackgroundImage,
|
|
476
|
-
title: (
|
|
533
|
+
title: (_g = this.config.customization) === null || _g === void 0 ? void 0 : _g.title,
|
|
477
534
|
};
|
|
478
535
|
}),
|
|
479
536
|
});
|
|
480
537
|
server.endpoint({
|
|
481
538
|
method: 'GET',
|
|
482
539
|
path: '/get_base_config',
|
|
483
|
-
handler: (
|
|
484
|
-
var
|
|
485
|
-
const cookieParsed = this.auth.verify(cookies['adminforth_jwt']);
|
|
540
|
+
handler: (_h) => __awaiter(this, [_h], void 0, function* ({ input, adminUser, cookies }) {
|
|
541
|
+
var _j, _k;
|
|
486
542
|
let username = '';
|
|
487
543
|
let userFullName = '';
|
|
488
|
-
if (
|
|
544
|
+
if (adminUser.isRoot) {
|
|
489
545
|
username = this.config.rootUser.username;
|
|
490
546
|
}
|
|
491
547
|
else {
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
filters: [
|
|
496
|
-
{ field: userResource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: cookieParsed['pk'] },
|
|
497
|
-
],
|
|
498
|
-
limit: 1,
|
|
499
|
-
offset: 0,
|
|
500
|
-
sort: [],
|
|
501
|
-
});
|
|
502
|
-
if (!user.data.length) {
|
|
503
|
-
return { error: 'Unauthorized' };
|
|
504
|
-
}
|
|
505
|
-
username = user.data[0][this.config.auth.usernameField];
|
|
506
|
-
userFullName = user.data[0][this.config.auth.userFullNameField];
|
|
548
|
+
const dbUser = adminUser.dbUser;
|
|
549
|
+
username = dbUser[this.config.auth.usernameField];
|
|
550
|
+
userFullName = dbUser[this.config.auth.userFullNameField];
|
|
507
551
|
}
|
|
508
552
|
const userData = {
|
|
509
553
|
[this.config.auth.usernameField]: username,
|
|
@@ -523,8 +567,8 @@ class AdminForth {
|
|
|
523
567
|
deleteConfirmation: this.config.deleteConfirmation,
|
|
524
568
|
auth: this.config.auth,
|
|
525
569
|
usernameField: this.config.auth.usernameField,
|
|
526
|
-
title: (
|
|
527
|
-
emptyFieldPlaceholder: (
|
|
570
|
+
title: (_j = this.config.customization) === null || _j === void 0 ? void 0 : _j.title,
|
|
571
|
+
emptyFieldPlaceholder: (_k = this.config.customization) === null || _k === void 0 ? void 0 : _k.emptyFieldPlaceholder,
|
|
528
572
|
},
|
|
529
573
|
adminUser,
|
|
530
574
|
version: ADMINFORTH_VERSION,
|
|
@@ -534,7 +578,7 @@ class AdminForth {
|
|
|
534
578
|
server.endpoint({
|
|
535
579
|
method: 'POST',
|
|
536
580
|
path: '/get_resource',
|
|
537
|
-
handler: (
|
|
581
|
+
handler: (_l) => __awaiter(this, [_l], void 0, function* ({ body }) {
|
|
538
582
|
const { resourceId } = body;
|
|
539
583
|
if (!this.statuses.dbDiscover) {
|
|
540
584
|
return { error: 'Database discovery not started' };
|
|
@@ -553,8 +597,8 @@ class AdminForth {
|
|
|
553
597
|
server.endpoint({
|
|
554
598
|
method: 'POST',
|
|
555
599
|
path: '/get_resource_data',
|
|
556
|
-
handler: (
|
|
557
|
-
var
|
|
600
|
+
handler: (_m) => __awaiter(this, [_m], void 0, function* ({ body, adminUser }) {
|
|
601
|
+
var _o, _p, _q, _r;
|
|
558
602
|
const { resourceId, source } = body;
|
|
559
603
|
if (['show', 'list'].includes(source) === false) {
|
|
560
604
|
return { error: 'Invalid source, should be list or show' };
|
|
@@ -569,7 +613,7 @@ class AdminForth {
|
|
|
569
613
|
if (!resource) {
|
|
570
614
|
return { error: `Resource ${resourceId} not found` };
|
|
571
615
|
}
|
|
572
|
-
for (const hook of
|
|
616
|
+
for (const hook of listify((_p = (_o = resource.hooks) === null || _o === void 0 ? void 0 : _o[source]) === null || _p === void 0 ? void 0 : _p.beforeDatasourceRequest)) {
|
|
573
617
|
const resp = yield hook({ resource, query: body, adminUser });
|
|
574
618
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
575
619
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -636,7 +680,7 @@ class AdminForth {
|
|
|
636
680
|
item[col.name] = targetDataMap[item[col.name]];
|
|
637
681
|
});
|
|
638
682
|
})));
|
|
639
|
-
for (const hook of
|
|
683
|
+
for (const hook of listify((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q[source]) === null || _r === void 0 ? void 0 : _r.afterDatasourceResponse)) {
|
|
640
684
|
const resp = yield hook({ resource, response: data.data, adminUser });
|
|
641
685
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
642
686
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -662,8 +706,8 @@ class AdminForth {
|
|
|
662
706
|
server.endpoint({
|
|
663
707
|
method: 'POST',
|
|
664
708
|
path: '/get_resource_foreign_data',
|
|
665
|
-
handler: (
|
|
666
|
-
var
|
|
709
|
+
handler: (_s) => __awaiter(this, [_s], void 0, function* ({ body, adminUser }) {
|
|
710
|
+
var _t, _u, _v, _w;
|
|
667
711
|
const { resourceId, column } = body;
|
|
668
712
|
if (!this.statuses.dbDiscover) {
|
|
669
713
|
return { error: 'Database discovery not started' };
|
|
@@ -684,8 +728,8 @@ class AdminForth {
|
|
|
684
728
|
}
|
|
685
729
|
const targetResourceId = columnConfig.foreignResource.resourceId;
|
|
686
730
|
const targetResource = this.config.resources.find((res) => res.resourceId == targetResourceId);
|
|
687
|
-
for (const hook of
|
|
688
|
-
const resp = yield hook({ query: body, adminUser });
|
|
731
|
+
for (const hook of listify((_u = (_t = columnConfig.foreignResource.hooks) === null || _t === void 0 ? void 0 : _t.dropdownList) === null || _u === void 0 ? void 0 : _u.beforeDatasourceRequest)) {
|
|
732
|
+
const resp = yield hook({ query: body, adminUser, resource: targetResource });
|
|
689
733
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
690
734
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
691
735
|
}
|
|
@@ -713,8 +757,8 @@ class AdminForth {
|
|
|
713
757
|
const response = {
|
|
714
758
|
items
|
|
715
759
|
};
|
|
716
|
-
for (const hook of
|
|
717
|
-
const resp = yield hook({ response, adminUser });
|
|
760
|
+
for (const hook of listify((_w = (_v = columnConfig.foreignResource.hooks) === null || _v === void 0 ? void 0 : _v.dropdownList) === null || _w === void 0 ? void 0 : _w.afterDatasourceResponse)) {
|
|
761
|
+
const resp = yield hook({ response, adminUser, resource: targetResource });
|
|
718
762
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
719
763
|
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
720
764
|
}
|
|
@@ -728,7 +772,7 @@ class AdminForth {
|
|
|
728
772
|
server.endpoint({
|
|
729
773
|
method: 'POST',
|
|
730
774
|
path: '/get_min_max_for_columns',
|
|
731
|
-
handler: (
|
|
775
|
+
handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body }) {
|
|
732
776
|
const { resourceId } = body;
|
|
733
777
|
if (!this.statuses.dbDiscover) {
|
|
734
778
|
return { error: 'Database discovery not started' };
|
|
@@ -757,8 +801,8 @@ class AdminForth {
|
|
|
757
801
|
server.endpoint({
|
|
758
802
|
method: 'POST',
|
|
759
803
|
path: '/create_record',
|
|
760
|
-
handler: (
|
|
761
|
-
var
|
|
804
|
+
handler: (_y) => __awaiter(this, [_y], void 0, function* ({ body, adminUser }) {
|
|
805
|
+
var _z, _0, _1, _2, _3;
|
|
762
806
|
console.log('create_record', body, this.config.resources);
|
|
763
807
|
const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
764
808
|
if (!resource) {
|
|
@@ -766,7 +810,7 @@ class AdminForth {
|
|
|
766
810
|
}
|
|
767
811
|
const record = body['record'];
|
|
768
812
|
// execute hook if needed
|
|
769
|
-
for (const hook of
|
|
813
|
+
for (const hook of listify((_0 = (_z = resource.hooks) === null || _z === void 0 ? void 0 : _z.create) === null || _0 === void 0 ? void 0 : _0.beforeSave)) {
|
|
770
814
|
const resp = yield hook({ resource, record, adminUser });
|
|
771
815
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
772
816
|
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -783,7 +827,7 @@ class AdminForth {
|
|
|
783
827
|
});
|
|
784
828
|
}
|
|
785
829
|
}
|
|
786
|
-
if (((
|
|
830
|
+
if (((_1 = column.required) === null || _1 === void 0 ? void 0 : _1.create) && body['record'][column.name] === undefined) {
|
|
787
831
|
return { error: `Column '${column.name}' is required` };
|
|
788
832
|
}
|
|
789
833
|
if (column.isUnique) {
|
|
@@ -808,7 +852,7 @@ class AdminForth {
|
|
|
808
852
|
const connector = this.connectors[resource.dataSource];
|
|
809
853
|
yield connector.createRecord({ resource, record });
|
|
810
854
|
// execute hook if needed
|
|
811
|
-
for (const hook of
|
|
855
|
+
for (const hook of listify((_3 = (_2 = resource.hooks) === null || _2 === void 0 ? void 0 : _2.create) === null || _3 === void 0 ? void 0 : _3.afterSave)) {
|
|
812
856
|
const resp = yield hook({ resource, record, adminUser });
|
|
813
857
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
814
858
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -825,8 +869,8 @@ class AdminForth {
|
|
|
825
869
|
server.endpoint({
|
|
826
870
|
method: 'POST',
|
|
827
871
|
path: '/update_record',
|
|
828
|
-
handler: (
|
|
829
|
-
var
|
|
872
|
+
handler: (_4) => __awaiter(this, [_4], void 0, function* ({ body, adminUser }) {
|
|
873
|
+
var _5, _6, _7, _8;
|
|
830
874
|
const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
831
875
|
if (!resource) {
|
|
832
876
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
@@ -840,7 +884,7 @@ class AdminForth {
|
|
|
840
884
|
}
|
|
841
885
|
const record = body['record'];
|
|
842
886
|
// execute hook if needed
|
|
843
|
-
for (const hook of
|
|
887
|
+
for (const hook of listify((_6 = (_5 = resource.hooks) === null || _5 === void 0 ? void 0 : _5.edit) === null || _6 === void 0 ? void 0 : _6.beforeSave)) {
|
|
844
888
|
const resp = yield hook({ resource, record, adminUser });
|
|
845
889
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
846
890
|
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -868,7 +912,7 @@ class AdminForth {
|
|
|
868
912
|
yield connector.updateRecord({ resource, recordId, record, newValues });
|
|
869
913
|
}
|
|
870
914
|
// execute hook if needed
|
|
871
|
-
for (const hook of
|
|
915
|
+
for (const hook of listify((_8 = (_7 = resource.hooks) === null || _7 === void 0 ? void 0 : _7.edit) === null || _8 === void 0 ? void 0 : _8.afterSave)) {
|
|
872
916
|
const resp = yield hook({ resource, record, adminUser });
|
|
873
917
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
874
918
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -885,8 +929,8 @@ class AdminForth {
|
|
|
885
929
|
server.endpoint({
|
|
886
930
|
method: 'POST',
|
|
887
931
|
path: '/delete_record',
|
|
888
|
-
handler: (
|
|
889
|
-
var
|
|
932
|
+
handler: (_9) => __awaiter(this, [_9], void 0, function* ({ body, adminUser }) {
|
|
933
|
+
var _10, _11, _12, _13;
|
|
890
934
|
const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
891
935
|
const record = yield this.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
|
|
892
936
|
if (!resource) {
|
|
@@ -899,7 +943,7 @@ class AdminForth {
|
|
|
899
943
|
return { error: `Resource '${resource.resourceId}' does not allow delete action` };
|
|
900
944
|
}
|
|
901
945
|
// execute hook if needed
|
|
902
|
-
for (const hook of
|
|
946
|
+
for (const hook of listify((_11 = (_10 = resource.hooks) === null || _10 === void 0 ? void 0 : _10.delete) === null || _11 === void 0 ? void 0 : _11.beforeSave)) {
|
|
903
947
|
const resp = yield hook({ resource, record, adminUser });
|
|
904
948
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
905
949
|
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -911,7 +955,7 @@ class AdminForth {
|
|
|
911
955
|
const connector = this.connectors[resource.dataSource];
|
|
912
956
|
yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
|
|
913
957
|
// execute hook if needed
|
|
914
|
-
for (const hook of
|
|
958
|
+
for (const hook of listify((_13 = (_12 = resource.hooks) === null || _12 === void 0 ? void 0 : _12.delete) === null || _13 === void 0 ? void 0 : _13.afterSave)) {
|
|
915
959
|
const resp = yield hook({ resource, record, adminUser });
|
|
916
960
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
917
961
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
@@ -928,7 +972,7 @@ class AdminForth {
|
|
|
928
972
|
server.endpoint({
|
|
929
973
|
method: 'POST',
|
|
930
974
|
path: '/start_bulk_action',
|
|
931
|
-
handler: (
|
|
975
|
+
handler: (_14) => __awaiter(this, [_14], void 0, function* ({ body }) {
|
|
932
976
|
const { resourceId, actionId, recordIds } = body;
|
|
933
977
|
const resource = this.config.resources.find((res) => res.resourceId == resourceId);
|
|
934
978
|
if (!resource) {
|
|
@@ -959,7 +1003,7 @@ _a = AdminForth, _AdminForth_defaultConfig = new WeakMap();
|
|
|
959
1003
|
AdminForth.Types = AdminForthDataTypes;
|
|
960
1004
|
AdminForth.Utils = {
|
|
961
1005
|
generatePasswordHash: (password) => __awaiter(void 0, void 0, void 0, function* () {
|
|
962
|
-
return yield
|
|
1006
|
+
return yield AdminForthAuth.generatePasswordHash(password);
|
|
963
1007
|
})
|
|
964
1008
|
};
|
|
965
1009
|
export default AdminForth;
|
|
@@ -229,10 +229,13 @@ class CodeInjector {
|
|
|
229
229
|
const customResourceComponents = [];
|
|
230
230
|
this.adminforth.config.resources.forEach((resource) => {
|
|
231
231
|
var _a;
|
|
232
|
-
resource.columns.forEach((
|
|
233
|
-
if (
|
|
234
|
-
Object.values(
|
|
232
|
+
resource.columns.forEach((column) => {
|
|
233
|
+
if (column.components) {
|
|
234
|
+
Object.values(column.components).forEach(({ file }) => {
|
|
235
235
|
if (!customResourceComponents.includes(file)) {
|
|
236
|
+
if (file === undefined) {
|
|
237
|
+
throw new Error('file is undefined from field.components, field:' + JSON.stringify(column));
|
|
238
|
+
}
|
|
236
239
|
customResourceComponents.push(file);
|
|
237
240
|
}
|
|
238
241
|
});
|
|
@@ -242,6 +245,9 @@ class CodeInjector {
|
|
|
242
245
|
Object.values(injection).forEach((filePathes) => {
|
|
243
246
|
filePathes.forEach(({ file }) => {
|
|
244
247
|
if (!customResourceComponents.includes(file)) {
|
|
248
|
+
if (file === undefined) {
|
|
249
|
+
throw new Error('file is undefined');
|
|
250
|
+
}
|
|
245
251
|
customResourceComponents.push(file);
|
|
246
252
|
}
|
|
247
253
|
});
|
package/dist/modules/utils.js
CHANGED
|
@@ -28,16 +28,6 @@ export const ADMINFORTH_VERSION = package_json.version;
|
|
|
28
28
|
export function getComponentNameFromPath(filePath) {
|
|
29
29
|
return filePath.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
|
|
30
30
|
}
|
|
31
|
-
export function
|
|
32
|
-
|
|
33
|
-
if (Array.isArray(param)) {
|
|
34
|
-
return param;
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
return [param];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
31
|
+
export function listify(param) {
|
|
32
|
+
return param || [];
|
|
43
33
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AllowedActionsEnum } from "../../types/AdminForthConfig.js";
|
|
11
|
+
import AdminForthPlugin from "../base.js";
|
|
12
|
+
class AccessControlPlugin extends AdminForthPlugin {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
super(options, import.meta.url);
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
modifyResourceConfig(adminforth, resourceConfig) {
|
|
18
|
+
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
19
|
+
this.adminforth = adminforth;
|
|
20
|
+
if (!resourceConfig.hooks) {
|
|
21
|
+
resourceConfig.hooks = {};
|
|
22
|
+
}
|
|
23
|
+
const checkAccess = (adminUser, action, meta) => __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
const hasAccessOrError = yield this.options.hasAccess(adminUser, action, meta);
|
|
25
|
+
if (hasAccessOrError === true) {
|
|
26
|
+
return { ok: true };
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return { ok: false, error: hasAccessOrError || AccessControlPlugin.defaultError };
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const bindHookCheck = (action, hookName) => {
|
|
33
|
+
if (!resourceConfig.hooks[action]) {
|
|
34
|
+
resourceConfig.hooks[action] = {};
|
|
35
|
+
}
|
|
36
|
+
if (!resourceConfig.hooks[action][hookName]) {
|
|
37
|
+
resourceConfig.hooks[action][hookName] = [];
|
|
38
|
+
}
|
|
39
|
+
if (hookName === 'beforeDatasourceRequest') {
|
|
40
|
+
resourceConfig.hooks[action][hookName].unshift((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, query }) {
|
|
41
|
+
return checkAccess(adminUser, action, { query });
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
resourceConfig.hooks[action][hookName].unshift((_b) => __awaiter(this, [_b], void 0, function* ({ adminUser, record }) {
|
|
46
|
+
return checkAccess(adminUser, action, { record });
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
// List check
|
|
51
|
+
bindHookCheck(AllowedActionsEnum.list, 'beforeDatasourceRequest');
|
|
52
|
+
// Show check
|
|
53
|
+
bindHookCheck(AllowedActionsEnum.show, 'beforeDatasourceRequest');
|
|
54
|
+
// Edit check
|
|
55
|
+
bindHookCheck(AllowedActionsEnum.edit, 'beforeDatasourceRequest');
|
|
56
|
+
// create check
|
|
57
|
+
bindHookCheck(AllowedActionsEnum.create, 'beforeSave');
|
|
58
|
+
// edit check
|
|
59
|
+
bindHookCheck(AllowedActionsEnum.edit, 'beforeSave');
|
|
60
|
+
// delete check
|
|
61
|
+
bindHookCheck(AllowedActionsEnum.delete, 'beforeSave');
|
|
62
|
+
console.log('resourceConfig', resourceConfig);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
AccessControlPlugin.defaultError = 'Sorry, you do not have access to this resource.';
|
|
66
|
+
export default AccessControlPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|