@saltcorn/server 1.1.1-beta.5 → 1.1.1-beta.7

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 (153) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/auth/admin.js +8 -0
  3. package/locales/en.json +16 -1
  4. package/markup/admin.js +8 -3
  5. package/markup/blockly.js +4 -4
  6. package/package.json +9 -9
  7. package/public/blockly/blockly_compressed.js +2016 -0
  8. package/public/blockly/blockly_compressed.js.map +1 -0
  9. package/public/blockly/blocks_compressed.js +212 -0
  10. package/public/blockly/blocks_compressed.js.map +1 -0
  11. package/public/blockly/javascript_compressed.js +121 -0
  12. package/public/blockly/javascript_compressed.js.map +1 -0
  13. package/public/blockly/msg/ab.js +440 -0
  14. package/public/blockly/msg/ace.js +440 -0
  15. package/public/blockly/msg/af.js +440 -0
  16. package/public/blockly/msg/am.js +440 -0
  17. package/public/blockly/msg/ar.js +440 -0
  18. package/public/blockly/msg/ast.js +440 -0
  19. package/public/blockly/msg/az.js +440 -0
  20. package/public/blockly/msg/ba.js +440 -0
  21. package/public/blockly/msg/bcc.js +440 -0
  22. package/public/blockly/msg/be-tarask.js +440 -0
  23. package/public/blockly/msg/be.js +440 -0
  24. package/public/blockly/msg/bg.js +440 -0
  25. package/public/blockly/msg/bn.js +440 -0
  26. package/public/blockly/msg/br.js +440 -0
  27. package/public/blockly/msg/bs.js +440 -0
  28. package/public/blockly/msg/ca.js +440 -0
  29. package/public/blockly/msg/cdo.js +440 -0
  30. package/public/blockly/msg/cs.js +440 -0
  31. package/public/blockly/msg/da.js +440 -0
  32. package/public/blockly/msg/de.js +440 -0
  33. package/public/blockly/msg/diq.js +440 -0
  34. package/public/blockly/msg/dty.js +440 -0
  35. package/public/blockly/msg/ee.js +440 -0
  36. package/public/blockly/msg/el.js +440 -0
  37. package/public/blockly/msg/en-gb.js +440 -0
  38. package/public/blockly/msg/en.js +440 -0
  39. package/public/blockly/msg/eo.js +440 -0
  40. package/public/blockly/msg/es.js +440 -0
  41. package/public/blockly/msg/et.js +440 -0
  42. package/public/blockly/msg/eu.js +440 -0
  43. package/public/blockly/msg/fa.js +440 -0
  44. package/public/blockly/msg/fi.js +440 -0
  45. package/public/blockly/msg/fo.js +440 -0
  46. package/public/blockly/msg/fr.js +440 -0
  47. package/public/blockly/msg/frr.js +440 -0
  48. package/public/blockly/msg/gl.js +440 -0
  49. package/public/blockly/msg/gn.js +440 -0
  50. package/public/blockly/msg/gor.js +440 -0
  51. package/public/blockly/msg/ha.js +440 -0
  52. package/public/blockly/msg/hak.js +440 -0
  53. package/public/blockly/msg/he.js +440 -0
  54. package/public/blockly/msg/hi.js +440 -0
  55. package/public/blockly/msg/hr.js +440 -0
  56. package/public/blockly/msg/hrx.js +440 -0
  57. package/public/blockly/msg/hu.js +440 -0
  58. package/public/blockly/msg/hy.js +440 -0
  59. package/public/blockly/msg/ia.js +440 -0
  60. package/public/blockly/msg/id.js +440 -0
  61. package/public/blockly/msg/ig.js +440 -0
  62. package/public/blockly/msg/inh.js +440 -0
  63. package/public/blockly/msg/is.js +440 -0
  64. package/public/blockly/msg/it.js +440 -0
  65. package/public/blockly/msg/ja.js +440 -0
  66. package/public/blockly/msg/ka.js +440 -0
  67. package/public/blockly/msg/kab.js +440 -0
  68. package/public/blockly/msg/kbd-cyrl.js +440 -0
  69. package/public/blockly/msg/km.js +440 -0
  70. package/public/blockly/msg/kn.js +440 -0
  71. package/public/blockly/msg/ko.js +440 -0
  72. package/public/blockly/msg/ksh.js +440 -0
  73. package/public/blockly/msg/ku-latn.js +440 -0
  74. package/public/blockly/msg/ky.js +440 -0
  75. package/public/blockly/msg/la.js +440 -0
  76. package/public/blockly/msg/lb.js +440 -0
  77. package/public/blockly/msg/lki.js +440 -0
  78. package/public/blockly/msg/lo.js +440 -0
  79. package/public/blockly/msg/lrc.js +440 -0
  80. package/public/blockly/msg/lt.js +440 -0
  81. package/public/blockly/msg/lv.js +440 -0
  82. package/public/blockly/msg/mg.js +440 -0
  83. package/public/blockly/msg/mk.js +440 -0
  84. package/public/blockly/msg/ml.js +440 -0
  85. package/public/blockly/msg/mnw.js +440 -0
  86. package/public/blockly/msg/ms.js +440 -0
  87. package/public/blockly/msg/msg.d.ts +444 -0
  88. package/public/blockly/msg/my.js +440 -0
  89. package/public/blockly/msg/mzn.js +440 -0
  90. package/public/blockly/msg/nb.js +440 -0
  91. package/public/blockly/msg/ne.js +440 -0
  92. package/public/blockly/msg/nl.js +440 -0
  93. package/public/blockly/msg/oc.js +440 -0
  94. package/public/blockly/msg/olo.js +440 -0
  95. package/public/blockly/msg/pa.js +440 -0
  96. package/public/blockly/msg/pl.js +440 -0
  97. package/public/blockly/msg/pms.js +440 -0
  98. package/public/blockly/msg/ps.js +440 -0
  99. package/public/blockly/msg/pt-br.js +440 -0
  100. package/public/blockly/msg/pt.js +440 -0
  101. package/public/blockly/msg/ro.js +440 -0
  102. package/public/blockly/msg/ru.js +440 -0
  103. package/public/blockly/msg/sc.js +440 -0
  104. package/public/blockly/msg/sco.js +440 -0
  105. package/public/blockly/msg/sd.js +440 -0
  106. package/public/blockly/msg/shn.js +440 -0
  107. package/public/blockly/msg/si.js +440 -0
  108. package/public/blockly/msg/sk.js +440 -0
  109. package/public/blockly/msg/skr-arab.js +440 -0
  110. package/public/blockly/msg/sl.js +440 -0
  111. package/public/blockly/msg/smn.js +440 -0
  112. package/public/blockly/msg/sq.js +440 -0
  113. package/public/blockly/msg/sr-latn.js +440 -0
  114. package/public/blockly/msg/sr.js +440 -0
  115. package/public/blockly/msg/sv.js +440 -0
  116. package/public/blockly/msg/sw.js +440 -0
  117. package/public/blockly/msg/ta.js +440 -0
  118. package/public/blockly/msg/tcy.js +440 -0
  119. package/public/blockly/msg/te.js +440 -0
  120. package/public/blockly/msg/th.js +440 -0
  121. package/public/blockly/msg/ti.js +440 -0
  122. package/public/blockly/msg/tl.js +440 -0
  123. package/public/blockly/msg/tlh.js +440 -0
  124. package/public/blockly/msg/tr.js +440 -0
  125. package/public/blockly/msg/ug-arab.js +440 -0
  126. package/public/blockly/msg/uk.js +440 -0
  127. package/public/blockly/msg/ur.js +440 -0
  128. package/public/blockly/msg/uz.js +440 -0
  129. package/public/blockly/msg/vi.js +440 -0
  130. package/public/blockly/msg/xmf.js +440 -0
  131. package/public/blockly/msg/yo.js +440 -0
  132. package/public/blockly/msg/yue.js +440 -0
  133. package/public/blockly/msg/zgh.js +440 -0
  134. package/public/blockly/msg/zh-hans.js +440 -0
  135. package/public/blockly/msg/zh-hant.js +440 -0
  136. package/public/saltcorn-common.js +8 -1
  137. package/public/saltcorn.js +12 -0
  138. package/routes/actions.js +32 -25
  139. package/routes/admin.js +90 -63
  140. package/routes/common_lists.js +46 -24
  141. package/routes/fields.js +31 -9
  142. package/routes/list.js +17 -4
  143. package/routes/notifications.js +27 -20
  144. package/routes/pageedit.js +14 -13
  145. package/routes/plugins.js +0 -32
  146. package/routes/tables.js +173 -92
  147. package/routes/utils.js +27 -0
  148. package/routes/view.js +2 -1
  149. package/routes/viewedit.js +14 -13
  150. package/serve.js +52 -1
  151. package/tests/api.test.js +0 -18
  152. package/tests/plugins.test.js +2 -2
  153. package/wrapper.js +80 -42
package/routes/tables.js CHANGED
@@ -27,7 +27,12 @@ const {
27
27
  recalculate_for_stored,
28
28
  expressionValidator,
29
29
  } = require("@saltcorn/data/models/expression");
30
- const { isAdmin, error_catcher, setTenant } = require("./utils.js");
30
+ const {
31
+ isAdmin,
32
+ error_catcher,
33
+ setTenant,
34
+ isAdminOrHasConfigMinRole,
35
+ } = require("./utils.js");
31
36
  const Form = require("@saltcorn/data/models/form");
32
37
  const {
33
38
  span,
@@ -230,7 +235,7 @@ const tableForm = async (table, req) => {
230
235
  */
231
236
  router.get(
232
237
  "/new/",
233
- isAdmin,
238
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
234
239
  error_catcher(async (req, res) => {
235
240
  const table_provider_names = Object.keys(getState().table_providers);
236
241
  res.sendWrap(req.__(`New table`), {
@@ -377,7 +382,7 @@ router.post(
377
382
  */
378
383
  router.get(
379
384
  "/create-from-csv",
380
- isAdmin,
385
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
381
386
  error_catcher(async (req, res) => {
382
387
  res.sendWrap(req.__(`Create table from CSV file`), {
383
388
  above: [
@@ -430,7 +435,7 @@ router.get(
430
435
  router.post(
431
436
  "/create-from-csv",
432
437
  setTenant,
433
- isAdmin,
438
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
434
439
  error_catcher(async (req, res) => {
435
440
  if (req.body.name && req.files && req.files.file) {
436
441
  const name = req.body.name;
@@ -573,7 +578,10 @@ const screenshotPanel = () =>
573
578
  */
574
579
  router.get(
575
580
  "/relationship-diagram",
576
- isAdmin,
581
+ isAdminOrHasConfigMinRole([
582
+ "min_role_edit_tables",
583
+ "min_role_inspect_tables",
584
+ ]),
577
585
  error_catcher(async (req, res) => {
578
586
  const tables = await Table.find_with_external({}, { orderBy: "name" });
579
587
  res.sendWrap(
@@ -711,7 +719,7 @@ const attribBadges = (f) => {
711
719
  "table",
712
720
  "agg_field",
713
721
  "agg_relation",
714
- "calc_joinfields"
722
+ "calc_joinfields",
715
723
  ].includes(k)
716
724
  )
717
725
  return;
@@ -734,7 +742,10 @@ const attribBadges = (f) => {
734
742
  */
735
743
  router.get(
736
744
  "/:idorname",
737
- isAdmin,
745
+ isAdminOrHasConfigMinRole([
746
+ "min_role_edit_tables",
747
+ "min_role_inspect_tables",
748
+ ]),
738
749
  error_catcher(async (req, res) => {
739
750
  const { idorname } = req.params;
740
751
  let id = parseInt(idorname);
@@ -749,6 +760,18 @@ router.get(
749
760
  res.redirect(`/table`);
750
761
  return;
751
762
  }
763
+
764
+ const user_can_edit_tables =
765
+ req.user.role_id === 1 ||
766
+ getState().getConfig("min_role_edit_tables", 1) >= req.user.role_id;
767
+
768
+ const user_can_edit_views =
769
+ req.user.role_id === 1 ||
770
+ getState().getConfig("min_role_edit_views", 1) >= req.user.role_id;
771
+ const user_can_edit_triggers =
772
+ req.user.role_id === 1 ||
773
+ getState().getConfig("min_role_edit_triggers", 1) >= req.user.role_id;
774
+
752
775
  const nrows = await table.countRows({}, { forUser: req.user });
753
776
  const fields = table.getFields();
754
777
  const { child_relations } = await table.get_child_relations();
@@ -764,13 +787,14 @@ router.get(
764
787
  fieldCard = [
765
788
  h4(req.__(`No fields defined in %s table`, table.name)),
766
789
  p(req.__("Fields define the columns in your table.")),
767
- a(
768
- {
769
- href: `/field/new/${table.id}`,
770
- class: "btn btn-primary add-field",
771
- },
772
- req.__("Add field to table")
773
- ),
790
+ user_can_edit_tables &&
791
+ a(
792
+ {
793
+ href: `/field/new/${table.id}`,
794
+ class: "btn btn-primary add-field",
795
+ },
796
+ req.__("Add field to table")
797
+ ),
774
798
  ];
775
799
  } else {
776
800
  const tableHtml = mkTable(
@@ -787,7 +811,7 @@ router.get(
787
811
  r.typename +
788
812
  span({ class: "badge bg-danger ms-1" }, "Unknown type"),
789
813
  },
790
- ...(table.external
814
+ ...(table.external || !user_can_edit_tables
791
815
  ? []
792
816
  : [
793
817
  {
@@ -804,7 +828,7 @@ router.get(
804
828
  key: (r) => attribBadges(r),
805
829
  },
806
830
  { label: req.__("Variable name"), key: (t) => code(t.name) },
807
- ...(table.external
831
+ ...(table.external || !user_can_edit_tables
808
832
  ? []
809
833
  : [
810
834
  {
@@ -842,6 +866,7 @@ router.get(
842
866
  : "",
843
867
  !table.external &&
844
868
  !table.provider_name &&
869
+ user_can_edit_tables &&
845
870
  a(
846
871
  {
847
872
  href: `/field/new/${table.id}`,
@@ -869,41 +894,48 @@ router.get(
869
894
  p(req.__("Views define how table rows are displayed to the user"))
870
895
  );
871
896
  }
872
- viewCard = {
873
- type: "card",
874
- id: "table-views",
875
- title: req.__("Views of this table"),
876
- contents:
877
- viewCardContents +
878
- a(
879
- {
880
- href: `/viewedit/new?table=${encodeURIComponent(
881
- table.name
882
- )}&on_done_redirect=${encodeURIComponent(`table/${table.name}`)}`,
883
- class: "btn btn-primary",
884
- },
885
- req.__("Create view")
886
- ),
887
- };
888
-
889
- triggerCard = {
890
- type: "card",
891
- id: "table-triggers",
892
- title: req.__("Triggers on table"),
893
- contents:
894
- (triggers.length
895
- ? await getTriggerList(triggers, req)
896
- : p("Triggers run actions in response to events on this table")) +
897
- a(
898
- {
899
- href: `/actions/new?table=${encodeURIComponent(
900
- table.name
901
- )}&on_done_redirect=${encodeURIComponent(`table/${table.name}`)}`,
902
- class: "btn btn-primary",
903
- },
904
- req.__("Create trigger")
905
- ),
906
- };
897
+ if (user_can_edit_views)
898
+ viewCard = {
899
+ type: "card",
900
+ id: "table-views",
901
+ title: req.__("Views of this table"),
902
+ contents:
903
+ viewCardContents +
904
+ a(
905
+ {
906
+ href: `/viewedit/new?table=${encodeURIComponent(
907
+ table.name
908
+ )}&on_done_redirect=${encodeURIComponent(
909
+ `table/${table.name}`
910
+ )}`,
911
+ class: "btn btn-primary",
912
+ },
913
+ req.__("Create view")
914
+ ),
915
+ };
916
+ if (user_can_edit_triggers)
917
+ triggerCard = {
918
+ type: "card",
919
+ id: "table-triggers",
920
+ title: req.__("Triggers on table"),
921
+ contents:
922
+ (triggers.length
923
+ ? await getTriggerList(triggers, req, {
924
+ on_done_redirect: encodeURIComponent(`table/${table.name}`),
925
+ })
926
+ : p("Triggers run actions in response to events on this table")) +
927
+ a(
928
+ {
929
+ href: `/actions/new?table=${encodeURIComponent(
930
+ table.name
931
+ )}&on_done_redirect=${encodeURIComponent(
932
+ `table/${table.name}`
933
+ )}`,
934
+ class: "btn btn-primary",
935
+ },
936
+ req.__("Create trigger")
937
+ ),
938
+ };
907
939
  }
908
940
  const models = await Model.find({ table_id: table.id });
909
941
  const modelCard = div(
@@ -1025,6 +1057,7 @@ router.get(
1025
1057
  settingsDropdown(`dataMenuButton`, [
1026
1058
  // rename table doesnt supported for sqlite
1027
1059
  !db.isSQLite &&
1060
+ user_can_edit_tables &&
1028
1061
  table.name !== "users" &&
1029
1062
  a(
1030
1063
  {
@@ -1039,21 +1072,24 @@ router.get(
1039
1072
  req.__("Recalculate stored fields"),
1040
1073
  req
1041
1074
  ),
1042
- post_dropdown_item(
1043
- `/table/delete-all-rows/${encodeURIComponent(table.name)}`,
1044
- '<i class="far fa-trash-alt"></i>&nbsp;' +
1045
- req.__("Delete all rows"),
1046
- req,
1047
- true
1048
- ),
1049
- table.name !== "users" &&
1075
+ user_can_edit_tables &&
1076
+ post_dropdown_item(
1077
+ `/table/delete-all-rows/${encodeURIComponent(table.name)}`,
1078
+ '<i class="far fa-trash-alt"></i>&nbsp;' +
1079
+ req.__("Delete all rows"),
1080
+ req,
1081
+ true
1082
+ ),
1083
+ user_can_edit_tables &&
1084
+ table.name !== "users" &&
1050
1085
  post_dropdown_item(
1051
1086
  `/table/forget-table/${table.id}`,
1052
1087
  '<i class="fas fa-recycle"></i>&nbsp;' + req.__("Forget table"),
1053
1088
  req,
1054
1089
  true
1055
1090
  ),
1056
- table.name !== "users" &&
1091
+ user_can_edit_tables &&
1092
+ table.name !== "users" &&
1057
1093
  post_dropdown_item(
1058
1094
  `/table/delete/${table.id}`,
1059
1095
  '<i class="fas fa-trash"></i>&nbsp;' + req.__("Delete table"),
@@ -1067,6 +1103,9 @@ router.get(
1067
1103
  if (table.ownership_formula && !table.ownership_field_id)
1068
1104
  table.ownership_field_id = "_formula";
1069
1105
  const tblForm = await tableForm(table, req);
1106
+ if (!user_can_edit_tables) {
1107
+ tblForm.fields.forEach((f) => (f.disabled = true));
1108
+ }
1070
1109
  res.sendWrap(req.__(`%s table`, table.name), {
1071
1110
  above: [
1072
1111
  {
@@ -1105,7 +1144,7 @@ router.get(
1105
1144
  titleAjaxIndicator: true,
1106
1145
  contents: renderForm(tblForm, req.csrfToken()),
1107
1146
  },
1108
- ...(Model.has_templates
1147
+ ...(Model.has_templates && req.user.role_id === 1
1109
1148
  ? [
1110
1149
  {
1111
1150
  type: "card",
@@ -1127,7 +1166,7 @@ router.get(
1127
1166
  */
1128
1167
  router.post(
1129
1168
  "/",
1130
- isAdmin,
1169
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1131
1170
  error_catcher(async (req, res) => {
1132
1171
  const v = req.body;
1133
1172
  if (typeof v.id === "undefined" && typeof v.external === "undefined") {
@@ -1228,7 +1267,7 @@ router.post(
1228
1267
  */
1229
1268
  router.post(
1230
1269
  "/delete/:id",
1231
- isAdmin,
1270
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1232
1271
  error_catcher(async (req, res) => {
1233
1272
  const { id } = req.params;
1234
1273
  const t = Table.findOne({ id });
@@ -1280,7 +1319,7 @@ router.post(
1280
1319
  );
1281
1320
  router.post(
1282
1321
  "/forget-table/:id",
1283
- isAdmin,
1322
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1284
1323
  error_catcher(async (req, res) => {
1285
1324
  const { id } = req.params;
1286
1325
  const t = Table.findOne({ id });
@@ -1317,7 +1356,10 @@ router.post(
1317
1356
  */
1318
1357
  router.get(
1319
1358
  "/",
1320
- isAdmin,
1359
+ isAdminOrHasConfigMinRole([
1360
+ "min_role_edit_tables",
1361
+ "min_role_inspect_tables",
1362
+ ]),
1321
1363
  error_catcher(async (req, res) => {
1322
1364
  const tblq = {};
1323
1365
  let filterOnTag;
@@ -1329,6 +1371,11 @@ router.get(
1329
1371
  tblq.id = { in: tagEntries.map((te) => te.table_id).filter(Boolean) };
1330
1372
  filterOnTag = await Tag.findOne({ id: +req.query._tag });
1331
1373
  }
1374
+
1375
+ const user_can_edit_tables =
1376
+ req.user.role_id === 1 ||
1377
+ getState().getConfig("min_role_edit_tables", 1) >= req.user.role_id;
1378
+
1332
1379
  const rows = await Table.find_with_external(tblq, {
1333
1380
  orderBy: "name",
1334
1381
  nocase: true,
@@ -1337,20 +1384,23 @@ router.get(
1337
1384
  const getRole = (rid) => roles.find((r) => r.id === rid).role;
1338
1385
  const mainCard = await tablesList(rows, req, { filterOnTag });
1339
1386
  const createCard = div(
1340
- a(
1341
- { href: `/table/new`, class: "btn btn-primary mt-1 me-3" },
1342
- i({ class: "fas fa-plus-square me-1" }),
1343
- req.__("Create table")
1344
- ),
1345
- a(
1346
- {
1347
- href: `/table/create-from-csv`,
1348
- class: "btn btn-secondary me-3 mt-1",
1349
- },
1350
- i({ class: "fas fa-upload me-1" }),
1351
- req.__("Create from CSV upload")
1352
- ),
1353
- !db.isSQLite &&
1387
+ user_can_edit_tables &&
1388
+ a(
1389
+ { href: `/table/new`, class: "btn btn-primary mt-1 me-3" },
1390
+ i({ class: "fas fa-plus-square me-1" }),
1391
+ req.__("Create table")
1392
+ ),
1393
+ user_can_edit_tables &&
1394
+ a(
1395
+ {
1396
+ href: `/table/create-from-csv`,
1397
+ class: "btn btn-secondary me-3 mt-1",
1398
+ },
1399
+ i({ class: "fas fa-upload me-1" }),
1400
+ req.__("Create from CSV upload")
1401
+ ),
1402
+ req.user.role_id === 1 &&
1403
+ !db.isSQLite &&
1354
1404
  a(
1355
1405
  {
1356
1406
  href: `/table/discover`,
@@ -1396,10 +1446,18 @@ router.get(
1396
1446
  */
1397
1447
  router.get(
1398
1448
  "/download/:name",
1399
- isAdmin,
1449
+ isAdminOrHasConfigMinRole([
1450
+ "min_role_edit_tables",
1451
+ "min_role_inspect_tables",
1452
+ ]),
1400
1453
  error_catcher(async (req, res) => {
1401
1454
  const { name } = req.params;
1402
1455
  const table = Table.findOne({ name });
1456
+ if (table.min_role_read < req.user.role_id) {
1457
+ req.flash("error", "Not authorized to read table");
1458
+ res.redirect(`/table/${table.id}`);
1459
+ return;
1460
+ }
1403
1461
  const rows = await table.getRows({}, { orderBy: "id", forUser: req.user });
1404
1462
  res.setHeader("Content-Type", "text/csv");
1405
1463
  res.setHeader("Content-Disposition", `attachment; filename="${name}.csv"`);
@@ -1440,7 +1498,10 @@ router.get(
1440
1498
  */
1441
1499
  router.get(
1442
1500
  "/constraints/:id",
1443
- isAdmin,
1501
+ isAdminOrHasConfigMinRole([
1502
+ "min_role_edit_tables",
1503
+ "min_role_inspect_tables",
1504
+ ]),
1444
1505
  error_catcher(async (req, res) => {
1445
1506
  const { id } = req.params;
1446
1507
  const table = Table.findOne({ id });
@@ -1642,7 +1703,7 @@ router.get(
1642
1703
  */
1643
1704
  router.post(
1644
1705
  "/add-constraint/:id/:type",
1645
- isAdmin,
1706
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1646
1707
  error_catcher(async (req, res) => {
1647
1708
  const { id, type } = req.params;
1648
1709
  const table = Table.findOne({ id });
@@ -1738,7 +1799,7 @@ router.get(
1738
1799
  */
1739
1800
  router.post(
1740
1801
  "/rename/:id",
1741
- isAdmin,
1802
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1742
1803
  error_catcher(async (req, res) => {
1743
1804
  const { id } = req.params;
1744
1805
  const table = Table.findOne({ id });
@@ -1762,7 +1823,7 @@ router.post(
1762
1823
  */
1763
1824
  router.post(
1764
1825
  "/delete-constraint/:id",
1765
- isAdmin,
1826
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1766
1827
  error_catcher(async (req, res) => {
1767
1828
  const { id } = req.params;
1768
1829
  const cons = await TableConstraint.findOne({ id });
@@ -1879,7 +1940,10 @@ const previewCSV = async ({ newPath, table, req, res, full }) => {
1879
1940
  router.post(
1880
1941
  "/upload_to_table/:name",
1881
1942
  setTenant, // TODO why is this needed?????
1882
- isAdmin,
1943
+ isAdminOrHasConfigMinRole([
1944
+ "min_role_edit_tables",
1945
+ "min_role_inspect_tables",
1946
+ ]),
1883
1947
  error_catcher(async (req, res) => {
1884
1948
  const { name } = req.params;
1885
1949
  const table = Table.findOne({ name });
@@ -1888,6 +1952,11 @@ router.post(
1888
1952
  res.redirect(`/table/${table.id}`);
1889
1953
  return;
1890
1954
  }
1955
+ if (table.min_role_write < req.user.role_id) {
1956
+ req.flash("error", "Not authorized to write to table");
1957
+ res.redirect(`/table/${table.id}`);
1958
+ return;
1959
+ }
1891
1960
 
1892
1961
  const newPath = File.get_new_path();
1893
1962
  await req.files.file.mv(newPath);
@@ -1898,7 +1967,10 @@ router.post(
1898
1967
 
1899
1968
  router.get(
1900
1969
  "/preview_full_csv_file/:name/:filename",
1901
- isAdmin,
1970
+ isAdminOrHasConfigMinRole([
1971
+ "min_role_edit_tables",
1972
+ "min_role_inspect_tables",
1973
+ ]),
1902
1974
  error_catcher(async (req, res) => {
1903
1975
  const { name, filename } = req.params;
1904
1976
  const table = Table.findOne({ name });
@@ -1909,7 +1981,10 @@ router.get(
1909
1981
 
1910
1982
  router.post(
1911
1983
  "/finish_upload_to_table/:name/:filename",
1912
- isAdmin,
1984
+ isAdminOrHasConfigMinRole([
1985
+ "min_role_edit_tables",
1986
+ "min_role_inspect_tables",
1987
+ ]),
1913
1988
  error_catcher(async (req, res) => {
1914
1989
  const { name, filename } = req.params;
1915
1990
  const table = Table.findOne({ name });
@@ -1938,7 +2013,7 @@ router.post(
1938
2013
  */
1939
2014
  router.post(
1940
2015
  "/delete-all-rows/:name",
1941
- isAdmin,
2016
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1942
2017
  error_catcher(async (req, res) => {
1943
2018
  const { name } = req.params;
1944
2019
  const table = Table.findOne({ name });
@@ -1963,7 +2038,10 @@ router.post(
1963
2038
  */
1964
2039
  router.post(
1965
2040
  "/recalc-stored/:name",
1966
- isAdmin,
2041
+ isAdminOrHasConfigMinRole([
2042
+ "min_role_edit_tables",
2043
+ "min_role_inspect_tables",
2044
+ ]),
1967
2045
  error_catcher(async (req, res) => {
1968
2046
  const { name } = req.params;
1969
2047
  const table = Table.findOne({ name });
@@ -2054,7 +2132,10 @@ const get_provider_workflow = (table, req) => {
2054
2132
 
2055
2133
  router.get(
2056
2134
  "/provider-cfg/:id",
2057
- isAdmin,
2135
+ isAdminOrHasConfigMinRole([
2136
+ "min_role_edit_tables",
2137
+ "min_role_inspect_tables",
2138
+ ]),
2058
2139
  error_catcher(async (req, res) => {
2059
2140
  const { id } = req.params;
2060
2141
  const { step } = req.query;
@@ -2080,7 +2161,7 @@ router.get(
2080
2161
 
2081
2162
  router.post(
2082
2163
  "/provider-cfg/:id",
2083
- isAdmin,
2164
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
2084
2165
  error_catcher(async (req, res) => {
2085
2166
  const { id } = req.params;
2086
2167
  const { step } = req.query;
@@ -2099,7 +2180,7 @@ router.post(
2099
2180
 
2100
2181
  router.post(
2101
2182
  "/repair-composite-primary/:id",
2102
- isAdmin,
2183
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
2103
2184
  error_catcher(async (req, res) => {
2104
2185
  const { id } = req.params;
2105
2186
 
package/routes/utils.js CHANGED
@@ -95,6 +95,32 @@ function isAdmin(req, res, next) {
95
95
  }
96
96
  }
97
97
 
98
+ const isAdminOrHasConfigMinRole = (cfg) => (req, res, next) => {
99
+ const cur_tenant = db.getTenantSchema();
100
+ //console.log({ cur_tenant, user: req.user });
101
+ if (
102
+ req.user &&
103
+ (req.user.role_id === 1 ||
104
+ (Array.isArray(cfg)
105
+ ? cfg.some(
106
+ (one_cfg) => getState().getConfig(one_cfg, 1) >= req.user.role_id
107
+ )
108
+ : getState().getConfig(cfg, 1) >= req.user.role_id)) &&
109
+ req.user.tenant === cur_tenant
110
+ ) {
111
+ next();
112
+ } else {
113
+ req.flash("danger", req.__("Must be admin"));
114
+ res.redirect(
115
+ req.user && req.user.pending_user
116
+ ? "/auth/twofa/login/totp"
117
+ : req.user
118
+ ? "/"
119
+ : `/auth/login?dest=${encodeURIComponent(req.originalUrl)}`
120
+ );
121
+ }
122
+ };
123
+
98
124
  /**
99
125
  * Sets language for HTTP Request / HTTP Responce
100
126
  * @param {object} req
@@ -590,6 +616,7 @@ module.exports = {
590
616
  csrfField,
591
617
  loggedIn,
592
618
  isAdmin,
619
+ isAdminOrHasConfigMinRole,
593
620
  get_base_url,
594
621
  error_catcher,
595
622
  scan_for_page_title,
package/routes/view.js CHANGED
@@ -16,6 +16,7 @@ const {
16
16
  error_catcher,
17
17
  scan_for_page_title,
18
18
  setTenant,
19
+ isAdminOrHasConfigMinRole,
19
20
  } = require("../routes/utils.js");
20
21
  const { add_edit_bar } = require("../markup/admin.js");
21
22
  const { InvalidConfiguration, isTest } = require("@saltcorn/data/utils");
@@ -166,7 +167,7 @@ router.get(
166
167
  */
167
168
  router.post(
168
169
  "/:viewname/preview",
169
- isAdmin,
170
+ isAdminOrHasConfigMinRole("min_role_edit_views"),
170
171
  error_catcher(async (req, res) => {
171
172
  const { viewname } = req.params;
172
173