@saltcorn/server 0.7.1-beta.3 → 0.7.2-beta.2

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
@@ -30,7 +30,6 @@ const {
30
30
  } = require("./routes/utils.js");
31
31
  const { getAllTenants } = require("@saltcorn/admin-models/models/tenant");
32
32
  const path = require("path");
33
- const fileUpload = require("express-fileupload");
34
33
  const helmet = require("helmet");
35
34
  const wrapper = require("./wrapper");
36
35
  const csrf = require("csurf");
@@ -40,13 +39,31 @@ const is = require("contractis/is");
40
39
  const Trigger = require("@saltcorn/data/models/trigger");
41
40
  const s3storage = require("./s3storage");
42
41
  const TotpStrategy = require("passport-totp").Strategy;
42
+ const JwtStrategy = require("passport-jwt").Strategy;
43
+ const ExtractJwt = require("passport-jwt").ExtractJwt;
44
+ const cors = require("cors");
45
+
43
46
  const locales = Object.keys(available_languages);
44
47
  // i18n configuration
45
48
  const i18n = new I18n({
46
49
  locales,
47
50
  directory: path.join(__dirname, "locales"),
48
51
  });
49
- // todo console.log app instance info when app starts - avoid to show secrets (password, etc)
52
+ // jwt config
53
+ const jwt_secret = db.connectObj.jwt_secret;
54
+ const jwtOpts = {
55
+ jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme("jwt"),
56
+ secretOrKey: jwt_secret,
57
+ issuer: "saltcorn@saltcorn",
58
+ audience: "saltcorn-mobile-app",
59
+ };
60
+
61
+ const disabledCsurf = (req, res, next) => {
62
+ req.csrfToken = () => "";
63
+ next();
64
+ };
65
+
66
+ // todo console.log app instance info when app stxarts - avoid to show secrets (password, etc)
50
67
 
51
68
  /**
52
69
  * @param {object} [opts = {}]
@@ -68,6 +85,8 @@ const getApp = async (opts = {}) => {
68
85
  // https://www.npmjs.com/package/helmet
69
86
  // helmet is secure app by adding HTTP headers
70
87
  app.use(helmet());
88
+ // TODO ch find a better solution
89
+ app.use(cors());
71
90
  app.use(
72
91
  express.json({
73
92
  limit: "5mb",
@@ -93,7 +112,18 @@ const getApp = async (opts = {}) => {
93
112
  app.use(getSessionStore());
94
113
 
95
114
  app.use(passport.initialize());
96
- app.use(passport.session());
115
+ app.use(passport.authenticate(["jwt", "session"]));
116
+ app.use((req, res, next) => {
117
+ if (
118
+ ExtractJwt.fromAuthHeaderWithScheme("jwt")(req) &&
119
+ req.cookies &&
120
+ req.cookies["connect.sid"]
121
+ )
122
+ throw new Error(
123
+ "Don't set a session cookie and JSON Web Token at the same time."
124
+ );
125
+ next();
126
+ });
97
127
  app.use(flash());
98
128
 
99
129
  //static serving
@@ -192,6 +222,23 @@ const getApp = async (opts = {}) => {
192
222
  }
193
223
  })
194
224
  );
225
+ passport.use(
226
+ new JwtStrategy(jwtOpts, (jwt_payload, done) => {
227
+ User.findOne({ email: jwt_payload.sub }).then((u) => {
228
+ if (u) {
229
+ return done(null, {
230
+ email: u.email,
231
+ id: u.id,
232
+ role_id: u.role_id,
233
+ language: u.language,
234
+ tenant: db.getTenantSchema(),
235
+ });
236
+ } else {
237
+ return done(null, { role_id: 10 });
238
+ }
239
+ });
240
+ })
241
+ );
195
242
  passport.use(
196
243
  new TotpStrategy(function (user, done) {
197
244
  // setup function, supply key and period to done callback
@@ -217,14 +264,22 @@ const getApp = async (opts = {}) => {
217
264
  const csurf = csrf();
218
265
  if (!opts.disableCsrf)
219
266
  app.use(function (req, res, next) {
220
- if (req.url.startsWith("/api/")) return next();
267
+ if (
268
+ req.url.startsWith("/api/") ||
269
+ req.url === "/auth/login-with/jwt" ||
270
+ ExtractJwt.fromAuthHeaderWithScheme("jwt")(req)
271
+ )
272
+ return disabledCsurf(req, res, next);
221
273
  csurf(req, res, next);
222
274
  });
223
- else
224
- app.use((req, res, next) => {
225
- req.csrfToken = () => "";
226
- next();
227
- });
275
+ else app.use(disabledCsurf);
276
+
277
+ app.use(function (req, res, next) {
278
+ if (req.headers["x-saltcorn-client"] === "mobile-app") {
279
+ req.smr = true; // saltcorn-mobile-request
280
+ }
281
+ return next();
282
+ });
228
283
 
229
284
  mountRoutes(app);
230
285
  // set tenant homepage as / root
package/auth/routes.js CHANGED
@@ -61,6 +61,8 @@ const fs = require("fs");
61
61
  const base32 = require("thirty-two");
62
62
  const qrcode = require("qrcode");
63
63
  const totp = require("notp").totp;
64
+ const jwt = require("jsonwebtoken");
65
+
64
66
  /**
65
67
  * @type {object}
66
68
  * @const
@@ -197,6 +199,24 @@ const getAuthLinks = (current, noMethods) => {
197
199
  return links;
198
200
  };
199
201
 
202
+ const loginWithJwt = async (req, res) => {
203
+ const { email, password } = req.query;
204
+ const user = await User.findOne({ email });
205
+ if (user && user.checkPassword(password)) {
206
+ const jwt_secret = db.connectObj.jwt_secret;
207
+ const token = jwt.sign(
208
+ {
209
+ sub: email,
210
+ role_id: user.role_id,
211
+ iss: "saltcorn@saltcorn",
212
+ aud: "saltcorn-mobile-app",
213
+ },
214
+ jwt_secret
215
+ );
216
+ res.json(token);
217
+ }
218
+ };
219
+
200
220
  /**
201
221
  * @name get/login
202
222
  * @function
@@ -973,15 +993,19 @@ router.get(
973
993
  "/login-with/:method",
974
994
  error_catcher(async (req, res, next) => {
975
995
  const { method } = req.params;
976
- const auth = getState().auth_methods[method];
977
- if (auth) {
978
- passport.authenticate(method, auth.parameters)(req, res, next);
996
+ if (method === "jwt") {
997
+ await loginWithJwt(req, res);
979
998
  } else {
980
- req.flash(
981
- "danger",
982
- req.__("Unknown authentication method %s", text(method))
983
- );
984
- res.redirect("/");
999
+ const auth = getState().auth_methods[method];
1000
+ if (auth) {
1001
+ passport.authenticate(method, auth.parameters)(req, res, next);
1002
+ } else {
1003
+ req.flash(
1004
+ "danger",
1005
+ req.__("Unknown authentication method %s", text(method))
1006
+ );
1007
+ res.redirect("/");
1008
+ }
985
1009
  }
986
1010
  })
987
1011
  );
@@ -1014,7 +1038,7 @@ router.post(
1014
1038
  );
1015
1039
 
1016
1040
  /**
1017
- * @param {object}} req
1041
+ * @param {object} req
1018
1042
  * @param {object} res
1019
1043
  * @returns {void}
1020
1044
  */
@@ -1212,14 +1236,14 @@ const userSettings = async ({ req, res, pwform, user }) => {
1212
1236
  href: "/auth/twofa/disable/totp",
1213
1237
  class: "btn btn-danger mt-2",
1214
1238
  },
1215
- "Disable"
1239
+ req.__("Disable TWA")
1216
1240
  )
1217
1241
  : a(
1218
1242
  {
1219
1243
  href: "/auth/twofa/setup/totp",
1220
1244
  class: "btn btn-primary mt-2",
1221
1245
  },
1222
- "Enable"
1246
+ req.__("Enable TWA")
1223
1247
  )
1224
1248
  ),
1225
1249
  ],
@@ -1541,7 +1565,7 @@ router.get(
1541
1565
  contents: [
1542
1566
  h4(req.__("1. Scan this QR code in your Authenticator app")),
1543
1567
  img({ src: image }),
1544
- p("Or enter this code:"),
1568
+ p(req.__("Or enter this code:")),
1545
1569
  code(pre(encodedKey.toString())),
1546
1570
  h4(
1547
1571
  req.__(
@@ -1669,7 +1693,7 @@ const randomKey = function (len) {
1669
1693
  chars = "abcdefghijklmnopqrstuvwxyz0123456789",
1670
1694
  charlen = chars.length;
1671
1695
 
1672
- for (var i = 0; i < len; ++i) {
1696
+ for (let i = 0; i < len; ++i) {
1673
1697
  buf.push(chars[getRandomInt(0, charlen - 1)]);
1674
1698
  }
1675
1699
 
package/load_plugins.js CHANGED
@@ -13,35 +13,39 @@ const fs = require("fs");
13
13
  const proc = require("child_process");
14
14
  const tmp = require("tmp-promise");
15
15
 
16
+ const staticDependencies = {
17
+ "@saltcorn/markup": require("@saltcorn/markup"),
18
+ "@saltcorn/markup/tags": require("@saltcorn/markup/tags"),
19
+ "@saltcorn/markup/layout": require("@saltcorn/markup/layout"),
20
+ "@saltcorn/markup/helpers": require("@saltcorn/markup/helpers"),
21
+ "@saltcorn/markup/layout_utils": require("@saltcorn/markup/layout_utils"),
22
+ "@saltcorn/data": require("@saltcorn/data"),
23
+ "@saltcorn/data/db": require("@saltcorn/data/db"),
24
+ "@saltcorn/data/utils": require("@saltcorn/data/utils"),
25
+ "@saltcorn/data/db/state": require("@saltcorn/data/db/state"),
26
+ "@saltcorn/data/plugin-helper": require("@saltcorn/data/plugin-helper"),
27
+ "@saltcorn/data/plugin-testing": require("@saltcorn/data/plugin-testing"),
28
+ "@saltcorn/data/models/field": require("@saltcorn/data/models/field"),
29
+ "@saltcorn/data/models/fieldrepeat": require("@saltcorn/data/models/fieldrepeat"),
30
+ "@saltcorn/data/models/table": require("@saltcorn/data/models/table"),
31
+ "@saltcorn/data/models/form": require("@saltcorn/data/models/form"),
32
+ "@saltcorn/data/models/view": require("@saltcorn/data/models/view"),
33
+ "@saltcorn/data/models/page": require("@saltcorn/data/models/page"),
34
+ "@saltcorn/data/models/file": require("@saltcorn/data/models/file"),
35
+ "@saltcorn/data/models/user": require("@saltcorn/data/models/user"),
36
+ "@saltcorn/data/models/layout": require("@saltcorn/data/models/layout"),
37
+ "@saltcorn/data/models/expression": require("@saltcorn/data/models/expression"),
38
+ "@saltcorn/data/models/workflow": require("@saltcorn/data/models/workflow"),
39
+ };
40
+
16
41
  /**
17
42
  * Create plugin manager with default list of core plugins
18
43
  * @type {PluginManager}
19
44
  */
20
- const manager = new PluginManager({
45
+ const defaultManager = new PluginManager({
21
46
  staticDependencies: {
22
47
  contractis: require("contractis"),
23
- "@saltcorn/markup": require("@saltcorn/markup"),
24
- "@saltcorn/markup/tags": require("@saltcorn/markup/tags"),
25
- "@saltcorn/markup/layout": require("@saltcorn/markup/layout"),
26
- "@saltcorn/markup/helpers": require("@saltcorn/markup/helpers"),
27
- "@saltcorn/markup/layout_utils": require("@saltcorn/markup/layout_utils"),
28
- "@saltcorn/data": require("@saltcorn/data"),
29
- "@saltcorn/data/db": require("@saltcorn/data/db"),
30
- "@saltcorn/data/utils": require("@saltcorn/data/utils"),
31
- "@saltcorn/data/db/state": require("@saltcorn/data/db/state"),
32
- "@saltcorn/data/plugin-helper": require("@saltcorn/data/plugin-helper"),
33
- "@saltcorn/data/plugin-testing": require("@saltcorn/data/plugin-testing"),
34
- "@saltcorn/data/models/field": require("@saltcorn/data/models/field"),
35
- "@saltcorn/data/models/fieldrepeat": require("@saltcorn/data/models/fieldrepeat"),
36
- "@saltcorn/data/models/table": require("@saltcorn/data/models/table"),
37
- "@saltcorn/data/models/form": require("@saltcorn/data/models/form"),
38
- "@saltcorn/data/models/view": require("@saltcorn/data/models/view"),
39
- "@saltcorn/data/models/page": require("@saltcorn/data/models/page"),
40
- "@saltcorn/data/models/file": require("@saltcorn/data/models/file"),
41
- "@saltcorn/data/models/user": require("@saltcorn/data/models/user"),
42
- "@saltcorn/data/models/layout": require("@saltcorn/data/models/layout"),
43
- "@saltcorn/data/models/expression": require("@saltcorn/data/models/expression"),
44
- "@saltcorn/data/models/workflow": require("@saltcorn/data/models/workflow"),
48
+ ...staticDependencies,
45
49
  },
46
50
  });
47
51
 
@@ -103,7 +107,7 @@ const gitPullOrClone = async (plugin) => {
103
107
  return dir;
104
108
  };
105
109
 
106
- const requirePlugin = async (plugin, force) => {
110
+ const requirePlugin = async (plugin, force, manager = defaultManager) => {
107
111
  const installed_plugins = (await manager.list()).map((p) => p.name);
108
112
  if (
109
113
  ["@saltcorn/base-plugin", "@saltcorn/sbadmin2"].includes(plugin.location)
@@ -207,4 +211,5 @@ module.exports = {
207
211
  loadAllPlugins,
208
212
  loadPlugin,
209
213
  requirePlugin,
214
+ staticDependencies,
210
215
  };
package/locales/da.json CHANGED
@@ -487,7 +487,7 @@
487
487
  "Role to create tenants": "Role to create tenants",
488
488
  "Minimum user role required to create a new tenant": "Minimum user role required to create a new tenant",
489
489
  "Create tenant warning": "Create tenant warning",
490
- "Show a warning to users creating a tenant disclaiming warrenty of availability or security": "Show a warning to users creating a tenant disclaiming warrenty of availability or security",
490
+ "Show a warning to users creating a tenant disclaiming warranty of availability or security": "Show a warning to users creating a tenant disclaiming warranty of availability or security",
491
491
  "Multitenancy settings": "Multitenancy settings",
492
492
  "Actions available": "Actions available",
493
493
  "Event types": "Event types",