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/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import Auth from './auth.js';
2
+ import AdminForthAuth from './auth.js';
3
3
  import MongoConnector from './dataConnectors/mongo.js';
4
4
  import PostgresConnector from './dataConnectors/postgres.js';
5
5
  import SQLiteConnector from './dataConnectors/sqlite.js';
@@ -8,10 +8,13 @@ import { guessLabelFromName } from './modules/utils.js';
8
8
  import ExpressServer from './servers/express.js';
9
9
  import {v1 as uuid} from 'uuid';
10
10
  import fs from 'fs';
11
- import { ADMINFORTH_VERSION } from './modules/utils.js';
11
+ import { ADMINFORTH_VERSION, listify } from './modules/utils.js';
12
12
  import { AdminForthConfig, AdminForthClass, AdminForthComponentDeclaration, AdminForthComponentDeclarationFull,
13
- AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages } from './types/AdminForthConfig.js';
14
- import { getFunctionList } from './modules/utils.js';
13
+ AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages, GenericHttpServer,
14
+ BeforeSaveFunction,
15
+ AfterSaveFunction,
16
+ BeforeDataSourceRequestFunction,
17
+ AfterDataSourceResponseFunction} from './types/AdminForthConfig.js';
15
18
  import path from 'path';
16
19
  import AdminForthPlugin from './plugins/base.js';
17
20
 
@@ -26,7 +29,7 @@ class AdminForth implements AdminForthClass {
26
29
 
27
30
  static Utils = {
28
31
  generatePasswordHash: async (password) => {
29
- return await Auth.generatePasswordHash(password);
32
+ return await AdminForthAuth.generatePasswordHash(password);
30
33
  }
31
34
  }
32
35
 
@@ -36,7 +39,7 @@ class AdminForth implements AdminForthClass {
36
39
 
37
40
  config: AdminForthConfig;
38
41
  express: ExpressServer;
39
- auth: Auth;
42
+ auth: AdminForthAuth;
40
43
  codeInjector: CodeInjector;
41
44
  connectors: any;
42
45
  connectorClasses: any;
@@ -57,7 +60,7 @@ class AdminForth implements AdminForthClass {
57
60
  this.validateConfig(); // revalidate after plugins
58
61
 
59
62
  this.express = new ExpressServer(this);
60
- this.auth = new Auth();
63
+ this.auth = new AdminForthAuth(this);
61
64
  this.connectors = {};
62
65
  this.statuses = {};
63
66
 
@@ -83,7 +86,7 @@ class AdminForth implements AdminForthClass {
83
86
  return [];
84
87
  }
85
88
 
86
- validateComponent(component: AdminForthComponentDeclaration, errors: Array<string>): AdminForthComponentDeclaration {
89
+ validateComponent(component: AdminForthComponentDeclaration, errors: Array<string>, ignoreExistsCheck: boolean = false): AdminForthComponentDeclaration {
87
90
  if (!component) {
88
91
  return component;
89
92
  }
@@ -93,7 +96,9 @@ class AdminForth implements AdminForthClass {
93
96
  } else {
94
97
  obj = component;
95
98
  }
96
- errors.push(...this.checkCustomFileExists(obj.file));
99
+ if (!ignoreExistsCheck) {
100
+ errors.push(...this.checkCustomFileExists(obj.file));
101
+ }
97
102
 
98
103
  return obj;
99
104
  }
@@ -235,7 +240,7 @@ class AdminForth implements AdminForthClass {
235
240
  if(res.options?.bulkActions){
236
241
  let bulkActions = res.options.bulkActions;
237
242
 
238
- if(!Array.isArray(bulkActions)){
243
+ if (!Array.isArray(bulkActions)) {
239
244
  errors.push(`Resource "${res.resourceId}" bulkActions must be an array`);
240
245
  bulkActions = [];
241
246
  }
@@ -292,7 +297,40 @@ class AdminForth implements AdminForthClass {
292
297
  } else {
293
298
  res.options.allowedActions = DEFAULT_ALLOWED_ACTIONS;
294
299
  }
295
- })
300
+
301
+ // transform all hooks Functions to array of functions
302
+ if (res.hooks) {
303
+ for (const value of [res.hooks.show, res.hooks.list]) {
304
+ if (value) {
305
+ if (value.beforeDatasourceRequest) {
306
+ if (!Array.isArray(value.beforeDatasourceRequest)) {
307
+ value.beforeDatasourceRequest = [value.beforeDatasourceRequest];
308
+ }
309
+ }
310
+ if (value.afterDatasourceResponse) {
311
+ if (!Array.isArray(value.afterDatasourceResponse)) {
312
+ value.afterDatasourceResponse = [value.afterDatasourceResponse];
313
+ }
314
+ }
315
+ }
316
+ }
317
+ for (const value of [res.hooks.create, res.hooks.edit, res.hooks.delete]) {
318
+ if (value) {
319
+
320
+ if (value.beforeSave) {
321
+ if (!Array.isArray(value.beforeSave)) {
322
+ value.beforeSave = [value.beforeSave];
323
+ }
324
+ }
325
+ if (value.afterSave) {
326
+ if (!Array.isArray(value.afterSave)) {
327
+ value.afterSave = [value.afterSave];
328
+ }
329
+ }
330
+ }
331
+ }
332
+ }
333
+ });
296
334
 
297
335
 
298
336
 
@@ -371,15 +409,15 @@ class AdminForth implements AdminForthClass {
371
409
  for (const resource of this.config.resources) {
372
410
  for (const column of resource.columns) {
373
411
  if (column.components) {
374
- console.log('🔧🔧🔧 Validating components for resource', column.components);
375
412
 
376
- for (const comp of Object.values(column.components) as Array<AdminForthComponentDeclarationFull>) {
377
- if (this.codeInjector.allComponentNames[comp.file]) {
378
- // not obvious, but if we are in this if, it means that this is plugin component
379
- // and there is no sense to check if it exists in users folder
380
- continue;
381
- }
382
- this.validateComponent(comp, errors);
413
+ for (const [key, comp] of Object.entries(column.components as Record<string, AdminForthComponentDeclarationFull>)) {
414
+ let ignoreExistsCheck = false;
415
+ if (this.codeInjector.allComponentNames[comp.file]) {
416
+ // not obvious, but if we are in this if, it means that this is plugin component
417
+ // and there is no sense to check if it exists in users folder
418
+ ignoreExistsCheck = true;
419
+ }
420
+ column.components[key] = this.validateComponent(comp, errors, ignoreExistsCheck);
383
421
  }
384
422
  }
385
423
  }
@@ -458,7 +496,24 @@ class AdminForth implements AdminForthClass {
458
496
  this.codeInjector.bundleNow({ hotReload, verbose });
459
497
  }
460
498
 
461
- setupEndpoints(server) {
499
+ async getUserByPk(pk: string) {
500
+ const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
501
+ if (!resource) {
502
+ throw new Error('No auth resource found');
503
+ }
504
+ const users = await this.connectors[resource.dataSource].getData({
505
+ resource,
506
+ filters: [
507
+ { field: resource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: pk },
508
+ ],
509
+ limit: 1,
510
+ offset: 0,
511
+ sort: [],
512
+ });
513
+ return users.data[0] || null;
514
+ }
515
+
516
+ setupEndpoints(server: GenericHttpServer) {
462
517
  server.endpoint({
463
518
  noAuth: true,
464
519
  method: 'POST',
@@ -504,7 +559,7 @@ class AdminForth implements AdminForthClass {
504
559
 
505
560
  const passwordHash = userRecord[this.config.auth.passwordHashField];
506
561
  console.log('User record', userRecord, passwordHash) // why does it has no hash?
507
- const valid = await Auth.verifyPassword(password, passwordHash);
562
+ const valid = await AdminForthAuth.verifyPassword(password, passwordHash);
508
563
  if (valid) {
509
564
  token = this.auth.issueJWT({
510
565
  username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
@@ -519,6 +574,14 @@ class AdminForth implements AdminForthClass {
519
574
  },
520
575
  });
521
576
 
577
+ server.endpoint({
578
+ method: 'POST',
579
+ path: '/check_auth',
580
+ handler: async ({ adminUser }) => {
581
+ return { ok: true };
582
+ },
583
+ });
584
+
522
585
  server.endpoint({
523
586
  noAuth: true,
524
587
  method: 'POST',
@@ -556,27 +619,14 @@ class AdminForth implements AdminForthClass {
556
619
  method: 'GET',
557
620
  path: '/get_base_config',
558
621
  handler: async ({input, adminUser, cookies}) => {
559
- const cookieParsed = this.auth.verify(cookies['adminforth_jwt']);
560
622
  let username = ''
561
623
  let userFullName = ''
562
- if (cookieParsed['pk'] == null) {
624
+ if (adminUser.isRoot) {
563
625
  username = this.config.rootUser.username;
564
626
  } else {
565
- const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
566
- const user = await this.connectors[userResource.dataSource].getData({
567
- resource: userResource,
568
- filters: [
569
- { field: userResource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: cookieParsed['pk'] },
570
- ],
571
- limit: 1,
572
- offset: 0,
573
- sort: [],
574
- });
575
- if (!user.data.length) {
576
- return { error: 'Unauthorized' };
577
- }
578
- username = user.data[0][this.config.auth.usernameField];
579
- userFullName = user.data[0][this.config.auth.userFullNameField];
627
+ const dbUser = adminUser.dbUser;
628
+ username = dbUser[this.config.auth.usernameField];
629
+ userFullName =dbUser[this.config.auth.userFullNameField];
580
630
  }
581
631
 
582
632
  const userData = {
@@ -606,6 +656,8 @@ class AdminForth implements AdminForthClass {
606
656
  },
607
657
  });
608
658
 
659
+
660
+
609
661
  server.endpoint({
610
662
  method: 'POST',
611
663
  path: '/get_resource',
@@ -644,7 +696,7 @@ class AdminForth implements AdminForthClass {
644
696
  return { error: `Resource ${resourceId} not found` };
645
697
  }
646
698
 
647
- for (const hook of getFunctionList(resource.hooks?.[source]?.beforeDatasourceRequest)) {
699
+ for (const hook of listify(resource.hooks?.[source]?.beforeDatasourceRequest)) {
648
700
  const resp = await hook({ resource, query: body, adminUser });
649
701
  if (!resp || (!resp.ok && !resp.error)) {
650
702
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -675,7 +727,7 @@ class AdminForth implements AdminForthClass {
675
727
  // nonsense
676
728
  return { data: [], total: 0 };
677
729
  }
678
- }
730
+ }
679
731
 
680
732
  const data = await this.connectors[resource.dataSource].getData({
681
733
  resource,
@@ -720,7 +772,7 @@ class AdminForth implements AdminForthClass {
720
772
  })
721
773
  );
722
774
 
723
- for (const hook of getFunctionList(resource.hooks?.[source]?.afterDatasourceResponse)) {
775
+ for (const hook of listify(resource.hooks?.[source]?.afterDatasourceResponse)) {
724
776
  const resp = await hook({ resource, response: data.data, adminUser });
725
777
  if (!resp || (!resp.ok && !resp.error)) {
726
778
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -775,8 +827,8 @@ class AdminForth implements AdminForthClass {
775
827
  const targetResourceId = columnConfig.foreignResource.resourceId;
776
828
  const targetResource = this.config.resources.find((res) => res.resourceId == targetResourceId);
777
829
 
778
- for (const hook of getFunctionList(columnConfig.foreignResource.hooks?.dropdownList?.beforeDatasourceRequest)) {
779
- const resp = await hook({ query: body, adminUser });
830
+ for (const hook of listify(columnConfig.foreignResource.hooks?.dropdownList?.beforeDatasourceRequest as BeforeDataSourceRequestFunction[])) {
831
+ const resp = await hook({ query: body, adminUser, resource: targetResource });
780
832
  if (!resp || (!resp.ok && !resp.error)) {
781
833
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
782
834
  }
@@ -806,8 +858,8 @@ class AdminForth implements AdminForthClass {
806
858
  items
807
859
  };
808
860
 
809
- for (const hook of getFunctionList(columnConfig.foreignResource.hooks?.dropdownList?.afterDatasourceResponse)) {
810
- const resp = await hook({ response, adminUser });
861
+ for (const hook of listify(columnConfig.foreignResource.hooks?.dropdownList?.afterDatasourceResponse as AfterDataSourceResponseFunction[])) {
862
+ const resp = await hook({ response, adminUser, resource: targetResource });
811
863
  if (!resp || (!resp.ok && !resp.error)) {
812
864
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
813
865
  }
@@ -863,7 +915,7 @@ class AdminForth implements AdminForthClass {
863
915
 
864
916
  const record = body['record'];
865
917
  // execute hook if needed
866
- for (const hook of getFunctionList(resource.hooks?.create?.beforeSave)) {
918
+ for (const hook of listify(resource.hooks?.create?.beforeSave as BeforeSaveFunction[])) {
867
919
  const resp = await hook({ resource, record, adminUser });
868
920
  if (!resp || (!resp.ok && !resp.error)) {
869
921
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -909,7 +961,7 @@ class AdminForth implements AdminForthClass {
909
961
  const connector = this.connectors[resource.dataSource];
910
962
  await connector.createRecord({ resource, record });
911
963
  // execute hook if needed
912
- for (const hook of getFunctionList(resource.hooks?.create?.afterSave)) {
964
+ for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
913
965
  const resp = await hook({ resource, record, adminUser });
914
966
  if (!resp || (!resp.ok && !resp.error)) {
915
967
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -944,7 +996,7 @@ class AdminForth implements AdminForthClass {
944
996
  const record = body['record'];
945
997
 
946
998
  // execute hook if needed
947
- for (const hook of getFunctionList(resource.hooks?.edit?.beforeSave)) {
999
+ for (const hook of listify(resource.hooks?.edit?.beforeSave as BeforeSaveFunction[])) {
948
1000
  const resp = await hook({ resource, record, adminUser });
949
1001
  if (!resp || (!resp.ok && !resp.error)) {
950
1002
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -975,7 +1027,7 @@ class AdminForth implements AdminForthClass {
975
1027
  }
976
1028
 
977
1029
  // execute hook if needed
978
- for (const hook of getFunctionList(resource.hooks?.edit?.afterSave)) {
1030
+ for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
979
1031
  const resp = await hook({ resource, record, adminUser });
980
1032
  if (!resp || (!resp.ok && !resp.error)) {
981
1033
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -1008,7 +1060,7 @@ class AdminForth implements AdminForthClass {
1008
1060
  }
1009
1061
 
1010
1062
  // execute hook if needed
1011
- for (const hook of getFunctionList(resource.hooks?.delete?.beforeSave)) {
1063
+ for (const hook of listify(resource.hooks?.delete?.beforeSave as BeforeSaveFunction[])) {
1012
1064
  const resp = await hook({ resource, record, adminUser });
1013
1065
  if (!resp || (!resp.ok && !resp.error)) {
1014
1066
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -1023,7 +1075,7 @@ class AdminForth implements AdminForthClass {
1023
1075
  await connector.deleteRecord({ resource, recordId: body['primaryKey']});
1024
1076
 
1025
1077
  // execute hook if needed
1026
- for (const hook of getFunctionList(resource.hooks?.delete?.afterSave)) {
1078
+ for (const hook of listify(resource.hooks?.delete?.afterSave as BeforeSaveFunction[])) {
1027
1079
  const resp = await hook({ resource, record, adminUser });
1028
1080
  if (!resp || (!resp.ok && !resp.error)) {
1029
1081
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -254,10 +254,13 @@ class CodeInjector implements CodeInjectorType {
254
254
  // for each custom component generate import statement
255
255
  const customResourceComponents = [];
256
256
  this.adminforth.config.resources.forEach((resource) => {
257
- resource.columns.forEach((field) => {
258
- if (field.components) {
259
- Object.values(field.components).forEach(({ file }: {file: string}) => {
257
+ resource.columns.forEach((column) => {
258
+ if (column.components) {
259
+ Object.values(column.components).forEach(({ file }: {file: string}) => {
260
260
  if (!customResourceComponents.includes(file)) {
261
+ if (file === undefined) {
262
+ throw new Error('file is undefined from field.components, field:' + JSON.stringify(column));
263
+ }
261
264
  customResourceComponents.push(file);
262
265
  }
263
266
  });
@@ -267,6 +270,9 @@ class CodeInjector implements CodeInjectorType {
267
270
  Object.values(injection).forEach((filePathes: {file: string}[]) => {
268
271
  filePathes.forEach(({ file }) => {
269
272
  if (!customResourceComponents.includes(file)) {
273
+ if (file === undefined) {
274
+ throw new Error('file is undefined');
275
+ }
270
276
  customResourceComponents.push(file);
271
277
  }
272
278
  });
package/modules/utils.ts CHANGED
@@ -34,14 +34,6 @@ export function getComponentNameFromPath(filePath) {
34
34
  return filePath.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
35
35
  }
36
36
 
37
- export function getFunctionList(param?: Function | Array<Function>) {
38
- if (param) {
39
- if (Array.isArray(param)) {
40
- return param;
41
- } else {
42
- return [param];
43
- }
44
- } else {
45
- return [];
46
- }
37
+ export function listify(param?: Array<Function>) {
38
+ return param || [];
47
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,83 @@
1
+ import {
2
+ AdminForthResource, AdminForthClass, BeforeDataSourceRequestFunction,
3
+ AllowedActionsEnum,
4
+ BeforeSaveFunction
5
+ } from "../../types/AdminForthConfig.js";
6
+ import AdminForthPlugin from "../base.js";
7
+ import { PluginOptions } from "./types.js";
8
+
9
+
10
+
11
+ export default class AccessControlPlugin extends AdminForthPlugin {
12
+
13
+ options: PluginOptions;
14
+ adminforth: AdminForthClass;
15
+
16
+ constructor(options: PluginOptions) {
17
+ super(options, import.meta.url);
18
+ this.options = options;
19
+ }
20
+
21
+ static defaultError = 'Sorry, you do not have access to this resource.'
22
+
23
+
24
+ modifyResourceConfig(adminforth: AdminForthClass, resourceConfig: AdminForthResource) {
25
+ super.modifyResourceConfig(adminforth, resourceConfig);
26
+ this.adminforth = adminforth;
27
+
28
+ if (!resourceConfig.hooks) {
29
+ resourceConfig.hooks = {};
30
+ }
31
+
32
+ const checkAccess = async (adminUser: any, action: AllowedActionsEnum, meta: any) => {
33
+ const hasAccessOrError = await this.options.hasAccess(adminUser, action, meta);
34
+ if (hasAccessOrError === true) {
35
+ return { ok: true };
36
+ } else {
37
+ return { ok: false, error: hasAccessOrError || AccessControlPlugin.defaultError };
38
+ }
39
+ }
40
+
41
+ const bindHookCheck = (action: AllowedActionsEnum, hookName: string) => {
42
+ if (!resourceConfig.hooks[action]) {
43
+ resourceConfig.hooks[action] = {};
44
+ }
45
+ if (!resourceConfig.hooks[action][hookName]) {
46
+ resourceConfig.hooks[action][hookName] = [];
47
+ }
48
+ if (hookName === 'beforeDatasourceRequest') {
49
+ (resourceConfig.hooks[action][hookName] as Array<BeforeDataSourceRequestFunction>).unshift(
50
+ async ({adminUser, query}: {resource: AdminForthResource, adminUser: any, query: any}) => {
51
+ return checkAccess(adminUser, action, { query });
52
+ }
53
+ );
54
+ } else {
55
+ (resourceConfig.hooks[action][hookName] as Array<BeforeSaveFunction>).unshift(
56
+ async ({adminUser, record}: {resource: AdminForthResource, adminUser: any, record: any}) => {
57
+ return checkAccess(adminUser, action, { record });
58
+ }
59
+ );
60
+ }
61
+ }
62
+
63
+ // List check
64
+ bindHookCheck(AllowedActionsEnum.list, 'beforeDatasourceRequest');
65
+
66
+ // Show check
67
+ bindHookCheck(AllowedActionsEnum.show, 'beforeDatasourceRequest');
68
+
69
+ // Edit check
70
+ bindHookCheck(AllowedActionsEnum.edit, 'beforeDatasourceRequest');
71
+
72
+ // create check
73
+ bindHookCheck(AllowedActionsEnum.create, 'beforeSave');
74
+
75
+ // edit check
76
+ bindHookCheck(AllowedActionsEnum.edit, 'beforeSave');
77
+
78
+ // delete check
79
+ bindHookCheck(AllowedActionsEnum.delete, 'beforeSave');
80
+
81
+ console.log('resourceConfig', resourceConfig);
82
+ }
83
+ }
@@ -0,0 +1,14 @@
1
+
2
+ import { AllowedActionsEnum, AdminUser } from '../../types/AdminForthConfig.js';
3
+
4
+
5
+ export type PluginOptions = {
6
+ /**
7
+ * Called to check if user has access to a page
8
+ * @param user - The user object
9
+ * @param page - The page to check access for
10
+ * @param meta - The meta object containing query for list/show or record for save/edit
11
+ * @returns true if user has access, false or a string with an error message
12
+ */
13
+ hasAccess: (user: AdminUser, page: AllowedActionsEnum, meta: any) => Promise<boolean | string>;
14
+ }
@@ -90,6 +90,7 @@
90
90
  :totalRows="totalRows"
91
91
  :checkboxes="checkboxes"
92
92
  />
93
+
93
94
  </td>
94
95
  </template>
95
96
 
@@ -114,7 +115,7 @@ const loading = ref(true);
114
115
  const page = ref(1);
115
116
  const sort = ref([]);
116
117
  const checkboxes = ref([]);
117
- const pageSize = computed(() => listResource.value?.options?.pageSize || 10);
118
+ const pageSize = computed(() => listResource.value?.options?.listPageSize || 10);
118
119
 
119
120
  const rows = ref(null);
120
121
  const totalRows = ref(0);
@@ -207,6 +208,17 @@ async function getList() {
207
208
  }
208
209
  });
209
210
 
211
+ if (data.error) {
212
+ window.adminforth.alert({
213
+ message: data.error,
214
+ variant: 'danger',
215
+ timeout: 'unlimited',
216
+ });
217
+ rows.value = [];
218
+ totalRows.value = 0;
219
+ return;
220
+ }
221
+
210
222
  rows.value = data.data?.map(row => {
211
223
  row._primaryKeyValue = row[listResource.value.columns.find(c => c.primaryKey).name];
212
224
  return row;
@@ -23,7 +23,8 @@ export default class ForeignInlineListPlugin extends AdminForthPlugin {
23
23
  return { error: `Resource ${this.options.foreignResourceId} not found` };
24
24
  }
25
25
  // exclude "plugins" key
26
- const resourceCopy = { ...resource, plugins: undefined };
26
+ const resourceCopy = JSON.parse(JSON.stringify({ ...resource, plugins: undefined }));
27
+
27
28
  if (this.options.modifyTableResourceConfig) {
28
29
  this.options.modifyTableResourceConfig(resourceCopy);
29
30
  }
@@ -157,7 +157,7 @@ class ExpressServer implements ExpressHttpServer {
157
157
  res.status(401).send('Unauthorized by AdminForth');
158
158
  return
159
159
  }
160
- const adminforthUser = this.adminforth.auth.verify(jwt);
160
+ const adminforthUser = await this.adminforth.auth.verify(jwt);
161
161
  if (!adminforthUser) {
162
162
  res.status(401).send('Unauthorized by AdminForth');
163
163
  } else {
@@ -201,7 +201,7 @@ class ExpressServer implements ExpressHttpServer {
201
201
  this.message = message;
202
202
  }
203
203
  };
204
- const input = { body, query, headers, cookies, response, _raw_express_req: req, _raw_express_res: res};
204
+ const input = { body, query, headers, cookies, adminUser, response, _raw_express_req: req, _raw_express_res: res};
205
205
 
206
206
  let output;
207
207
  try {
package/spa/src/App.vue CHANGED
@@ -64,7 +64,6 @@
64
64
  aria-label="Sidebar"
65
65
  >
66
66
  <div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800 border-r">
67
-
68
67
  <div class="flex ms-2 md:me-24 m-4 ">
69
68
  <img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
70
69
  <span class="self-center text-header-text-size font-semibold sm:text-header-text-size whitespace-nowrap dark:text-header-text text-header-logo-color">
@@ -168,6 +167,7 @@ import { RouterLink, RouterView } from 'vue-router';
168
167
  import { initFlowbite } from 'flowbite'
169
168
  import './index.scss'
170
169
  import { useCoreStore } from '@/stores/core';
170
+ import { useUserStore } from '@/stores/user';
171
171
  import {useModalStore} from '@/stores/modal';
172
172
  import { IconMoonSolid, IconSunSolid } from '@iconify-prerendered/vue-flowbite';
173
173
  import AcceptModal from './components/AcceptModal.vue';
@@ -184,6 +184,7 @@ import { FrontendAPI } from '@/composables/useStores'
184
184
  const coreStore = useCoreStore();
185
185
  const modalStore = useModalStore();
186
186
  const toastStore = useToastStore();
187
+ const userStore = useUserStore();
187
188
  const frontendApi = new FrontendAPI();
188
189
  frontendApi.init();
189
190
  const splitAtLast = (str: string, separator: string) => {
@@ -221,8 +222,9 @@ if (opened.value.includes(label)) {
221
222
  }
222
223
 
223
224
  async function logout() {
224
- await coreStore.logout();
225
- router.push({ name: 'login' });
225
+ userStore.unauthorize();
226
+ await userStore.logout();
227
+ router.push({ name: 'login' })
226
228
  }
227
229
 
228
230
 
@@ -244,6 +246,8 @@ async function loadMenu() {
244
246
  });
245
247
  }
246
248
 
249
+
250
+
247
251
  watch(route, () => {
248
252
  title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
249
253
  useHead({
@@ -46,16 +46,17 @@ const props = defineProps<{
46
46
  toast:{message: string;
47
47
  variant: string;
48
48
  id: string;
49
- duration?: number;}
49
+ timeout?: number|'unlimited';}
50
50
  }>();
51
51
  function closeToast() {
52
52
  emit('close');
53
53
  }
54
54
 
55
55
  onMounted(() => {
56
- setTimeout(() => {
57
- emit('close');
58
- }, props.toast.duration || 5000 );
56
+ if (props.toast.timeout === 'unlimited') return;
57
+ else {
58
+ setTimeout(() => {emit('close');}, props.toast.timeout || 10000 );
59
+ }
59
60
  });
60
61
 
61
62
  </script>
@@ -41,7 +41,7 @@ import { useRoute, useRouter } from 'vue-router';
41
41
  import { useCoreStore } from '@/stores/core';
42
42
 
43
43
  const coreStore = useCoreStore();
44
- const route = useRoute();
44
+ const route = useRoute();
45
45
 
46
46
 
47
47
  dayjs.extend(utc);
@@ -56,7 +56,8 @@ export class FrontendAPI implements FrontendAPIInterface {
56
56
  alert(params: AlertParams): void {
57
57
  this.toastStore.addToast({
58
58
  message: params.message,
59
- variant: params.variant
59
+ variant: params.variant,
60
+ timeout: params.timeout
60
61
  })
61
62
  }
62
63
 
@@ -1,5 +1,6 @@
1
1
  import { createRouter, createWebHistory } from 'vue-router'
2
2
  import ResourceParent from '@/views/ResourceParent.vue'
3
+ import { useUserStore } from '@/stores/user'
3
4
  /* IMPORTANT:ADMINFORTH ROUTES IMPORTS */
4
5
 
5
6
  const router = createRouter({
@@ -9,7 +10,15 @@ const router = createRouter({
9
10
  path: '/login',
10
11
  name: 'login',
11
12
  component: () => import('@/views/LoginView.vue'),
12
- meta: { title: 'login' }
13
+ meta: { title: 'login' },
14
+ beforeEnter: async (to, from, next) => {
15
+ const userStore = useUserStore()
16
+ if (await userStore.checkAuth(true)) {
17
+ next({ name: 'home' })
18
+ } else {
19
+ next()
20
+ }
21
+ }
13
22
  },
14
23
  {
15
24
  path: '/resource/:resourceId',
@@ -49,4 +58,7 @@ const router = createRouter({
49
58
  })
50
59
 
51
60
 
61
+
62
+
63
+
52
64
  export default router