@saltcorn/server 0.8.5-beta.3 → 0.8.5-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/auth/testhelp.js +15 -0
- package/locales/en.json +8 -1
- package/package.json +8 -8
- package/public/saltcorn.css +23 -0
- package/public/vis-network.min.js +49 -0
- package/routes/admin.js +14 -6
- package/routes/api.js +65 -10
- package/routes/fields.js +11 -1
- package/routes/list.js +8 -5
- package/routes/plugins.js +47 -16
- package/routes/tables.js +1 -2
- package/routes/tenant.js +4 -0
- package/tests/api.test.js +2 -2
- package/tests/table.test.js +17 -0
- package/wrapper.js +14 -2
package/routes/admin.js
CHANGED
|
@@ -1321,12 +1321,16 @@ router.get(
|
|
|
1321
1321
|
"/configuration-check",
|
|
1322
1322
|
isAdmin,
|
|
1323
1323
|
error_catcher(async (req, res) => {
|
|
1324
|
-
const
|
|
1324
|
+
const start = new Date();
|
|
1325
|
+
const filename = `${moment(start).format("YYYYMMDDHHmm")}.html`;
|
|
1325
1326
|
await File.new_folder("configuration_checks");
|
|
1326
1327
|
const go = async () => {
|
|
1327
1328
|
const { passes, errors, pass, warnings } = await runConfigurationCheck(
|
|
1328
1329
|
req
|
|
1329
1330
|
);
|
|
1331
|
+
const end = new Date();
|
|
1332
|
+
const secs = Math.round((end.getTime() - start.getTime()) / 1000);
|
|
1333
|
+
|
|
1330
1334
|
const mkError = (err) =>
|
|
1331
1335
|
div(
|
|
1332
1336
|
{ class: "alert alert-danger", role: "alert" },
|
|
@@ -1355,6 +1359,11 @@ router.get(
|
|
|
1355
1359
|
h3("Passes"),
|
|
1356
1360
|
|
|
1357
1361
|
pre(code(passes.join("\n")))
|
|
1362
|
+
) +
|
|
1363
|
+
p(
|
|
1364
|
+
`Configuration check completed in ${
|
|
1365
|
+
secs > 60 ? `${Math.floor(secs / 60)}m ${secs % 60}s` : secs + "s"
|
|
1366
|
+
}`
|
|
1358
1367
|
);
|
|
1359
1368
|
await File.from_contents(
|
|
1360
1369
|
filename,
|
|
@@ -1964,8 +1973,9 @@ router.post(
|
|
|
1964
1973
|
* @returns {Promise<Form>} form
|
|
1965
1974
|
*/
|
|
1966
1975
|
const dev_form = async (req) => {
|
|
1967
|
-
const
|
|
1968
|
-
"
|
|
1976
|
+
const tenants_set_npm_modules = getRootState().getConfig(
|
|
1977
|
+
"tenants_set_npm_modules",
|
|
1978
|
+
false
|
|
1969
1979
|
);
|
|
1970
1980
|
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
1971
1981
|
|
|
@@ -1976,9 +1986,7 @@ const dev_form = async (req) => {
|
|
|
1976
1986
|
"log_sql",
|
|
1977
1987
|
"log_client_errors",
|
|
1978
1988
|
"log_level",
|
|
1979
|
-
...(isRoot ||
|
|
1980
|
-
? ["npm_available_js_code"]
|
|
1981
|
-
: []),
|
|
1989
|
+
...(isRoot || tenants_set_npm_modules ? ["npm_available_js_code"] : []),
|
|
1982
1990
|
],
|
|
1983
1991
|
action: "/admin/dev",
|
|
1984
1992
|
});
|
package/routes/api.js
CHANGED
|
@@ -31,6 +31,7 @@ const {
|
|
|
31
31
|
readState,
|
|
32
32
|
strictParseInt,
|
|
33
33
|
} = require("@saltcorn/data/plugin-helper");
|
|
34
|
+
const Crash = require("@saltcorn/data/models/crash");
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* @type {object}
|
|
@@ -66,7 +67,7 @@ const limitFields = (fields) => (r) => {
|
|
|
66
67
|
* @param {Table} table
|
|
67
68
|
* @returns {boolean}
|
|
68
69
|
*/
|
|
69
|
-
function accessAllowedRead(req, user, table) {
|
|
70
|
+
function accessAllowedRead(req, user, table, allow_ownership) {
|
|
70
71
|
const role =
|
|
71
72
|
req.user && req.user.id
|
|
72
73
|
? req.user.role_id
|
|
@@ -74,7 +75,12 @@ function accessAllowedRead(req, user, table) {
|
|
|
74
75
|
? user.role_id
|
|
75
76
|
: 10;
|
|
76
77
|
|
|
77
|
-
return
|
|
78
|
+
return (
|
|
79
|
+
role <= table.min_role_read ||
|
|
80
|
+
((req.user?.id || user?.id) &&
|
|
81
|
+
allow_ownership &&
|
|
82
|
+
(table.ownership_field_id || table.ownership_formula))
|
|
83
|
+
);
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
/**
|
|
@@ -92,7 +98,11 @@ function accessAllowedWrite(req, user, table) {
|
|
|
92
98
|
? user.role_id
|
|
93
99
|
: 10;
|
|
94
100
|
|
|
95
|
-
return
|
|
101
|
+
return (
|
|
102
|
+
role <= table.min_role_write ||
|
|
103
|
+
((req.user?.id || user?.id) &&
|
|
104
|
+
(table.ownership_field_id || table.ownership_formula))
|
|
105
|
+
);
|
|
96
106
|
}
|
|
97
107
|
/**
|
|
98
108
|
* Check that user has right to trigger call
|
|
@@ -126,6 +136,7 @@ router.post(
|
|
|
126
136
|
const view = await View.findOne({ name: viewName });
|
|
127
137
|
const db = require("@saltcorn/data/db");
|
|
128
138
|
if (!view) {
|
|
139
|
+
getState().log(3, `API viewQuery ${view.name} not found`);
|
|
129
140
|
res.status(404).json({
|
|
130
141
|
error: req.__("View %s not found", viewName),
|
|
131
142
|
view: viewName,
|
|
@@ -152,6 +163,10 @@ router.post(
|
|
|
152
163
|
const resp = await queries[queryName](...args, true);
|
|
153
164
|
res.json({ success: resp, alerts: getFlashes(req) });
|
|
154
165
|
} else {
|
|
166
|
+
getState().log(
|
|
167
|
+
3,
|
|
168
|
+
`API viewQuery ${view.name} ${queryName} not found`
|
|
169
|
+
);
|
|
155
170
|
res.status(404).json({
|
|
156
171
|
error: req.__("Query %s not found", queryName),
|
|
157
172
|
view: viewName,
|
|
@@ -163,6 +178,7 @@ router.post(
|
|
|
163
178
|
});
|
|
164
179
|
}
|
|
165
180
|
} else {
|
|
181
|
+
getState().log(3, `API viewQuery ${view.name} not authorized`);
|
|
166
182
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
167
183
|
}
|
|
168
184
|
}
|
|
@@ -208,6 +224,10 @@ router.get(
|
|
|
208
224
|
}
|
|
209
225
|
res.json({ success: dvs });
|
|
210
226
|
} else {
|
|
227
|
+
getState().log(
|
|
228
|
+
3,
|
|
229
|
+
`API distinct ${table.name}.${fieldName} not authorized`
|
|
230
|
+
);
|
|
211
231
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
212
232
|
}
|
|
213
233
|
}
|
|
@@ -234,6 +254,7 @@ router.get(
|
|
|
234
254
|
: { name: tableName }
|
|
235
255
|
);
|
|
236
256
|
if (!table) {
|
|
257
|
+
getState().log(3, `API get ${tableName} table not found`);
|
|
237
258
|
res.status(404).json({ error: req.__("Not found") });
|
|
238
259
|
return;
|
|
239
260
|
}
|
|
@@ -242,11 +263,13 @@ router.get(
|
|
|
242
263
|
["api-bearer", "jwt"],
|
|
243
264
|
{ session: false },
|
|
244
265
|
async function (err, user, info) {
|
|
245
|
-
if (accessAllowedRead(req, user, table)) {
|
|
266
|
+
if (accessAllowedRead(req, user, table, true)) {
|
|
246
267
|
let rows;
|
|
247
268
|
if (versioncount === "on") {
|
|
248
269
|
const joinOpts = {
|
|
249
270
|
orderBy: "id",
|
|
271
|
+
forUser: req.user || user || { role_id: 10 },
|
|
272
|
+
forPublic: !(req.user || user),
|
|
250
273
|
aggregations: {
|
|
251
274
|
_versions: {
|
|
252
275
|
table: table.name + "__history",
|
|
@@ -266,12 +289,22 @@ router.get(
|
|
|
266
289
|
state: req_query,
|
|
267
290
|
table,
|
|
268
291
|
});
|
|
269
|
-
rows = await table.getRows(qstate
|
|
292
|
+
rows = await table.getRows(qstate, {
|
|
293
|
+
forPublic: !(req.user || user),
|
|
294
|
+
forUser: req.user || user,
|
|
295
|
+
});
|
|
270
296
|
} else {
|
|
271
|
-
rows = await table.getRows(
|
|
297
|
+
rows = await table.getRows(
|
|
298
|
+
{},
|
|
299
|
+
{
|
|
300
|
+
forPublic: !(req.user || user),
|
|
301
|
+
forUser: req.user || user,
|
|
302
|
+
}
|
|
303
|
+
);
|
|
272
304
|
}
|
|
273
305
|
res.json({ success: rows.map(limitFields(fields)) });
|
|
274
306
|
} else {
|
|
307
|
+
getState().log(3, `API get ${table.name} not authorized`);
|
|
275
308
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
276
309
|
}
|
|
277
310
|
}
|
|
@@ -301,6 +334,7 @@ router.post(
|
|
|
301
334
|
});
|
|
302
335
|
|
|
303
336
|
if (!trigger) {
|
|
337
|
+
getState().log(3, `API action ${actionname} not found`);
|
|
304
338
|
res.status(400).json({ error: req.__("Not found") });
|
|
305
339
|
return;
|
|
306
340
|
}
|
|
@@ -318,9 +352,11 @@ router.post(
|
|
|
318
352
|
});
|
|
319
353
|
res.json({ success: true, data: resp });
|
|
320
354
|
} catch (e) {
|
|
355
|
+
Crash.create(e, req);
|
|
321
356
|
res.status(400).json({ success: false, error: e.message });
|
|
322
357
|
}
|
|
323
358
|
} else {
|
|
359
|
+
getState().log(3, `API action ${actionname} not authorized`);
|
|
324
360
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
325
361
|
}
|
|
326
362
|
}
|
|
@@ -340,6 +376,7 @@ router.post(
|
|
|
340
376
|
const { tableName } = req.params;
|
|
341
377
|
const table = await Table.findOne({ name: tableName });
|
|
342
378
|
if (!table) {
|
|
379
|
+
getState().log(3, `API POST ${tableName} not found`);
|
|
343
380
|
res.status(404).json({ error: req.__("Not found") });
|
|
344
381
|
return;
|
|
345
382
|
}
|
|
@@ -378,6 +415,10 @@ router.post(
|
|
|
378
415
|
}
|
|
379
416
|
});
|
|
380
417
|
if (hasErrors) {
|
|
418
|
+
getState().log(
|
|
419
|
+
2,
|
|
420
|
+
`API POST ${table.name} error: ${errors.join(", ")}`
|
|
421
|
+
);
|
|
381
422
|
res.status(400).json({ error: errors.join(", ") });
|
|
382
423
|
return;
|
|
383
424
|
}
|
|
@@ -385,9 +426,12 @@ router.post(
|
|
|
385
426
|
row,
|
|
386
427
|
req.user || user || { role_id: 10 }
|
|
387
428
|
);
|
|
388
|
-
if (ins_res.error)
|
|
389
|
-
|
|
429
|
+
if (ins_res.error) {
|
|
430
|
+
getState().log(2, `API POST ${table.name} error: ${ins_res.error}`);
|
|
431
|
+
res.status(400).json(ins_res);
|
|
432
|
+
} else res.json(ins_res);
|
|
390
433
|
} else {
|
|
434
|
+
getState().log(3, `API POST ${table.name} not authorized`);
|
|
391
435
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
392
436
|
}
|
|
393
437
|
}
|
|
@@ -408,6 +452,7 @@ router.post(
|
|
|
408
452
|
const { tableName, id } = req.params;
|
|
409
453
|
const table = await Table.findOne({ name: tableName });
|
|
410
454
|
if (!table) {
|
|
455
|
+
getState().log(3, `API POST ${tableName} not found`);
|
|
411
456
|
res.status(404).json({ error: req.__("Not found") });
|
|
412
457
|
return;
|
|
413
458
|
}
|
|
@@ -445,6 +490,10 @@ router.post(
|
|
|
445
490
|
}
|
|
446
491
|
}
|
|
447
492
|
if (hasErrors) {
|
|
493
|
+
getState().log(
|
|
494
|
+
2,
|
|
495
|
+
`API POST ${table.name} error: ${errors.join(", ")}`
|
|
496
|
+
);
|
|
448
497
|
res.status(400).json({ error: errors.join(", ") });
|
|
449
498
|
return;
|
|
450
499
|
}
|
|
@@ -454,9 +503,12 @@ router.post(
|
|
|
454
503
|
user || req.user || { role_id: 10 }
|
|
455
504
|
);
|
|
456
505
|
|
|
457
|
-
if (ins_res.error)
|
|
458
|
-
|
|
506
|
+
if (ins_res.error) {
|
|
507
|
+
getState().log(2, `API POST ${table.name} error: ${ins_res.error}`);
|
|
508
|
+
res.status(400).json(ins_res);
|
|
509
|
+
} else res.json(ins_res);
|
|
459
510
|
} else {
|
|
511
|
+
getState().log(3, `API POST ${table.name} not authorized`);
|
|
460
512
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
461
513
|
}
|
|
462
514
|
}
|
|
@@ -477,6 +529,7 @@ router.delete(
|
|
|
477
529
|
const { tableName, id } = req.params;
|
|
478
530
|
const table = await Table.findOne({ name: tableName });
|
|
479
531
|
if (!table) {
|
|
532
|
+
getState().log(3, `API DELETE ${tableName} not found`);
|
|
480
533
|
res.status(404).json({ error: req.__("Not found") });
|
|
481
534
|
return;
|
|
482
535
|
}
|
|
@@ -502,9 +555,11 @@ router.delete(
|
|
|
502
555
|
);
|
|
503
556
|
res.json({ success: true });
|
|
504
557
|
} catch (e) {
|
|
558
|
+
getState().log(2, `API DELETE ${table.name} error: ${e.message}`);
|
|
505
559
|
res.status(400).json({ error: e.message });
|
|
506
560
|
}
|
|
507
561
|
} else {
|
|
562
|
+
getState().log(3, `API DELETE ${table.name} not authorized`);
|
|
508
563
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
509
564
|
}
|
|
510
565
|
}
|
package/routes/fields.js
CHANGED
|
@@ -398,9 +398,16 @@ const fieldFlow = (req) =>
|
|
|
398
398
|
];
|
|
399
399
|
const keyfields = orderedFields
|
|
400
400
|
.filter((f) => !f.calculated || f.stored)
|
|
401
|
+
.sort((a, b) =>
|
|
402
|
+
a.type?.name === "String" && b.type?.name !== "String"
|
|
403
|
+
? -1
|
|
404
|
+
: a.type?.name !== "String" && b.type?.name === "String"
|
|
405
|
+
? 1
|
|
406
|
+
: 0
|
|
407
|
+
)
|
|
401
408
|
.map((f) => ({
|
|
402
409
|
value: f.name,
|
|
403
|
-
label: f.label
|
|
410
|
+
label: `${f.label} [${f.type?.name || f.type}]`,
|
|
404
411
|
}));
|
|
405
412
|
const textfields = orderedFields
|
|
406
413
|
.filter(
|
|
@@ -412,6 +419,9 @@ const fieldFlow = (req) =>
|
|
|
412
419
|
new Field({
|
|
413
420
|
name: "summary_field",
|
|
414
421
|
label: req.__("Summary field"),
|
|
422
|
+
sublabel: req.__(
|
|
423
|
+
"The field that will be shown to the user when choosing a value"
|
|
424
|
+
),
|
|
415
425
|
input_type: "select",
|
|
416
426
|
options: keyfields,
|
|
417
427
|
}),
|
package/routes/list.js
CHANGED
|
@@ -241,13 +241,16 @@ router.get(
|
|
|
241
241
|
for (const f of fields) {
|
|
242
242
|
if (f.type === "File") f.attributes = { select_file_where: {} };
|
|
243
243
|
await f.fill_fkey_options();
|
|
244
|
+
|
|
245
|
+
if (f.type === "File") {
|
|
246
|
+
//add existing values in folders
|
|
247
|
+
const dvs = await f.distinct_values();
|
|
248
|
+
dvs.forEach((dv) => {
|
|
249
|
+
if (dv?.value?.includes("/")) f.options.push(dv);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
244
252
|
}
|
|
245
253
|
|
|
246
|
-
//console.log(fields);
|
|
247
|
-
// todo remove keyfields - unused
|
|
248
|
-
const keyfields = fields
|
|
249
|
-
.filter((f) => f.type === "Key" || f.type === "File")
|
|
250
|
-
.map((f) => ({ name: f.name, type: f.reftype }));
|
|
251
254
|
const jsfields = arrangeIdFirst(fields).map((f) =>
|
|
252
255
|
typeToGridType(f.type, f)
|
|
253
256
|
);
|
package/routes/plugins.js
CHANGED
|
@@ -14,11 +14,18 @@ const {
|
|
|
14
14
|
post_btn,
|
|
15
15
|
post_delete_btn,
|
|
16
16
|
} = require("@saltcorn/markup");
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
getState,
|
|
19
|
+
restart_tenant,
|
|
20
|
+
getRootState,
|
|
21
|
+
} = require("@saltcorn/data/db/state");
|
|
18
22
|
const Form = require("@saltcorn/data/models/form");
|
|
19
23
|
const Field = require("@saltcorn/data/models/field");
|
|
20
24
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
21
25
|
const { fetch_available_packs } = require("@saltcorn/admin-models/models/pack");
|
|
26
|
+
const {
|
|
27
|
+
upgrade_all_tenants_plugins,
|
|
28
|
+
} = require("@saltcorn/admin-models/models/tenant");
|
|
22
29
|
const { getConfig, setConfig } = require("@saltcorn/data/models/config");
|
|
23
30
|
const db = require("@saltcorn/data/db");
|
|
24
31
|
const {
|
|
@@ -150,7 +157,10 @@ const local_has_theme = (name) => {
|
|
|
150
157
|
const get_store_items = async () => {
|
|
151
158
|
const installed_plugins = await Plugin.find({});
|
|
152
159
|
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
153
|
-
|
|
160
|
+
const tenants_unsafe_plugins = getRootState().getConfig(
|
|
161
|
+
"tenants_unsafe_plugins",
|
|
162
|
+
false
|
|
163
|
+
);
|
|
154
164
|
const instore = await Plugin.store_plugins_available();
|
|
155
165
|
const packs_available = await fetch_available_packs();
|
|
156
166
|
const packs_installed = getState().getConfig("installed_packs", []);
|
|
@@ -168,7 +178,7 @@ const get_store_items = async () => {
|
|
|
168
178
|
has_auth: plugin.has_auth,
|
|
169
179
|
unsafe: plugin.unsafe,
|
|
170
180
|
}))
|
|
171
|
-
.filter((p) => !p.unsafe || isRoot);
|
|
181
|
+
.filter((p) => !p.unsafe || isRoot || tenants_unsafe_plugins);
|
|
172
182
|
const local_logins = installed_plugins
|
|
173
183
|
.filter((p) => !store_plugin_names.includes(p.name) && p.name !== "base")
|
|
174
184
|
.map((plugin) => ({
|
|
@@ -424,8 +434,12 @@ const filter_items_set = (items, query) => {
|
|
|
424
434
|
* @param {object} req
|
|
425
435
|
* @returns {div}
|
|
426
436
|
*/
|
|
427
|
-
const store_actions_dropdown = (req) =>
|
|
428
|
-
|
|
437
|
+
const store_actions_dropdown = (req) => {
|
|
438
|
+
const tenants_install_git = getRootState().getConfig(
|
|
439
|
+
"tenants_install_git",
|
|
440
|
+
false
|
|
441
|
+
);
|
|
442
|
+
return div(
|
|
429
443
|
{ class: "dropdown" },
|
|
430
444
|
button(
|
|
431
445
|
{
|
|
@@ -460,7 +474,8 @@ const store_actions_dropdown = (req) =>
|
|
|
460
474
|
'<i class="far fa-arrow-alt-circle-up"></i> ' +
|
|
461
475
|
req.__("Upgrade installed modules")
|
|
462
476
|
),
|
|
463
|
-
db.getTenantSchema() === db.connectObj.default_schema
|
|
477
|
+
(db.getTenantSchema() === db.connectObj.default_schema ||
|
|
478
|
+
tenants_install_git) &&
|
|
464
479
|
a(
|
|
465
480
|
{
|
|
466
481
|
class: "dropdown-item",
|
|
@@ -488,6 +503,7 @@ const store_actions_dropdown = (req) =>
|
|
|
488
503
|
//create pack
|
|
489
504
|
)
|
|
490
505
|
);
|
|
506
|
+
};
|
|
491
507
|
|
|
492
508
|
/**
|
|
493
509
|
* @param {object[]} items
|
|
@@ -926,14 +942,22 @@ router.get(
|
|
|
926
942
|
"/upgrade",
|
|
927
943
|
isAdmin,
|
|
928
944
|
error_catcher(async (req, res) => {
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
await
|
|
945
|
+
const schema = db.getTenantSchema();
|
|
946
|
+
if (schema === db.connectObj.default_schema) {
|
|
947
|
+
await upgrade_all_tenants_plugins((p, f) =>
|
|
948
|
+
load_plugins.loadPlugin(p, f)
|
|
949
|
+
);
|
|
950
|
+
req.flash("success", req.__(`Modules up-to-date. Please restart server`));
|
|
951
|
+
} else {
|
|
952
|
+
const installed_plugins = await Plugin.find({});
|
|
953
|
+
for (const plugin of installed_plugins) {
|
|
954
|
+
await plugin.upgrade_version((p, f) => load_plugins.loadPlugin(p, f));
|
|
955
|
+
}
|
|
956
|
+
req.flash("success", req.__(`Modules up-to-date`));
|
|
957
|
+
await restart_tenant(loadAllPlugins);
|
|
958
|
+
process.send &&
|
|
959
|
+
process.send({ restart_tenant: true, tenant: db.getTenantSchema() });
|
|
932
960
|
}
|
|
933
|
-
req.flash("success", req.__(`Modules up-to-date`));
|
|
934
|
-
await restart_tenant(loadAllPlugins);
|
|
935
|
-
process.send &&
|
|
936
|
-
process.send({ restart_tenant: true, tenant: db.getTenantSchema() });
|
|
937
961
|
res.redirect(`/plugins`);
|
|
938
962
|
})
|
|
939
963
|
);
|
|
@@ -970,7 +994,11 @@ router.post(
|
|
|
970
994
|
error_catcher(async (req, res) => {
|
|
971
995
|
const plugin = new Plugin(req.body);
|
|
972
996
|
const schema = db.getTenantSchema();
|
|
973
|
-
|
|
997
|
+
const tenants_install_git = getRootState().getConfig(
|
|
998
|
+
"tenants_install_git",
|
|
999
|
+
false
|
|
1000
|
+
);
|
|
1001
|
+
if (schema !== db.connectObj.default_schema && !tenants_install_git) {
|
|
974
1002
|
req.flash(
|
|
975
1003
|
"error",
|
|
976
1004
|
req.__(`Only store modules can be installed on tenant instances`)
|
|
@@ -1039,7 +1067,10 @@ router.post(
|
|
|
1039
1067
|
isAdmin,
|
|
1040
1068
|
error_catcher(async (req, res) => {
|
|
1041
1069
|
const { name } = req.params;
|
|
1042
|
-
|
|
1070
|
+
const tenants_unsafe_plugins = getRootState().getConfig(
|
|
1071
|
+
"tenants_unsafe_plugins",
|
|
1072
|
+
false
|
|
1073
|
+
);
|
|
1043
1074
|
const plugin = await Plugin.store_by_name(decodeURIComponent(name));
|
|
1044
1075
|
if (!plugin) {
|
|
1045
1076
|
req.flash(
|
|
@@ -1050,7 +1081,7 @@ router.post(
|
|
|
1050
1081
|
return;
|
|
1051
1082
|
}
|
|
1052
1083
|
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
1053
|
-
if (!isRoot && plugin.unsafe) {
|
|
1084
|
+
if (!isRoot && plugin.unsafe && !tenants_unsafe_plugins) {
|
|
1054
1085
|
req.flash(
|
|
1055
1086
|
"error",
|
|
1056
1087
|
req.__("Cannot install unsafe modules on subdomain tenants")
|
package/routes/tables.js
CHANGED
package/routes/tenant.js
CHANGED
|
@@ -426,6 +426,10 @@ const tenant_settings_form = (req) =>
|
|
|
426
426
|
"create_tenant_warning",
|
|
427
427
|
"create_tenant_warning_text",
|
|
428
428
|
"tenant_template",
|
|
429
|
+
{ section_header: "Tenant application capabilities" },
|
|
430
|
+
"tenants_install_git",
|
|
431
|
+
"tenants_set_npm_modules",
|
|
432
|
+
"tenants_unsafe_plugins",
|
|
429
433
|
],
|
|
430
434
|
action: "/tenant/settings",
|
|
431
435
|
submitLabel: req.__("Save"),
|
package/tests/api.test.js
CHANGED
|
@@ -19,7 +19,7 @@ beforeAll(async () => {
|
|
|
19
19
|
afterAll(db.close);
|
|
20
20
|
|
|
21
21
|
describe("API read", () => {
|
|
22
|
-
it("should get books for public", async () => {
|
|
22
|
+
it("should get books for public simple", async () => {
|
|
23
23
|
const app = await getApp({ disableCsrf: true });
|
|
24
24
|
await request(app)
|
|
25
25
|
.get("/api/books/")
|
|
@@ -32,7 +32,7 @@ describe("API read", () => {
|
|
|
32
32
|
)
|
|
33
33
|
);
|
|
34
34
|
});
|
|
35
|
-
it("should get books for public", async () => {
|
|
35
|
+
it("should get books for public fts", async () => {
|
|
36
36
|
const app = await getApp({ disableCsrf: true });
|
|
37
37
|
await request(app)
|
|
38
38
|
.get("/api/books/?_fts=Herman")
|
package/tests/table.test.js
CHANGED
|
@@ -5,11 +5,13 @@ const Field = require("@saltcorn/data/models/field");
|
|
|
5
5
|
const {
|
|
6
6
|
getStaffLoginCookie,
|
|
7
7
|
getAdminLoginCookie,
|
|
8
|
+
getUserLoginCookie,
|
|
8
9
|
itShouldRedirectUnauthToLogin,
|
|
9
10
|
toInclude,
|
|
10
11
|
toNotInclude,
|
|
11
12
|
toRedirect,
|
|
12
13
|
resetToFixtures,
|
|
14
|
+
succeedJsonWith,
|
|
13
15
|
} = require("../auth/testhelp");
|
|
14
16
|
const db = require("@saltcorn/data/db");
|
|
15
17
|
const User = require("@saltcorn/data/models/user");
|
|
@@ -262,7 +264,22 @@ describe("deletion to table with row ownership", () => {
|
|
|
262
264
|
const row = await persons.insertRow({ name: "something", owner: user.id });
|
|
263
265
|
expect(await persons.countRows()).toBe(1);
|
|
264
266
|
const loginCookie = await getStaffLoginCookie();
|
|
267
|
+
const uloginCookie = await getUserLoginCookie();
|
|
265
268
|
const app = await getApp({ disableCsrf: true });
|
|
269
|
+
await request(app).get("/api/owned").expect(401);
|
|
270
|
+
await request(app)
|
|
271
|
+
.get("/api/owned")
|
|
272
|
+
.set("Cookie", loginCookie)
|
|
273
|
+
.expect(
|
|
274
|
+
succeedJsonWith(
|
|
275
|
+
(rows) => rows.length == 1 && rows[0].name === "something"
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
await request(app)
|
|
279
|
+
.get("/api/owned")
|
|
280
|
+
.set("Cookie", uloginCookie)
|
|
281
|
+
.expect(succeedJsonWith((rows) => rows.length == 0));
|
|
282
|
+
|
|
266
283
|
await request(app)
|
|
267
284
|
.post("/delete/owned/" + row)
|
|
268
285
|
.expect(toRedirect("/list/owned"));
|
package/wrapper.js
CHANGED
|
@@ -58,10 +58,22 @@ const get_menu = (req) => {
|
|
|
58
58
|
]
|
|
59
59
|
: [
|
|
60
60
|
...(allow_signup
|
|
61
|
-
? [
|
|
61
|
+
? [
|
|
62
|
+
{
|
|
63
|
+
link: "/auth/signup",
|
|
64
|
+
icon: "fas fa-user-plus",
|
|
65
|
+
label: req.__("Sign up"),
|
|
66
|
+
},
|
|
67
|
+
]
|
|
62
68
|
: []),
|
|
63
69
|
...(login_menu
|
|
64
|
-
? [
|
|
70
|
+
? [
|
|
71
|
+
{
|
|
72
|
+
link: "/auth/login",
|
|
73
|
+
icon: "fas fa-sign-in-alt",
|
|
74
|
+
label: req.__("Login"),
|
|
75
|
+
},
|
|
76
|
+
]
|
|
65
77
|
: []),
|
|
66
78
|
];
|
|
67
79
|
// const schema = db.getTenantSchema();
|