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

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.
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,46 @@ 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
+ : p("Triggers run actions in response to events on this table")) +
925
+ a(
926
+ {
927
+ href: `/actions/new?table=${encodeURIComponent(
928
+ table.name
929
+ )}&on_done_redirect=${encodeURIComponent(
930
+ `table/${table.name}`
931
+ )}`,
932
+ class: "btn btn-primary",
933
+ },
934
+ req.__("Create trigger")
935
+ ),
936
+ };
907
937
  }
908
938
  const models = await Model.find({ table_id: table.id });
909
939
  const modelCard = div(
@@ -1025,6 +1055,7 @@ router.get(
1025
1055
  settingsDropdown(`dataMenuButton`, [
1026
1056
  // rename table doesnt supported for sqlite
1027
1057
  !db.isSQLite &&
1058
+ user_can_edit_tables &&
1028
1059
  table.name !== "users" &&
1029
1060
  a(
1030
1061
  {
@@ -1039,21 +1070,24 @@ router.get(
1039
1070
  req.__("Recalculate stored fields"),
1040
1071
  req
1041
1072
  ),
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" &&
1073
+ user_can_edit_tables &&
1074
+ post_dropdown_item(
1075
+ `/table/delete-all-rows/${encodeURIComponent(table.name)}`,
1076
+ '<i class="far fa-trash-alt"></i>&nbsp;' +
1077
+ req.__("Delete all rows"),
1078
+ req,
1079
+ true
1080
+ ),
1081
+ user_can_edit_tables &&
1082
+ table.name !== "users" &&
1050
1083
  post_dropdown_item(
1051
1084
  `/table/forget-table/${table.id}`,
1052
1085
  '<i class="fas fa-recycle"></i>&nbsp;' + req.__("Forget table"),
1053
1086
  req,
1054
1087
  true
1055
1088
  ),
1056
- table.name !== "users" &&
1089
+ user_can_edit_tables &&
1090
+ table.name !== "users" &&
1057
1091
  post_dropdown_item(
1058
1092
  `/table/delete/${table.id}`,
1059
1093
  '<i class="fas fa-trash"></i>&nbsp;' + req.__("Delete table"),
@@ -1067,6 +1101,9 @@ router.get(
1067
1101
  if (table.ownership_formula && !table.ownership_field_id)
1068
1102
  table.ownership_field_id = "_formula";
1069
1103
  const tblForm = await tableForm(table, req);
1104
+ if (!user_can_edit_tables) {
1105
+ tblForm.fields.forEach((f) => (f.disabled = true));
1106
+ }
1070
1107
  res.sendWrap(req.__(`%s table`, table.name), {
1071
1108
  above: [
1072
1109
  {
@@ -1105,7 +1142,7 @@ router.get(
1105
1142
  titleAjaxIndicator: true,
1106
1143
  contents: renderForm(tblForm, req.csrfToken()),
1107
1144
  },
1108
- ...(Model.has_templates
1145
+ ...(Model.has_templates && req.user.role_id === 1
1109
1146
  ? [
1110
1147
  {
1111
1148
  type: "card",
@@ -1127,7 +1164,7 @@ router.get(
1127
1164
  */
1128
1165
  router.post(
1129
1166
  "/",
1130
- isAdmin,
1167
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1131
1168
  error_catcher(async (req, res) => {
1132
1169
  const v = req.body;
1133
1170
  if (typeof v.id === "undefined" && typeof v.external === "undefined") {
@@ -1228,7 +1265,7 @@ router.post(
1228
1265
  */
1229
1266
  router.post(
1230
1267
  "/delete/:id",
1231
- isAdmin,
1268
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1232
1269
  error_catcher(async (req, res) => {
1233
1270
  const { id } = req.params;
1234
1271
  const t = Table.findOne({ id });
@@ -1280,7 +1317,7 @@ router.post(
1280
1317
  );
1281
1318
  router.post(
1282
1319
  "/forget-table/:id",
1283
- isAdmin,
1320
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1284
1321
  error_catcher(async (req, res) => {
1285
1322
  const { id } = req.params;
1286
1323
  const t = Table.findOne({ id });
@@ -1317,7 +1354,10 @@ router.post(
1317
1354
  */
1318
1355
  router.get(
1319
1356
  "/",
1320
- isAdmin,
1357
+ isAdminOrHasConfigMinRole([
1358
+ "min_role_edit_tables",
1359
+ "min_role_inspect_tables",
1360
+ ]),
1321
1361
  error_catcher(async (req, res) => {
1322
1362
  const tblq = {};
1323
1363
  let filterOnTag;
@@ -1329,6 +1369,11 @@ router.get(
1329
1369
  tblq.id = { in: tagEntries.map((te) => te.table_id).filter(Boolean) };
1330
1370
  filterOnTag = await Tag.findOne({ id: +req.query._tag });
1331
1371
  }
1372
+
1373
+ const user_can_edit_tables =
1374
+ req.user.role_id === 1 ||
1375
+ getState().getConfig("min_role_edit_tables", 1) >= req.user.role_id;
1376
+
1332
1377
  const rows = await Table.find_with_external(tblq, {
1333
1378
  orderBy: "name",
1334
1379
  nocase: true,
@@ -1337,20 +1382,23 @@ router.get(
1337
1382
  const getRole = (rid) => roles.find((r) => r.id === rid).role;
1338
1383
  const mainCard = await tablesList(rows, req, { filterOnTag });
1339
1384
  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 &&
1385
+ user_can_edit_tables &&
1386
+ a(
1387
+ { href: `/table/new`, class: "btn btn-primary mt-1 me-3" },
1388
+ i({ class: "fas fa-plus-square me-1" }),
1389
+ req.__("Create table")
1390
+ ),
1391
+ user_can_edit_tables &&
1392
+ a(
1393
+ {
1394
+ href: `/table/create-from-csv`,
1395
+ class: "btn btn-secondary me-3 mt-1",
1396
+ },
1397
+ i({ class: "fas fa-upload me-1" }),
1398
+ req.__("Create from CSV upload")
1399
+ ),
1400
+ req.user.role_id === 1 &&
1401
+ !db.isSQLite &&
1354
1402
  a(
1355
1403
  {
1356
1404
  href: `/table/discover`,
@@ -1396,10 +1444,18 @@ router.get(
1396
1444
  */
1397
1445
  router.get(
1398
1446
  "/download/:name",
1399
- isAdmin,
1447
+ isAdminOrHasConfigMinRole([
1448
+ "min_role_edit_tables",
1449
+ "min_role_inspect_tables",
1450
+ ]),
1400
1451
  error_catcher(async (req, res) => {
1401
1452
  const { name } = req.params;
1402
1453
  const table = Table.findOne({ name });
1454
+ if (table.min_role_read < req.user.role_id) {
1455
+ req.flash("error", "Not authorized to read table");
1456
+ res.redirect(`/table/${table.id}`);
1457
+ return;
1458
+ }
1403
1459
  const rows = await table.getRows({}, { orderBy: "id", forUser: req.user });
1404
1460
  res.setHeader("Content-Type", "text/csv");
1405
1461
  res.setHeader("Content-Disposition", `attachment; filename="${name}.csv"`);
@@ -1440,7 +1496,10 @@ router.get(
1440
1496
  */
1441
1497
  router.get(
1442
1498
  "/constraints/:id",
1443
- isAdmin,
1499
+ isAdminOrHasConfigMinRole([
1500
+ "min_role_edit_tables",
1501
+ "min_role_inspect_tables",
1502
+ ]),
1444
1503
  error_catcher(async (req, res) => {
1445
1504
  const { id } = req.params;
1446
1505
  const table = Table.findOne({ id });
@@ -1642,7 +1701,7 @@ router.get(
1642
1701
  */
1643
1702
  router.post(
1644
1703
  "/add-constraint/:id/:type",
1645
- isAdmin,
1704
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1646
1705
  error_catcher(async (req, res) => {
1647
1706
  const { id, type } = req.params;
1648
1707
  const table = Table.findOne({ id });
@@ -1738,7 +1797,7 @@ router.get(
1738
1797
  */
1739
1798
  router.post(
1740
1799
  "/rename/:id",
1741
- isAdmin,
1800
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1742
1801
  error_catcher(async (req, res) => {
1743
1802
  const { id } = req.params;
1744
1803
  const table = Table.findOne({ id });
@@ -1762,7 +1821,7 @@ router.post(
1762
1821
  */
1763
1822
  router.post(
1764
1823
  "/delete-constraint/:id",
1765
- isAdmin,
1824
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1766
1825
  error_catcher(async (req, res) => {
1767
1826
  const { id } = req.params;
1768
1827
  const cons = await TableConstraint.findOne({ id });
@@ -1879,7 +1938,10 @@ const previewCSV = async ({ newPath, table, req, res, full }) => {
1879
1938
  router.post(
1880
1939
  "/upload_to_table/:name",
1881
1940
  setTenant, // TODO why is this needed?????
1882
- isAdmin,
1941
+ isAdminOrHasConfigMinRole([
1942
+ "min_role_edit_tables",
1943
+ "min_role_inspect_tables",
1944
+ ]),
1883
1945
  error_catcher(async (req, res) => {
1884
1946
  const { name } = req.params;
1885
1947
  const table = Table.findOne({ name });
@@ -1888,6 +1950,11 @@ router.post(
1888
1950
  res.redirect(`/table/${table.id}`);
1889
1951
  return;
1890
1952
  }
1953
+ if (table.min_role_write < req.user.role_id) {
1954
+ req.flash("error", "Not authorized to write to table");
1955
+ res.redirect(`/table/${table.id}`);
1956
+ return;
1957
+ }
1891
1958
 
1892
1959
  const newPath = File.get_new_path();
1893
1960
  await req.files.file.mv(newPath);
@@ -1898,7 +1965,10 @@ router.post(
1898
1965
 
1899
1966
  router.get(
1900
1967
  "/preview_full_csv_file/:name/:filename",
1901
- isAdmin,
1968
+ isAdminOrHasConfigMinRole([
1969
+ "min_role_edit_tables",
1970
+ "min_role_inspect_tables",
1971
+ ]),
1902
1972
  error_catcher(async (req, res) => {
1903
1973
  const { name, filename } = req.params;
1904
1974
  const table = Table.findOne({ name });
@@ -1909,7 +1979,10 @@ router.get(
1909
1979
 
1910
1980
  router.post(
1911
1981
  "/finish_upload_to_table/:name/:filename",
1912
- isAdmin,
1982
+ isAdminOrHasConfigMinRole([
1983
+ "min_role_edit_tables",
1984
+ "min_role_inspect_tables",
1985
+ ]),
1913
1986
  error_catcher(async (req, res) => {
1914
1987
  const { name, filename } = req.params;
1915
1988
  const table = Table.findOne({ name });
@@ -1938,7 +2011,7 @@ router.post(
1938
2011
  */
1939
2012
  router.post(
1940
2013
  "/delete-all-rows/:name",
1941
- isAdmin,
2014
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
1942
2015
  error_catcher(async (req, res) => {
1943
2016
  const { name } = req.params;
1944
2017
  const table = Table.findOne({ name });
@@ -1963,7 +2036,10 @@ router.post(
1963
2036
  */
1964
2037
  router.post(
1965
2038
  "/recalc-stored/:name",
1966
- isAdmin,
2039
+ isAdminOrHasConfigMinRole([
2040
+ "min_role_edit_tables",
2041
+ "min_role_inspect_tables",
2042
+ ]),
1967
2043
  error_catcher(async (req, res) => {
1968
2044
  const { name } = req.params;
1969
2045
  const table = Table.findOne({ name });
@@ -2054,7 +2130,10 @@ const get_provider_workflow = (table, req) => {
2054
2130
 
2055
2131
  router.get(
2056
2132
  "/provider-cfg/:id",
2057
- isAdmin,
2133
+ isAdminOrHasConfigMinRole([
2134
+ "min_role_edit_tables",
2135
+ "min_role_inspect_tables",
2136
+ ]),
2058
2137
  error_catcher(async (req, res) => {
2059
2138
  const { id } = req.params;
2060
2139
  const { step } = req.query;
@@ -2080,7 +2159,7 @@ router.get(
2080
2159
 
2081
2160
  router.post(
2082
2161
  "/provider-cfg/:id",
2083
- isAdmin,
2162
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
2084
2163
  error_catcher(async (req, res) => {
2085
2164
  const { id } = req.params;
2086
2165
  const { step } = req.query;
@@ -2099,7 +2178,7 @@ router.post(
2099
2178
 
2100
2179
  router.post(
2101
2180
  "/repair-composite-primary/:id",
2102
- isAdmin,
2181
+ isAdminOrHasConfigMinRole("min_role_edit_tables"),
2103
2182
  error_catcher(async (req, res) => {
2104
2183
  const { id } = req.params;
2105
2184
 
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