@saltcorn/server 0.8.3-alpha.1 → 0.8.3-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/auth/admin.js +2 -1
- package/auth/routes.js +29 -26
- package/auth/testhelp.js +25 -0
- package/load_plugins.js +6 -2
- package/locales/da.json +126 -1
- package/locales/en.json +8 -1
- package/locales/ru.json +6 -1
- package/package.json +16 -16
- package/routes/actions.js +8 -2
- package/routes/admin.js +16 -13
- package/routes/api.js +1 -1
- package/routes/delete.js +5 -2
- package/routes/edit.js +16 -8
- package/routes/fields.js +12 -5
- package/routes/pageedit.js +10 -4
- package/routes/search.js +30 -11
- package/routes/utils.js +6 -0
- package/tests/assets/large-image.png +0 -0
- package/tests/auth.test.js +4 -2
- package/tests/files.test.js +45 -4
package/auth/admin.js
CHANGED
|
@@ -354,6 +354,7 @@ const permissions_settings_form = async (req) =>
|
|
|
354
354
|
field_names: [
|
|
355
355
|
"min_role_upload",
|
|
356
356
|
"min_role_apikeygen",
|
|
357
|
+
"min_role_search",
|
|
357
358
|
//hidden "exttables_min_role_read",
|
|
358
359
|
],
|
|
359
360
|
action: "/useradmin/permissions",
|
|
@@ -1101,7 +1102,7 @@ router.post(
|
|
|
1101
1102
|
const { id } = req.params;
|
|
1102
1103
|
const u = await User.findOne({ id });
|
|
1103
1104
|
if (u) {
|
|
1104
|
-
u.relogin(req);
|
|
1105
|
+
await u.relogin(req);
|
|
1105
1106
|
req.flash(
|
|
1106
1107
|
"success",
|
|
1107
1108
|
req.__(
|
package/auth/routes.js
CHANGED
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
loggedIn,
|
|
18
18
|
csrfField,
|
|
19
19
|
setTenant,
|
|
20
|
+
is_relative_url,
|
|
20
21
|
} = require("../routes/utils.js");
|
|
21
22
|
const { getState } = require("@saltcorn/data/db/state");
|
|
22
23
|
const { send_reset_email } = require("./resetpw");
|
|
@@ -323,18 +324,21 @@ router.get("/logout", async (req, res, next) => {
|
|
|
323
324
|
await user.updateLastMobileLogin(null);
|
|
324
325
|
res.json({ success: true });
|
|
325
326
|
} else if (req.logout) {
|
|
326
|
-
req.logout()
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
327
|
+
req.logout(function (err) {
|
|
328
|
+
if (req.session.destroy)
|
|
329
|
+
req.session.destroy((err) => {
|
|
330
|
+
if (err) return next(err);
|
|
331
|
+
req.logout(() => {
|
|
332
|
+
res.redirect("/auth/login");
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
else {
|
|
336
|
+
req.logout(function (err) {
|
|
337
|
+
req.session = null;
|
|
338
|
+
res.redirect("/auth/login");
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
338
342
|
}
|
|
339
343
|
});
|
|
340
344
|
|
|
@@ -392,7 +396,7 @@ router.get(
|
|
|
392
396
|
else if (result) {
|
|
393
397
|
req.flash("success", req.__("Email verified"));
|
|
394
398
|
const u = await User.findForSession({ email });
|
|
395
|
-
if (u) u.relogin(req);
|
|
399
|
+
if (u) await u.relogin(req);
|
|
396
400
|
}
|
|
397
401
|
res.redirect("/");
|
|
398
402
|
})
|
|
@@ -1075,7 +1079,7 @@ router.post(
|
|
|
1075
1079
|
}
|
|
1076
1080
|
if (getState().get2FApolicy(req.user) === "Mandatory") {
|
|
1077
1081
|
res.redirect("/auth/twofa/setup/totp");
|
|
1078
|
-
} else if (req.body.dest) {
|
|
1082
|
+
} else if (req.body.dest && is_relative_url(req.body.dest)) {
|
|
1079
1083
|
res.redirect(decodeURIComponent(req.body.dest));
|
|
1080
1084
|
} else res.redirect("/");
|
|
1081
1085
|
})
|
|
@@ -1129,7 +1133,6 @@ router.post(
|
|
|
1129
1133
|
error_catcher(async (req, res, next) => {
|
|
1130
1134
|
const { method } = req.params;
|
|
1131
1135
|
const auth = getState().auth_methods[method];
|
|
1132
|
-
console.log(method, auth);
|
|
1133
1136
|
if (auth) {
|
|
1134
1137
|
passport.authenticate(method, auth.parameters)(
|
|
1135
1138
|
req,
|
|
@@ -1446,15 +1449,15 @@ router.get(
|
|
|
1446
1449
|
error_catcher(async (req, res) => {
|
|
1447
1450
|
const user = await User.findOne({ id: req.user.id });
|
|
1448
1451
|
if (!user) {
|
|
1449
|
-
req.logout()
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1452
|
+
req.logout(() => {
|
|
1453
|
+
req.flash("danger", req.__("Must be logged in first"));
|
|
1454
|
+
res.redirect("/auth/login");
|
|
1455
|
+
});
|
|
1456
|
+
} else
|
|
1457
|
+
res.sendWrap(
|
|
1458
|
+
req.__("User settings") || "User settings",
|
|
1459
|
+
await userSettings({ req, res, pwform: changPwForm(req), user })
|
|
1460
|
+
);
|
|
1458
1461
|
})
|
|
1459
1462
|
);
|
|
1460
1463
|
|
|
@@ -1616,7 +1619,7 @@ router.all(
|
|
|
1616
1619
|
const user = await User.findForSession({ id: req.user.id });
|
|
1617
1620
|
await user.set_to_verified();
|
|
1618
1621
|
req.flash("success", req.__("User verified"));
|
|
1619
|
-
user.relogin(req);
|
|
1622
|
+
await user.relogin(req);
|
|
1620
1623
|
}
|
|
1621
1624
|
if (wfres.verified === false) {
|
|
1622
1625
|
req.flash("danger", req.__("User verification failed"));
|
|
@@ -1843,7 +1846,7 @@ router.post(
|
|
|
1843
1846
|
}),
|
|
1844
1847
|
error_catcher(async (req, res) => {
|
|
1845
1848
|
const user = await User.findForSession({ id: req.user.pending_user.id });
|
|
1846
|
-
user.relogin(req);
|
|
1849
|
+
await user.relogin(req);
|
|
1847
1850
|
Trigger.emitEvent("Login", null, user);
|
|
1848
1851
|
res.redirect("/");
|
|
1849
1852
|
})
|
package/auth/testhelp.js
CHANGED
|
@@ -65,6 +65,29 @@ const toSucceed =
|
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @param {number} expCode
|
|
71
|
+
* @returns {void}
|
|
72
|
+
* @throws {Error}
|
|
73
|
+
*/
|
|
74
|
+
const toSucceedWithImage =
|
|
75
|
+
({ expCode = 200, lengthIs }) =>
|
|
76
|
+
(res) => {
|
|
77
|
+
if (res.statusCode !== expCode) {
|
|
78
|
+
console.log(res.text);
|
|
79
|
+
throw new Error(`Expected status ${expCode}, received ${res.statusCode}`);
|
|
80
|
+
}
|
|
81
|
+
if (res.type.split("/")[0] !== "image") {
|
|
82
|
+
throw new Error(`Expected response type image/*, received ${res.type}`);
|
|
83
|
+
}
|
|
84
|
+
if (lengthIs && !lengthIs(res.body.length)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Image response not accepted. Length not satisfied. Received ${res.body.length} bytes`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
68
91
|
/**
|
|
69
92
|
*
|
|
70
93
|
* @param {number} txt
|
|
@@ -209,4 +232,6 @@ module.exports = {
|
|
|
209
232
|
succeedJsonWith,
|
|
210
233
|
notAuthorized,
|
|
211
234
|
respondJsonWith,
|
|
235
|
+
toSucceedWithImage,
|
|
236
|
+
resToLoginCookie,
|
|
212
237
|
};
|
package/load_plugins.js
CHANGED
|
@@ -56,13 +56,17 @@ const defaultManager = new PluginManager({
|
|
|
56
56
|
* @param force - force flag
|
|
57
57
|
*/
|
|
58
58
|
const loadPlugin = async (plugin, force) => {
|
|
59
|
-
// load
|
|
59
|
+
// load plugin
|
|
60
60
|
const res = await requirePlugin(plugin, force);
|
|
61
|
+
const configuration =
|
|
62
|
+
typeof plugin.configuration === "string"
|
|
63
|
+
? JSON.parse(plugin.configuration)
|
|
64
|
+
: plugin.configuration;
|
|
61
65
|
// register plugin
|
|
62
66
|
getState().registerPlugin(
|
|
63
67
|
res.plugin_module.plugin_name || plugin.name,
|
|
64
68
|
res.plugin_module,
|
|
65
|
-
|
|
69
|
+
configuration,
|
|
66
70
|
res.location,
|
|
67
71
|
res.name
|
|
68
72
|
);
|
package/locales/da.json
CHANGED
|
@@ -558,5 +558,130 @@
|
|
|
558
558
|
"List options": "List options",
|
|
559
559
|
"Modules": "Modules",
|
|
560
560
|
"File not found": "File not found",
|
|
561
|
-
"Welcome, %s!": "Welcome, %s!"
|
|
561
|
+
"Welcome, %s!": "Welcome, %s!",
|
|
562
|
+
"CSV upload": "CSV upload",
|
|
563
|
+
"Upload file(s)": "Upload file(s)",
|
|
564
|
+
"API token": "API token",
|
|
565
|
+
"No API token issued": "No API token issued",
|
|
566
|
+
"Generate": "Generate",
|
|
567
|
+
"Two-factor authentication": "Two-factor authentication",
|
|
568
|
+
"Two-factor authentication is disabled": "Two-factor authentication is disabled",
|
|
569
|
+
"Enable TWA": "Enable TWA",
|
|
570
|
+
"Pattern": "Pattern",
|
|
571
|
+
"You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.": "You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.",
|
|
572
|
+
"Views potentially affected": "Views potentially affected",
|
|
573
|
+
"History": "History",
|
|
574
|
+
"External": "External",
|
|
575
|
+
"Discover tables that are already in the Database, but not known to Saltcorn": "Discover tables that are already in the Database, but not known to Saltcorn",
|
|
576
|
+
"Forget table": "Forget table",
|
|
577
|
+
"Ownership field": "Ownership field",
|
|
578
|
+
"The user referred to in this field will be the owner of the row": "The user referred to in this field will be the owner of the row",
|
|
579
|
+
"None": "None",
|
|
580
|
+
"Ownership formula": "Ownership formula",
|
|
581
|
+
"User is treated as owner if true. In scope: ": "User is treated as owner if true. In scope: ",
|
|
582
|
+
"User group": "User group",
|
|
583
|
+
"Add relations to this table in dropdown options for ownership field": "Add relations to this table in dropdown options for ownership field",
|
|
584
|
+
"User must have this role or higher to read rows from the table, unless they are the owner": "User must have this role or higher to read rows from the table, unless they are the owner",
|
|
585
|
+
"User must have this role or higher to edit or create new rows in the table, unless they are the owner": "User must have this role or higher to edit or create new rows in the table, unless they are the owner",
|
|
586
|
+
"Edit properties": "Edit properties",
|
|
587
|
+
"Preset %s": "Preset %s",
|
|
588
|
+
"%s configuration": "%s configuration",
|
|
589
|
+
"Module Store endpoint": "Module Store endpoint",
|
|
590
|
+
"The endpoint of plugins store.": "The endpoint of plugins store.",
|
|
591
|
+
"Packs Store endpoint": "Packs Store endpoint",
|
|
592
|
+
"The endpoint of packs store.": "The endpoint of packs store.",
|
|
593
|
+
"Mobile app": "Mobile app",
|
|
594
|
+
"Backup now": "Backup now",
|
|
595
|
+
"Frequency": "Frequency",
|
|
596
|
+
"Destination": "Destination",
|
|
597
|
+
"Directory": "Directory",
|
|
598
|
+
"Expiration in days": "Expiration in days",
|
|
599
|
+
"Delete old backup files in this directory after the set number of days": "Delete old backup files in this directory after the set number of days",
|
|
600
|
+
"Snapshot now": "Snapshot now",
|
|
601
|
+
"Periodic snapshots enabled": "Periodic snapshots enabled",
|
|
602
|
+
"Snapshot will be made every hour if there are changes": "Snapshot will be made every hour if there are changes",
|
|
603
|
+
"Manual backup": "Manual backup",
|
|
604
|
+
"Automated backup": "Automated backup",
|
|
605
|
+
"Restore/download automated backups »": "Restore/download automated backups »",
|
|
606
|
+
"Snapshots": "Snapshots",
|
|
607
|
+
"Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).": "Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).",
|
|
608
|
+
"List/download snapshots »": "List/download snapshots »",
|
|
609
|
+
"Configuration check": "Configuration check",
|
|
610
|
+
"Check for updates": "Check for updates",
|
|
611
|
+
"Database type": "Database type",
|
|
612
|
+
"Database host": "Database host",
|
|
613
|
+
"Database port": "Database port",
|
|
614
|
+
"Database name": "Database name",
|
|
615
|
+
"Database user": "Database user",
|
|
616
|
+
"Database schema": "Database schema",
|
|
617
|
+
"Build mobile app": "Build mobile app",
|
|
618
|
+
"Entry point": "Entry point",
|
|
619
|
+
"Platform": "Platform",
|
|
620
|
+
"docker": "docker",
|
|
621
|
+
"android": "android",
|
|
622
|
+
"iOS": "iOS",
|
|
623
|
+
"App file": "App file",
|
|
624
|
+
"Server URL": "Server URL",
|
|
625
|
+
"Log client errors": "Log client errors",
|
|
626
|
+
"Record all client errors in the crash log": "Record all client errors in the crash log",
|
|
627
|
+
"System logging verbosity": "System logging verbosity",
|
|
628
|
+
"NPM packages in code": "NPM packages in code",
|
|
629
|
+
"Comma-separated list of packages which will be available in JavaScript actions": "Comma-separated list of packages which will be available in JavaScript actions",
|
|
630
|
+
"Development settings": "Development settings",
|
|
631
|
+
"Module store": "Module store",
|
|
632
|
+
"Upgrading modules...": "Upgrading modules...",
|
|
633
|
+
"Upgrade installed modules": "Upgrade installed modules",
|
|
634
|
+
"Add another module": "Add another module",
|
|
635
|
+
"Module": "Module",
|
|
636
|
+
"Become user": "Become user",
|
|
637
|
+
"Send password reset email": "Send password reset email",
|
|
638
|
+
"Login and Signup": "Login and Signup",
|
|
639
|
+
"Table access": "Table access",
|
|
640
|
+
"HTTP": "HTTP",
|
|
641
|
+
"Permissions": "Permissions",
|
|
642
|
+
"2FA policy": "2FA policy",
|
|
643
|
+
"Cookie duration (hours)": "Cookie duration (hours)",
|
|
644
|
+
"Set to 0 for expiration at the end of browser session": "Set to 0 for expiration at the end of browser session",
|
|
645
|
+
"Cookie duration (hours) when remember ticked": "Cookie duration (hours) when remember ticked",
|
|
646
|
+
"Public cache TTL (minutes)": "Public cache TTL (minutes)",
|
|
647
|
+
"Cache-control max-age for public views and pages. 0 to disable": "Cache-control max-age for public views and pages. 0 to disable",
|
|
648
|
+
"HTTP settings": "HTTP settings",
|
|
649
|
+
"Role to generate API keys": "Role to generate API keys",
|
|
650
|
+
"User should have this role or higher to generate API keys in their user settings": "User should have this role or higher to generate API keys in their user settings",
|
|
651
|
+
"Role for search": "Role for search",
|
|
652
|
+
"Min role to access search page": "Min role to access search page",
|
|
653
|
+
"Permissions settings": "Permissions settings",
|
|
654
|
+
"Update": "Update",
|
|
655
|
+
"Add": "Add",
|
|
656
|
+
"Recalculate dynamic": "Recalculate dynamic",
|
|
657
|
+
"Order field": "Order field",
|
|
658
|
+
"Section field": "Section field",
|
|
659
|
+
"Optional. String type with options, each of which will become a menu section": "Optional. String type with options, each of which will become a menu section",
|
|
660
|
+
"Label formula": "Label formula",
|
|
661
|
+
"URL formula": "URL formula",
|
|
662
|
+
"Include formula": "Include formula",
|
|
663
|
+
"If specified, only include in menu rows that evaluate to true": "If specified, only include in menu rows that evaluate to true",
|
|
664
|
+
"Location": "Location",
|
|
665
|
+
"Not all themes support all locations": "Not all themes support all locations",
|
|
666
|
+
"Library": "Library",
|
|
667
|
+
"Tags": "Tags",
|
|
668
|
+
"Diagram": "Diagram",
|
|
669
|
+
"Library: component assemblies that can be used in the builder": "Library: component assemblies that can be used in the builder",
|
|
670
|
+
"Creator email": "Creator email",
|
|
671
|
+
"Created": "Created",
|
|
672
|
+
"Create tenant warning text": "Create tenant warning text",
|
|
673
|
+
"Provide your own create warning text if need": "Provide your own create warning text if need",
|
|
674
|
+
"New tenant template": "New tenant template",
|
|
675
|
+
"Copy site structure for new tenants from this tenant": "Copy site structure for new tenants from this tenant",
|
|
676
|
+
"Tagname": "Tagname",
|
|
677
|
+
"Create tag": "Create tag",
|
|
678
|
+
"Application diagram": "Application diagram",
|
|
679
|
+
"Trigger": "Trigger",
|
|
680
|
+
"All entities": "All entities",
|
|
681
|
+
"no tags": "no tags",
|
|
682
|
+
"Add tag": "Add tag",
|
|
683
|
+
"Pages are the web pages of your application built with a drag-and-drop builder. They have static content, and by embedding views, dynamic content.": "Pages are the web pages of your application built with a drag-and-drop builder. They have static content, and by embedding views, dynamic content.",
|
|
684
|
+
"Triggers run actions in response to events.": "Triggers run actions in response to events.",
|
|
685
|
+
"No triggers": "No triggers",
|
|
686
|
+
"Code": "Code"
|
|
562
687
|
}
|
package/locales/en.json
CHANGED
|
@@ -1086,5 +1086,12 @@
|
|
|
1086
1086
|
"Views potentially affected": "Views potentially affected",
|
|
1087
1087
|
"Empty view": "Empty view",
|
|
1088
1088
|
"A view that will be shown only if there are no tables rows to show": "A view that will be shown only if there are no tables rows to show",
|
|
1089
|
-
"Calculated field will be stored in Database": "Calculated field will be stored in Database"
|
|
1089
|
+
"Calculated field will be stored in Database": "Calculated field will be stored in Database",
|
|
1090
|
+
"Set Email": "Set Email",
|
|
1091
|
+
"Please enter your email address": "Please enter your email address",
|
|
1092
|
+
"Role for search": "Role for search",
|
|
1093
|
+
"Min role to access search page": "Min role to access search page",
|
|
1094
|
+
"Update": "Update",
|
|
1095
|
+
"Add": "Add",
|
|
1096
|
+
"Recalculate dynamic": "Recalculate dynamic"
|
|
1090
1097
|
}
|
package/locales/ru.json
CHANGED
|
@@ -968,5 +968,10 @@
|
|
|
968
968
|
"Defines how long to wait for data before aborting file upload. Set to 0 if you want to turn off timeout checks. ": "Определяет максимальный допустимый интервал времени ожидания загрузки данных файла. 0 отключает проверку. ",
|
|
969
969
|
"User group": "Группа пользователей",
|
|
970
970
|
"Add relations to this table in dropdown options for ownership field": "Добавить связь с этой таблицей в выбор опций для поля Владельца ",
|
|
971
|
-
"Calculated field will be stored in Database": "Вычисляемое будет сохранено в базе данных"
|
|
971
|
+
"Calculated field will be stored in Database": "Вычисляемое будет сохранено в базе данных",
|
|
972
|
+
"Save indicator": "Индикатор сохранения",
|
|
973
|
+
"%s configuration": "%s конфигурация",
|
|
974
|
+
"Show only matches in table:": "Показывать только совпадения в таблице:",
|
|
975
|
+
"Role for search": "Роль для поиска",
|
|
976
|
+
"Min role to access search page": "Минимальная роль для доступа к странице поиска"
|
|
972
977
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.3-
|
|
3
|
+
"version": "0.8.3-beta.0",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@saltcorn/base-plugin": "0.8.3-
|
|
10
|
-
"@saltcorn/builder": "0.8.3-
|
|
11
|
-
"@saltcorn/data": "0.8.3-
|
|
12
|
-
"@saltcorn/admin-models": "0.8.3-
|
|
13
|
-
"@saltcorn/filemanager": "0.8.3-
|
|
14
|
-
"@saltcorn/markup": "0.8.3-
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.3-
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.3-beta.0",
|
|
10
|
+
"@saltcorn/builder": "0.8.3-beta.0",
|
|
11
|
+
"@saltcorn/data": "0.8.3-beta.0",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.3-beta.0",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.3-beta.0",
|
|
14
|
+
"@saltcorn/markup": "0.8.3-beta.0",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.3-beta.0",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.1.0",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"aws-sdk": "^2.1037.0",
|
|
@@ -35,22 +35,22 @@
|
|
|
35
35
|
"i18n": "^0.14.0",
|
|
36
36
|
"jsonwebtoken": "^9.0.0",
|
|
37
37
|
"live-plugin-manager": "^0.16.0",
|
|
38
|
-
"moment": "^2.
|
|
39
|
-
"multer": "
|
|
38
|
+
"moment": "^2.29.4",
|
|
39
|
+
"multer": "1.4.5-lts.1",
|
|
40
40
|
"multer-s3": "^2.10.0",
|
|
41
|
-
"node-fetch": "2.6.
|
|
41
|
+
"node-fetch": "2.6.9",
|
|
42
42
|
"node-watch": "^0.7.2",
|
|
43
43
|
"notp": "2.0.3",
|
|
44
|
-
"passport": "^0.
|
|
44
|
+
"passport": "^0.6.0",
|
|
45
45
|
"passport-custom": "^1.1.1",
|
|
46
46
|
"passport-http-bearer": "^1.0.1",
|
|
47
47
|
"passport-jwt": "4.0.1",
|
|
48
48
|
"passport-totp": "0.0.2",
|
|
49
49
|
"pg": "^8.2.1",
|
|
50
50
|
"pluralize": "^8.0.0",
|
|
51
|
-
"qrcode": "1.5.
|
|
52
|
-
"resize-with-sharp-or-jimp": "0.1.
|
|
53
|
-
"socket.io": "4.
|
|
51
|
+
"qrcode": "1.5.1",
|
|
52
|
+
"resize-with-sharp-or-jimp": "0.1.6",
|
|
53
|
+
"socket.io": "4.6.0",
|
|
54
54
|
"thirty-two": "1.0.2",
|
|
55
55
|
"tmp-promise": "^3.0.2",
|
|
56
56
|
"uuid": "^8.2.0",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"jest": "26.6.3",
|
|
66
66
|
"jest-environment-jsdom": "^26.6.2",
|
|
67
|
-
"supertest": "^
|
|
67
|
+
"supertest": "^6.3.3"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"dev": "nodemon index.js",
|
package/routes/actions.js
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* @subcategory routes
|
|
6
6
|
*/
|
|
7
7
|
const Router = require("express-promise-router");
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
isAdmin,
|
|
10
|
+
error_catcher,
|
|
11
|
+
addOnDoneRedirect,
|
|
12
|
+
is_relative_url,
|
|
13
|
+
} = require("./utils.js");
|
|
9
14
|
const { getState } = require("@saltcorn/data/db/state");
|
|
10
15
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
11
16
|
const { getTriggerList } = require("./common_lists");
|
|
@@ -524,7 +529,8 @@ router.post(
|
|
|
524
529
|
}
|
|
525
530
|
req.flash("success", "Action configuration saved");
|
|
526
531
|
res.redirect(
|
|
527
|
-
req.query.on_done_redirect
|
|
532
|
+
req.query.on_done_redirect &&
|
|
533
|
+
is_relative_url(req.query.on_done_redirect)
|
|
528
534
|
? `/${req.query.on_done_redirect}`
|
|
529
535
|
: "/actions/"
|
|
530
536
|
);
|
package/routes/admin.js
CHANGED
|
@@ -603,7 +603,7 @@ router.post(
|
|
|
603
603
|
snap.created
|
|
604
604
|
).fromNow()}`
|
|
605
605
|
);
|
|
606
|
-
res.redirect(`/${type}edit`);
|
|
606
|
+
res.redirect(/^[a-z]+$/g.test(type) ? `/${type}edit` : "/");
|
|
607
607
|
})
|
|
608
608
|
);
|
|
609
609
|
router.get(
|
|
@@ -1820,18 +1820,21 @@ router.post(
|
|
|
1820
1820
|
await db.deleteWhere("users");
|
|
1821
1821
|
await db.deleteWhere("_sc_roles", { not: { id: { in: [1, 4, 8, 10] } } });
|
|
1822
1822
|
if (db.reset_sequence) await db.reset_sequence("users");
|
|
1823
|
-
req.logout()
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1823
|
+
req.logout(function (err) {
|
|
1824
|
+
if (req.session.destroy)
|
|
1825
|
+
req.session.destroy((err) => {
|
|
1826
|
+
req.logout(() => {
|
|
1827
|
+
res.redirect(`/auth/create_first_user`);
|
|
1828
|
+
});
|
|
1829
|
+
});
|
|
1830
|
+
else {
|
|
1831
|
+
req.logout(() => {
|
|
1832
|
+
req.session = null; // todo make configurable - redirect to create first user
|
|
1833
|
+
// redirect to create first user
|
|
1834
|
+
res.redirect(`/auth/create_first_user`);
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
});
|
|
1835
1838
|
} else {
|
|
1836
1839
|
req.flash(
|
|
1837
1840
|
"success",
|
package/routes/api.js
CHANGED
|
@@ -147,7 +147,7 @@ router.post(
|
|
|
147
147
|
(await view.authorise_get({ req, ...view })) // TODO set query to state
|
|
148
148
|
) {
|
|
149
149
|
const queries = view.queries(false, req);
|
|
150
|
-
if (queries
|
|
150
|
+
if (Object.prototype.hasOwnProperty.call(queries, queryName)) {
|
|
151
151
|
const { args } = req.body;
|
|
152
152
|
const resp = await queries[queryName](...args, true);
|
|
153
153
|
res.json({ success: resp, alerts: getFlashes(req) });
|
package/routes/delete.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const Router = require("express-promise-router");
|
|
8
8
|
|
|
9
|
-
const { error_catcher } = require("./utils.js");
|
|
9
|
+
const { error_catcher, is_relative_url } = require("./utils.js");
|
|
10
10
|
const Table = require("@saltcorn/data/models/table");
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -52,6 +52,9 @@ router.post(
|
|
|
52
52
|
req.flash("error", e.message);
|
|
53
53
|
}
|
|
54
54
|
if (req.xhr) res.send("OK");
|
|
55
|
-
else
|
|
55
|
+
else
|
|
56
|
+
res.redirect(
|
|
57
|
+
(is_relative_url(redirect) && redirect) || `/list/${table.name}`
|
|
58
|
+
);
|
|
56
59
|
})
|
|
57
60
|
);
|
package/routes/edit.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const Router = require("express-promise-router");
|
|
8
8
|
|
|
9
|
-
const { error_catcher } = require("./utils.js");
|
|
9
|
+
const { error_catcher, is_relative_url } = require("./utils.js");
|
|
10
10
|
const Table = require("@saltcorn/data/models/table");
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -32,15 +32,23 @@ router.post(
|
|
|
32
32
|
const { redirect } = req.query;
|
|
33
33
|
// todo check that works after where change
|
|
34
34
|
const table = await Table.findOne({ name: tableName });
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
req.
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
|
|
36
|
+
const row = await table.getRow(
|
|
37
|
+
{ [table.pk_name]: id },
|
|
38
|
+
{ forUser: req.user, forPublic: !req.user }
|
|
39
|
+
);
|
|
40
|
+
if (row)
|
|
41
|
+
await table.updateRow(
|
|
42
|
+
{ [field_name]: !row[field_name] },
|
|
43
|
+
id,
|
|
44
|
+
req.user || { role_id: 10 }
|
|
41
45
|
);
|
|
46
|
+
|
|
42
47
|
if (req.xhr) res.send("OK");
|
|
43
48
|
else if (req.get("referer")) res.redirect(req.get("referer"));
|
|
44
|
-
else
|
|
49
|
+
else
|
|
50
|
+
res.redirect(
|
|
51
|
+
(is_relative_url(redirect) && redirect) || `/list/${table.name}`
|
|
52
|
+
);
|
|
45
53
|
})
|
|
46
54
|
);
|
package/routes/fields.js
CHANGED
|
@@ -137,9 +137,7 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
|
|
|
137
137
|
new Field({
|
|
138
138
|
label: req.__("Stored"),
|
|
139
139
|
name: "stored",
|
|
140
|
-
sublabel: req.__(
|
|
141
|
-
"Calculated field will be stored in Database"
|
|
142
|
-
),
|
|
140
|
+
sublabel: req.__("Calculated field will be stored in Database"),
|
|
143
141
|
type: "Bool",
|
|
144
142
|
disabled: !!id,
|
|
145
143
|
showIf: { calculated: true },
|
|
@@ -927,16 +925,25 @@ router.post(
|
|
|
927
925
|
res.send("");
|
|
928
926
|
return;
|
|
929
927
|
}
|
|
928
|
+
const firefox = /firefox/i.test(req.headers["user-agent"]);
|
|
930
929
|
const fv = fieldviews[fieldview];
|
|
931
930
|
if (!fv && field.type === "Key" && fieldview === "select")
|
|
932
|
-
res.send(
|
|
931
|
+
res.send(
|
|
932
|
+
`<input ${
|
|
933
|
+
firefox ? "readonly" : "disabled"
|
|
934
|
+
} class="form-control form-select"></input>`
|
|
935
|
+
);
|
|
933
936
|
else if (!fv) res.send("");
|
|
934
937
|
else if (fv.isEdit || fv.isFilter)
|
|
935
938
|
res.send(
|
|
936
939
|
fv.run(
|
|
937
940
|
field.name,
|
|
938
941
|
undefined,
|
|
939
|
-
{
|
|
942
|
+
{
|
|
943
|
+
...(firefox ? { readonly: true } : { disabled: true }),
|
|
944
|
+
...configuration,
|
|
945
|
+
...(field.attributes || {}),
|
|
946
|
+
},
|
|
940
947
|
"",
|
|
941
948
|
false,
|
|
942
949
|
field
|
package/routes/pageedit.js
CHANGED
|
@@ -21,7 +21,12 @@ const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
|
|
|
21
21
|
const db = require("@saltcorn/data/db");
|
|
22
22
|
const { getPageList } = require("./common_lists");
|
|
23
23
|
|
|
24
|
-
const {
|
|
24
|
+
const {
|
|
25
|
+
isAdmin,
|
|
26
|
+
error_catcher,
|
|
27
|
+
addOnDoneRedirect,
|
|
28
|
+
is_relative_url,
|
|
29
|
+
} = require("./utils.js");
|
|
25
30
|
const {
|
|
26
31
|
mkTable,
|
|
27
32
|
renderForm,
|
|
@@ -422,9 +427,10 @@ router.post(
|
|
|
422
427
|
error_catcher(async (req, res) => {
|
|
423
428
|
const { pagename } = req.params;
|
|
424
429
|
|
|
425
|
-
let redirectTarget =
|
|
426
|
-
|
|
427
|
-
|
|
430
|
+
let redirectTarget =
|
|
431
|
+
req.query.on_done_redirect && is_relative_url(req.query.on_done_redirect)
|
|
432
|
+
? `/${req.query.on_done_redirect}`
|
|
433
|
+
: "/pageedit";
|
|
428
434
|
const page = await Page.findOne({ name: pagename });
|
|
429
435
|
if (!page) {
|
|
430
436
|
req.flash("error", req.__(`Page %s not found`, pagename));
|
package/routes/search.js
CHANGED
|
@@ -28,16 +28,17 @@ const router = new Router();
|
|
|
28
28
|
module.exports = router;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
+
* Search Configuration form
|
|
31
32
|
* @param {object[]} tables
|
|
32
33
|
* @param {object[]} views
|
|
33
34
|
* @param {object} req
|
|
34
35
|
* @returns {Forms}
|
|
35
36
|
*/
|
|
36
37
|
const searchConfigForm = (tables, views, req) => {
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
let fields = [];
|
|
39
|
+
let tbls_noviews = [];
|
|
39
40
|
for (const t of tables) {
|
|
40
|
-
|
|
41
|
+
const ok_views = views.filter(
|
|
41
42
|
(v) =>
|
|
42
43
|
v.table_id === t.id && v.viewtemplateObj && v.viewtemplateObj.runMany
|
|
43
44
|
);
|
|
@@ -70,6 +71,7 @@ const searchConfigForm = (tables, views, req) => {
|
|
|
70
71
|
};
|
|
71
72
|
|
|
72
73
|
/**
|
|
74
|
+
* Show config on GET
|
|
73
75
|
* @name get/config
|
|
74
76
|
* @function
|
|
75
77
|
* @memberof module:routes/search~searchRouter
|
|
@@ -79,7 +81,7 @@ router.get(
|
|
|
79
81
|
"/config",
|
|
80
82
|
isAdmin,
|
|
81
83
|
error_catcher(async (req, res) => {
|
|
82
|
-
|
|
84
|
+
const views = await View.find({}, { orderBy: "name" });
|
|
83
85
|
const tables = await Table.find();
|
|
84
86
|
const form = searchConfigForm(tables, views, req);
|
|
85
87
|
form.values = getState().getConfig("globalSearch");
|
|
@@ -98,6 +100,7 @@ router.get(
|
|
|
98
100
|
);
|
|
99
101
|
|
|
100
102
|
/**
|
|
103
|
+
* Execute config update
|
|
101
104
|
* @name post/config
|
|
102
105
|
* @function
|
|
103
106
|
* @memberof module:routes/search~searchRouter
|
|
@@ -107,7 +110,7 @@ router.post(
|
|
|
107
110
|
"/config",
|
|
108
111
|
isAdmin,
|
|
109
112
|
error_catcher(async (req, res) => {
|
|
110
|
-
|
|
113
|
+
const views = await View.find({}, { orderBy: "name" });
|
|
111
114
|
const tables = await Table.find();
|
|
112
115
|
const form = searchConfigForm(tables, views, req);
|
|
113
116
|
const result = form.validate(req.body);
|
|
@@ -132,6 +135,7 @@ router.post(
|
|
|
132
135
|
);
|
|
133
136
|
|
|
134
137
|
/**
|
|
138
|
+
* Search form
|
|
135
139
|
* @returns {Form}
|
|
136
140
|
*/
|
|
137
141
|
const searchForm = () =>
|
|
@@ -150,6 +154,7 @@ const searchForm = () =>
|
|
|
150
154
|
});
|
|
151
155
|
|
|
152
156
|
/**
|
|
157
|
+
* Run search
|
|
153
158
|
* @param {object} opts
|
|
154
159
|
* @param {*} opts.q
|
|
155
160
|
* @param {*} opts._page
|
|
@@ -161,7 +166,9 @@ const searchForm = () =>
|
|
|
161
166
|
*/
|
|
162
167
|
const runSearch = async ({ q, _page, table }, req, res) => {
|
|
163
168
|
const role = (req.user || {}).role_id || 10;
|
|
169
|
+
// globalSearch contains list of pairs: table, view
|
|
164
170
|
const cfg = getState().getConfig("globalSearch");
|
|
171
|
+
const page_size = getState().getConfig("search_page_size");
|
|
165
172
|
|
|
166
173
|
if (!cfg) {
|
|
167
174
|
req.flash("warning", req.__("Search not configured"));
|
|
@@ -169,7 +176,7 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
169
176
|
return;
|
|
170
177
|
}
|
|
171
178
|
const current_page = parseInt(_page) || 1;
|
|
172
|
-
const offset = (current_page - 1) *
|
|
179
|
+
const offset = (current_page - 1) * page_size;
|
|
173
180
|
let resp = [];
|
|
174
181
|
let tablesWithResults = [];
|
|
175
182
|
let tablesConfigured = 0;
|
|
@@ -182,18 +189,19 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
182
189
|
throw new InvalidConfiguration(
|
|
183
190
|
`View ${viewName} selected as search results for ${tableName}: view not found`
|
|
184
191
|
);
|
|
192
|
+
// search table using view
|
|
185
193
|
const vresps = await view.runMany(
|
|
186
194
|
{ _fts: q },
|
|
187
|
-
{ res, req, limit:
|
|
195
|
+
{ res, req, limit: page_size, offset }
|
|
188
196
|
);
|
|
189
197
|
let paginate = "";
|
|
190
|
-
if (vresps.length ===
|
|
198
|
+
if (vresps.length === page_size || current_page > 1) {
|
|
191
199
|
paginate = pagination({
|
|
192
200
|
current_page,
|
|
193
|
-
pages: current_page + (vresps.length ===
|
|
194
|
-
trailing_ellipsis: vresps.length ===
|
|
201
|
+
pages: current_page + (vresps.length === page_size ? 1 : 0),
|
|
202
|
+
trailing_ellipsis: vresps.length === page_size,
|
|
195
203
|
get_page_link: (n) =>
|
|
196
|
-
`javascript:gopage(${n},
|
|
204
|
+
`javascript:gopage(${n}, ${page_size}, undefined, {table:'${tableName}'})`,
|
|
197
205
|
});
|
|
198
206
|
}
|
|
199
207
|
|
|
@@ -207,9 +215,11 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
207
215
|
}
|
|
208
216
|
}
|
|
209
217
|
|
|
218
|
+
// Prepare search form
|
|
210
219
|
const form = searchForm();
|
|
211
220
|
form.validate({ q });
|
|
212
221
|
|
|
222
|
+
// Prepare search result visualization
|
|
213
223
|
const searchResult =
|
|
214
224
|
resp.length === 0
|
|
215
225
|
? [{ type: "card", contents: req.__("Not found") }]
|
|
@@ -250,6 +260,7 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
250
260
|
};
|
|
251
261
|
|
|
252
262
|
/**
|
|
263
|
+
* Execute search or only show search form
|
|
253
264
|
* @name get
|
|
254
265
|
* @function
|
|
255
266
|
* @memberof module:routes/search~searchRouter
|
|
@@ -258,6 +269,14 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
258
269
|
router.get(
|
|
259
270
|
"/",
|
|
260
271
|
error_catcher(async (req, res) => {
|
|
272
|
+
|
|
273
|
+
const min_role = getState().getConfig("min_role_search");
|
|
274
|
+
const role = (req.user || {}).role_id || 10;
|
|
275
|
+
if(role>min_role){
|
|
276
|
+
res.redirect("/"); // silent redirect to home page
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
261
280
|
if (req.query && req.query.q) {
|
|
262
281
|
await runSearch(req.query, req, res);
|
|
263
282
|
} else {
|
package/routes/utils.js
CHANGED
|
@@ -295,6 +295,11 @@ const addOnDoneRedirect = (oldPath, req) => {
|
|
|
295
295
|
return oldPath;
|
|
296
296
|
};
|
|
297
297
|
|
|
298
|
+
//https://stackoverflow.com/a/38979205/19839414
|
|
299
|
+
const is_relative_url = (url) => {
|
|
300
|
+
return typeof url === "string" && !url.includes(":/") && !url.includes("//");
|
|
301
|
+
};
|
|
302
|
+
|
|
298
303
|
module.exports = {
|
|
299
304
|
sqlsanitize,
|
|
300
305
|
csrfField,
|
|
@@ -308,4 +313,5 @@ module.exports = {
|
|
|
308
313
|
setTenant,
|
|
309
314
|
get_tenant_from_req,
|
|
310
315
|
addOnDoneRedirect,
|
|
316
|
+
is_relative_url,
|
|
311
317
|
};
|
|
Binary file
|
package/tests/auth.test.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
toSucceed,
|
|
13
13
|
resetToFixtures,
|
|
14
14
|
toNotInclude,
|
|
15
|
+
resToLoginCookie,
|
|
15
16
|
} = require("../auth/testhelp");
|
|
16
17
|
const db = require("@saltcorn/data/db");
|
|
17
18
|
const { getState } = require("@saltcorn/data/db/state");
|
|
@@ -99,14 +100,15 @@ describe("user settings", () => {
|
|
|
99
100
|
it("should change language", async () => {
|
|
100
101
|
const app = await getApp({ disableCsrf: true });
|
|
101
102
|
const loginCookie = await getAdminLoginCookie();
|
|
102
|
-
await request(app)
|
|
103
|
+
const res = await request(app)
|
|
103
104
|
.post("/auth/setlanguage")
|
|
104
105
|
.set("Cookie", loginCookie)
|
|
105
106
|
.send("locale=it")
|
|
106
107
|
.expect(toRedirect("/auth/settings"));
|
|
108
|
+
const newCookie = resToLoginCookie(res);
|
|
107
109
|
await request(app)
|
|
108
110
|
.get("/auth/settings")
|
|
109
|
-
.set("Cookie",
|
|
111
|
+
.set("Cookie", newCookie)
|
|
110
112
|
.expect(toInclude("Cambia password"));
|
|
111
113
|
});
|
|
112
114
|
});
|
package/tests/files.test.js
CHANGED
|
@@ -9,9 +9,11 @@ const {
|
|
|
9
9
|
toSucceed,
|
|
10
10
|
toNotInclude,
|
|
11
11
|
resetToFixtures,
|
|
12
|
+
toSucceedWithImage,
|
|
12
13
|
} = require("../auth/testhelp");
|
|
13
14
|
const db = require("@saltcorn/data/db");
|
|
14
15
|
const fs = require("fs").promises;
|
|
16
|
+
const path = require("path");
|
|
15
17
|
const File = require("@saltcorn/data/models/file");
|
|
16
18
|
const Field = require("@saltcorn/data/models/field");
|
|
17
19
|
const Table = require("@saltcorn/data/models/table");
|
|
@@ -20,14 +22,31 @@ const { table } = require("console");
|
|
|
20
22
|
|
|
21
23
|
beforeAll(async () => {
|
|
22
24
|
await resetToFixtures();
|
|
23
|
-
|
|
24
|
-
await fs.writeFile(fnm, "nevergonnagiveyouup");
|
|
25
|
-
};
|
|
25
|
+
await File.ensure_file_store();
|
|
26
26
|
await File.from_req_files(
|
|
27
|
-
{
|
|
27
|
+
{
|
|
28
|
+
mimetype: "image/png",
|
|
29
|
+
name: "rick.png",
|
|
30
|
+
mv: async (fnm) => {
|
|
31
|
+
await fs.writeFile(fnm, "nevergonnagiveyouup");
|
|
32
|
+
},
|
|
33
|
+
size: 245752,
|
|
34
|
+
},
|
|
28
35
|
1,
|
|
29
36
|
4
|
|
30
37
|
);
|
|
38
|
+
await File.from_req_files(
|
|
39
|
+
{
|
|
40
|
+
mimetype: "image/png",
|
|
41
|
+
name: "large-image.png",
|
|
42
|
+
mv: async (fnm) => {
|
|
43
|
+
await fs.copyFile(path.join(__dirname, "assets/large-image.png"), fnm);
|
|
44
|
+
},
|
|
45
|
+
size: 219422,
|
|
46
|
+
},
|
|
47
|
+
1,
|
|
48
|
+
10
|
|
49
|
+
);
|
|
31
50
|
});
|
|
32
51
|
afterAll(db.close);
|
|
33
52
|
|
|
@@ -70,6 +89,28 @@ describe("files admin", () => {
|
|
|
70
89
|
const app = await getApp({ disableCsrf: true });
|
|
71
90
|
await request(app).get("/files/serve/rick.png").expect(404);
|
|
72
91
|
});
|
|
92
|
+
it("serve public file", async () => {
|
|
93
|
+
const app = await getApp({ disableCsrf: true });
|
|
94
|
+
await request(app)
|
|
95
|
+
.get("/files/serve/large-image.png")
|
|
96
|
+
.expect(toSucceedWithImage({ lengthIs: (bs) => bs === 219422 }));
|
|
97
|
+
});
|
|
98
|
+
it("serve resized file", async () => {
|
|
99
|
+
const app = await getApp({ disableCsrf: true });
|
|
100
|
+
await request(app)
|
|
101
|
+
.get("/files/resize/200/100/large-image.png")
|
|
102
|
+
.expect(
|
|
103
|
+
toSucceedWithImage({ lengthIs: (bs) => bs < 100000 && bs > 2000 })
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
it("serve resized file without height", async () => {
|
|
107
|
+
const app = await getApp({ disableCsrf: true });
|
|
108
|
+
await request(app)
|
|
109
|
+
.get("/files/resize/200/0/large-image.png")
|
|
110
|
+
.expect(
|
|
111
|
+
toSucceedWithImage({ lengthIs: (bs) => bs < 100000 && bs > 2000 })
|
|
112
|
+
);
|
|
113
|
+
});
|
|
73
114
|
it("not download file to public", async () => {
|
|
74
115
|
const app = await getApp({ disableCsrf: true });
|
|
75
116
|
await request(app).get("/files/download/rick.png").expect(404);
|