@saltcorn/server 0.8.1-rc.2 → 0.8.1

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/auth/admin.js CHANGED
@@ -336,6 +336,7 @@ const http_settings_form = async (req) =>
336
336
  "cookie_duration",
337
337
  "cookie_duration_remember",
338
338
  "cookie_sessions",
339
+ "public_cache_maxage",
339
340
  "custom_http_headers",
340
341
  ],
341
342
  action: "/useradmin/http",
@@ -753,9 +754,7 @@ router.get(
753
754
  for (const table of tables) {
754
755
  if (table.external) continue;
755
756
  const fields = await table.getFields();
756
- const userFields = fields
757
- .filter((f) => f.reftable_name === "users")
758
- .map((f) => ({ value: f.id, label: f.name }));
757
+ const ownership_opts = await table.ownership_options();
759
758
  const form = new Form({
760
759
  action: "/table",
761
760
  noSubmitButton: true,
@@ -770,7 +769,7 @@ router.get(
770
769
  input_type: "select",
771
770
  options: [
772
771
  { value: "", label: req.__("None") },
773
- ...userFields,
772
+ ...ownership_opts,
774
773
  { value: "_formula", label: req.__("Formula") },
775
774
  ],
776
775
  },
package/auth/routes.js CHANGED
@@ -1061,6 +1061,7 @@ router.post(
1061
1061
  else req.session.cookie.expires = false;
1062
1062
  }
1063
1063
  Trigger.emitEvent("Login", null, req.user);
1064
+ res?.cookie?.("loggedin", "true");
1064
1065
  req.flash("success", req.__("Welcome, %s!", req.user.email));
1065
1066
  if (req.smr) {
1066
1067
  const dbUser = await User.findOne({ id: req.user.id });
package/auth/testhelp.js CHANGED
@@ -88,6 +88,9 @@ const toNotInclude =
88
88
  }
89
89
  };
90
90
 
91
+ const resToLoginCookie = (res) =>
92
+ res.headers["set-cookie"].find((c) => c.includes("connect.sid"));
93
+
91
94
  /**
92
95
  *
93
96
  * @returns {Promise<void>}
@@ -99,7 +102,7 @@ const getStaffLoginCookie = async () => {
99
102
  .send("email=staff@foo.com")
100
103
  .send("password=ghrarhr54hg");
101
104
  if (res.statusCode !== 302) console.log(res.text);
102
- return res.headers["set-cookie"][0];
105
+ return resToLoginCookie(res);
103
106
  };
104
107
 
105
108
  /**
@@ -113,8 +116,7 @@ const getAdminLoginCookie = async () => {
113
116
  .send("email=admin@foo.com")
114
117
  .send("password=AhGGr6rhu45");
115
118
  if (res.statusCode !== 302) console.log(res.text);
116
-
117
- return res.headers["set-cookie"][0];
119
+ return resToLoginCookie(res);
118
120
  };
119
121
 
120
122
  /**
package/locales/da.json CHANGED
@@ -555,5 +555,8 @@
555
555
  "Create database view": "Create database view",
556
556
  "Create an SQL view in the database with the fields in this list": "Create an SQL view in the database with the fields in this list",
557
557
  "Rows per page": "Rows per page",
558
- "List options": "List options"
558
+ "List options": "List options",
559
+ "Modules": "Modules",
560
+ "File not found": "File not found",
561
+ "Welcome, %s!": "Welcome, %s!"
559
562
  }
package/locales/en.json CHANGED
@@ -1077,5 +1077,10 @@
1077
1077
  "Saved 2FA policy for role": "Saved 2FA policy for role",
1078
1078
  "HTTP settings updated": "HTTP settings updated",
1079
1079
  "%s configuration": "%s configuration",
1080
- "Save indicator": "Save indicator"
1080
+ "Save indicator": "Save indicator",
1081
+ "Public cache TTL (minutes)": "Public cache TTL (minutes)",
1082
+ "Cache-control max-age for public views and pages. 0 to disable": "Cache-control max-age for public views and pages. 0 to disable",
1083
+ "Files accept filter": "Files accept filter",
1084
+ "User group": "User group",
1085
+ "Add relations to this table in dropdown options for ownership field": "Add relations to this table in dropdown options for ownership field"
1081
1086
  }
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.8.1-rc.2",
3
+ "version": "0.8.1",
4
4
  "description": "Server app for Saltcorn, open-source no-code platform",
5
5
  "homepage": "https://saltcorn.com",
6
6
  "main": "index.js",
7
7
  "license": "MIT",
8
8
  "dependencies": {
9
- "@saltcorn/base-plugin": "0.8.1-rc.2",
10
- "@saltcorn/builder": "0.8.1-rc.2",
11
- "@saltcorn/data": "0.8.1-rc.2",
12
- "@saltcorn/admin-models": "0.8.1-rc.2",
13
- "@saltcorn/filemanager": "0.8.1-rc.2",
14
- "@saltcorn/markup": "0.8.1-rc.2",
15
- "@saltcorn/sbadmin2": "0.8.1-rc.2",
9
+ "@saltcorn/base-plugin": "0.8.1",
10
+ "@saltcorn/builder": "0.8.1",
11
+ "@saltcorn/data": "0.8.1",
12
+ "@saltcorn/admin-models": "0.8.1",
13
+ "@saltcorn/filemanager": "0.8.1",
14
+ "@saltcorn/markup": "0.8.1",
15
+ "@saltcorn/sbadmin2": "0.8.1",
16
16
  "@socket.io/cluster-adapter": "^0.1.0",
17
17
  "@socket.io/sticky": "^1.0.1",
18
18
  "aws-sdk": "^2.1037.0",
package/routes/admin.js CHANGED
@@ -97,6 +97,7 @@ const View = require("@saltcorn/data/models/view");
97
97
  const { getConfigFile } = require("@saltcorn/data/db/connect");
98
98
  const os = require("os");
99
99
  const Page = require("@saltcorn/data/models/page");
100
+ const { getSafeSaltcornCmd } = require("@saltcorn/data/utils");
100
101
 
101
102
  const router = new Router();
102
103
  module.exports = router;
@@ -1545,7 +1546,22 @@ router.get(
1545
1546
  const checkFiles = async (outDir, fileNames) => {
1546
1547
  const rootFolder = await File.rootFolder();
1547
1548
  const mobile_app_dir = path.join(rootFolder.location, "mobile_app", outDir);
1548
- const entries = fs.readdirSync(mobile_app_dir);
1549
+ const unsafeFiles = await Promise.all(
1550
+ fs
1551
+ .readdirSync(mobile_app_dir)
1552
+ .map(
1553
+ async (outFile) => await File.from_file_on_disk(outFile, mobile_app_dir)
1554
+ )
1555
+ );
1556
+ const entries = unsafeFiles
1557
+ .filter(
1558
+ (file) =>
1559
+ file.user_id &&
1560
+ !isNaN(file.user_id) &&
1561
+ file.min_role_read &&
1562
+ !isNaN(file.min_role_read)
1563
+ )
1564
+ .map((file) => file.filename);
1549
1565
  return fileNames.some((fileName) => entries.indexOf(fileName) >= 0);
1550
1566
  };
1551
1567
 
@@ -1638,6 +1654,8 @@ router.post(
1638
1654
  buildDir,
1639
1655
  "-b",
1640
1656
  `${os.userInfo().homedir}/mobile_app_build`,
1657
+ "-u",
1658
+ req.user.email, // ensured by isAdmin
1641
1659
  ];
1642
1660
  if (useDocker) spawnParams.push("-d");
1643
1661
  if (androidPlatform) spawnParams.push("-p", "android");
@@ -1659,7 +1677,7 @@ router.post(
1659
1677
  // end http call, return the out directory name
1660
1678
  // the gui polls for results
1661
1679
  res.json({ build_dir_name: outDirName });
1662
- const child = spawn("saltcorn", spawnParams, {
1680
+ const child = spawn(getSafeSaltcornCmd(), spawnParams, {
1663
1681
  stdio: ["ignore", "pipe", "pipe"],
1664
1682
  cwd: ".",
1665
1683
  });
@@ -1672,29 +1690,36 @@ router.post(
1672
1690
  // console.log(data.toString());
1673
1691
  childOutputs.push(data ? data.toString() : req.__("An error occurred"));
1674
1692
  });
1675
- child.on("exit", async function (exitCode, signal) {
1693
+ child.on("exit", (exitCode, signal) => {
1676
1694
  const logFile = exitCode === 0 ? "logs.txt" : "error_logs.txt";
1677
1695
  fs.writeFile(
1678
1696
  path.join(buildDir, logFile),
1679
1697
  childOutputs.join("\n"),
1680
- (error) => {
1698
+ async (error) => {
1681
1699
  if (error) {
1682
1700
  console.log(`unable to write '${logFile}' to '${buildDir}'`);
1683
1701
  console.log(error);
1702
+ } else {
1703
+ // no transaction, '/build-mobile-app/finished' filters for valid attributes
1704
+ await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
1684
1705
  }
1685
1706
  }
1686
1707
  );
1687
1708
  });
1688
- child.on("error", function (msg) {
1709
+ child.on("error", (msg) => {
1689
1710
  const message = msg.message ? msg.message : msg.code;
1690
1711
  const stack = msg.stack ? msg.stack : "";
1712
+ const logFile = "error_logs.txt";
1691
1713
  fs.writeFile(
1692
1714
  path.join(buildDir, "error_logs.txt"),
1693
1715
  [message, stack].join("\n"),
1694
- (error) => {
1716
+ async (error) => {
1695
1717
  if (error) {
1696
1718
  console.log(`unable to write logFile to '${buildDir}'`);
1697
1719
  console.log(error);
1720
+ } else {
1721
+ // no transaction, '/build-mobile-app/finished' filters for valid attributes
1722
+ await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
1698
1723
  }
1699
1724
  }
1700
1725
  );
package/routes/fields.js CHANGED
@@ -996,7 +996,8 @@ router.post(
996
996
  formStyle: "vert",
997
997
  fields: formFields,
998
998
  });
999
- if (_columndef) form.values = JSON.parse(_columndef);
999
+ if (_columndef && _columndef !== "undefined")
1000
+ form.values = JSON.parse(_columndef);
1000
1001
  res.send(mkFormContentNoLayout(form));
1001
1002
  })
1002
1003
  );
package/routes/files.js CHANGED
@@ -393,6 +393,13 @@ router.post(
393
393
  f.s3_store ? s3storage.unlinkObject : undefined
394
394
  );
395
395
  if (result && result.error) {
396
+ if (req.xhr) {
397
+ const root = path.join(db.connectObj.file_store, db.getTenantSchema());
398
+ res.json({
399
+ error: result.error.replaceAll(root, ""),
400
+ });
401
+ return;
402
+ }
396
403
  req.flash("error", result.error);
397
404
  }
398
405
  res.redirect(`/files?dir=${encodeURIComponent(f.current_folder)}`);
package/routes/tables.js CHANGED
@@ -73,9 +73,7 @@ const tableForm = async (table, req) => {
73
73
  value: r.id,
74
74
  label: r.role,
75
75
  }));
76
- const userFields = fields
77
- .filter((f) => f.reftable_name === "users")
78
- .map((f) => ({ value: f.id, label: f.name }));
76
+ const ownership_opts = await table.ownership_options();
79
77
  const form = new Form({
80
78
  action: "/table",
81
79
  noSubmitButton: true,
@@ -92,7 +90,7 @@ const tableForm = async (table, req) => {
92
90
  input_type: "select",
93
91
  options: [
94
92
  { value: "", label: req.__("None") },
95
- ...userFields,
93
+ ...ownership_opts,
96
94
  { value: "_formula", label: req.__("Formula") },
97
95
  ],
98
96
  },
@@ -109,6 +107,14 @@ const tableForm = async (table, req) => {
109
107
  .join(", "),
110
108
  showIf: { ownership_field_id: "_formula" },
111
109
  },
110
+ {
111
+ label: req.__("User group"),
112
+ sublabel: req.__(
113
+ "Add relations to this table in dropdown options for ownership field"
114
+ ),
115
+ name: "is_user_group",
116
+ type: "Bool",
117
+ },
112
118
  ]
113
119
  : []),
114
120
  // description of table
@@ -898,6 +904,12 @@ router.post(
898
904
  notify = req.__(`Invalid ownership formula: %s`, fmlValidRes);
899
905
  hasError = true;
900
906
  }
907
+ } else if (
908
+ typeof rest.ownership_field_id === "string" &&
909
+ rest.ownership_field_id.startsWith("Fml:")
910
+ ) {
911
+ rest.ownership_formula = rest.ownership_field_id.replace("Fml:", "");
912
+ rest.ownership_field_id = null;
901
913
  } else rest.ownership_formula = null;
902
914
  await table.update(rest);
903
915
 
package/routes/utils.js CHANGED
@@ -76,7 +76,7 @@ const setLanguage = (req, res, state) => {
76
76
  } else if (req.cookies?.lang) {
77
77
  req.setLocale(req.cookies?.lang);
78
78
  }
79
- set_custom_http_headers(res, state);
79
+ set_custom_http_headers(res, req, state);
80
80
  };
81
81
 
82
82
  /**
@@ -85,8 +85,17 @@ const setLanguage = (req, res, state) => {
85
85
  * @param {string} state
86
86
  * @returns {void}
87
87
  */
88
- const set_custom_http_headers = (res, state) => {
89
- const hdrs = (state || getState()).getConfig("custom_http_headers");
88
+ const set_custom_http_headers = (res, req, state) => {
89
+ const state1 = state || getState();
90
+ const hdrs = state1.getConfig("custom_http_headers");
91
+ if (!req.user) {
92
+ const public_cache_maxage = +state1.getConfig("public_cache_maxage", 0);
93
+ if (public_cache_maxage)
94
+ res.header(
95
+ "Cache-Control",
96
+ `public, max-age=${public_cache_maxage * 60}`
97
+ );
98
+ }
90
99
  if (!hdrs) return;
91
100
  for (const ln of hdrs.split("\n")) {
92
101
  const [k, v] = ln.split(":");
@@ -464,7 +464,7 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
464
464
  type: "breadcrumbs",
465
465
  crumbs: [
466
466
  { text: req.__("Views"), href: "/viewedit" },
467
- { href: `/viewedit/edit/${view.name}`, text: view.name },
467
+ { href: `/view/${view.name}`, text: view.name },
468
468
  { workflow: wf, step: wfres },
469
469
  ],
470
470
  },
package/serve.js CHANGED
@@ -377,7 +377,7 @@ const setupSocket = (...servers) => {
377
377
  const view = View.findOne({ name: viewname });
378
378
  if (view.viewtemplateObj.authorize_join) {
379
379
  view.viewtemplateObj
380
- .authorize_join(view.configuration, room_id, socket.request.user)
380
+ .authorize_join(view, room_id, socket.request.user)
381
381
  .then((authorized) => {
382
382
  if (authorized) socket.join(`${ten}_${viewname}_${room_id}`);
383
383
  });