@saltcorn/server 0.7.4 → 0.8.0-beta.0

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/app.js CHANGED
@@ -122,12 +122,10 @@ const getApp = async (opts = {}) => {
122
122
  app.use(passport.initialize());
123
123
  app.use(passport.authenticate(["jwt", "session"]));
124
124
  app.use((req, res, next) => {
125
- if (jwt_extractor(req) && req.cookies && req.cookies["connect.sid"])
126
- throw new Error(
127
- "Don't set a session cookie and JSON Web Token at the same time."
128
- );
129
- next();
130
- });
125
+ // no jwt and session id at the same time
126
+ if (!(jwt_extractor(req) && req.cookies && req.cookies["connect.sid"]))
127
+ next();
128
+ });
131
129
  app.use(flash());
132
130
 
133
131
  //static serving
@@ -170,6 +168,15 @@ const getApp = async (opts = {}) => {
170
168
  }
171
169
  )
172
170
  );
171
+ app.use(
172
+ `/static_assets/${version_tag}`,
173
+ express.static(
174
+ path.dirname(require.resolve("@saltcorn/filemanager/package.json")) + "/public/build",
175
+ {
176
+ maxAge: development_mode ? 0 : "100d",
177
+ }
178
+ )
179
+ );
173
180
 
174
181
  passport.use(
175
182
  "local",
@@ -248,7 +255,7 @@ const getApp = async (opts = {}) => {
248
255
  };
249
256
  if (
250
257
  db.is_it_multi_tenant() &&
251
- jwt_payload.tenant?.length > 0 &&
258
+ jwt_payload.tenant?.length > 0 &&
252
259
  jwt_payload.tenant !== db.connectObj.default_schema
253
260
  ) {
254
261
  return await db.runWithTenant(jwt_payload.tenant, userCheck);
@@ -341,13 +348,13 @@ Sitemap: ${base}sitemap.xml
341
348
  <urlset
342
349
  xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
343
350
  ${urls
344
- .map(
345
- (url) => `<url>
351
+ .map(
352
+ (url) => `<url>
346
353
  <loc>${url}</loc>
347
354
  <lastmod>${now}</lastmod>
348
355
  </url>`
349
- )
350
- .join("")}
356
+ )
357
+ .join("")}
351
358
 
352
359
  </urlset>`);
353
360
  })
package/auth/admin.js CHANGED
@@ -38,7 +38,9 @@ const {
38
38
  is_hsts_tld,
39
39
  } = require("../markup/admin");
40
40
  const { send_verification_email } = require("@saltcorn/data/models/email");
41
-
41
+ const {
42
+ expressionValidator,
43
+ } = require("@saltcorn/data/models/expression");
42
44
  /**
43
45
  * @type {object}
44
46
  * @const
@@ -177,33 +179,33 @@ const user_dropdown = (user, req, can_reset) =>
177
179
  req
178
180
  ),
179
181
  can_reset &&
180
- post_dropdown_item(
181
- `/useradmin/reset-password/${user.id}`,
182
- '<i class="fas fa-envelope"></i>&nbsp;' +
183
- req.__("Send password reset email"),
184
- req
185
- ),
182
+ post_dropdown_item(
183
+ `/useradmin/reset-password/${user.id}`,
184
+ '<i class="fas fa-envelope"></i>&nbsp;' +
185
+ req.__("Send password reset email"),
186
+ req
187
+ ),
186
188
  can_reset &&
187
- !user.verified_on &&
188
- getState().getConfig("verification_view", "") &&
189
- post_dropdown_item(
190
- `/useradmin/send-verification/${user.id}`,
191
- '<i class="fas fa-envelope"></i>&nbsp;' +
192
- req.__("Send verification email"),
193
- req
194
- ),
189
+ !user.verified_on &&
190
+ getState().getConfig("verification_view", "") &&
191
+ post_dropdown_item(
192
+ `/useradmin/send-verification/${user.id}`,
193
+ '<i class="fas fa-envelope"></i>&nbsp;' +
194
+ req.__("Send verification email"),
195
+ req
196
+ ),
195
197
  user.disabled &&
196
- post_dropdown_item(
197
- `/useradmin/enable/${user.id}`,
198
- '<i class="fas fa-play"></i>&nbsp;' + req.__("Enable"),
199
- req
200
- ),
198
+ post_dropdown_item(
199
+ `/useradmin/enable/${user.id}`,
200
+ '<i class="fas fa-play"></i>&nbsp;' + req.__("Enable"),
201
+ req
202
+ ),
201
203
  !user.disabled &&
202
- post_dropdown_item(
203
- `/useradmin/disable/${user.id}`,
204
- '<i class="fas fa-pause"></i>&nbsp;' + req.__("Disable"),
205
- req
206
- ),
204
+ post_dropdown_item(
205
+ `/useradmin/disable/${user.id}`,
206
+ '<i class="fas fa-pause"></i>&nbsp;' + req.__("Disable"),
207
+ req
208
+ ),
207
209
  div({ class: "dropdown-divider" }),
208
210
  post_dropdown_item(
209
211
  `/useradmin/delete/${user.id}`,
@@ -257,8 +259,8 @@ router.get(
257
259
  key: (r) =>
258
260
  !!r.verified_on
259
261
  ? i({
260
- class: "fas fa-check-circle text-success",
261
- })
262
+ class: "fas fa-check-circle text-success",
263
+ })
262
264
  : "",
263
265
  },
264
266
  { label: req.__("Role"), key: (r) => roleMap[r.role_id] },
@@ -421,15 +423,15 @@ router.get(
421
423
  above: [
422
424
  ...(letsencrypt && has_custom
423
425
  ? [
424
- {
425
- type: "card",
426
- contents: p(
427
- req.__(
428
- "You have enabled both Let's Encrypt certificates and custom SSL certificates. Let's Encrypt takes priority and the custom certificates will be ignored."
429
- )
430
- ),
431
- },
432
- ]
426
+ {
427
+ type: "card",
428
+ contents: p(
429
+ req.__(
430
+ "You have enabled both Let's Encrypt certificates and custom SSL certificates. Let's Encrypt takes priority and the custom certificates will be ignored."
431
+ )
432
+ ),
433
+ },
434
+ ]
433
435
  : []),
434
436
  {
435
437
  type: "card",
@@ -450,33 +452,33 @@ router.get(
450
452
  ),
451
453
  letsencrypt
452
454
  ? post_btn(
453
- "/config/delete/letsencrypt",
454
- req.__("Disable LetsEncrypt HTTPS"),
455
- req.csrfToken(),
456
- { btnClass: "btn-danger", req }
457
- )
455
+ "/config/delete/letsencrypt",
456
+ req.__("Disable LetsEncrypt HTTPS"),
457
+ req.csrfToken(),
458
+ { btnClass: "btn-danger", req }
459
+ )
458
460
  : post_btn(
459
- "/admin/enable-letsencrypt",
460
- req.__("Enable LetsEncrypt HTTPS"),
461
- req.csrfToken(),
462
- { confirm: true, req }
463
- ),
461
+ "/admin/enable-letsencrypt",
462
+ req.__("Enable LetsEncrypt HTTPS"),
463
+ req.csrfToken(),
464
+ { confirm: true, req }
465
+ ),
464
466
  !letsencrypt &&
465
- show_warning &&
466
- !has_custom &&
467
- div(
468
- { class: "mt-3 alert alert-danger" },
469
- p(
470
- req.__(
471
- "The address you are using to reach Saltcorn does not match the Base URL."
472
- )
473
- ),
474
- p(
475
- req.__(
476
- "The DNS A records (for * and @, or a subdomain) should point to this server's IP address before enabling LetsEncrypt"
477
- )
467
+ show_warning &&
468
+ !has_custom &&
469
+ div(
470
+ { class: "mt-3 alert alert-danger" },
471
+ p(
472
+ req.__(
473
+ "The address you are using to reach Saltcorn does not match the Base URL."
478
474
  )
479
475
  ),
476
+ p(
477
+ req.__(
478
+ "The DNS A records (for * and @, or a subdomain) should point to this server's IP address before enabling LetsEncrypt"
479
+ )
480
+ )
481
+ ),
480
482
  ],
481
483
  },
482
484
  {
@@ -570,8 +572,8 @@ router.post(
570
572
  req.flash(
571
573
  "success",
572
574
  req.__("Custom SSL enabled. Restart for changes to take effect.") +
573
- " " +
574
- a({ href: "/admin/system" }, req.__("Restart here"))
575
+ " " +
576
+ a({ href: "/admin/system" }, req.__("Restart here"))
575
577
  );
576
578
  if (!req.xhr) {
577
579
  res.redirect("/useradmin/ssl");
@@ -580,6 +582,103 @@ router.post(
580
582
  })
581
583
  );
582
584
 
585
+ /**
586
+ * @name get/ssl/custom
587
+ * @function
588
+ * @memberof module:auth/admin~auth/adminRouter
589
+ */
590
+ router.get(
591
+ "/table-access",
592
+ isAdmin,
593
+ error_catcher(async (req, res) => {
594
+ const tables = await Table.find()
595
+ const roleOptions = (await User.get_roles()).map((r) => ({
596
+ value: r.id,
597
+ label: r.role,
598
+ }));
599
+
600
+ const contents = []
601
+ for (const table of tables) {
602
+ if (table.external) continue
603
+ const fields = await table.getFields();
604
+ const userFields = fields
605
+ .filter((f) => f.reftable_name === "users")
606
+ .map((f) => ({ value: f.id, label: f.name }));
607
+ const form = new Form({
608
+ action: "/table",
609
+ noSubmitButton: true,
610
+ onChange: "saveAndContinue(this)",
611
+ fields: [
612
+ {
613
+ label: req.__("Ownership field"),
614
+ name: "ownership_field_id",
615
+ sublabel: req.__(
616
+ "The user referred to in this field will be the owner of the row"
617
+ ),
618
+ input_type: "select",
619
+ options: [
620
+ { value: "", label: req.__("None") },
621
+ ...userFields,
622
+ { value: "_formula", label: req.__("Formula") },
623
+ ],
624
+ },
625
+ {
626
+ name: "ownership_formula",
627
+ label: req.__("Ownership formula"),
628
+ validator: expressionValidator,
629
+ type: "String",
630
+ class: "validate-expression",
631
+ sublabel:
632
+ req.__("User is treated as owner if true. In scope: ") +
633
+ ["user", ...fields.map((f) => f.name)]
634
+ .map((fn) => code(fn))
635
+ .join(", "),
636
+ showIf: { ownership_field_id: "_formula" },
637
+ },
638
+ {
639
+ label: req.__("Minimum role to read"),
640
+ sublabel: req.__(
641
+ "User must have this role or higher to read rows from the table, unless they are the owner"
642
+ ),
643
+ name: "min_role_read",
644
+ input_type: "select",
645
+ options: roleOptions,
646
+ attributes: { asideNext: true }
647
+ },
648
+ {
649
+ label: req.__("Minimum role to write"),
650
+ name: "min_role_write",
651
+ input_type: "select",
652
+ sublabel: req.__(
653
+ "User must have this role or higher to edit or create new rows in the table, unless they are the owner"
654
+ ),
655
+ options: roleOptions,
656
+ },
657
+ ]
658
+ })
659
+ form.hidden("id", "name");
660
+ form.values = table
661
+ if (table.ownership_formula && !table.ownership_field_id)
662
+ form.values.ownership_field_id = "_formula";
663
+ contents.push(div(
664
+ h5(a({ href: `/table/${table.id}` }, table.name)),
665
+ renderForm(form, req.csrfToken())
666
+ ))
667
+ }
668
+ send_users_page({
669
+ res,
670
+ req,
671
+ active_sub: "Table access",
672
+ contents: {
673
+ type: "card",
674
+ title: req.__("Table access"),
675
+ contents
676
+ },
677
+ });
678
+ })
679
+ );
680
+
681
+
583
682
  /**
584
683
  * @name get/:id
585
684
  * @function
@@ -613,9 +712,9 @@ router.get(
613
712
  div(
614
713
  user.api_token
615
714
  ? span(
616
- { class: "me-1" },
617
- req.__("API token for this user: ")
618
- ) + code(user.api_token)
715
+ { class: "me-1" },
716
+ req.__("API token for this user: ")
717
+ ) + code(user.api_token)
619
718
  : req.__("No API token issued")
620
719
  ),
621
720
  // button for reset or generate api token
@@ -629,16 +728,16 @@ router.get(
629
728
  ),
630
729
  // button for remove api token
631
730
  user.api_token &&
632
- div(
633
- { class: "mt-4 ms-2 d-inline-block" },
634
- post_btn(
635
- `/useradmin/remove-api-token/${user.id}`,
636
- // TBD localization
637
- user.api_token ? req.__("Remove") : req.__("Generate"),
638
- req.csrfToken(),
639
- { req: req, confirm: true }
640
- )
641
- ),
731
+ div(
732
+ { class: "mt-4 ms-2 d-inline-block" },
733
+ post_btn(
734
+ `/useradmin/remove-api-token/${user.id}`,
735
+ // TBD localization
736
+ user.api_token ? req.__("Remove") : req.__("Generate"),
737
+ req.csrfToken(),
738
+ { req: req, confirm: true }
739
+ )
740
+ ),
642
741
  ],
643
742
  },
644
743
  ],
package/auth/routes.js CHANGED
@@ -201,32 +201,57 @@ const getAuthLinks = (current, noMethods) => {
201
201
 
202
202
  const loginWithJwt = async (email, password, saltcornApp, res) => {
203
203
  const loginFn = async () => {
204
- const user = await User.findOne({ email });
205
- if (user && user.checkPassword(password)) {
206
- const now = new Date();
207
- const jwt_secret = db.connectObj.jwt_secret;
204
+ const publicUserLink = getState().getConfig("public_user_link");
205
+ const jwt_secret = db.connectObj.jwt_secret;
206
+ if (email && password) {
207
+ // with credentials
208
+ const user = await User.findOne({ email });
209
+ if (user && user.checkPassword(password)) {
210
+ const now = new Date();
211
+ const token = jwt.sign(
212
+ {
213
+ sub: email,
214
+ user: {
215
+ id: user.id,
216
+ email: user.email,
217
+ role_id: user.role_id,
218
+ language: user.language ? user.language : "en",
219
+ disabled: user.disabled,
220
+ },
221
+ iss: "saltcorn@saltcorn",
222
+ aud: "saltcorn-mobile-app",
223
+ iat: now.valueOf(),
224
+ tenant: db.getTenantSchema(),
225
+ },
226
+ jwt_secret
227
+ );
228
+ if (!user.last_mobile_login) await user.updateLastMobileLogin(now);
229
+ res.json(token);
230
+ } else {
231
+ res.json({
232
+ alerts: [{ type: "danger", msg: "Incorrect user or password" }],
233
+ });
234
+ }
235
+ } else if (publicUserLink) {
236
+ // public login
208
237
  const token = jwt.sign(
209
238
  {
210
- sub: email,
239
+ sub: "public",
211
240
  user: {
212
- id: user.id,
213
- email: user.email,
214
- role_id: user.role_id,
215
- language: user.language ? user.language : "en",
216
- disabled: user.disabled,
241
+ role_id: 10,
242
+ language: "en",
217
243
  },
218
244
  iss: "saltcorn@saltcorn",
219
245
  aud: "saltcorn-mobile-app",
220
- iat: now.valueOf(),
246
+ iat: new Date().valueOf(),
221
247
  tenant: db.getTenantSchema(),
222
248
  },
223
249
  jwt_secret
224
250
  );
225
- if (!user.last_mobile_login) await user.updateLastMobileLogin(now);
226
251
  res.json(token);
227
252
  } else {
228
253
  res.json({
229
- alerts: [{ type: "danger", msg: "Incorrect user or password" }],
254
+ alerts: [{ type: "danger", msg: "The public login is deactivated" }],
230
255
  });
231
256
  }
232
257
  };
@@ -1158,7 +1183,7 @@ const setLanguageForm = (req, user) =>
1158
1183
  option(
1159
1184
  {
1160
1185
  value: locale,
1161
- ...(user && user.language === locale && { selected: true }),
1186
+ ...(((user && user.language === locale) || (user && !user.language && req.getLocale() === locale)) && { selected: true }),
1162
1187
  },
1163
1188
  language
1164
1189
  )
@@ -1382,7 +1407,7 @@ router.get(
1382
1407
  return;
1383
1408
  }
1384
1409
  res.sendWrap(
1385
- req.__("User settings"),
1410
+ req.__("User settings") || "User settings",
1386
1411
  await userSettings({ req, res, pwform: changPwForm(req), user })
1387
1412
  );
1388
1413
  })
package/locales/en.json CHANGED
@@ -991,6 +991,49 @@
991
991
  "Add triggers": "Add triggers",
992
992
  "Formula value": "Formula value",
993
993
  "The build was successfully": "The build was successfully",
994
- "Unable to build the app:": "Unable to build the app:",
995
- "Add tag": "Add tag"
996
- }
994
+ "Unable to build the app": "Unable to build the app",
995
+ "Add tag": "Add tag",
996
+ "Create new row": "Create new row",
997
+ "Specify how to create a new row": "Specify how to create a new row",
998
+ "Preview": "Preview",
999
+ "Minimum role updated": "Minimum role updated",
1000
+ "Module not found": "Module not found",
1001
+ "View %s not found": "View %s not found",
1002
+ "Query %s not found": "Query %s not found",
1003
+ "Open": "Open",
1004
+ "Only the android build supports docker.": "Only the android build supports docker.",
1005
+ "Please enter a valid server URL.": "Please enter a valid server URL.",
1006
+ "Table access": "Table access",
1007
+ "Download one of the backups above": "Download one of the backups above",
1008
+ "Clear this application": "Clear this application",
1009
+ "(tick all boxes)": "(tick all boxes)",
1010
+ "When prompted to create the first user, click the link to restore a backup": "When prompted to create the first user, click the link to restore a backup",
1011
+ "Select the downloaded backup file": "Select the downloaded backup file",
1012
+ "Units": "Units",
1013
+ "Descending?": "Descending?",
1014
+ "Small": "Small",
1015
+ "Medium": "Medium",
1016
+ "Large": "Large",
1017
+ "Extra-large": "Extra-large",
1018
+ "Please select at least one item": "Please select at least one item",
1019
+ "Only include rows where this formula is true. ": "Only include rows where this formula is true. ",
1020
+ "Use %s to access current user ID": "Use %s to access current user ID",
1021
+ "Action not found": "Action not found",
1022
+ "Development settings": "Development settings",
1023
+ "All entities": "All entities",
1024
+ "no tags": "no tags",
1025
+ "Development mode settings updated": "Development mode settings updated",
1026
+ "Locale identifier short code, e.g. en, zh, fr, ar etc. ": "Locale identifier short code, e.g. en, zh, fr, ar etc. ",
1027
+ "Is this the default language in which the application is built?": "Is this the default language in which the application is built?",
1028
+ "Database type": "Database type",
1029
+ "Database schema name": "Database schema name",
1030
+ "Database user": "Database user",
1031
+ "Database host": "Database host",
1032
+ "Database port": "Database port",
1033
+ "Creator email": "Creator email",
1034
+ "Create tenant warning text": "Create tenant warning text",
1035
+ "Provide your own create warning text if need": "Provide your own create warning text if need",
1036
+ "Specify some description for tenant if need": "Specify some description for tenant if need",
1037
+ "Created": "Created",
1038
+ "First user E-mail": "First user E-mail"
1039
+ }