adminforth 1.1.13 → 1.1.15

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 (47) hide show
  1. package/auth.ts +17 -3
  2. package/dist/auth.js +32 -18
  3. package/dist/index.js +114 -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/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +248 -0
  10. package/dist/servers/express.js +2 -2
  11. package/dist/spa/spa/src/App.vue +7 -3
  12. package/dist/spa/spa/src/components/Toast.vue +5 -4
  13. package/dist/spa/spa/src/components/ValueRenderer.vue +1 -1
  14. package/dist/spa/spa/src/composables/useStores.ts +2 -1
  15. package/dist/spa/spa/src/router/index.ts +13 -1
  16. package/dist/spa/spa/src/stores/core.ts +13 -11
  17. package/dist/spa/spa/src/stores/user.ts +54 -0
  18. package/dist/spa/spa/src/utils.ts +9 -4
  19. package/dist/spa/spa/src/views/CreateView.vue +7 -0
  20. package/dist/spa/spa/src/views/EditView.vue +8 -1
  21. package/dist/spa/spa/src/views/ListView.vue +10 -1
  22. package/dist/spa/spa/src/views/LoginView.vue +4 -0
  23. package/dist/types/AdminForthConfig.js +9 -0
  24. package/dist/types/FrontendAPI.js +4 -4
  25. package/index.ts +106 -42
  26. package/modules/codeInjector.ts +0 -1
  27. package/modules/utils.ts +2 -10
  28. package/package.json +3 -2
  29. package/plugins/AccessControl/index.ts +83 -0
  30. package/plugins/AccessControl/types.ts +14 -0
  31. package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +11 -0
  32. package/plugins/ForeignInlineListPlugin/index.ts +2 -1
  33. package/servers/express.ts +2 -2
  34. package/spa/src/App.vue +7 -3
  35. package/spa/src/components/Toast.vue +5 -4
  36. package/spa/src/components/ValueRenderer.vue +1 -1
  37. package/spa/src/composables/useStores.ts +2 -1
  38. package/spa/src/router/index.ts +13 -1
  39. package/spa/src/stores/core.ts +13 -11
  40. package/spa/src/stores/user.ts +54 -0
  41. package/spa/src/utils.ts +9 -4
  42. package/spa/src/views/CreateView.vue +7 -0
  43. package/spa/src/views/EditView.vue +8 -1
  44. package/spa/src/views/ListView.vue +10 -1
  45. package/spa/src/views/LoginView.vue +4 -0
  46. package/types/AdminForthConfig.ts +73 -21
  47. 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
 
@@ -226,6 +229,21 @@ class AdminForth implements AdminForthClass {
226
229
  errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid showIn value "${wrongShowIn}", allowed values are ${Object.keys(AdminForthResourcePages).join(', ')}`);
227
230
  }
228
231
  col.showIn = col.showIn || Object.values(AdminForthResourcePages);
232
+
233
+ if (col.foreignResource) {
234
+ const befHook = col.foreignResource.hooks?.dropdownList?.beforeDatasourceRequest;
235
+ if (befHook) {
236
+ if (!Array.isArray(befHook)) {
237
+ col.foreignResource.hooks.dropdownList.beforeDatasourceRequest = [befHook];
238
+ }
239
+ }
240
+ const aftHook = col.foreignResource.hooks?.dropdownList?.afterDatasourceResponse;
241
+ if (aftHook) {
242
+ if (!Array.isArray(aftHook)) {
243
+ col.foreignResource.hooks.dropdownList.afterDatasourceResponse = [aftHook];
244
+ }
245
+ }
246
+ }
229
247
  })
230
248
 
231
249
  if (!res.options) {
@@ -237,7 +255,7 @@ class AdminForth implements AdminForthClass {
237
255
  if(res.options?.bulkActions){
238
256
  let bulkActions = res.options.bulkActions;
239
257
 
240
- if(!Array.isArray(bulkActions)){
258
+ if (!Array.isArray(bulkActions)) {
241
259
  errors.push(`Resource "${res.resourceId}" bulkActions must be an array`);
242
260
  bulkActions = [];
243
261
  }
@@ -294,7 +312,40 @@ class AdminForth implements AdminForthClass {
294
312
  } else {
295
313
  res.options.allowedActions = DEFAULT_ALLOWED_ACTIONS;
296
314
  }
297
- })
315
+
316
+ // transform all hooks Functions to array of functions
317
+ if (res.hooks) {
318
+ for (const value of [res.hooks.show, res.hooks.list]) {
319
+ if (value) {
320
+ if (value.beforeDatasourceRequest) {
321
+ if (!Array.isArray(value.beforeDatasourceRequest)) {
322
+ value.beforeDatasourceRequest = [value.beforeDatasourceRequest];
323
+ }
324
+ }
325
+ if (value.afterDatasourceResponse) {
326
+ if (!Array.isArray(value.afterDatasourceResponse)) {
327
+ value.afterDatasourceResponse = [value.afterDatasourceResponse];
328
+ }
329
+ }
330
+ }
331
+ }
332
+ for (const value of [res.hooks.create, res.hooks.edit, res.hooks.delete]) {
333
+ if (value) {
334
+
335
+ if (value.beforeSave) {
336
+ if (!Array.isArray(value.beforeSave)) {
337
+ value.beforeSave = [value.beforeSave];
338
+ }
339
+ }
340
+ if (value.afterSave) {
341
+ if (!Array.isArray(value.afterSave)) {
342
+ value.afterSave = [value.afterSave];
343
+ }
344
+ }
345
+ }
346
+ }
347
+ }
348
+ });
298
349
 
299
350
 
300
351
 
@@ -373,7 +424,6 @@ class AdminForth implements AdminForthClass {
373
424
  for (const resource of this.config.resources) {
374
425
  for (const column of resource.columns) {
375
426
  if (column.components) {
376
- console.log('🔧🔧🔧 Validating components for resource', column.components);
377
427
 
378
428
  for (const [key, comp] of Object.entries(column.components as Record<string, AdminForthComponentDeclarationFull>)) {
379
429
  let ignoreExistsCheck = false;
@@ -461,7 +511,24 @@ class AdminForth implements AdminForthClass {
461
511
  this.codeInjector.bundleNow({ hotReload, verbose });
462
512
  }
463
513
 
464
- setupEndpoints(server) {
514
+ async getUserByPk(pk: string) {
515
+ const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
516
+ if (!resource) {
517
+ throw new Error('No auth resource found');
518
+ }
519
+ const users = await this.connectors[resource.dataSource].getData({
520
+ resource,
521
+ filters: [
522
+ { field: resource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: pk },
523
+ ],
524
+ limit: 1,
525
+ offset: 0,
526
+ sort: [],
527
+ });
528
+ return users.data[0] || null;
529
+ }
530
+
531
+ setupEndpoints(server: GenericHttpServer) {
465
532
  server.endpoint({
466
533
  noAuth: true,
467
534
  method: 'POST',
@@ -507,7 +574,7 @@ class AdminForth implements AdminForthClass {
507
574
 
508
575
  const passwordHash = userRecord[this.config.auth.passwordHashField];
509
576
  console.log('User record', userRecord, passwordHash) // why does it has no hash?
510
- const valid = await Auth.verifyPassword(password, passwordHash);
577
+ const valid = await AdminForthAuth.verifyPassword(password, passwordHash);
511
578
  if (valid) {
512
579
  token = this.auth.issueJWT({
513
580
  username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
@@ -522,6 +589,14 @@ class AdminForth implements AdminForthClass {
522
589
  },
523
590
  });
524
591
 
592
+ server.endpoint({
593
+ method: 'POST',
594
+ path: '/check_auth',
595
+ handler: async ({ adminUser }) => {
596
+ return { ok: true };
597
+ },
598
+ });
599
+
525
600
  server.endpoint({
526
601
  noAuth: true,
527
602
  method: 'POST',
@@ -559,27 +634,14 @@ class AdminForth implements AdminForthClass {
559
634
  method: 'GET',
560
635
  path: '/get_base_config',
561
636
  handler: async ({input, adminUser, cookies}) => {
562
- const cookieParsed = this.auth.verify(cookies['adminforth_jwt']);
563
637
  let username = ''
564
638
  let userFullName = ''
565
- if (cookieParsed['pk'] == null) {
639
+ if (adminUser.isRoot) {
566
640
  username = this.config.rootUser.username;
567
641
  } else {
568
- const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
569
- const user = await this.connectors[userResource.dataSource].getData({
570
- resource: userResource,
571
- filters: [
572
- { field: userResource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: cookieParsed['pk'] },
573
- ],
574
- limit: 1,
575
- offset: 0,
576
- sort: [],
577
- });
578
- if (!user.data.length) {
579
- return { error: 'Unauthorized' };
580
- }
581
- username = user.data[0][this.config.auth.usernameField];
582
- userFullName = user.data[0][this.config.auth.userFullNameField];
642
+ const dbUser = adminUser.dbUser;
643
+ username = dbUser[this.config.auth.usernameField];
644
+ userFullName =dbUser[this.config.auth.userFullNameField];
583
645
  }
584
646
 
585
647
  const userData = {
@@ -609,6 +671,8 @@ class AdminForth implements AdminForthClass {
609
671
  },
610
672
  });
611
673
 
674
+
675
+
612
676
  server.endpoint({
613
677
  method: 'POST',
614
678
  path: '/get_resource',
@@ -647,7 +711,7 @@ class AdminForth implements AdminForthClass {
647
711
  return { error: `Resource ${resourceId} not found` };
648
712
  }
649
713
 
650
- for (const hook of getFunctionList(resource.hooks?.[source]?.beforeDatasourceRequest)) {
714
+ for (const hook of listify(resource.hooks?.[source]?.beforeDatasourceRequest)) {
651
715
  const resp = await hook({ resource, query: body, adminUser });
652
716
  if (!resp || (!resp.ok && !resp.error)) {
653
717
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -678,7 +742,7 @@ class AdminForth implements AdminForthClass {
678
742
  // nonsense
679
743
  return { data: [], total: 0 };
680
744
  }
681
- }
745
+ }
682
746
 
683
747
  const data = await this.connectors[resource.dataSource].getData({
684
748
  resource,
@@ -723,7 +787,7 @@ class AdminForth implements AdminForthClass {
723
787
  })
724
788
  );
725
789
 
726
- for (const hook of getFunctionList(resource.hooks?.[source]?.afterDatasourceResponse)) {
790
+ for (const hook of listify(resource.hooks?.[source]?.afterDatasourceResponse)) {
727
791
  const resp = await hook({ resource, response: data.data, adminUser });
728
792
  if (!resp || (!resp.ok && !resp.error)) {
729
793
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -778,8 +842,8 @@ class AdminForth implements AdminForthClass {
778
842
  const targetResourceId = columnConfig.foreignResource.resourceId;
779
843
  const targetResource = this.config.resources.find((res) => res.resourceId == targetResourceId);
780
844
 
781
- for (const hook of getFunctionList(columnConfig.foreignResource.hooks?.dropdownList?.beforeDatasourceRequest)) {
782
- const resp = await hook({ query: body, adminUser });
845
+ for (const hook of listify(columnConfig.foreignResource.hooks?.dropdownList?.beforeDatasourceRequest as BeforeDataSourceRequestFunction[])) {
846
+ const resp = await hook({ query: body, adminUser, resource: targetResource });
783
847
  if (!resp || (!resp.ok && !resp.error)) {
784
848
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
785
849
  }
@@ -809,8 +873,8 @@ class AdminForth implements AdminForthClass {
809
873
  items
810
874
  };
811
875
 
812
- for (const hook of getFunctionList(columnConfig.foreignResource.hooks?.dropdownList?.afterDatasourceResponse)) {
813
- const resp = await hook({ response, adminUser });
876
+ for (const hook of listify(columnConfig.foreignResource.hooks?.dropdownList?.afterDatasourceResponse as AfterDataSourceResponseFunction[])) {
877
+ const resp = await hook({ response, adminUser, resource: targetResource });
814
878
  if (!resp || (!resp.ok && !resp.error)) {
815
879
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
816
880
  }
@@ -866,7 +930,7 @@ class AdminForth implements AdminForthClass {
866
930
 
867
931
  const record = body['record'];
868
932
  // execute hook if needed
869
- for (const hook of getFunctionList(resource.hooks?.create?.beforeSave)) {
933
+ for (const hook of listify(resource.hooks?.create?.beforeSave as BeforeSaveFunction[])) {
870
934
  const resp = await hook({ resource, record, adminUser });
871
935
  if (!resp || (!resp.ok && !resp.error)) {
872
936
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -912,7 +976,7 @@ class AdminForth implements AdminForthClass {
912
976
  const connector = this.connectors[resource.dataSource];
913
977
  await connector.createRecord({ resource, record });
914
978
  // execute hook if needed
915
- for (const hook of getFunctionList(resource.hooks?.create?.afterSave)) {
979
+ for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
916
980
  const resp = await hook({ resource, record, adminUser });
917
981
  if (!resp || (!resp.ok && !resp.error)) {
918
982
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -947,7 +1011,7 @@ class AdminForth implements AdminForthClass {
947
1011
  const record = body['record'];
948
1012
 
949
1013
  // execute hook if needed
950
- for (const hook of getFunctionList(resource.hooks?.edit?.beforeSave)) {
1014
+ for (const hook of listify(resource.hooks?.edit?.beforeSave as BeforeSaveFunction[])) {
951
1015
  const resp = await hook({ resource, record, adminUser });
952
1016
  if (!resp || (!resp.ok && !resp.error)) {
953
1017
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -978,7 +1042,7 @@ class AdminForth implements AdminForthClass {
978
1042
  }
979
1043
 
980
1044
  // execute hook if needed
981
- for (const hook of getFunctionList(resource.hooks?.edit?.afterSave)) {
1045
+ for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
982
1046
  const resp = await hook({ resource, record, adminUser });
983
1047
  if (!resp || (!resp.ok && !resp.error)) {
984
1048
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -1011,7 +1075,7 @@ class AdminForth implements AdminForthClass {
1011
1075
  }
1012
1076
 
1013
1077
  // execute hook if needed
1014
- for (const hook of getFunctionList(resource.hooks?.delete?.beforeSave)) {
1078
+ for (const hook of listify(resource.hooks?.delete?.beforeSave as BeforeSaveFunction[])) {
1015
1079
  const resp = await hook({ resource, record, adminUser });
1016
1080
  if (!resp || (!resp.ok && !resp.error)) {
1017
1081
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -1026,7 +1090,7 @@ class AdminForth implements AdminForthClass {
1026
1090
  await connector.deleteRecord({ resource, recordId: body['primaryKey']});
1027
1091
 
1028
1092
  // execute hook if needed
1029
- for (const hook of getFunctionList(resource.hooks?.delete?.afterSave)) {
1093
+ for (const hook of listify(resource.hooks?.delete?.afterSave as BeforeSaveFunction[])) {
1030
1094
  const resp = await hook({ resource, record, adminUser });
1031
1095
  if (!resp || (!resp.ok && !resp.error)) {
1032
1096
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -282,7 +282,6 @@ class CodeInjector implements CodeInjectorType {
282
282
 
283
283
  });
284
284
 
285
- console.log('🔧 🔧 Injecting code into Vue sources...', customResourceComponents);
286
285
  customResourceComponents.forEach((filePath) => {
287
286
  const componentName = getComponentNameFromPath(filePath);
288
287
  this.allComponentNames[filePath] = componentName;
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,13 +1,14 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "test": "echo \"Error: no test specified\" && exit 1",
9
9
  "build": "tsc",
10
- "rollout": "tsc && cp -r spa dist/spa && npm version patch && npm publish && cd documentation && npm run build && npm run deploy",
10
+ "prepareDist": "cp -r spa dist/spa && find plugins -type f ! -name '*.ts' -exec cp --parents {} dist/plugins \\;",
11
+ "rollout": "tsc && npm run prepareDist && npm version patch && npm publish && cd documentation && npm run build && npm run deploy",
11
12
  "docs": "typedoc"
12
13
  },
13
14
  "author": "devforth.io",
@@ -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
+ }
@@ -208,6 +208,17 @@ async function getList() {
208
208
  }
209
209
  });
210
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
+
211
222
  rows.value = data.data?.map(row => {
212
223
  row._primaryKeyValue = row[listResource.value.columns.find(c => c.primaryKey).name];
213
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(localStorage.getItem('isAuthorized') === '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