@saltcorn/server 1.1.2-beta.10 → 1.1.2-beta.12
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/CHANGELOG.md +8 -0
- package/package.json +9 -9
- package/routes/actions.js +1 -2
- package/routes/admin.js +52 -40
- package/routes/delete.js +4 -2
- package/routes/list.js +4 -3
- package/routes/pageedit.js +1 -1
- package/routes/tables.js +22 -10
- package/tests/admin.test.js +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
|
|
16
16
|
* Upgrade a large number of dependencies (express, typescript, oclif, pg, webpack, typescript, axios, mjml, svelte). Node.js 18+ is require for this release.
|
|
17
17
|
|
|
18
|
+
### Security
|
|
19
|
+
|
|
20
|
+
* View roles are now strictly enforced, including when views are embedded.
|
|
21
|
+
|
|
22
|
+
### Fixes
|
|
23
|
+
|
|
24
|
+
* Much work on primary keys not called "id"
|
|
25
|
+
|
|
18
26
|
## 1.1.1 - Released 2 February 2025
|
|
19
27
|
|
|
20
28
|
* Full-text search improvements:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "1.1.2-beta.
|
|
3
|
+
"version": "1.1.2-beta.12",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@aws-sdk/client-s3": "^3.735.0",
|
|
10
10
|
"@dr.pogodin/csurf": "^1.14.1",
|
|
11
|
-
"@saltcorn/base-plugin": "1.1.2-beta.
|
|
12
|
-
"@saltcorn/builder": "1.1.2-beta.
|
|
13
|
-
"@saltcorn/data": "1.1.2-beta.
|
|
14
|
-
"@saltcorn/admin-models": "1.1.2-beta.
|
|
15
|
-
"@saltcorn/filemanager": "1.1.2-beta.
|
|
16
|
-
"@saltcorn/markup": "1.1.2-beta.
|
|
17
|
-
"@saltcorn/plugins-loader": "1.1.2-beta.
|
|
18
|
-
"@saltcorn/sbadmin2": "1.1.2-beta.
|
|
11
|
+
"@saltcorn/base-plugin": "1.1.2-beta.12",
|
|
12
|
+
"@saltcorn/builder": "1.1.2-beta.12",
|
|
13
|
+
"@saltcorn/data": "1.1.2-beta.12",
|
|
14
|
+
"@saltcorn/admin-models": "1.1.2-beta.12",
|
|
15
|
+
"@saltcorn/filemanager": "1.1.2-beta.12",
|
|
16
|
+
"@saltcorn/markup": "1.1.2-beta.12",
|
|
17
|
+
"@saltcorn/plugins-loader": "1.1.2-beta.12",
|
|
18
|
+
"@saltcorn/sbadmin2": "1.1.2-beta.12",
|
|
19
19
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
20
20
|
"@socket.io/sticky": "^1.0.1",
|
|
21
21
|
"adm-zip": "0.5.16",
|
package/routes/actions.js
CHANGED
|
@@ -653,7 +653,7 @@ const getWorkflowStepForm = async (
|
|
|
653
653
|
...(field.showIf || {}),
|
|
654
654
|
},
|
|
655
655
|
};
|
|
656
|
-
if (cfgFld.input_type === "code") cfgFld.input_type = "textarea";
|
|
656
|
+
//if (cfgFld.input_type === "code") cfgFld.input_type = "textarea";
|
|
657
657
|
actionConfigFields.push(cfgFld);
|
|
658
658
|
}
|
|
659
659
|
} catch {}
|
|
@@ -1854,7 +1854,6 @@ interactive workflows for not logged in
|
|
|
1854
1854
|
actions can declare which variables they inject into scope
|
|
1855
1855
|
|
|
1856
1856
|
show unconnected steps
|
|
1857
|
-
why is code not initialising
|
|
1858
1857
|
drag and drop edges
|
|
1859
1858
|
|
|
1860
1859
|
*/
|
package/routes/admin.js
CHANGED
|
@@ -709,8 +709,8 @@ router.post(
|
|
|
709
709
|
type === "trigger"
|
|
710
710
|
? `/actions`
|
|
711
711
|
: /^[a-z]+$/g.test(type)
|
|
712
|
-
|
|
713
|
-
|
|
712
|
+
? `/${type}edit`
|
|
713
|
+
: "/"
|
|
714
714
|
);
|
|
715
715
|
})
|
|
716
716
|
);
|
|
@@ -1195,7 +1195,7 @@ router.get(
|
|
|
1195
1195
|
th({ valign: "top" }, req.__("Saltcorn version")),
|
|
1196
1196
|
td(
|
|
1197
1197
|
packagejson.version,
|
|
1198
|
-
isRoot
|
|
1198
|
+
isRoot && can_update
|
|
1199
1199
|
? post_btn(
|
|
1200
1200
|
"/admin/upgrade",
|
|
1201
1201
|
req.__("Upgrade") + " (latest)",
|
|
@@ -1206,21 +1206,22 @@ router.get(
|
|
|
1206
1206
|
}
|
|
1207
1207
|
)
|
|
1208
1208
|
: isRoot && is_latest
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1209
|
+
? span(
|
|
1210
|
+
{ class: "badge bg-primary ms-2" },
|
|
1211
|
+
req.__("Latest")
|
|
1212
|
+
) +
|
|
1213
|
+
post_btn(
|
|
1214
|
+
"/admin/check-for-upgrade",
|
|
1215
|
+
req.__("Check updates"),
|
|
1216
|
+
req.csrfToken(),
|
|
1217
|
+
{
|
|
1218
|
+
btnClass: "btn-primary btn-sm px-1 py-0",
|
|
1219
|
+
formClass: "d-inline",
|
|
1220
|
+
}
|
|
1221
|
+
)
|
|
1222
|
+
: "",
|
|
1223
|
+
isRoot &&
|
|
1224
|
+
!git_commit &&
|
|
1224
1225
|
a(
|
|
1225
1226
|
{
|
|
1226
1227
|
id: rndid,
|
|
@@ -1563,21 +1564,30 @@ const cleanNodeModules = async () => {
|
|
|
1563
1564
|
};
|
|
1564
1565
|
|
|
1565
1566
|
const doInstall = async (req, res, version, deepClean, runPull) => {
|
|
1567
|
+
const state = getState();
|
|
1568
|
+
let res_write = (s) => {
|
|
1569
|
+
try {
|
|
1570
|
+
res.write(s);
|
|
1571
|
+
state.log(5, s);
|
|
1572
|
+
} catch (e) {
|
|
1573
|
+
console.error("Install write error: ", e?.message || e);
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1566
1576
|
if (db.getTenantSchema() !== db.connectObj.default_schema) {
|
|
1567
1577
|
req.flash("error", req.__("Not possible for tenant"));
|
|
1568
1578
|
res.redirect("/admin");
|
|
1569
1579
|
} else {
|
|
1570
|
-
|
|
1580
|
+
res_write(
|
|
1571
1581
|
version === "latest"
|
|
1572
1582
|
? req.__("Starting upgrade, please wait...\n")
|
|
1573
1583
|
: req.__("Installing %s, please wait...\n", version)
|
|
1574
1584
|
);
|
|
1575
1585
|
if (deepClean) {
|
|
1576
|
-
|
|
1586
|
+
res_write(req.__("Cleaning node_modules...\n"));
|
|
1577
1587
|
try {
|
|
1578
1588
|
await cleanNodeModules();
|
|
1579
1589
|
} catch (e) {
|
|
1580
|
-
|
|
1590
|
+
res_write(req.__("Error cleaning node_modules: %s\n", e.message));
|
|
1581
1591
|
}
|
|
1582
1592
|
}
|
|
1583
1593
|
const child = spawn(
|
|
@@ -1588,33 +1598,37 @@ const doInstall = async (req, res, version, deepClean, runPull) => {
|
|
|
1588
1598
|
}
|
|
1589
1599
|
);
|
|
1590
1600
|
child.stdout.on("data", (data) => {
|
|
1591
|
-
|
|
1601
|
+
res_write(data);
|
|
1592
1602
|
});
|
|
1593
1603
|
child.stderr?.on("data", (data) => {
|
|
1594
|
-
|
|
1604
|
+
res_write(data);
|
|
1595
1605
|
});
|
|
1596
1606
|
child.on("exit", async function (code, signal) {
|
|
1597
1607
|
if (code === 0) {
|
|
1598
1608
|
if (deepClean) {
|
|
1599
|
-
|
|
1609
|
+
res_write(req.__("Installing sd-notify") + "\n");
|
|
1600
1610
|
const sdNotifyCode = await tryInstallSdNotify(req, res);
|
|
1601
|
-
|
|
1611
|
+
res_write(
|
|
1602
1612
|
req.__("sd-notify install done with code %s", sdNotifyCode) + "\n"
|
|
1603
1613
|
);
|
|
1604
1614
|
}
|
|
1605
1615
|
if (runPull) {
|
|
1606
|
-
|
|
1616
|
+
res_write(
|
|
1607
1617
|
req.__("Pulling the capacitor-builder docker image...") + "\n"
|
|
1608
1618
|
);
|
|
1609
1619
|
const pullCode = await pullCapacitorBuilder(req, res, version);
|
|
1610
|
-
|
|
1620
|
+
res_write(req.__("Pull done with code %s", pullCode) + "\n");
|
|
1611
1621
|
if (pullCode === 0) {
|
|
1612
|
-
|
|
1622
|
+
res_write(req.__("Pruning docker...") + "\n");
|
|
1613
1623
|
const pruneCode = await pruneDocker(req, res);
|
|
1614
|
-
|
|
1624
|
+
res_write(req.__("Prune done with code %s", pruneCode) + "\n");
|
|
1615
1625
|
}
|
|
1616
1626
|
}
|
|
1617
1627
|
}
|
|
1628
|
+
setTimeout(() => {
|
|
1629
|
+
getState().processSend("RestartServer");
|
|
1630
|
+
process.exit(0);
|
|
1631
|
+
}, 200);
|
|
1618
1632
|
res.end(
|
|
1619
1633
|
version === "latest"
|
|
1620
1634
|
? req.__(
|
|
@@ -1624,10 +1638,6 @@ const doInstall = async (req, res, version, deepClean, runPull) => {
|
|
|
1624
1638
|
`Install done with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
|
|
1625
1639
|
)
|
|
1626
1640
|
);
|
|
1627
|
-
setTimeout(() => {
|
|
1628
|
-
getState().processSend("RestartServer");
|
|
1629
|
-
process.exit(0);
|
|
1630
|
-
}, 100);
|
|
1631
1641
|
});
|
|
1632
1642
|
}
|
|
1633
1643
|
};
|
|
@@ -1974,9 +1984,8 @@ router.get(
|
|
|
1974
1984
|
const filename = `${moment(start).format("YYYYMMDDHHmm")}.html`;
|
|
1975
1985
|
await File.new_folder("configuration_checks");
|
|
1976
1986
|
const go = async () => {
|
|
1977
|
-
const { passes, errors, pass, warnings } =
|
|
1978
|
-
req
|
|
1979
|
-
);
|
|
1987
|
+
const { passes, errors, pass, warnings } =
|
|
1988
|
+
await runConfigurationCheck(req);
|
|
1980
1989
|
const end = new Date();
|
|
1981
1990
|
const secs = Math.round((end.getTime() - start.getTime()) / 1000);
|
|
1982
1991
|
|
|
@@ -3250,8 +3259,8 @@ router.get(
|
|
|
3250
3259
|
mode === "prepare"
|
|
3251
3260
|
? "_prepare_step"
|
|
3252
3261
|
: mode === "finish"
|
|
3253
|
-
|
|
3254
|
-
|
|
3262
|
+
? "_finish_step"
|
|
3263
|
+
: "";
|
|
3255
3264
|
res.json({
|
|
3256
3265
|
finished: await checkFiles(out_dir_name, [
|
|
3257
3266
|
`logs${stepDesc}.txt`,
|
|
@@ -3326,8 +3335,8 @@ router.get(
|
|
|
3326
3335
|
mode === "prepare"
|
|
3327
3336
|
? "_prepare_step"
|
|
3328
3337
|
: mode === "finish"
|
|
3329
|
-
|
|
3330
|
-
|
|
3338
|
+
? "_finish_step"
|
|
3339
|
+
: "";
|
|
3331
3340
|
const resultMsg = files.find(
|
|
3332
3341
|
(file) => file.filename === `logs${stepDesc}.txt`
|
|
3333
3342
|
)
|
|
@@ -3786,6 +3795,9 @@ router.post(
|
|
|
3786
3795
|
}
|
|
3787
3796
|
if (form.values.triggers) {
|
|
3788
3797
|
await db.deleteWhere("_sc_tag_entries", { not: { trigger_id: null } });
|
|
3798
|
+
await db.deleteWhere("_sc_workflow_trace");
|
|
3799
|
+
await db.deleteWhere("_sc_workflow_runs");
|
|
3800
|
+
await db.deleteWhere("_sc_workflow_steps");
|
|
3789
3801
|
await db.deleteWhere("_sc_triggers");
|
|
3790
3802
|
await getState().refresh_triggers();
|
|
3791
3803
|
}
|
package/routes/delete.js
CHANGED
|
@@ -35,9 +35,11 @@ router.post(
|
|
|
35
35
|
// todo check that works after where change
|
|
36
36
|
const table = Table.findOne({ name: tableName });
|
|
37
37
|
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
38
|
+
const where = { [table.pk_name]: id };
|
|
39
|
+
|
|
38
40
|
try {
|
|
39
41
|
if (role <= table.min_role_write)
|
|
40
|
-
await table.deleteRows(
|
|
42
|
+
await table.deleteRows(where, req.user || { role_id: 100 });
|
|
41
43
|
else if (
|
|
42
44
|
(table.ownership_field_id || table.ownership_formula) &&
|
|
43
45
|
req.user
|
|
@@ -47,7 +49,7 @@ router.post(
|
|
|
47
49
|
{ forUser: req.user, forPublic: !req.user }
|
|
48
50
|
);
|
|
49
51
|
if (row && table.is_owner(req.user, row))
|
|
50
|
-
await table.deleteRows(
|
|
52
|
+
await table.deleteRows(where, req.user || { role_id: 100 });
|
|
51
53
|
else req.flash("error", req.__("Not authorized"));
|
|
52
54
|
} else
|
|
53
55
|
req.flash(
|
package/routes/list.js
CHANGED
|
@@ -282,6 +282,7 @@ router.get(
|
|
|
282
282
|
cellClick: "__delete_tabulator_row",
|
|
283
283
|
});
|
|
284
284
|
const isDark = getState().getLightDarkMode(req.user) === "dark";
|
|
285
|
+
const pkNm = table.pk_name
|
|
285
286
|
res.sendWrap(
|
|
286
287
|
{
|
|
287
288
|
title: req.__(`%s data table`, table.name),
|
|
@@ -428,7 +429,7 @@ router.get(
|
|
|
428
429
|
ajax_indicator(true);
|
|
429
430
|
$.ajax({
|
|
430
431
|
type: "POST",
|
|
431
|
-
url: "/api/${table.name}/" + (row
|
|
432
|
+
url: "/api/${table.name}/" + (row.${pkNm}||""),
|
|
432
433
|
data: row,
|
|
433
434
|
headers: {
|
|
434
435
|
"CSRF-Token": _sc_globalCsrf,
|
|
@@ -438,8 +439,8 @@ router.get(
|
|
|
438
439
|
ajax_indicator(false);
|
|
439
440
|
//if (item._versions) item._versions = +item._versions + 1;
|
|
440
441
|
//data.resolve(fixKeys(item));
|
|
441
|
-
if(resp.success &&(typeof resp.success ==="number" || typeof resp.success ==="string") && !row
|
|
442
|
-
window.tabulator_table.updateRow(cell.getRow(), {
|
|
442
|
+
if(resp.success &&(typeof resp.success ==="number" || typeof resp.success ==="string") && !row.${pkNm}) {
|
|
443
|
+
window.tabulator_table.updateRow(cell.getRow(), {${pkNm}: resp.success});
|
|
443
444
|
}
|
|
444
445
|
|
|
445
446
|
}).fail(function (resp) {
|
package/routes/pageedit.js
CHANGED
|
@@ -289,7 +289,7 @@ const getRootPageForm = (pages, pageGroups, roles, req) => {
|
|
|
289
289
|
input_type: "select",
|
|
290
290
|
options: [
|
|
291
291
|
"",
|
|
292
|
-
...pages.map((p) => p.name),
|
|
292
|
+
...pages.filter((p) => p.min_role >= r.id).map((p) => p.name),
|
|
293
293
|
...pageGroups.map((g) => ({
|
|
294
294
|
label: `${g.name} (group)`,
|
|
295
295
|
value: g.name,
|
package/routes/tables.js
CHANGED
|
@@ -50,6 +50,7 @@ const {
|
|
|
50
50
|
code,
|
|
51
51
|
pre,
|
|
52
52
|
button,
|
|
53
|
+
text_attr,
|
|
53
54
|
} = require("@saltcorn/markup/tags");
|
|
54
55
|
const { stringify } = require("csv-stringify");
|
|
55
56
|
const TableConstraint = require("@saltcorn/data/models/table_constraints");
|
|
@@ -696,7 +697,14 @@ const typeBadges = (f, req) => {
|
|
|
696
697
|
if (f.primary_key) s += badge("warning", req.__("Primary key"));
|
|
697
698
|
if (f.required) s += badge("primary", req.__("Required"));
|
|
698
699
|
if (f.is_unique) s += badge("success", req.__("Unique"));
|
|
699
|
-
if (f.calculated)
|
|
700
|
+
if (f.calculated)
|
|
701
|
+
s += badge(
|
|
702
|
+
"info",
|
|
703
|
+
req.__("Calculated"),
|
|
704
|
+
f.expression && f.expression !== "__aggregation"
|
|
705
|
+
? text_attr(f.expression)
|
|
706
|
+
: undefined
|
|
707
|
+
);
|
|
700
708
|
if (f.stored) s += badge("warning", req.__("Stored"));
|
|
701
709
|
return s;
|
|
702
710
|
};
|
|
@@ -978,8 +986,8 @@ router.get(
|
|
|
978
986
|
table.name === "users"
|
|
979
987
|
? `/useradmin/`
|
|
980
988
|
: fields.length === 1
|
|
981
|
-
|
|
982
|
-
|
|
989
|
+
? `javascript:;` // Fix problem with edition of table with only one column ID / Primary Key
|
|
990
|
+
: `/list/${encodeURIComponent(table.name)}`,
|
|
983
991
|
},
|
|
984
992
|
i({ class: "fas fa-2x fa-edit" }),
|
|
985
993
|
"<br/>",
|
|
@@ -1459,7 +1467,10 @@ router.get(
|
|
|
1459
1467
|
res.redirect(`/table/${table.id}`);
|
|
1460
1468
|
return;
|
|
1461
1469
|
}
|
|
1462
|
-
const rows = await table.getRows(
|
|
1470
|
+
const rows = await table.getRows(
|
|
1471
|
+
{},
|
|
1472
|
+
{ orderBy: table.pk_name, forUser: req.user }
|
|
1473
|
+
);
|
|
1463
1474
|
res.setHeader("Content-Type", "text/csv");
|
|
1464
1475
|
res.setHeader("Content-Disposition", `attachment; filename="${name}.csv"`);
|
|
1465
1476
|
res.setHeader("Cache-Control", "no-cache");
|
|
@@ -1535,12 +1546,12 @@ router.get(
|
|
|
1535
1546
|
r.type === "Unique"
|
|
1536
1547
|
? r.configuration.fields.join(", ")
|
|
1537
1548
|
: r.type === "Index" && r.configuration?.field === "_fts"
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1549
|
+
? "Full text search"
|
|
1550
|
+
: r.type === "Index"
|
|
1551
|
+
? r.configuration.field
|
|
1552
|
+
: r.type === "Formula"
|
|
1553
|
+
? r.configuration.formula
|
|
1554
|
+
: "",
|
|
1544
1555
|
},
|
|
1545
1556
|
{
|
|
1546
1557
|
label: req.__("Delete"),
|
|
@@ -2011,6 +2022,7 @@ router.post(
|
|
|
2011
2022
|
if (parse_res.error) req.flash("error", parse_res.error);
|
|
2012
2023
|
else req.flash("success", parse_res.success);
|
|
2013
2024
|
} catch (e) {
|
|
2025
|
+
console.error("CSV upload error", e);
|
|
2014
2026
|
req.flash("error", e.message);
|
|
2015
2027
|
}
|
|
2016
2028
|
await fs.unlink(f.location);
|
package/tests/admin.test.js
CHANGED
|
@@ -647,6 +647,9 @@ describe("clear all page", () => {
|
|
|
647
647
|
.send("users=on")
|
|
648
648
|
.send("config=on")
|
|
649
649
|
.send("plugins=on")
|
|
650
|
+
.send("triggers=on")
|
|
651
|
+
.send("library=on")
|
|
652
|
+
.send("eventlog=on")
|
|
650
653
|
.expect(toRedirect("/auth/create_first_user"));
|
|
651
654
|
});
|
|
652
655
|
it("restores backup after clear all", async () => {
|