@saltcorn/server 0.8.5-beta.7 → 0.8.5-rc.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
@@ -7,6 +7,7 @@
7
7
  // todo refactor to few modules + rename to be in sync with router url
8
8
  const Router = require("express-promise-router");
9
9
  const { contract, is } = require("contractis");
10
+ const { X509Certificate } = require("crypto");
10
11
  const db = require("@saltcorn/data/db");
11
12
  const User = require("@saltcorn/data/models/user");
12
13
  const View = require("@saltcorn/data/models/view");
@@ -593,6 +594,12 @@ router.get(
593
594
  const show_warning =
594
595
  !hostname_matches_baseurl(req, getBaseDomain()) &&
595
596
  is_hsts_tld(getBaseDomain());
597
+ let expiry = "";
598
+ if (has_custom) {
599
+ const cert = getState().getConfig("custom_ssl_certificate", "");
600
+ const { validTo } = new X509Certificate(cert);
601
+ expiry = div({ class: "me-2" }, "Expires: ", validTo);
602
+ }
596
603
  send_users_page({
597
604
  res,
598
605
  req,
@@ -674,6 +681,7 @@ router.get(
674
681
  ? span({ class: "badge bg-primary" }, req.__("Enabled"))
675
682
  : span({ class: "badge bg-secondary" }, req.__("Disabled"))
676
683
  ),
684
+ has_custom && expiry,
677
685
  // TBD change to button
678
686
  link(
679
687
  "/useradmin/ssl/custom",
package/locales/en.json CHANGED
@@ -1144,5 +1144,11 @@
1144
1144
  "Table provider": "Table provider",
1145
1145
  "Database table": "Database table",
1146
1146
  "Configure provider": "Configure provider",
1147
- "In scope:": "In scope:"
1147
+ "In scope:": "In scope:",
1148
+ "SSL expiry": "SSL expiry",
1149
+ "A page with this name already exists": "A page with this name already exists",
1150
+ "Tenant Base URL": "Tenant Base URL",
1151
+ "Base hostname for newly created tenants. If unset, defaults to hostname": "Base hostname for newly created tenants. If unset, defaults to hostname",
1152
+ "Redirect unathorized": "Redirect unathorized",
1153
+ "If tenant creation is not authorized, redirect to this URL": "If tenant creation is not authorized, redirect to this URL"
1148
1154
  }
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.8.5-beta.7",
3
+ "version": "0.8.5-rc.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.5-beta.7",
10
- "@saltcorn/builder": "0.8.5-beta.7",
11
- "@saltcorn/data": "0.8.5-beta.7",
12
- "@saltcorn/admin-models": "0.8.5-beta.7",
13
- "@saltcorn/filemanager": "0.8.5-beta.7",
14
- "@saltcorn/markup": "0.8.5-beta.7",
15
- "@saltcorn/sbadmin2": "0.8.5-beta.7",
9
+ "@saltcorn/base-plugin": "0.8.5-rc.1",
10
+ "@saltcorn/builder": "0.8.5-rc.1",
11
+ "@saltcorn/data": "0.8.5-rc.1",
12
+ "@saltcorn/admin-models": "0.8.5-rc.1",
13
+ "@saltcorn/filemanager": "0.8.5-rc.1",
14
+ "@saltcorn/markup": "0.8.5-rc.1",
15
+ "@saltcorn/sbadmin2": "0.8.5-rc.1",
16
16
  "@socket.io/cluster-adapter": "^0.2.1",
17
17
  "@socket.io/sticky": "^1.0.1",
18
18
  "adm-zip": "0.5.10",
@@ -141,6 +141,13 @@ div.settings-panel input[type="number"] {
141
141
  border-width: 1px;
142
142
  }
143
143
 
144
+ div.settings-panel .form-control {
145
+ padding: 0.375rem 0.75rem;
146
+ }
147
+ div.settings-panel .form-select {
148
+ padding: 0.375rem 2.25rem 0.375rem 0.75rem;
149
+ }
150
+
144
151
  button.btnstylesel {
145
152
  padding-left: 5px;
146
153
  padding-right: 2px;
@@ -153,9 +153,15 @@ function apply_showif() {
153
153
  };
154
154
 
155
155
  const cache = e.prop("data-fetch-options-cache") || {};
156
- if (cache[qs]) {
156
+ if (cache[qs] === "fetching") {
157
+ // do nothing, this will be activated by someone else
158
+ } else if (cache[qs]) {
157
159
  activate(cache[qs], qs);
158
- } else
160
+ } else {
161
+ e.prop("data-fetch-options-cache", {
162
+ ...cache,
163
+ [qs]: "fetching",
164
+ });
159
165
  $.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
160
166
  if (resp.success) {
161
167
  activate(resp.success, qs);
@@ -164,8 +170,15 @@ function apply_showif() {
164
170
  ...cacheNow,
165
171
  [qs]: resp.success,
166
172
  });
173
+ } else {
174
+ const cacheNow = e.prop("data-fetch-options-cache") || {};
175
+ e.prop("data-fetch-options-cache", {
176
+ ...cacheNow,
177
+ [qs]: undefined,
178
+ });
167
179
  }
168
180
  });
181
+ }
169
182
  });
170
183
  $("[data-filter-table]").each(function (ix, element) {
171
184
  const e = $(element);
@@ -271,7 +284,13 @@ function get_form_record(e, select_labels) {
271
284
  }
272
285
  function showIfFormulaInputs(e, fml) {
273
286
  const rec = get_form_record(e);
274
- return new Function(`{${Object.keys(rec).join(",")}}`, "return " + fml)(rec);
287
+ try {
288
+ return new Function(`{${Object.keys(rec).join(",")}}`, "return " + fml)(
289
+ rec
290
+ );
291
+ } catch (e) {
292
+ throw new Error(`Error in evaluating showIf formula ${fml}: ${e.message}`);
293
+ }
275
294
  }
276
295
 
277
296
  function rep_del(e) {
package/routes/admin.js CHANGED
@@ -19,6 +19,7 @@ const File = require("@saltcorn/data/models/file");
19
19
  const { spawn } = require("child_process");
20
20
  const User = require("@saltcorn/data/models/user");
21
21
  const path = require("path");
22
+ const { X509Certificate } = require("crypto");
22
23
  const { getAllTenants } = require("@saltcorn/admin-models/models/tenant");
23
24
  const {
24
25
  post_btn,
@@ -768,6 +769,26 @@ router.get(
768
769
  !is_latest && !process.env.SALTCORN_DISABLE_UPGRADE && !git_commit;
769
770
  const dbversion = await db.getVersion(true);
770
771
  const { memUsage, diskUsage, cpuUsage } = await get_sys_info();
772
+ const custom_ssl_certificate = getRootState().getConfig(
773
+ "custom_ssl_certificate",
774
+ false
775
+ );
776
+ let expiry = "";
777
+ if (custom_ssl_certificate) {
778
+ const { validTo } = new X509Certificate(custom_ssl_certificate);
779
+ const diffTime = Math.abs(new Date(validTo) - new Date());
780
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
781
+ expiry = tr(
782
+ th(req.__("SSL expiry")),
783
+ diffDays < 14
784
+ ? td(
785
+ { class: "text-danger fw-bold" },
786
+ moment(new Date(validTo)).fromNow(),
787
+ i({ class: "fas fa-exclamation-triangle ms-1" })
788
+ )
789
+ : td(moment(new Date(validTo)).fromNow())
790
+ );
791
+ }
771
792
  send_admin_page({
772
793
  res,
773
794
  req,
@@ -871,10 +892,13 @@ router.get(
871
892
  td(db.isSQLite ? "SQLite " : "PostgreSQL ", dbversion)
872
893
  ),
873
894
  isRoot
874
- ? tr(th(req.__("Database host")), td(db.connectObj.host))
875
- : "",
876
- isRoot
877
- ? tr(th(req.__("Database port")), td(db.connectObj.port))
895
+ ? tr(
896
+ th(req.__("Database host")),
897
+ td(
898
+ db.connectObj.host +
899
+ (db.connectObj.port ? ":" + db.connectObj.port : "")
900
+ )
901
+ )
878
902
  : "",
879
903
  isRoot
880
904
  ? tr(
@@ -902,7 +926,8 @@ router.get(
902
926
  : td(diskUsage, "%")
903
927
  ),
904
928
  tr(th(req.__("CPU usage")), td(cpuUsage, "%")),
905
- tr(th(req.__("Mem usage")), td(memUsage, "%"))
929
+ tr(th(req.__("Mem usage")), td(memUsage, "%")),
930
+ expiry
906
931
  )
907
932
  ),
908
933
  p(
@@ -55,9 +55,9 @@ module.exports = router;
55
55
  * @param {object} req
56
56
  * @returns {Promise<Form>}
57
57
  */
58
- const pagePropertiesForm = async (req) => {
58
+ const pagePropertiesForm = async (req, isNew) => {
59
59
  const roles = await User.get_roles();
60
-
60
+ const pages = (await Page.find()).map((p) => p.name);
61
61
  const form = new Form({
62
62
  action: addOnDoneRedirect("/pageedit/edit-properties", req),
63
63
  fields: [
@@ -67,6 +67,8 @@ const pagePropertiesForm = async (req) => {
67
67
  required: true,
68
68
  validator(s) {
69
69
  if (s.length < 1) return req.__("Missing name");
70
+ if (pages.includes(s) && isNew)
71
+ return req.__("A page with this name already exists");
70
72
  },
71
73
  sublabel: req.__("A short name that will be in your URL"),
72
74
  type: "String",
@@ -331,7 +333,7 @@ router.get(
331
333
  "/new",
332
334
  isAdmin,
333
335
  error_catcher(async (req, res) => {
334
- const form = await pagePropertiesForm(req);
336
+ const form = await pagePropertiesForm(req, true);
335
337
  res.sendWrap(
336
338
  req.__(`Page attributes`),
337
339
  wrap(renderForm(form, req.csrfToken()), false, req)
@@ -349,7 +351,7 @@ router.post(
349
351
  "/edit-properties",
350
352
  isAdmin,
351
353
  error_catcher(async (req, res) => {
352
- const form = await pagePropertiesForm(req);
354
+ const form = await pagePropertiesForm(req, !req.body.id);
353
355
  form.hidden("id");
354
356
  form.validate(req.body);
355
357
  if (form.hasErrors) {
package/routes/tenant.js CHANGED
@@ -7,7 +7,11 @@
7
7
 
8
8
  const Router = require("express-promise-router");
9
9
  const Form = require("@saltcorn/data/models/form");
10
- const { getState, add_tenant } = require("@saltcorn/data/db/state");
10
+ const {
11
+ getState,
12
+ add_tenant,
13
+ getRootState,
14
+ } = require("@saltcorn/data/db/state");
11
15
  const {
12
16
  create_tenant,
13
17
  getAllTenants,
@@ -65,6 +69,9 @@ const { getConfig } = require("@saltcorn/data/models/config");
65
69
  const router = new Router();
66
70
  module.exports = router;
67
71
 
72
+ const remove_leading_chars = (cs, s) =>
73
+ s.startsWith(cs) ? remove_leading_chars(cs, s.substring(cs.length)) : s;
74
+
68
75
  /**
69
76
  * Declare Form to create Tenant
70
77
  * @param {object} req - Request
@@ -72,7 +79,8 @@ module.exports = router;
72
79
  * @category server
73
80
  */
74
81
  // TBD add form field email for tenant admin
75
- const tenant_form = (req) =>
82
+
83
+ const tenant_form = (req, base_url) =>
76
84
  new Form({
77
85
  action: "/tenant/create",
78
86
  submitLabel: req.__("Create"),
@@ -85,7 +93,7 @@ const tenant_form = (req) =>
85
93
  name: "subdomain",
86
94
  label: req.__("Application name"),
87
95
  input_type: "text",
88
- postText: text(req.hostname),
96
+ postText: text("." + base_url),
89
97
  },
90
98
  ],
91
99
  });
@@ -100,7 +108,8 @@ const tenant_form = (req) =>
100
108
  */
101
109
  // TBD To allow few roles to create tenants - currently only one role has such rights simultaneously
102
110
  const create_tenant_allowed = (req) => {
103
- const required_role = +getState().getConfig("role_to_create_tenant") || 10;
111
+ const required_role =
112
+ +getRootState().getConfig("role_to_create_tenant") || 10;
104
113
  const user_role = req.user ? req.user.role_id : 10;
105
114
  return user_role <= required_role;
106
115
  };
@@ -117,6 +126,13 @@ const is_ip_address = (hostname) => {
117
126
  return hostname.split(".").every((s) => +s >= 0 && +s <= 255);
118
127
  };
119
128
 
129
+ const get_cfg_tenant_base_url = (req) =>
130
+ remove_leading_chars(
131
+ ".",
132
+ getRootState().getConfig("tenant_baseurl", req.hostname)
133
+ )
134
+ .replace("http://", "")
135
+ .replace("https://", "");
120
136
  /**
121
137
  * Create tenant screen runnning
122
138
  * @name get/create
@@ -126,10 +142,7 @@ const is_ip_address = (hostname) => {
126
142
  router.get(
127
143
  "/create",
128
144
  error_catcher(async (req, res) => {
129
- if (
130
- !db.is_it_multi_tenant() ||
131
- db.getTenantSchema() !== db.connectObj.default_schema
132
- ) {
145
+ if (!db.is_it_multi_tenant()) {
133
146
  res.sendWrap(
134
147
  req.__("Create application"),
135
148
  req.__("Multi-tenancy not enabled")
@@ -137,7 +150,9 @@ router.get(
137
150
  return;
138
151
  }
139
152
  if (!create_tenant_allowed(req)) {
140
- res.sendWrap(req.__("Create application"), req.__("Not allowed"));
153
+ const redir = getState().getConfig("tenant_create_unauth_redirect");
154
+ if (redir) res.redirect(redir);
155
+ else res.sendWrap(req.__("Create application"), req.__("Not allowed"));
141
156
  return;
142
157
  }
143
158
 
@@ -149,6 +164,7 @@ router.get(
149
164
  )
150
165
  );
151
166
  let create_tenant_warning_text = "";
167
+ const base_url = get_cfg_tenant_base_url(req);
152
168
  if (getState().getConfig("create_tenant_warning")) {
153
169
  create_tenant_warning_text = getState().getConfig(
154
170
  "create_tenant_warning_text"
@@ -192,13 +208,13 @@ router.get(
192
208
  res.sendWrap(
193
209
  req.__("Create application"),
194
210
  create_tenant_warning_text +
195
- renderForm(tenant_form(req), req.csrfToken()) +
211
+ renderForm(tenant_form(req, base_url), req.csrfToken()) +
196
212
  p(
197
213
  { class: "mt-2" },
198
214
  req.__("To login to a previously created application, go to: "),
199
215
  code(`${req.protocol}://`) +
200
216
  i(req.__("Application name")) +
201
- code("." + req.hostname)
217
+ code("." + base_url)
202
218
  )
203
219
  );
204
220
  })
@@ -209,14 +225,14 @@ router.get(
209
225
  * @param {string} subdomain - Tenant Subdomain name string
210
226
  * @returns {string}
211
227
  */
212
- const getNewURL = (req, subdomain) => {
228
+ const getNewURL = (req, subdomain, base_url) => {
213
229
  var ports = "";
214
230
  const host = req.get("host");
215
231
  if (typeof host === "string") {
216
232
  const hosts = host.split(":");
217
233
  if (hosts.length > 1) ports = `:${hosts[1]}`;
218
234
  }
219
- const hostname = req.hostname;
235
+ const hostname = base_url || req.hostname;
220
236
  // return newurl
221
237
  return `${req.protocol}://${subdomain}.${hostname}${ports}/`;
222
238
  };
@@ -231,10 +247,7 @@ router.post(
231
247
  "/create",
232
248
  error_catcher(async (req, res) => {
233
249
  // check that multi-tenancy is enabled
234
- if (
235
- !db.is_it_multi_tenant() ||
236
- db.getTenantSchema() !== db.connectObj.default_schema
237
- ) {
250
+ if (!db.is_it_multi_tenant()) {
238
251
  res.sendWrap(
239
252
  req.__("Create application"),
240
253
  req.__("Multi-tenancy not enabled")
@@ -274,7 +287,8 @@ router.post(
274
287
  );
275
288
  } else {
276
289
  // tenant url
277
- const newurl = getNewURL(req, subdomain);
290
+ const base_url = get_cfg_tenant_base_url(req);
291
+ const newurl = getNewURL(req, subdomain, base_url);
278
292
  // tenant template
279
293
  const tenant_template = getState().getConfig("tenant_template");
280
294
  // tenant creator
@@ -426,6 +440,8 @@ const tenant_settings_form = (req) =>
426
440
  "create_tenant_warning",
427
441
  "create_tenant_warning_text",
428
442
  "tenant_template",
443
+ "tenant_baseurl",
444
+ "tenant_create_unauth_redirect",
429
445
  { section_header: "Tenant application capabilities" },
430
446
  "tenants_install_git",
431
447
  "tenants_set_npm_modules",
package/serve.js CHANGED
@@ -195,6 +195,10 @@ module.exports =
195
195
  dev,
196
196
  ...appargs
197
197
  } = {}) => {
198
+ process.on("unhandledRejection", (reason, p) => {
199
+ console.error(reason, "Unhandled Rejection at Promise");
200
+ });
201
+
198
202
  if (dev && cluster.isMaster) {
199
203
  listenForChanges(getRelevantPackages(), await getPluginDirectories());
200
204
  }
package/wrapper.js CHANGED
@@ -146,11 +146,15 @@ const get_menu = (req) => {
146
146
  section: req.__("Admin"),
147
147
  items: adminItems,
148
148
  },
149
- {
150
- section: req.__("User"),
151
- isUser: true,
152
- items: authItems,
153
- },
149
+ ...(authItems.length
150
+ ? [
151
+ {
152
+ section: req.__("User"),
153
+ isUser: true,
154
+ items: authItems,
155
+ },
156
+ ]
157
+ : []),
154
158
  ].filter((s) => s);
155
159
  };
156
160
  /**