@saltcorn/server 0.7.2-beta.0 → 0.7.2-beta.4
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 +64 -9
- package/auth/routes.js +37 -13
- package/load_plugins.js +29 -24
- package/locales/da.json +1 -1
- package/locales/de.json +155 -155
- package/locales/en.json +8 -5
- package/locales/it.json +1 -1
- package/locales/ru.json +52 -18
- package/locales/zh.json +3 -3
- package/package.json +11 -8
- package/public/saltcorn-common.js +105 -0
- package/public/saltcorn.css +61 -0
- package/public/saltcorn.js +55 -83
- package/routes/admin.js +1 -1
- package/routes/api.js +36 -1
- package/routes/edit.js +2 -1
- package/routes/fields.js +12 -0
- package/routes/viewedit.js +9 -0
- package/tests/admin.test.js +72 -1
- package/tests/clientjs.test.js +1 -0
- package/tests/viewedit.test.js +94 -0
- package/wrapper.js +1 -0
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
|
-
//
|
|
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 (
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
977
|
-
|
|
978
|
-
passport.authenticate(method, auth.parameters)(req, res, next);
|
|
996
|
+
if (method === "jwt") {
|
|
997
|
+
await loginWithJwt(req, res);
|
|
979
998
|
} else {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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}
|
|
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 (
|
|
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
|
|
45
|
+
const defaultManager = new PluginManager({
|
|
21
46
|
staticDependencies: {
|
|
22
47
|
contractis: require("contractis"),
|
|
23
|
-
|
|
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
|
|
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",
|