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.
Files changed (44) hide show
  1. package/auth.ts +17 -3
  2. package/dist/auth.js +32 -18
  3. package/dist/index.js +107 -63
  4. package/dist/modules/codeInjector.js +9 -3
  5. package/dist/modules/utils.js +2 -12
  6. package/dist/plugins/AccessControl/index.js +66 -0
  7. package/dist/plugins/AccessControl/types.js +1 -0
  8. package/dist/plugins/ForeignInlineListPlugin/index.js +1 -1
  9. package/dist/servers/express.js +2 -2
  10. package/dist/spa/spa/src/App.vue +7 -3
  11. package/dist/spa/spa/src/components/Toast.vue +5 -4
  12. package/dist/spa/spa/src/components/ValueRenderer.vue +1 -1
  13. package/dist/spa/spa/src/composables/useStores.ts +2 -1
  14. package/dist/spa/spa/src/router/index.ts +13 -1
  15. package/dist/spa/spa/src/stores/core.ts +13 -11
  16. package/dist/spa/spa/src/stores/user.ts +50 -0
  17. package/dist/spa/spa/src/utils.ts +8 -3
  18. package/dist/spa/spa/src/views/CreateView.vue +7 -0
  19. package/dist/spa/spa/src/views/EditView.vue +8 -1
  20. package/dist/spa/spa/src/views/ListView.vue +10 -1
  21. package/dist/types/AdminForthConfig.js +9 -0
  22. package/dist/types/FrontendAPI.js +4 -4
  23. package/index.ts +103 -51
  24. package/modules/codeInjector.ts +9 -3
  25. package/modules/utils.ts +2 -10
  26. package/package.json +1 -1
  27. package/plugins/AccessControl/index.ts +83 -0
  28. package/plugins/AccessControl/types.ts +14 -0
  29. package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +13 -1
  30. package/plugins/ForeignInlineListPlugin/index.ts +2 -1
  31. package/servers/express.ts +2 -2
  32. package/spa/src/App.vue +7 -3
  33. package/spa/src/components/Toast.vue +5 -4
  34. package/spa/src/components/ValueRenderer.vue +1 -1
  35. package/spa/src/composables/useStores.ts +2 -1
  36. package/spa/src/router/index.ts +13 -1
  37. package/spa/src/stores/core.ts +13 -11
  38. package/spa/src/stores/user.ts +50 -0
  39. package/spa/src/utils.ts +8 -3
  40. package/spa/src/views/CreateView.vue +7 -0
  41. package/spa/src/views/EditView.vue +8 -1
  42. package/spa/src/views/ListView.vue +10 -1
  43. package/types/AdminForthConfig.ts +73 -21
  44. 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
- const decoded = jwt.verify(jwtToken, secret);
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
- // read ADMINFORH_SECRET from environment if not drop error
39
- const secret = process.env.ADMINFORTH_SECRET;
40
- if (!secret) {
41
- throw new Error('ADMINFORTH_SECRET environment not set');
42
- }
43
- try {
44
- // verify JWT token
45
- const decoded = jwt.verify(jwtToken, secret);
46
- return decoded;
47
- }
48
- catch (err) {
49
- if (err.name === 'TokenExpiredError') {
50
- console.error('Token expired:', err.message);
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
- else if (err.name === 'JsonWebTokenError') {
53
- console.error('Token error:', err.message);
64
+ const { pk } = decoded;
65
+ if (pk === null) {
66
+ decoded.isRoot = true;
54
67
  }
55
68
  else {
56
- console.error('Failed to verify JWT token', err);
69
+ const dbUser = yield this.adminforth.getUserByPk(pk);
70
+ decoded.dbUser = dbUser;
57
71
  }
58
- return null;
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 Auth from './auth.js';
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 Auth();
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
- errors.push(...this.checkCustomFileExists(obj.file));
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
- console.log('🔧🔧🔧 Validating components for resource', column.components);
317
- for (const comp of Object.values(column.components)) {
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
- continue;
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 Auth.verifyPassword(password, passwordHash);
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: (_d) => __awaiter(this, [_d], void 0, function* ({ response }) {
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: (_e) => __awaiter(this, [_e], void 0, function* ({ body }) {
464
- var _f;
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: (_f = this.config.customization) === null || _f === void 0 ? void 0 : _f.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: (_g) => __awaiter(this, [_g], void 0, function* ({ input, adminUser, cookies }) {
484
- var _h, _j;
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 (cookieParsed['pk'] == null) {
544
+ if (adminUser.isRoot) {
489
545
  username = this.config.rootUser.username;
490
546
  }
491
547
  else {
492
- const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
493
- const user = yield this.connectors[userResource.dataSource].getData({
494
- resource: userResource,
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: (_h = this.config.customization) === null || _h === void 0 ? void 0 : _h.title,
527
- emptyFieldPlaceholder: (_j = this.config.customization) === null || _j === void 0 ? void 0 : _j.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: (_k) => __awaiter(this, [_k], void 0, function* ({ body }) {
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: (_l) => __awaiter(this, [_l], void 0, function* ({ body, adminUser }) {
557
- var _m, _o, _p, _q;
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 getFunctionList((_o = (_m = resource.hooks) === null || _m === void 0 ? void 0 : _m[source]) === null || _o === void 0 ? void 0 : _o.beforeDatasourceRequest)) {
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 getFunctionList((_q = (_p = resource.hooks) === null || _p === void 0 ? void 0 : _p[source]) === null || _q === void 0 ? void 0 : _q.afterDatasourceResponse)) {
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: (_r) => __awaiter(this, [_r], void 0, function* ({ body, adminUser }) {
666
- var _s, _t, _u, _v;
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 getFunctionList((_t = (_s = columnConfig.foreignResource.hooks) === null || _s === void 0 ? void 0 : _s.dropdownList) === null || _t === void 0 ? void 0 : _t.beforeDatasourceRequest)) {
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 getFunctionList((_v = (_u = columnConfig.foreignResource.hooks) === null || _u === void 0 ? void 0 : _u.dropdownList) === null || _v === void 0 ? void 0 : _v.afterDatasourceResponse)) {
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: (_w) => __awaiter(this, [_w], void 0, function* ({ body }) {
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: (_x) => __awaiter(this, [_x], void 0, function* ({ body, adminUser }) {
761
- var _y, _z, _0, _1, _2;
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 getFunctionList((_z = (_y = resource.hooks) === null || _y === void 0 ? void 0 : _y.create) === null || _z === void 0 ? void 0 : _z.beforeSave)) {
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 (((_0 = column.required) === null || _0 === void 0 ? void 0 : _0.create) && body['record'][column.name] === undefined) {
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 getFunctionList((_2 = (_1 = resource.hooks) === null || _1 === void 0 ? void 0 : _1.create) === null || _2 === void 0 ? void 0 : _2.afterSave)) {
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: (_3) => __awaiter(this, [_3], void 0, function* ({ body, adminUser }) {
829
- var _4, _5, _6, _7;
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 getFunctionList((_5 = (_4 = resource.hooks) === null || _4 === void 0 ? void 0 : _4.edit) === null || _5 === void 0 ? void 0 : _5.beforeSave)) {
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 getFunctionList((_7 = (_6 = resource.hooks) === null || _6 === void 0 ? void 0 : _6.edit) === null || _7 === void 0 ? void 0 : _7.afterSave)) {
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: (_8) => __awaiter(this, [_8], void 0, function* ({ body, adminUser }) {
889
- var _9, _10, _11, _12;
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 getFunctionList((_10 = (_9 = resource.hooks) === null || _9 === void 0 ? void 0 : _9.delete) === null || _10 === void 0 ? void 0 : _10.beforeSave)) {
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 getFunctionList((_12 = (_11 = resource.hooks) === null || _11 === void 0 ? void 0 : _11.delete) === null || _12 === void 0 ? void 0 : _12.afterSave)) {
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: (_13) => __awaiter(this, [_13], void 0, function* ({ body }) {
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 Auth.generatePasswordHash(password);
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((field) => {
233
- if (field.components) {
234
- Object.values(field.components).forEach(({ file }) => {
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
  });
@@ -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 getFunctionList(param) {
32
- if (param) {
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 {};