adminforth 1.1.13 → 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 +99 -58
  4. package/dist/modules/codeInjector.js +0 -1
  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 +91 -42
  24. package/modules/codeInjector.ts +0 -1
  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 +11 -0
  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`);
@@ -254,6 +253,37 @@ class AdminForth {
254
253
  else {
255
254
  res.options.allowedActions = DEFAULT_ALLOWED_ACTIONS;
256
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
+ }
257
287
  });
258
288
  if (!this.config.menu) {
259
289
  errors.push('No config.menu defined');
@@ -315,7 +345,6 @@ class AdminForth {
315
345
  for (const resource of this.config.resources) {
316
346
  for (const column of resource.columns) {
317
347
  if (column.components) {
318
- console.log('🔧🔧🔧 Validating components for resource', column.components);
319
348
  for (const [key, comp] of Object.entries(column.components)) {
320
349
  let ignoreExistsCheck = false;
321
350
  if (this.codeInjector.allComponentNames[comp.file]) {
@@ -393,6 +422,24 @@ class AdminForth {
393
422
  this.codeInjector.bundleNow({ hotReload, verbose });
394
423
  });
395
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
+ }
396
443
  setupEndpoints(server) {
397
444
  server.endpoint({
398
445
  noAuth: true,
@@ -436,7 +483,7 @@ class AdminForth {
436
483
  }
437
484
  const passwordHash = userRecord[this.config.auth.passwordHashField];
438
485
  console.log('User record', userRecord, passwordHash); // why does it has no hash?
439
- const valid = yield Auth.verifyPassword(password, passwordHash);
486
+ const valid = yield AdminForthAuth.verifyPassword(password, passwordHash);
440
487
  if (valid) {
441
488
  token = this.auth.issueJWT({
442
489
  username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
@@ -450,11 +497,18 @@ class AdminForth {
450
497
  return { ok: true };
451
498
  }),
452
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
+ });
453
507
  server.endpoint({
454
508
  noAuth: true,
455
509
  method: 'POST',
456
510
  path: '/logout',
457
- handler: (_d) => __awaiter(this, [_d], void 0, function* ({ response }) {
511
+ handler: (_e) => __awaiter(this, [_e], void 0, function* ({ response }) {
458
512
  response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
459
513
  return { ok: true };
460
514
  }),
@@ -463,8 +517,8 @@ class AdminForth {
463
517
  noAuth: true,
464
518
  method: 'GET',
465
519
  path: '/get_public_config',
466
- handler: (_e) => __awaiter(this, [_e], void 0, function* ({ body }) {
467
- var _f;
520
+ handler: (_f) => __awaiter(this, [_f], void 0, function* ({ body }) {
521
+ var _g;
468
522
  // find resource
469
523
  if (!this.config.auth) {
470
524
  throw new Error('No config.auth defined');
@@ -476,37 +530,24 @@ class AdminForth {
476
530
  brandName: this.config.customization.brandName,
477
531
  usernameFieldName: usernameColumn.label,
478
532
  loginBackgroundImage: this.config.auth.loginBackgroundImage,
479
- 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,
480
534
  };
481
535
  }),
482
536
  });
483
537
  server.endpoint({
484
538
  method: 'GET',
485
539
  path: '/get_base_config',
486
- handler: (_g) => __awaiter(this, [_g], void 0, function* ({ input, adminUser, cookies }) {
487
- var _h, _j;
488
- const cookieParsed = this.auth.verify(cookies['adminforth_jwt']);
540
+ handler: (_h) => __awaiter(this, [_h], void 0, function* ({ input, adminUser, cookies }) {
541
+ var _j, _k;
489
542
  let username = '';
490
543
  let userFullName = '';
491
- if (cookieParsed['pk'] == null) {
544
+ if (adminUser.isRoot) {
492
545
  username = this.config.rootUser.username;
493
546
  }
494
547
  else {
495
- const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
496
- const user = yield this.connectors[userResource.dataSource].getData({
497
- resource: userResource,
498
- filters: [
499
- { field: userResource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: cookieParsed['pk'] },
500
- ],
501
- limit: 1,
502
- offset: 0,
503
- sort: [],
504
- });
505
- if (!user.data.length) {
506
- return { error: 'Unauthorized' };
507
- }
508
- username = user.data[0][this.config.auth.usernameField];
509
- 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];
510
551
  }
511
552
  const userData = {
512
553
  [this.config.auth.usernameField]: username,
@@ -526,8 +567,8 @@ class AdminForth {
526
567
  deleteConfirmation: this.config.deleteConfirmation,
527
568
  auth: this.config.auth,
528
569
  usernameField: this.config.auth.usernameField,
529
- title: (_h = this.config.customization) === null || _h === void 0 ? void 0 : _h.title,
530
- 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,
531
572
  },
532
573
  adminUser,
533
574
  version: ADMINFORTH_VERSION,
@@ -537,7 +578,7 @@ class AdminForth {
537
578
  server.endpoint({
538
579
  method: 'POST',
539
580
  path: '/get_resource',
540
- handler: (_k) => __awaiter(this, [_k], void 0, function* ({ body }) {
581
+ handler: (_l) => __awaiter(this, [_l], void 0, function* ({ body }) {
541
582
  const { resourceId } = body;
542
583
  if (!this.statuses.dbDiscover) {
543
584
  return { error: 'Database discovery not started' };
@@ -556,8 +597,8 @@ class AdminForth {
556
597
  server.endpoint({
557
598
  method: 'POST',
558
599
  path: '/get_resource_data',
559
- handler: (_l) => __awaiter(this, [_l], void 0, function* ({ body, adminUser }) {
560
- var _m, _o, _p, _q;
600
+ handler: (_m) => __awaiter(this, [_m], void 0, function* ({ body, adminUser }) {
601
+ var _o, _p, _q, _r;
561
602
  const { resourceId, source } = body;
562
603
  if (['show', 'list'].includes(source) === false) {
563
604
  return { error: 'Invalid source, should be list or show' };
@@ -572,7 +613,7 @@ class AdminForth {
572
613
  if (!resource) {
573
614
  return { error: `Resource ${resourceId} not found` };
574
615
  }
575
- 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)) {
576
617
  const resp = yield hook({ resource, query: body, adminUser });
577
618
  if (!resp || (!resp.ok && !resp.error)) {
578
619
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -639,7 +680,7 @@ class AdminForth {
639
680
  item[col.name] = targetDataMap[item[col.name]];
640
681
  });
641
682
  })));
642
- 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)) {
643
684
  const resp = yield hook({ resource, response: data.data, adminUser });
644
685
  if (!resp || (!resp.ok && !resp.error)) {
645
686
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -665,8 +706,8 @@ class AdminForth {
665
706
  server.endpoint({
666
707
  method: 'POST',
667
708
  path: '/get_resource_foreign_data',
668
- handler: (_r) => __awaiter(this, [_r], void 0, function* ({ body, adminUser }) {
669
- var _s, _t, _u, _v;
709
+ handler: (_s) => __awaiter(this, [_s], void 0, function* ({ body, adminUser }) {
710
+ var _t, _u, _v, _w;
670
711
  const { resourceId, column } = body;
671
712
  if (!this.statuses.dbDiscover) {
672
713
  return { error: 'Database discovery not started' };
@@ -687,8 +728,8 @@ class AdminForth {
687
728
  }
688
729
  const targetResourceId = columnConfig.foreignResource.resourceId;
689
730
  const targetResource = this.config.resources.find((res) => res.resourceId == targetResourceId);
690
- 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)) {
691
- 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 });
692
733
  if (!resp || (!resp.ok && !resp.error)) {
693
734
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
694
735
  }
@@ -716,8 +757,8 @@ class AdminForth {
716
757
  const response = {
717
758
  items
718
759
  };
719
- 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)) {
720
- 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 });
721
762
  if (!resp || (!resp.ok && !resp.error)) {
722
763
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
723
764
  }
@@ -731,7 +772,7 @@ class AdminForth {
731
772
  server.endpoint({
732
773
  method: 'POST',
733
774
  path: '/get_min_max_for_columns',
734
- handler: (_w) => __awaiter(this, [_w], void 0, function* ({ body }) {
775
+ handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body }) {
735
776
  const { resourceId } = body;
736
777
  if (!this.statuses.dbDiscover) {
737
778
  return { error: 'Database discovery not started' };
@@ -760,8 +801,8 @@ class AdminForth {
760
801
  server.endpoint({
761
802
  method: 'POST',
762
803
  path: '/create_record',
763
- handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body, adminUser }) {
764
- var _y, _z, _0, _1, _2;
804
+ handler: (_y) => __awaiter(this, [_y], void 0, function* ({ body, adminUser }) {
805
+ var _z, _0, _1, _2, _3;
765
806
  console.log('create_record', body, this.config.resources);
766
807
  const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
767
808
  if (!resource) {
@@ -769,7 +810,7 @@ class AdminForth {
769
810
  }
770
811
  const record = body['record'];
771
812
  // execute hook if needed
772
- 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)) {
773
814
  const resp = yield hook({ resource, record, adminUser });
774
815
  if (!resp || (!resp.ok && !resp.error)) {
775
816
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -786,7 +827,7 @@ class AdminForth {
786
827
  });
787
828
  }
788
829
  }
789
- 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) {
790
831
  return { error: `Column '${column.name}' is required` };
791
832
  }
792
833
  if (column.isUnique) {
@@ -811,7 +852,7 @@ class AdminForth {
811
852
  const connector = this.connectors[resource.dataSource];
812
853
  yield connector.createRecord({ resource, record });
813
854
  // execute hook if needed
814
- 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)) {
815
856
  const resp = yield hook({ resource, record, adminUser });
816
857
  if (!resp || (!resp.ok && !resp.error)) {
817
858
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -828,8 +869,8 @@ class AdminForth {
828
869
  server.endpoint({
829
870
  method: 'POST',
830
871
  path: '/update_record',
831
- handler: (_3) => __awaiter(this, [_3], void 0, function* ({ body, adminUser }) {
832
- var _4, _5, _6, _7;
872
+ handler: (_4) => __awaiter(this, [_4], void 0, function* ({ body, adminUser }) {
873
+ var _5, _6, _7, _8;
833
874
  const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
834
875
  if (!resource) {
835
876
  return { error: `Resource '${body['resourceId']}' not found` };
@@ -843,7 +884,7 @@ class AdminForth {
843
884
  }
844
885
  const record = body['record'];
845
886
  // execute hook if needed
846
- 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)) {
847
888
  const resp = yield hook({ resource, record, adminUser });
848
889
  if (!resp || (!resp.ok && !resp.error)) {
849
890
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -871,7 +912,7 @@ class AdminForth {
871
912
  yield connector.updateRecord({ resource, recordId, record, newValues });
872
913
  }
873
914
  // execute hook if needed
874
- 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)) {
875
916
  const resp = yield hook({ resource, record, adminUser });
876
917
  if (!resp || (!resp.ok && !resp.error)) {
877
918
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -888,8 +929,8 @@ class AdminForth {
888
929
  server.endpoint({
889
930
  method: 'POST',
890
931
  path: '/delete_record',
891
- handler: (_8) => __awaiter(this, [_8], void 0, function* ({ body, adminUser }) {
892
- var _9, _10, _11, _12;
932
+ handler: (_9) => __awaiter(this, [_9], void 0, function* ({ body, adminUser }) {
933
+ var _10, _11, _12, _13;
893
934
  const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
894
935
  const record = yield this.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
895
936
  if (!resource) {
@@ -902,7 +943,7 @@ class AdminForth {
902
943
  return { error: `Resource '${resource.resourceId}' does not allow delete action` };
903
944
  }
904
945
  // execute hook if needed
905
- 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)) {
906
947
  const resp = yield hook({ resource, record, adminUser });
907
948
  if (!resp || (!resp.ok && !resp.error)) {
908
949
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -914,7 +955,7 @@ class AdminForth {
914
955
  const connector = this.connectors[resource.dataSource];
915
956
  yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
916
957
  // execute hook if needed
917
- 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)) {
918
959
  const resp = yield hook({ resource, record, adminUser });
919
960
  if (!resp || (!resp.ok && !resp.error)) {
920
961
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -931,7 +972,7 @@ class AdminForth {
931
972
  server.endpoint({
932
973
  method: 'POST',
933
974
  path: '/start_bulk_action',
934
- handler: (_13) => __awaiter(this, [_13], void 0, function* ({ body }) {
975
+ handler: (_14) => __awaiter(this, [_14], void 0, function* ({ body }) {
935
976
  const { resourceId, actionId, recordIds } = body;
936
977
  const resource = this.config.resources.find((res) => res.resourceId == resourceId);
937
978
  if (!resource) {
@@ -962,7 +1003,7 @@ _a = AdminForth, _AdminForth_defaultConfig = new WeakMap();
962
1003
  AdminForth.Types = AdminForthDataTypes;
963
1004
  AdminForth.Utils = {
964
1005
  generatePasswordHash: (password) => __awaiter(void 0, void 0, void 0, function* () {
965
- return yield Auth.generatePasswordHash(password);
1006
+ return yield AdminForthAuth.generatePasswordHash(password);
966
1007
  })
967
1008
  };
968
1009
  export default AdminForth;
@@ -254,7 +254,6 @@ class CodeInjector {
254
254
  });
255
255
  });
256
256
  });
257
- console.log('🔧 🔧 Injecting code into Vue sources...', customResourceComponents);
258
257
  customResourceComponents.forEach((filePath) => {
259
258
  const componentName = getComponentNameFromPath(filePath);
260
259
  this.allComponentNames[filePath] = componentName;
@@ -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 {};
@@ -24,7 +24,7 @@ export default class ForeignInlineListPlugin extends AdminForthPlugin {
24
24
  return { error: `Resource ${this.options.foreignResourceId} not found` };
25
25
  }
26
26
  // exclude "plugins" key
27
- const resourceCopy = Object.assign(Object.assign({}, resource), { plugins: undefined });
27
+ const resourceCopy = JSON.parse(JSON.stringify(Object.assign(Object.assign({}, resource), { plugins: undefined })));
28
28
  if (this.options.modifyTableResourceConfig) {
29
29
  this.options.modifyTableResourceConfig(resourceCopy);
30
30
  }
@@ -149,7 +149,7 @@ class ExpressServer {
149
149
  res.status(401).send('Unauthorized by AdminForth');
150
150
  return;
151
151
  }
152
- const adminforthUser = this.adminforth.auth.verify(jwt);
152
+ const adminforthUser = yield this.adminforth.auth.verify(jwt);
153
153
  if (!adminforthUser) {
154
154
  res.status(401).send('Unauthorized by AdminForth');
155
155
  }
@@ -191,7 +191,7 @@ class ExpressServer {
191
191
  this.message = message;
192
192
  }
193
193
  };
194
- const input = { body, query, headers, cookies, response, _raw_express_req: req, _raw_express_res: res };
194
+ const input = { body, query, headers, cookies, adminUser, response, _raw_express_req: req, _raw_express_res: res };
195
195
  let output;
196
196
  try {
197
197
  output = yield handler(input);