@saltcorn/server 0.8.3-beta.1 → 0.8.3-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.js +5 -0
- package/auth/admin.js +27 -3
- package/auth/resetpw.js +58 -19
- package/auth/routes.js +23 -2
- package/auth/testhelp.js +13 -0
- package/locales/da.json +1 -1
- package/locales/de.json +2 -2
- package/locales/en.json +14 -3
- package/locales/it.json +20 -2
- package/locales/ru.json +2 -2
- package/package.json +11 -10
- package/public/saltcorn-common.js +7 -3
- package/routes/admin.js +68 -0
- package/routes/crashlog.js +0 -1
- package/routes/files.js +53 -1
- package/routes/index.js +0 -4
- package/routes/tables.js +14 -10
- package/tests/admin.test.js +74 -1
- package/tests/auth.test.js +70 -4
package/app.js
CHANGED
|
@@ -42,6 +42,8 @@ const TotpStrategy = require("passport-totp").Strategy;
|
|
|
42
42
|
const JwtStrategy = require("passport-jwt").Strategy;
|
|
43
43
|
const ExtractJwt = require("passport-jwt").ExtractJwt;
|
|
44
44
|
const cors = require("cors");
|
|
45
|
+
const api = require("./routes/api");
|
|
46
|
+
const scapi = require("./routes/scapi");
|
|
45
47
|
|
|
46
48
|
const locales = Object.keys(available_languages);
|
|
47
49
|
// i18n configuration
|
|
@@ -294,6 +296,9 @@ const getApp = async (opts = {}) => {
|
|
|
294
296
|
|
|
295
297
|
app.use(wrapper(version_tag));
|
|
296
298
|
|
|
299
|
+
app.use("/api", api);
|
|
300
|
+
app.use("/scapi", scapi);
|
|
301
|
+
|
|
297
302
|
const csurf = csrf();
|
|
298
303
|
if (!opts.disableCsrf)
|
|
299
304
|
app.use(function (req, res, next) {
|
package/auth/admin.js
CHANGED
|
@@ -200,6 +200,12 @@ const user_dropdown = (user, req, can_reset) =>
|
|
|
200
200
|
'<i class="fas fa-pause"></i> ' + req.__("Disable"),
|
|
201
201
|
req
|
|
202
202
|
),
|
|
203
|
+
!user.disabled &&
|
|
204
|
+
post_dropdown_item(
|
|
205
|
+
`/useradmin/force-logout/${user.id}`,
|
|
206
|
+
'<i class="fas fa-sign-out-alt"></i> ' + req.__("Force logout"),
|
|
207
|
+
req
|
|
208
|
+
),
|
|
203
209
|
div({ class: "dropdown-divider" }),
|
|
204
210
|
post_dropdown_item(
|
|
205
211
|
`/useradmin/delete/${user.id}`,
|
|
@@ -335,7 +341,7 @@ const http_settings_form = async (req) =>
|
|
|
335
341
|
"timeout",
|
|
336
342
|
"cookie_duration",
|
|
337
343
|
"cookie_duration_remember",
|
|
338
|
-
"cookie_sessions",
|
|
344
|
+
//"cookie_sessions",
|
|
339
345
|
"public_cache_maxage",
|
|
340
346
|
"custom_http_headers",
|
|
341
347
|
],
|
|
@@ -972,7 +978,8 @@ router.post(
|
|
|
972
978
|
|
|
973
979
|
req.flash("success", req.__(`User %s created`, email) + pwflash);
|
|
974
980
|
|
|
975
|
-
if (rnd_password && send_pwreset_email)
|
|
981
|
+
if (rnd_password && send_pwreset_email)
|
|
982
|
+
await send_reset_email(u, req, { creating: true });
|
|
976
983
|
}
|
|
977
984
|
}
|
|
978
985
|
res.redirect(`/useradmin`);
|
|
@@ -991,7 +998,7 @@ router.post(
|
|
|
991
998
|
error_catcher(async (req, res) => {
|
|
992
999
|
const { id } = req.params;
|
|
993
1000
|
const u = await User.findOne({ id });
|
|
994
|
-
await send_reset_email(u, req);
|
|
1001
|
+
await send_reset_email(u, req, { from_admin: true });
|
|
995
1002
|
req.flash("success", req.__(`Reset password link sent to %s`, u.email));
|
|
996
1003
|
|
|
997
1004
|
res.redirect(`/useradmin`);
|
|
@@ -1136,6 +1143,23 @@ router.post(
|
|
|
1136
1143
|
})
|
|
1137
1144
|
);
|
|
1138
1145
|
|
|
1146
|
+
/**
|
|
1147
|
+
* @name post/force-logout/:id
|
|
1148
|
+
* @function
|
|
1149
|
+
* @memberof module:auth/admin~auth/adminRouter
|
|
1150
|
+
*/
|
|
1151
|
+
router.post(
|
|
1152
|
+
"/force-logout/:id",
|
|
1153
|
+
isAdmin,
|
|
1154
|
+
error_catcher(async (req, res) => {
|
|
1155
|
+
const { id } = req.params;
|
|
1156
|
+
const u = await User.findOne({ id });
|
|
1157
|
+
await u.destroy_sessions();
|
|
1158
|
+
req.flash("success", req.__(`Logged out user %s`, u.email));
|
|
1159
|
+
res.redirect(`/useradmin`);
|
|
1160
|
+
})
|
|
1161
|
+
);
|
|
1162
|
+
|
|
1139
1163
|
/**
|
|
1140
1164
|
* @name post/enable/:id
|
|
1141
1165
|
* @function
|
package/auth/resetpw.js
CHANGED
|
@@ -13,49 +13,88 @@ const { get_base_url } = require("../routes/utils");
|
|
|
13
13
|
* @param {object} req
|
|
14
14
|
* @returns {void}
|
|
15
15
|
*/
|
|
16
|
-
const generate_email = (link, user, req) =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const generate_email = (link, user, req, options) => {
|
|
17
|
+
const subject = options?.creating
|
|
18
|
+
? req.__(`Welcome to %s`, getState().getConfig("site_name", "Saltcorn"))
|
|
19
|
+
: req.__("Reset password instructions");
|
|
20
|
+
const initial = options?.creating
|
|
21
|
+
? req.__(
|
|
22
|
+
"We have created an account for you on %s. You can set your new password through this link: ",
|
|
23
|
+
getState().getConfig("site_name", "Saltcorn")
|
|
24
|
+
)
|
|
25
|
+
: options?.from_admin
|
|
26
|
+
? req.__(
|
|
27
|
+
"We request that you change your password on %s. You can set your new password through this link: ",
|
|
28
|
+
getState().getConfig("site_name", "Saltcorn")
|
|
29
|
+
)
|
|
30
|
+
: req.__(
|
|
31
|
+
"You have requested a link to change your password. You can do this through this link:"
|
|
32
|
+
);
|
|
33
|
+
const base_url = getState().getConfig("base_url", "");
|
|
34
|
+
const final =
|
|
35
|
+
options?.creating && base_url
|
|
36
|
+
? req.__(
|
|
37
|
+
"Use this link to access the application once you have set your password: %s",
|
|
38
|
+
`<a href="${base_url}">${base_url}</a>`
|
|
39
|
+
)
|
|
40
|
+
: "";
|
|
41
|
+
const finalTxt =
|
|
42
|
+
options?.creating && base_url
|
|
43
|
+
? req.__(
|
|
44
|
+
"Use this link to access the application once you have set your password: %s",
|
|
45
|
+
base_url
|
|
46
|
+
)
|
|
47
|
+
: "";
|
|
48
|
+
return {
|
|
49
|
+
from: getState().getConfig("email_from"),
|
|
50
|
+
to: user.email,
|
|
51
|
+
subject,
|
|
52
|
+
text: `${req.__("Hi %s", user.email)},
|
|
21
53
|
|
|
22
|
-
${
|
|
23
|
-
"You have requested a link to change your password. You can do this through this link:"
|
|
24
|
-
)}
|
|
54
|
+
${initial}
|
|
25
55
|
|
|
26
56
|
${link}
|
|
27
57
|
|
|
28
|
-
${
|
|
29
|
-
|
|
58
|
+
${
|
|
59
|
+
!options?.creating && !options?.from_admin
|
|
60
|
+
? req.__("If you did not request this, please ignore this email.") + "\n"
|
|
61
|
+
: ""
|
|
62
|
+
}
|
|
30
63
|
${req.__(
|
|
31
64
|
"Your password will not change until you access the link above and set a new one."
|
|
32
65
|
)}
|
|
66
|
+
|
|
67
|
+
${finalTxt}
|
|
33
68
|
`,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
${req.__(
|
|
37
|
-
"You have requested a link to change your password. You can do this through this link:"
|
|
38
|
-
)}<br />
|
|
69
|
+
html: `${req.__("Hi %s", user.email)},<br /><br />
|
|
70
|
+
${initial}<br />
|
|
39
71
|
<br />
|
|
40
72
|
<a href="${link}">${req.__("Change my password")}</a><br />
|
|
41
73
|
<br />
|
|
42
|
-
${
|
|
74
|
+
${
|
|
75
|
+
!options?.creating && !options?.from_admin
|
|
76
|
+
? req.__("If you did not request this, please ignore this email.") +
|
|
77
|
+
"<br />"
|
|
78
|
+
: ""
|
|
79
|
+
}
|
|
43
80
|
<br />
|
|
44
81
|
${req.__(
|
|
45
82
|
"Your password will not change until you access the link above and set a new one."
|
|
46
83
|
)}<br />
|
|
84
|
+
${final ? `<br />${final}<br />` : ""}
|
|
47
85
|
`,
|
|
48
|
-
}
|
|
86
|
+
};
|
|
87
|
+
};
|
|
49
88
|
|
|
50
89
|
/**
|
|
51
90
|
* @param {object} user
|
|
52
91
|
* @param {object} req
|
|
53
92
|
* @returns {Promise<void>}
|
|
54
93
|
*/
|
|
55
|
-
const send_reset_email = async (user, req) => {
|
|
94
|
+
const send_reset_email = async (user, req, options = {}) => {
|
|
56
95
|
const link = await get_reset_link(user, req);
|
|
57
96
|
const transporter = getMailTransport();
|
|
58
|
-
await transporter.sendMail(generate_email(link, user, req));
|
|
97
|
+
await transporter.sendMail(generate_email(link, user, req, options));
|
|
59
98
|
};
|
|
60
99
|
|
|
61
100
|
/**
|
package/auth/routes.js
CHANGED
|
@@ -112,6 +112,9 @@ const loginForm = (req, isCreating) => {
|
|
|
112
112
|
label: req.__("Password"),
|
|
113
113
|
name: "password",
|
|
114
114
|
input_type: "password",
|
|
115
|
+
attributes: {
|
|
116
|
+
autocomplete: "current-password",
|
|
117
|
+
},
|
|
115
118
|
validator: isCreating
|
|
116
119
|
? (pw) => User.unacceptable_password_reason(pw)
|
|
117
120
|
: undefined,
|
|
@@ -1203,11 +1206,17 @@ const changPwForm = (req) =>
|
|
|
1203
1206
|
label: req.__("Old password"),
|
|
1204
1207
|
name: "password",
|
|
1205
1208
|
input_type: "password",
|
|
1209
|
+
attributes: {
|
|
1210
|
+
autocomplete: "current-password",
|
|
1211
|
+
},
|
|
1206
1212
|
},
|
|
1207
1213
|
{
|
|
1208
1214
|
label: req.__("New password"),
|
|
1209
1215
|
name: "new_password",
|
|
1210
1216
|
input_type: "password",
|
|
1217
|
+
attributes: {
|
|
1218
|
+
autocomplete: "new-password",
|
|
1219
|
+
},
|
|
1211
1220
|
validator: (pw) => User.unacceptable_password_reason(pw),
|
|
1212
1221
|
},
|
|
1213
1222
|
],
|
|
@@ -1353,14 +1362,14 @@ const userSettings = async ({ req, res, pwform, user }) => {
|
|
|
1353
1362
|
href: "/auth/twofa/disable/totp",
|
|
1354
1363
|
class: "btn btn-danger mt-2",
|
|
1355
1364
|
},
|
|
1356
|
-
req.__("Disable
|
|
1365
|
+
req.__("Disable 2FA")
|
|
1357
1366
|
)
|
|
1358
1367
|
: a(
|
|
1359
1368
|
{
|
|
1360
1369
|
href: "/auth/twofa/setup/totp",
|
|
1361
1370
|
class: "btn btn-primary mt-2",
|
|
1362
1371
|
},
|
|
1363
|
-
req.__("Enable
|
|
1372
|
+
req.__("Enable 2FA")
|
|
1364
1373
|
)
|
|
1365
1374
|
),
|
|
1366
1375
|
],
|
|
@@ -1791,6 +1800,12 @@ const totpForm = (req, action) =>
|
|
|
1791
1800
|
name: "totpCode",
|
|
1792
1801
|
label: req.__("Code"),
|
|
1793
1802
|
type: "Integer",
|
|
1803
|
+
attributes: {
|
|
1804
|
+
type: "text",
|
|
1805
|
+
inputmode: "numeric",
|
|
1806
|
+
pattern: "[0-9]*",
|
|
1807
|
+
autocomplete: "one-time-code",
|
|
1808
|
+
},
|
|
1794
1809
|
required: true,
|
|
1795
1810
|
},
|
|
1796
1811
|
],
|
|
@@ -1828,6 +1843,12 @@ router.get(
|
|
|
1828
1843
|
name: "code",
|
|
1829
1844
|
label: req.__("Code"),
|
|
1830
1845
|
type: "Integer",
|
|
1846
|
+
attributes: {
|
|
1847
|
+
type: "text",
|
|
1848
|
+
inputmode: "numeric",
|
|
1849
|
+
pattern: "[0-9]*",
|
|
1850
|
+
autocomplete: "one-time-code",
|
|
1851
|
+
},
|
|
1831
1852
|
required: true,
|
|
1832
1853
|
},
|
|
1833
1854
|
],
|
package/auth/testhelp.js
CHANGED
|
@@ -163,6 +163,18 @@ const itShouldRedirectUnauthToLogin = (path, dest) => {
|
|
|
163
163
|
});
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
+
const itShouldIncludeTextForAdmin = (path, text) => {
|
|
167
|
+
it(`should show admin ${text} on ${path}`, async () => {
|
|
168
|
+
const app = await getApp({ disableCsrf: true });
|
|
169
|
+
const loginCookie = await getAdminLoginCookie();
|
|
170
|
+
await request(app)
|
|
171
|
+
.get(path)
|
|
172
|
+
.set("Cookie", loginCookie)
|
|
173
|
+
.expect(200)
|
|
174
|
+
.expect(toInclude(text));
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
166
178
|
/**
|
|
167
179
|
* @returns {Promise<void>}
|
|
168
180
|
*/
|
|
@@ -234,4 +246,5 @@ module.exports = {
|
|
|
234
246
|
respondJsonWith,
|
|
235
247
|
toSucceedWithImage,
|
|
236
248
|
resToLoginCookie,
|
|
249
|
+
itShouldIncludeTextForAdmin,
|
|
237
250
|
};
|
package/locales/da.json
CHANGED
|
@@ -566,7 +566,7 @@
|
|
|
566
566
|
"Generate": "Generate",
|
|
567
567
|
"Two-factor authentication": "Two-factor authentication",
|
|
568
568
|
"Two-factor authentication is disabled": "Two-factor authentication is disabled",
|
|
569
|
-
"Enable
|
|
569
|
+
"Enable 2FA": "Enable 2FA",
|
|
570
570
|
"Pattern": "Pattern",
|
|
571
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
572
|
"Views potentially affected": "Views potentially affected",
|
package/locales/de.json
CHANGED
|
@@ -892,9 +892,9 @@
|
|
|
892
892
|
"Strings": "Strings",
|
|
893
893
|
"In default language": "In Standardsprache",
|
|
894
894
|
"In %s": "In %s",
|
|
895
|
-
"Enable
|
|
895
|
+
"Enable 2FA": "2FA aktivieren",
|
|
896
896
|
"Or enter this code:": "Oder gebe diesen Code ein:",
|
|
897
|
-
"Disable TWA": "
|
|
897
|
+
"Disable TWA": "2FA deaktivieren",
|
|
898
898
|
"Cascade delete to file": "Kaskadierendes Löschen der Datei",
|
|
899
899
|
"Deleting a row will also delete the file referenced by this field": "Löschen einer Zeile löscht auch die in diesem Feld referenzierte Datei",
|
|
900
900
|
"Has channels?": "Has channels?",
|
package/locales/en.json
CHANGED
|
@@ -892,9 +892,9 @@
|
|
|
892
892
|
"Strings": "Strings",
|
|
893
893
|
"In default language": "In default language",
|
|
894
894
|
"In %s": "In %s",
|
|
895
|
-
"Enable
|
|
895
|
+
"Enable 2FA": "Enable 2FA",
|
|
896
896
|
"Or enter this code:": "Or enter this code:",
|
|
897
|
-
"Disable
|
|
897
|
+
"Disable 2FA": "Disable 2FA",
|
|
898
898
|
"Cascade delete to file": "Cascade delete to file",
|
|
899
899
|
"Deleting a row will also delete the file referenced by this field": "Deleting a row will also delete the file referenced by this field",
|
|
900
900
|
"Has channels?": "Has channels?",
|
|
@@ -1093,5 +1093,16 @@
|
|
|
1093
1093
|
"Min role to access search page": "Min role to access search page",
|
|
1094
1094
|
"Update": "Update",
|
|
1095
1095
|
"Add": "Add",
|
|
1096
|
-
"Recalculate dynamic": "Recalculate dynamic"
|
|
1096
|
+
"Recalculate dynamic": "Recalculate dynamic",
|
|
1097
|
+
"Force logout": "Force logout",
|
|
1098
|
+
"Logged out user %s": "Logged out user %s",
|
|
1099
|
+
"Reset password link sent to %s": "Reset password link sent to %s",
|
|
1100
|
+
"We request that you change your password on %s. You can set your new password through this link: ": "We request that you change your password on %s. You can set your new password through this link: ",
|
|
1101
|
+
"Welcome to %s": "Welcome to %s",
|
|
1102
|
+
"We have created an account for you on %s. You can set your new password through this link: ": "We have created an account for you on %s. You can set your new password through this link: ",
|
|
1103
|
+
"Use this link to access the application once you have set your password: %s": "Use this link to access the application once you have set your password: %s",
|
|
1104
|
+
"Tag: %s": "Tag: %s",
|
|
1105
|
+
"Tag entry": "Tag entry",
|
|
1106
|
+
"Clear": "Clear",
|
|
1107
|
+
"Restore a snapshot": "Restore a snapshot"
|
|
1097
1108
|
}
|
package/locales/it.json
CHANGED
|
@@ -478,12 +478,30 @@
|
|
|
478
478
|
"Generate": "Generate",
|
|
479
479
|
"Two-factor authentication": "Two-factor authentication",
|
|
480
480
|
"Two-factor authentication is disabled": "Two-factor authentication is disabled",
|
|
481
|
-
"Enable
|
|
481
|
+
"Enable 2FA": "Enable 2FA",
|
|
482
482
|
"Modules": "Modules",
|
|
483
483
|
"Login and Signup": "Login and Signup",
|
|
484
484
|
"Table access": "Table access",
|
|
485
485
|
"HTTP": "HTTP",
|
|
486
486
|
"Rights": "Rights",
|
|
487
487
|
"Permissions": "Permissions",
|
|
488
|
-
"Become user": "Become user"
|
|
488
|
+
"Become user": "Become user",
|
|
489
|
+
"Force logout": "Force logout",
|
|
490
|
+
"Subdomain": "Subdomain",
|
|
491
|
+
"Creator email": "Creator email",
|
|
492
|
+
"Created": "Created",
|
|
493
|
+
"Information": "Information",
|
|
494
|
+
"Found %s tenants": "Found %s tenants",
|
|
495
|
+
"Create new tenant": "Create new tenant",
|
|
496
|
+
"Library": "Library",
|
|
497
|
+
"Languages": "Languages",
|
|
498
|
+
"Multitenancy": "Multitenancy",
|
|
499
|
+
"Tags": "Tags",
|
|
500
|
+
"Diagram": "Diagram",
|
|
501
|
+
"%s tenant statistics": "%s tenant statistics",
|
|
502
|
+
"First user E-mail": "First user E-mail",
|
|
503
|
+
"Table columns": "Table columns",
|
|
504
|
+
"Configuration items": "Configuration items",
|
|
505
|
+
"Crashlogs": "Crashlogs",
|
|
506
|
+
"Logged out user %s": "Logged out user %s"
|
|
489
507
|
}
|
package/locales/ru.json
CHANGED
|
@@ -780,8 +780,8 @@
|
|
|
780
780
|
"Or enter this code:": "Или введите этот код:",
|
|
781
781
|
"Two-factor authentication with Time-based One-Time Password enabled": "Двухфакторная аутентификаиця с использованием Time-based One-Time Password",
|
|
782
782
|
"Two-factor authentication is enabled": "Двухфакторная аутентификация включена",
|
|
783
|
-
"Disable
|
|
784
|
-
"Enable
|
|
783
|
+
"Disable 2FA": "Отключить TWA",
|
|
784
|
+
"Enable 2FA": "Включить TWA",
|
|
785
785
|
"Deleted all rows": "Удалены все строки",
|
|
786
786
|
"Use this link: <a href=\"%s\">%s</a> to revisit your application at any time.": "В дальнейшем используйте ссылку: <a href=\"%s\">%s</a> для входа в созданное приложение.",
|
|
787
787
|
"To login to a previously created application, go to: ": "Чтобы авторизоваться в ранее созданном приложении, перейдите по ссылке: ",
|
package/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.3-beta.
|
|
3
|
+
"version": "0.8.3-beta.2",
|
|
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-beta.
|
|
10
|
-
"@saltcorn/builder": "0.8.3-beta.
|
|
11
|
-
"@saltcorn/data": "0.8.3-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.8.3-beta.
|
|
13
|
-
"@saltcorn/filemanager": "0.8.3-beta.
|
|
14
|
-
"@saltcorn/markup": "0.8.3-beta.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.3-beta.
|
|
16
|
-
"@socket.io/cluster-adapter": "^0.1
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.3-beta.2",
|
|
10
|
+
"@saltcorn/builder": "0.8.3-beta.2",
|
|
11
|
+
"@saltcorn/data": "0.8.3-beta.2",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.3-beta.2",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.3-beta.2",
|
|
14
|
+
"@saltcorn/markup": "0.8.3-beta.2",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.3-beta.2",
|
|
16
|
+
"@socket.io/cluster-adapter": "^0.2.1",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
|
+
"adm-zip": "0.5.10",
|
|
18
19
|
"aws-sdk": "^2.1037.0",
|
|
19
20
|
"connect-flash": "^0.1.1",
|
|
20
21
|
"connect-pg-simple": "^6.1.0",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"greenlock": "^4.0.4",
|
|
33
34
|
"greenlock-express": "^4.0.3",
|
|
34
35
|
"helmet": "^3.23.3",
|
|
35
|
-
"i18n": "^0.
|
|
36
|
+
"i18n": "^0.15.1",
|
|
36
37
|
"jsonwebtoken": "^9.0.0",
|
|
37
38
|
"live-plugin-manager": "^0.16.0",
|
|
38
39
|
"moment": "^2.29.4",
|
|
@@ -198,9 +198,13 @@ function apply_showif() {
|
|
|
198
198
|
const $def = e
|
|
199
199
|
.closest(".form-namespace")
|
|
200
200
|
.find("input[name=_columndef]");
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
try {
|
|
202
|
+
const def = JSON.parse($def.val());
|
|
203
|
+
def[k] = v;
|
|
204
|
+
$def.val(JSON.stringify(def));
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.error("Invalid json", e);
|
|
207
|
+
}
|
|
204
208
|
});
|
|
205
209
|
};
|
|
206
210
|
|
package/routes/admin.js
CHANGED
|
@@ -69,6 +69,7 @@ const {
|
|
|
69
69
|
restore,
|
|
70
70
|
auto_backup_now,
|
|
71
71
|
} = require("@saltcorn/admin-models/models/backup");
|
|
72
|
+
const { install_pack } = require("@saltcorn/admin-models/models/pack");
|
|
72
73
|
const Snapshot = require("@saltcorn/admin-models/models/snapshot");
|
|
73
74
|
const {
|
|
74
75
|
runConfigurationCheck,
|
|
@@ -98,6 +99,7 @@ const { getConfigFile } = require("@saltcorn/data/db/connect");
|
|
|
98
99
|
const os = require("os");
|
|
99
100
|
const Page = require("@saltcorn/data/models/page");
|
|
100
101
|
const { getSafeSaltcornCmd } = require("@saltcorn/data/utils");
|
|
102
|
+
const stream = require("stream");
|
|
101
103
|
|
|
102
104
|
const router = new Router();
|
|
103
105
|
module.exports = router;
|
|
@@ -426,6 +428,36 @@ router.get(
|
|
|
426
428
|
a(
|
|
427
429
|
{ href: "/admin/snapshot-list" },
|
|
428
430
|
req.__("List/download snapshots »")
|
|
431
|
+
),
|
|
432
|
+
form(
|
|
433
|
+
{
|
|
434
|
+
method: "post",
|
|
435
|
+
action: "/admin/snapshot-restore-full",
|
|
436
|
+
encType: "multipart/form-data",
|
|
437
|
+
},
|
|
438
|
+
input({
|
|
439
|
+
type: "hidden",
|
|
440
|
+
name: "_csrf",
|
|
441
|
+
value: req.csrfToken(),
|
|
442
|
+
}),
|
|
443
|
+
label(
|
|
444
|
+
{
|
|
445
|
+
class: "btn-link",
|
|
446
|
+
for: "upload_to_snapshot",
|
|
447
|
+
style: { cursor: "pointer" },
|
|
448
|
+
},
|
|
449
|
+
i({ class: "fas fa-upload me-2 mt-2" }),
|
|
450
|
+
req.__("Restore a snapshot")
|
|
451
|
+
),
|
|
452
|
+
input({
|
|
453
|
+
id: "upload_to_snapshot",
|
|
454
|
+
class: "d-none",
|
|
455
|
+
name: "file",
|
|
456
|
+
type: "file",
|
|
457
|
+
accept: ".json,application/json",
|
|
458
|
+
onchange:
|
|
459
|
+
"notifyAlert('Restoring snapshot...', true);this.form.submit();",
|
|
460
|
+
})
|
|
429
461
|
)
|
|
430
462
|
),
|
|
431
463
|
},
|
|
@@ -555,6 +587,15 @@ router.get(
|
|
|
555
587
|
error_catcher(async (req, res) => {
|
|
556
588
|
const { id } = req.params;
|
|
557
589
|
const snap = await Snapshot.findOne({ id });
|
|
590
|
+
const readStream = new stream.PassThrough();
|
|
591
|
+
readStream.end(JSON.stringify(snap.pack));
|
|
592
|
+
res.type("application/json");
|
|
593
|
+
res.attachment(
|
|
594
|
+
`${getState().getConfig("site_name", db.getTenantSchema())}-snapshot-${
|
|
595
|
+
snap.id
|
|
596
|
+
}.json`
|
|
597
|
+
);
|
|
598
|
+
readStream.pipe(res);
|
|
558
599
|
res.send(snap.pack);
|
|
559
600
|
})
|
|
560
601
|
);
|
|
@@ -606,6 +647,33 @@ router.post(
|
|
|
606
647
|
res.redirect(/^[a-z]+$/g.test(type) ? `/${type}edit` : "/");
|
|
607
648
|
})
|
|
608
649
|
);
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* @name post/restore
|
|
653
|
+
* @function
|
|
654
|
+
* @memberof module:routes/admin~routes/adminRouter
|
|
655
|
+
*/
|
|
656
|
+
router.post(
|
|
657
|
+
"/snapshot-restore-full",
|
|
658
|
+
setTenant, // TODO why is this needed?????
|
|
659
|
+
isAdmin,
|
|
660
|
+
error_catcher(async (req, res) => {
|
|
661
|
+
if (req.files?.file?.tempFilePath) {
|
|
662
|
+
try {
|
|
663
|
+
const pack = JSON.parse(fs.readFileSync(req.files?.file?.tempFilePath));
|
|
664
|
+
await install_pack(pack, undefined, (p) =>
|
|
665
|
+
load_plugins.loadAndSaveNewPlugin(p)
|
|
666
|
+
);
|
|
667
|
+
req.flash("success", req.__("Snapshot restored"));
|
|
668
|
+
} catch (e) {
|
|
669
|
+
console.error(e);
|
|
670
|
+
req.flash("error", e.message);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
res.redirect(`/admin/backup`);
|
|
674
|
+
})
|
|
675
|
+
);
|
|
676
|
+
|
|
609
677
|
router.get(
|
|
610
678
|
"/auto-backup-download/:filename",
|
|
611
679
|
isAdmin,
|
package/routes/crashlog.js
CHANGED
package/routes/files.js
CHANGED
|
@@ -25,7 +25,9 @@ const {
|
|
|
25
25
|
} = require("../markup/admin");
|
|
26
26
|
const fs = require("fs");
|
|
27
27
|
const path = require("path");
|
|
28
|
-
|
|
28
|
+
const Zip = require("adm-zip");
|
|
29
|
+
const stream = require("stream");
|
|
30
|
+
const { extract } = require("@saltcorn/admin-models/models/backup");
|
|
29
31
|
/**
|
|
30
32
|
* @type {object}
|
|
31
33
|
* @const
|
|
@@ -143,6 +145,36 @@ router.get(
|
|
|
143
145
|
})
|
|
144
146
|
);
|
|
145
147
|
|
|
148
|
+
router.post(
|
|
149
|
+
"/download-zip",
|
|
150
|
+
isAdmin,
|
|
151
|
+
|
|
152
|
+
error_catcher(async (req, res) => {
|
|
153
|
+
const role = req.user && req.user.id ? req.user.role_id : 10;
|
|
154
|
+
const user_id = req.user && req.user.id;
|
|
155
|
+
const files = req.body.files;
|
|
156
|
+
const location = req.body.location;
|
|
157
|
+
const zip = new Zip();
|
|
158
|
+
|
|
159
|
+
for (const fileNm of files) {
|
|
160
|
+
const file = await File.findOne(path.join(location, fileNm));
|
|
161
|
+
if (
|
|
162
|
+
file &&
|
|
163
|
+
(role <= file.min_role_read || (user_id && user_id === file.user_id))
|
|
164
|
+
) {
|
|
165
|
+
zip.addLocalFile(file.location);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const readStream = new stream.PassThrough();
|
|
169
|
+
readStream.end(zip.toBuffer());
|
|
170
|
+
res.type("application/zip");
|
|
171
|
+
res.attachment(
|
|
172
|
+
`${getState().getConfig("site_name", db.getTenantSchema())}-files.zip`
|
|
173
|
+
);
|
|
174
|
+
readStream.pipe(res);
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
|
|
146
178
|
/**
|
|
147
179
|
* @name get/serve/:id
|
|
148
180
|
* @function
|
|
@@ -299,6 +331,26 @@ router.post(
|
|
|
299
331
|
})
|
|
300
332
|
);
|
|
301
333
|
|
|
334
|
+
/**
|
|
335
|
+
* @name post/unzip/:id
|
|
336
|
+
* @function
|
|
337
|
+
* @memberof module:routes/files~filesRouter
|
|
338
|
+
* @function
|
|
339
|
+
*/
|
|
340
|
+
router.post(
|
|
341
|
+
"/unzip/*",
|
|
342
|
+
isAdmin,
|
|
343
|
+
error_catcher(async (req, res) => {
|
|
344
|
+
const serve_path = req.params[0];
|
|
345
|
+
const filename = req.body.value;
|
|
346
|
+
|
|
347
|
+
const file = await File.findOne(serve_path);
|
|
348
|
+
const dir = path.dirname(file.location);
|
|
349
|
+
if (file) await extract(file.location, dir);
|
|
350
|
+
res.redirect(`/files?dir=${encodeURIComponent(file.current_folder)}`);
|
|
351
|
+
})
|
|
352
|
+
);
|
|
353
|
+
|
|
302
354
|
router.post(
|
|
303
355
|
"/new-folder",
|
|
304
356
|
isAdmin,
|
package/routes/index.js
CHANGED
|
@@ -58,7 +58,6 @@ const events = require("./events");
|
|
|
58
58
|
const tenant = require("./tenant");
|
|
59
59
|
const library = require("./library");
|
|
60
60
|
const settings = require("./settings");
|
|
61
|
-
const api = require("./api");
|
|
62
61
|
const plugins = require("./plugins");
|
|
63
62
|
const packs = require("./packs");
|
|
64
63
|
const edit = require("./edit");
|
|
@@ -69,7 +68,6 @@ const del = require("./delete");
|
|
|
69
68
|
const auth = require("../auth/routes");
|
|
70
69
|
const useradmin = require("../auth/admin");
|
|
71
70
|
const roleadmin = require("../auth/roleadmin");
|
|
72
|
-
const scapi = require("./scapi");
|
|
73
71
|
const tags = require("./tags");
|
|
74
72
|
const tagentries = require("./tag_entries");
|
|
75
73
|
const dataDiagram = require("./diagram");
|
|
@@ -102,13 +100,11 @@ module.exports =
|
|
|
102
100
|
app.use("/search", search);
|
|
103
101
|
app.use("/admin", admin);
|
|
104
102
|
app.use("/tenant", tenant);
|
|
105
|
-
app.use("/api", api);
|
|
106
103
|
app.use("/viewedit", viewedit);
|
|
107
104
|
app.use("/delete", del);
|
|
108
105
|
app.use("/auth", auth);
|
|
109
106
|
app.use("/useradmin", useradmin);
|
|
110
107
|
app.use("/roleadmin", roleadmin);
|
|
111
|
-
app.use("/scapi", scapi);
|
|
112
108
|
app.use("/tag", tags);
|
|
113
109
|
app.use("/tag-entries", tagentries);
|
|
114
110
|
app.use("/diagram", dataDiagram);
|
package/routes/tables.js
CHANGED
|
@@ -430,7 +430,7 @@ router.get(
|
|
|
430
430
|
label: `<b>${t.name}</b>\n${t.fields
|
|
431
431
|
.map((f) => `${f.name} : ${f.pretty_type}`)
|
|
432
432
|
.join("\n")}`,
|
|
433
|
-
title: t.description? t.description : t.name,
|
|
433
|
+
title: t.description ? t.description : t.name,
|
|
434
434
|
})),
|
|
435
435
|
edges,
|
|
436
436
|
};
|
|
@@ -652,19 +652,21 @@ router.get(
|
|
|
652
652
|
if (views.length > 0) {
|
|
653
653
|
viewCardContents = mkTable(
|
|
654
654
|
[
|
|
655
|
-
{ label: req.__("Name"), key: "name" },
|
|
656
|
-
{ label: req.__("Pattern"), key: "viewtemplate" },
|
|
657
655
|
{
|
|
658
|
-
label: req.__("
|
|
659
|
-
key: (r) =>
|
|
660
|
-
link(`/view/${encodeURIComponent(r.name)}`, req.__("Run")),
|
|
656
|
+
label: req.__("Name"),
|
|
657
|
+
key: (r) => link(`/view/${encodeURIComponent(r.name)}`, r.name),
|
|
661
658
|
},
|
|
659
|
+
{ label: req.__("Pattern"), key: "viewtemplate" },
|
|
662
660
|
{
|
|
663
|
-
label: req.__("
|
|
661
|
+
label: req.__("Configure"),
|
|
664
662
|
key: (r) =>
|
|
665
663
|
link(
|
|
666
|
-
`/viewedit/
|
|
667
|
-
|
|
664
|
+
`/viewedit/config/${encodeURIComponent(
|
|
665
|
+
r.name
|
|
666
|
+
)}?on_done_redirect=${encodeURIComponent(
|
|
667
|
+
`table/${table.name}`
|
|
668
|
+
)}`,
|
|
669
|
+
req.__("Configure")
|
|
668
670
|
),
|
|
669
671
|
},
|
|
670
672
|
{
|
|
@@ -692,7 +694,9 @@ router.get(
|
|
|
692
694
|
viewCardContents +
|
|
693
695
|
a(
|
|
694
696
|
{
|
|
695
|
-
href: `/viewedit/new?table=${encodeURIComponent(
|
|
697
|
+
href: `/viewedit/new?table=${encodeURIComponent(
|
|
698
|
+
table.name
|
|
699
|
+
)}&on_done_redirect=${encodeURIComponent(`table/${table.name}`)}`,
|
|
696
700
|
class: "btn btn-primary",
|
|
697
701
|
},
|
|
698
702
|
req.__("Create view")
|
package/tests/admin.test.js
CHANGED
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
getAdminLoginCookie,
|
|
6
6
|
toRedirect,
|
|
7
7
|
itShouldRedirectUnauthToLogin,
|
|
8
|
+
itShouldIncludeTextForAdmin,
|
|
8
9
|
toInclude,
|
|
9
10
|
toSucceed,
|
|
10
11
|
//toNotInclude,
|
|
@@ -73,6 +74,7 @@ describe("admin page", () => {
|
|
|
73
74
|
["/admin/email", "Email settings"],
|
|
74
75
|
["/admin/system", "Restart server"],
|
|
75
76
|
["/admin/dev", "Development"],
|
|
77
|
+
["/admin/build-mobile-app", "Build mobile app"],
|
|
76
78
|
]);
|
|
77
79
|
adminPageContains([
|
|
78
80
|
["/useradmin", "Create user"],
|
|
@@ -530,9 +532,80 @@ describe("localizer", () => {
|
|
|
530
532
|
.expect(toRedirect("/site-structure/localizer/edit/da"));
|
|
531
533
|
});
|
|
532
534
|
});
|
|
535
|
+
/**
|
|
536
|
+
* Diagram tests
|
|
537
|
+
*/
|
|
538
|
+
describe("diagram", () => {
|
|
539
|
+
itShouldRedirectUnauthToLogin("/diagram");
|
|
540
|
+
itShouldIncludeTextForAdmin("/diagram", ">All entities<");
|
|
541
|
+
|
|
542
|
+
it("get data diagram", async () => {
|
|
543
|
+
const app = await getApp({ disableCsrf: true });
|
|
544
|
+
const loginCookie = await getAdminLoginCookie();
|
|
545
|
+
await request(app)
|
|
546
|
+
.get("/diagram/data")
|
|
547
|
+
.set("Cookie", loginCookie)
|
|
548
|
+
.expect(
|
|
549
|
+
respondJsonWith(
|
|
550
|
+
200,
|
|
551
|
+
({ elements, style }) => elements && style.length > 3
|
|
552
|
+
)
|
|
553
|
+
);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
533
556
|
|
|
534
557
|
/**
|
|
535
|
-
*
|
|
558
|
+
* Diagram tests
|
|
559
|
+
*/
|
|
560
|
+
describe("tags", () => {
|
|
561
|
+
itShouldRedirectUnauthToLogin("/tag");
|
|
562
|
+
itShouldIncludeTextForAdmin("/tag", ">Create tag<");
|
|
563
|
+
itShouldIncludeTextForAdmin("/tag/new", ">Create<");
|
|
564
|
+
it("creates new tag", async () => {
|
|
565
|
+
const app = await getApp({ disableCsrf: true });
|
|
566
|
+
const loginCookie = await getAdminLoginCookie();
|
|
567
|
+
await request(app)
|
|
568
|
+
.post("/tag")
|
|
569
|
+
.set("Cookie", loginCookie)
|
|
570
|
+
.send("name=MyNewTestTag")
|
|
571
|
+
.expect(toRedirect("/tag/1?show_list=tables"));
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
itShouldIncludeTextForAdmin("/tag", "MyNewTestTag");
|
|
575
|
+
itShouldIncludeTextForAdmin("/tag/1", "MyNewTestTag");
|
|
576
|
+
itShouldIncludeTextForAdmin("/tag-entries/add/tables/1", "Add entries");
|
|
577
|
+
itShouldIncludeTextForAdmin("/tag-entries/add/pages/1", "Add entries");
|
|
578
|
+
itShouldIncludeTextForAdmin("/tag-entries/add/views/1", "Add entries");
|
|
579
|
+
itShouldIncludeTextForAdmin("/tag-entries/add/triggers/1", "Add entries");
|
|
580
|
+
it("adds view to tag ", async () => {
|
|
581
|
+
const app = await getApp({ disableCsrf: true });
|
|
582
|
+
const loginCookie = await getAdminLoginCookie();
|
|
583
|
+
await request(app)
|
|
584
|
+
.post("/tag-entries/add/views/1")
|
|
585
|
+
.set("Cookie", loginCookie)
|
|
586
|
+
.send("ids=1")
|
|
587
|
+
.expect(toRedirect("/tag/1?show_list=views"));
|
|
588
|
+
});
|
|
589
|
+
itShouldIncludeTextForAdmin("/diagram", "MyNewTestTag");
|
|
590
|
+
it("removes view from tag ", async () => {
|
|
591
|
+
const app = await getApp({ disableCsrf: true });
|
|
592
|
+
const loginCookie = await getAdminLoginCookie();
|
|
593
|
+
await request(app)
|
|
594
|
+
.post("/tag-entries/remove/views/1/1")
|
|
595
|
+
.set("Cookie", loginCookie)
|
|
596
|
+
.expect(toRedirect("/tag/1?show_list=views"));
|
|
597
|
+
});
|
|
598
|
+
it("deletes new tag", async () => {
|
|
599
|
+
const app = await getApp({ disableCsrf: true });
|
|
600
|
+
const loginCookie = await getAdminLoginCookie();
|
|
601
|
+
await request(app)
|
|
602
|
+
.post("/tag/delete/1")
|
|
603
|
+
.set("Cookie", loginCookie)
|
|
604
|
+
.expect(toRedirect("/tag"));
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
/**
|
|
608
|
+
* Clear all tests
|
|
536
609
|
*/
|
|
537
610
|
describe("clear all page", () => {
|
|
538
611
|
itShouldRedirectUnauthToLogin("/admin/clear-all");
|
package/tests/auth.test.js
CHANGED
|
@@ -63,9 +63,10 @@ describe("login process", () => {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
describe("user settings", () => {
|
|
66
|
+
let loginCookie;
|
|
66
67
|
it("should show user settings", async () => {
|
|
67
68
|
const app = await getApp({ disableCsrf: true });
|
|
68
|
-
|
|
69
|
+
loginCookie = await getStaffLoginCookie();
|
|
69
70
|
await request(app)
|
|
70
71
|
.get("/auth/settings")
|
|
71
72
|
.set("Cookie", loginCookie)
|
|
@@ -74,7 +75,7 @@ describe("user settings", () => {
|
|
|
74
75
|
|
|
75
76
|
it("should change password", async () => {
|
|
76
77
|
const app = await getApp({ disableCsrf: true });
|
|
77
|
-
const loginCookie = await getStaffLoginCookie();
|
|
78
|
+
//const loginCookie = await getStaffLoginCookie();
|
|
78
79
|
await request(app)
|
|
79
80
|
.post("/auth/settings")
|
|
80
81
|
.set("Cookie", loginCookie)
|
|
@@ -96,13 +97,20 @@ describe("user settings", () => {
|
|
|
96
97
|
.send("email=staff@foo.com")
|
|
97
98
|
.send("password=foHRrr46obar")
|
|
98
99
|
.expect(toRedirect("/"));
|
|
100
|
+
//change back
|
|
101
|
+
await request(app)
|
|
102
|
+
.post("/auth/settings")
|
|
103
|
+
.set("Cookie", loginCookie)
|
|
104
|
+
.send("password=foHRrr46obar")
|
|
105
|
+
.send("new_password=ghrarhr54hg")
|
|
106
|
+
.expect(toRedirect("/auth/settings"));
|
|
99
107
|
});
|
|
100
108
|
it("should change language", async () => {
|
|
101
109
|
const app = await getApp({ disableCsrf: true });
|
|
102
|
-
const
|
|
110
|
+
const adminLoginCookie = await getAdminLoginCookie();
|
|
103
111
|
const res = await request(app)
|
|
104
112
|
.post("/auth/setlanguage")
|
|
105
|
-
.set("Cookie",
|
|
113
|
+
.set("Cookie", adminLoginCookie)
|
|
106
114
|
.send("locale=it")
|
|
107
115
|
.expect(toRedirect("/auth/settings"));
|
|
108
116
|
const newCookie = resToLoginCookie(res);
|
|
@@ -260,6 +268,64 @@ describe("user admin", () => {
|
|
|
260
268
|
const delusers = await User.find({ email: "staff2@foo.com" });
|
|
261
269
|
expect(delusers.length).toBe(0);
|
|
262
270
|
});
|
|
271
|
+
if (!db.isSQLite)
|
|
272
|
+
it("can be disabled", async () => {
|
|
273
|
+
const staffLoginCookie = await getStaffLoginCookie();
|
|
274
|
+
const staffUser = await User.findOne({ email: "staff@foo.com" });
|
|
275
|
+
const adminLoginCookie = await getAdminLoginCookie();
|
|
276
|
+
const app = await getApp({ disableCsrf: true });
|
|
277
|
+
await request(app)
|
|
278
|
+
.post("/auth/login/")
|
|
279
|
+
.send("email=staff@foo.com")
|
|
280
|
+
.send("password=ghrarhr54hg")
|
|
281
|
+
.expect(toRedirect("/"));
|
|
282
|
+
await request(app)
|
|
283
|
+
.post(`/useradmin/disable/${staffUser.id}`)
|
|
284
|
+
.set("Cookie", adminLoginCookie)
|
|
285
|
+
.expect(toRedirect("/useradmin"));
|
|
286
|
+
await request(app)
|
|
287
|
+
.get("/auth/settings")
|
|
288
|
+
.set("Cookie", staffLoginCookie)
|
|
289
|
+
.expect(toRedirect("/auth/login"));
|
|
290
|
+
await request(app)
|
|
291
|
+
.post("/auth/login/")
|
|
292
|
+
.send("email=staff@foo.com")
|
|
293
|
+
.send("password=ghrarhr54hg")
|
|
294
|
+
.expect(toRedirect("/auth/login"));
|
|
295
|
+
await request(app)
|
|
296
|
+
.post(`/useradmin/enable/${staffUser.id}`)
|
|
297
|
+
.set("Cookie", adminLoginCookie)
|
|
298
|
+
.expect(toRedirect("/useradmin"));
|
|
299
|
+
await request(app)
|
|
300
|
+
.post("/auth/login/")
|
|
301
|
+
.send("email=staff@foo.com")
|
|
302
|
+
.send("password=ghrarhr54hg")
|
|
303
|
+
.expect(toRedirect("/"));
|
|
304
|
+
});
|
|
305
|
+
if (!db.isSQLite)
|
|
306
|
+
it("can be force logged out", async () => {
|
|
307
|
+
const staffLoginCookie = await getStaffLoginCookie();
|
|
308
|
+
const staffUser = await User.findOne({ email: "staff@foo.com" });
|
|
309
|
+
const adminLoginCookie = await getAdminLoginCookie();
|
|
310
|
+
const app = await getApp({ disableCsrf: true });
|
|
311
|
+
await request(app)
|
|
312
|
+
.get("/auth/settings")
|
|
313
|
+
.set("Cookie", staffLoginCookie)
|
|
314
|
+
.expect(toInclude(">staff@foo.com<"));
|
|
315
|
+
await request(app)
|
|
316
|
+
.post(`/useradmin/force-logout/${staffUser.id}`)
|
|
317
|
+
.set("Cookie", adminLoginCookie)
|
|
318
|
+
.expect(toRedirect("/useradmin"));
|
|
319
|
+
await request(app)
|
|
320
|
+
.get("/auth/settings")
|
|
321
|
+
.set("Cookie", staffLoginCookie)
|
|
322
|
+
.expect(toRedirect("/auth/login"));
|
|
323
|
+
await request(app)
|
|
324
|
+
.post("/auth/login/")
|
|
325
|
+
.send("email=staff@foo.com")
|
|
326
|
+
.send("password=ghrarhr54hg")
|
|
327
|
+
.expect(toRedirect("/"));
|
|
328
|
+
});
|
|
263
329
|
});
|
|
264
330
|
describe("User fields", () => {
|
|
265
331
|
it("should add fields", async () => {
|