@saltcorn/server 0.8.0-beta.3 → 0.8.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/app.js +7 -6
- package/auth/admin.js +226 -217
- package/auth/index.js +20 -20
- package/auth/roleadmin.js +2 -9
- package/auth/routes.js +193 -139
- package/auth/testhelp.js +62 -55
- package/fixture_persons.js +1 -1
- package/index.js +22 -22
- package/locales/en.json +3 -1
- package/locales/ru.json +25 -19
- package/markup/admin.js +86 -53
- package/markup/blockly.js +1 -1
- package/markup/expression_blurb.js +15 -15
- package/markup/forms.js +21 -22
- package/markup/index.js +20 -20
- package/markup/plugin-store.js +4 -4
- package/package.json +8 -8
- package/public/diagram_utils.js +22 -9
- package/public/saltcorn.css +6 -0
- package/restart_watcher.js +157 -157
- package/routes/actions.js +4 -11
- package/routes/admin.js +8 -5
- package/routes/api.js +9 -9
- package/routes/common_lists.js +127 -130
- package/routes/delete.js +2 -2
- package/routes/edit.js +1 -1
- package/routes/fields.js +4 -2
- package/routes/files.js +112 -94
- package/routes/homepage.js +1 -1
- package/routes/infoarch.js +1 -1
- package/routes/list.js +6 -5
- package/routes/packs.js +1 -2
- package/routes/pageedit.js +1 -1
- package/routes/tables.js +172 -165
- package/routes/tag_entries.js +1 -1
- package/routes/utils.js +3 -1
- package/routes/view.js +9 -2
- package/s3storage.js +6 -7
- package/serve.js +35 -31
- package/systemd.js +23 -21
- package/wrapper.js +44 -45
package/auth/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
// for the jsdoc documentation
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
* @category server
|
|
5
|
-
* @module auth/index
|
|
6
|
-
* @subcategory auth
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* All files in the auth module.
|
|
11
|
-
* @namespace auth_overview
|
|
12
|
-
* @property {module:auth/admin} admin
|
|
13
|
-
* @property {module:auth/resetpw} resetpw
|
|
14
|
-
* @property {module:auth/roleadmin} roleadmin
|
|
15
|
-
* @property {module:auth/routes} routes
|
|
16
|
-
* @property {module:auth/testhelp} testhelp
|
|
17
|
-
*
|
|
18
|
-
* @category server
|
|
19
|
-
* @subcategory auth
|
|
20
|
-
*/
|
|
1
|
+
// for the jsdoc documentation
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @category server
|
|
5
|
+
* @module auth/index
|
|
6
|
+
* @subcategory auth
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* All files in the auth module.
|
|
11
|
+
* @namespace auth_overview
|
|
12
|
+
* @property {module:auth/admin} admin
|
|
13
|
+
* @property {module:auth/resetpw} resetpw
|
|
14
|
+
* @property {module:auth/roleadmin} roleadmin
|
|
15
|
+
* @property {module:auth/routes} routes
|
|
16
|
+
* @property {module:auth/testhelp} testhelp
|
|
17
|
+
*
|
|
18
|
+
* @category server
|
|
19
|
+
* @subcategory auth
|
|
20
|
+
*/
|
package/auth/roleadmin.js
CHANGED
|
@@ -17,15 +17,8 @@ const {
|
|
|
17
17
|
} = require("@saltcorn/markup");
|
|
18
18
|
const { isAdmin, error_catcher, csrfField } = require("../routes/utils");
|
|
19
19
|
const { getState } = require("@saltcorn/data/db/state");
|
|
20
|
-
const {
|
|
21
|
-
|
|
22
|
-
form,
|
|
23
|
-
option,
|
|
24
|
-
select,
|
|
25
|
-
} = require("@saltcorn/markup/tags");
|
|
26
|
-
const {
|
|
27
|
-
send_users_page,
|
|
28
|
-
} = require("../markup/admin");
|
|
20
|
+
const { text, form, option, select } = require("@saltcorn/markup/tags");
|
|
21
|
+
const { send_users_page } = require("../markup/admin");
|
|
29
22
|
|
|
30
23
|
/**
|
|
31
24
|
* @type {object}
|
package/auth/routes.js
CHANGED
|
@@ -52,6 +52,9 @@ const rateLimit = require("express-rate-limit");
|
|
|
52
52
|
const moment = require("moment");
|
|
53
53
|
const View = require("@saltcorn/data/models/view");
|
|
54
54
|
const Table = require("@saltcorn/data/models/table");
|
|
55
|
+
const {
|
|
56
|
+
getForm,
|
|
57
|
+
} = require("@saltcorn/data/base-plugin/viewtemplates/viewable_fields");
|
|
55
58
|
const { InvalidConfiguration } = require("@saltcorn/data/utils");
|
|
56
59
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
57
60
|
const { restore_backup } = require("../markup/admin.js");
|
|
@@ -75,6 +78,7 @@ const router = new Router();
|
|
|
75
78
|
module.exports = router;
|
|
76
79
|
|
|
77
80
|
/**
|
|
81
|
+
* Login Form
|
|
78
82
|
* @param {object} req
|
|
79
83
|
* @param {boolean} isCreating
|
|
80
84
|
* @returns {Form}
|
|
@@ -100,7 +104,8 @@ const loginForm = (req, isCreating) => {
|
|
|
100
104
|
input_type: "email",
|
|
101
105
|
},
|
|
102
106
|
sublabel: user_sublabel || undefined,
|
|
103
|
-
|
|
107
|
+
// fixed correct size of email is 254 https://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address
|
|
108
|
+
validator: (s) => s.length < 255,
|
|
104
109
|
}),
|
|
105
110
|
new Field({
|
|
106
111
|
label: req.__("Password"),
|
|
@@ -117,6 +122,7 @@ const loginForm = (req, isCreating) => {
|
|
|
117
122
|
};
|
|
118
123
|
|
|
119
124
|
/**
|
|
125
|
+
* Forgot Form
|
|
120
126
|
* @param {object} req
|
|
121
127
|
* @returns {Form}
|
|
122
128
|
*/
|
|
@@ -133,7 +139,8 @@ const forgotForm = (req) =>
|
|
|
133
139
|
attributes: {
|
|
134
140
|
input_type: "email",
|
|
135
141
|
},
|
|
136
|
-
|
|
142
|
+
// fixed correct size of email is 254 https://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address
|
|
143
|
+
validator: (s) => s.length < 255,
|
|
137
144
|
}),
|
|
138
145
|
],
|
|
139
146
|
action: "/auth/forgot",
|
|
@@ -141,6 +148,7 @@ const forgotForm = (req) =>
|
|
|
141
148
|
});
|
|
142
149
|
|
|
143
150
|
/**
|
|
151
|
+
* Reset Form
|
|
144
152
|
* @param {object} body
|
|
145
153
|
* @param {object} req
|
|
146
154
|
* @returns {Form}
|
|
@@ -172,6 +180,7 @@ const resetForm = (body, req) => {
|
|
|
172
180
|
};
|
|
173
181
|
|
|
174
182
|
/**
|
|
183
|
+
* get Auth Links
|
|
175
184
|
* @param {string} current
|
|
176
185
|
* @param {boolean} noMethods
|
|
177
186
|
* @returns {object}
|
|
@@ -198,7 +207,13 @@ const getAuthLinks = (current, noMethods) => {
|
|
|
198
207
|
});
|
|
199
208
|
return links;
|
|
200
209
|
};
|
|
201
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Login with jwt
|
|
212
|
+
* @param {*} email
|
|
213
|
+
* @param {*} password
|
|
214
|
+
* @param {*} saltcornApp
|
|
215
|
+
* @param {*} res
|
|
216
|
+
*/
|
|
202
217
|
const loginWithJwt = async (email, password, saltcornApp, res) => {
|
|
203
218
|
const loginFn = async () => {
|
|
204
219
|
const publicUserLink = getState().getConfig("public_user_link");
|
|
@@ -263,6 +278,7 @@ const loginWithJwt = async (email, password, saltcornApp, res) => {
|
|
|
263
278
|
};
|
|
264
279
|
|
|
265
280
|
/**
|
|
281
|
+
* GET /auth/login
|
|
266
282
|
* @name get/login
|
|
267
283
|
* @function
|
|
268
284
|
* @memberof module:auth/routes~routesRouter
|
|
@@ -290,6 +306,7 @@ router.get(
|
|
|
290
306
|
);
|
|
291
307
|
|
|
292
308
|
/**
|
|
309
|
+
* GET /auth/logout
|
|
293
310
|
* @name get/logout
|
|
294
311
|
* @function
|
|
295
312
|
* @memberof module:auth/routes~routesRouter
|
|
@@ -433,6 +450,23 @@ router.post(
|
|
|
433
450
|
})
|
|
434
451
|
);
|
|
435
452
|
|
|
453
|
+
const default_signup_form = async (req) => {
|
|
454
|
+
const form = loginForm(req, true);
|
|
455
|
+
const new_user_form = getState().getConfig("new_user_form", "");
|
|
456
|
+
if (!new_user_form) {
|
|
457
|
+
const userTable = await Table.findOne({ name: "users" });
|
|
458
|
+
const userFields = await userTable.getFields();
|
|
459
|
+
|
|
460
|
+
for (const f of userFields) {
|
|
461
|
+
if (f.required && !f.calculated && !["id", "email"].includes(f.name))
|
|
462
|
+
form.fields.push(f);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
form.action = "/auth/signup";
|
|
466
|
+
form.submitLabel = req.__("Sign up");
|
|
467
|
+
return form;
|
|
468
|
+
};
|
|
469
|
+
|
|
436
470
|
/**
|
|
437
471
|
* @name get/signup
|
|
438
472
|
* @function
|
|
@@ -447,19 +481,7 @@ router.get(
|
|
|
447
481
|
return;
|
|
448
482
|
}
|
|
449
483
|
const defaultSignup = async () => {
|
|
450
|
-
const form =
|
|
451
|
-
const new_user_form = getState().getConfig("new_user_form", "");
|
|
452
|
-
if (!new_user_form) {
|
|
453
|
-
const userTable = await Table.findOne({ name: "users" });
|
|
454
|
-
const userFields = await userTable.getFields();
|
|
455
|
-
|
|
456
|
-
for (const f of userFields) {
|
|
457
|
-
if (f.required && !f.calculated && !["id", "email"].includes(f.name))
|
|
458
|
-
form.fields.push(f);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
form.action = "/auth/signup";
|
|
462
|
-
form.submitLabel = req.__("Sign up");
|
|
484
|
+
const form = await default_signup_form(req);
|
|
463
485
|
res.sendAuthWrap(req.__(`Sign up`), form, getAuthLinks("signup"));
|
|
464
486
|
};
|
|
465
487
|
const signup_form_name = getState().getConfig("signup_form", "");
|
|
@@ -503,11 +525,11 @@ router.get(
|
|
|
503
525
|
form,
|
|
504
526
|
{},
|
|
505
527
|
restore +
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
528
|
+
script(
|
|
529
|
+
domReady(
|
|
530
|
+
`$('form.create-first-user button[type=submit]').click(function(){press_store_button(this)})`
|
|
531
|
+
)
|
|
509
532
|
)
|
|
510
|
-
)
|
|
511
533
|
);
|
|
512
534
|
} else {
|
|
513
535
|
req.flash("danger", req.__("Users already present"));
|
|
@@ -536,7 +558,7 @@ router.post(
|
|
|
536
558
|
);
|
|
537
559
|
if (err) req.flash("error", err);
|
|
538
560
|
else req.flash("success", req.__("Successfully restored backup"));
|
|
539
|
-
fs.unlink(newPath, function () {
|
|
561
|
+
fs.unlink(newPath, function () {});
|
|
540
562
|
res.redirect(`/auth/login`);
|
|
541
563
|
} else {
|
|
542
564
|
req.flash("danger", req.__("Users already present"));
|
|
@@ -568,18 +590,15 @@ router.post(
|
|
|
568
590
|
} else {
|
|
569
591
|
const { email, password } = form.values;
|
|
570
592
|
const u = await User.create({ email, password, role_id: 1 });
|
|
571
|
-
req.login(
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
req.flash("danger", err);
|
|
579
|
-
res.redirect("/auth/signup");
|
|
580
|
-
}
|
|
593
|
+
req.login(u.session_object, function (err) {
|
|
594
|
+
if (!err) {
|
|
595
|
+
Trigger.emitEvent("Login", null, u);
|
|
596
|
+
res.redirect("/");
|
|
597
|
+
} else {
|
|
598
|
+
req.flash("danger", err);
|
|
599
|
+
res.redirect("/auth/signup");
|
|
581
600
|
}
|
|
582
|
-
);
|
|
601
|
+
});
|
|
583
602
|
}
|
|
584
603
|
} else {
|
|
585
604
|
req.flash("danger", req.__("Users already present"));
|
|
@@ -673,21 +692,18 @@ const getNewUserForm = async (new_user_view_name, req, askEmail) => {
|
|
|
673
692
|
* @returns {void}
|
|
674
693
|
*/
|
|
675
694
|
const signup_login_with_user = (u, req, res) =>
|
|
676
|
-
req.login(
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
req.flash("danger", err);
|
|
687
|
-
res.redirect("/auth/signup");
|
|
688
|
-
}
|
|
695
|
+
req.login(u.session_object, function (err) {
|
|
696
|
+
if (!err) {
|
|
697
|
+
Trigger.emitEvent("Login", null, u);
|
|
698
|
+
if (getState().verifier) res.redirect("/auth/verification-flow");
|
|
699
|
+
else if (getState().get2FApolicy(u) === "Mandatory")
|
|
700
|
+
res.redirect("/auth/twofa/setup/totp");
|
|
701
|
+
else res.redirect("/");
|
|
702
|
+
} else {
|
|
703
|
+
req.flash("danger", err);
|
|
704
|
+
res.redirect("/auth/signup");
|
|
689
705
|
}
|
|
690
|
-
);
|
|
706
|
+
});
|
|
691
707
|
|
|
692
708
|
/**
|
|
693
709
|
* @name get/signup_final_ext
|
|
@@ -717,6 +733,7 @@ router.get(
|
|
|
717
733
|
*/
|
|
718
734
|
router.post(
|
|
719
735
|
"/signup_final_ext",
|
|
736
|
+
setTenant,
|
|
720
737
|
error_catcher(async (req, res) => {
|
|
721
738
|
const new_user_form = getState().getConfig("new_user_form");
|
|
722
739
|
if (!req.user || req.user.id || !new_user_form) {
|
|
@@ -727,7 +744,7 @@ router.post(
|
|
|
727
744
|
const form = await getNewUserForm(new_user_form, req, !req.user.email);
|
|
728
745
|
form.action = "/auth/signup_final_ext";
|
|
729
746
|
|
|
730
|
-
form.
|
|
747
|
+
await form.asyncValidate(req.body);
|
|
731
748
|
if (form.hasErrors) {
|
|
732
749
|
res.sendAuthWrap(new_user_form, form, getAuthLinks("signup", true));
|
|
733
750
|
return;
|
|
@@ -769,6 +786,7 @@ router.post(
|
|
|
769
786
|
*/
|
|
770
787
|
router.post(
|
|
771
788
|
"/signup_final",
|
|
789
|
+
setTenant,
|
|
772
790
|
error_catcher(async (req, res) => {
|
|
773
791
|
if (getState().getConfig("allow_signup")) {
|
|
774
792
|
const new_user_form = getState().getConfig("new_user_form");
|
|
@@ -787,7 +805,7 @@ router.post(
|
|
|
787
805
|
});
|
|
788
806
|
}
|
|
789
807
|
}
|
|
790
|
-
form.
|
|
808
|
+
await form.asyncValidate(req.body);
|
|
791
809
|
if (form.hasErrors) {
|
|
792
810
|
res.sendAuthWrap(new_user_form, form, getAuthLinks("signup", true));
|
|
793
811
|
} else if (form.values.email && !check_email_mask(form.values.email)) {
|
|
@@ -830,6 +848,7 @@ router.post(
|
|
|
830
848
|
*/
|
|
831
849
|
router.post(
|
|
832
850
|
"/signup",
|
|
851
|
+
setTenant,
|
|
833
852
|
error_catcher(async (req, res) => {
|
|
834
853
|
if (!getState().getConfig("allow_signup")) {
|
|
835
854
|
req.flash("danger", req.__("Signups not enabled"));
|
|
@@ -884,16 +903,33 @@ router.post(
|
|
|
884
903
|
|
|
885
904
|
const signup_form_name = getState().getConfig("signup_form", "");
|
|
886
905
|
if (signup_form_name) {
|
|
887
|
-
const
|
|
888
|
-
if (
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
906
|
+
const signup_view = await View.findOne({ name: signup_form_name });
|
|
907
|
+
if (signup_view) {
|
|
908
|
+
const signup_form = await getForm(
|
|
909
|
+
Table.findOne({ name: "users" }),
|
|
910
|
+
signup_form_name,
|
|
911
|
+
signup_view.configuration.columns,
|
|
912
|
+
signup_view.configuration.layout,
|
|
913
|
+
undefined,
|
|
914
|
+
req,
|
|
915
|
+
false
|
|
916
|
+
);
|
|
917
|
+
await signup_form.asyncValidate(req.body);
|
|
918
|
+
if (signup_form.hasErrors) {
|
|
919
|
+
signup_form.action = "/auth/signup";
|
|
920
|
+
res.sendAuthWrap(
|
|
921
|
+
req.__(`Sign up`),
|
|
922
|
+
signup_form,
|
|
923
|
+
getAuthLinks("signup")
|
|
924
|
+
);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
//ensure pw repeat is set if used.
|
|
928
|
+
signup_form.fields.forEach((f) => {
|
|
929
|
+
if (f.name === "passwordRepeat")
|
|
930
|
+
signup_form.values[f.name] = signup_form.values[f.name] || "";
|
|
896
931
|
});
|
|
932
|
+
const userObject = signup_form.values;
|
|
897
933
|
const { email, password, passwordRepeat } = userObject;
|
|
898
934
|
if (await unsuitableEmailPassword(email, password, passwordRepeat))
|
|
899
935
|
return;
|
|
@@ -914,8 +950,8 @@ router.post(
|
|
|
914
950
|
}
|
|
915
951
|
}
|
|
916
952
|
|
|
917
|
-
const form =
|
|
918
|
-
form.
|
|
953
|
+
const form = await default_signup_form(req);
|
|
954
|
+
await form.asyncValidate(req.body);
|
|
919
955
|
|
|
920
956
|
if (form.hasErrors) {
|
|
921
957
|
form.action = "/auth/signup";
|
|
@@ -930,7 +966,7 @@ router.post(
|
|
|
930
966
|
form.values.password = password;
|
|
931
967
|
res.sendAuthWrap(new_user_form, form, getAuthLinks("signup", true));
|
|
932
968
|
} else {
|
|
933
|
-
const u = await User.create(
|
|
969
|
+
const u = await User.create(form.values);
|
|
934
970
|
await send_verification_email(u, req);
|
|
935
971
|
if (req.smr)
|
|
936
972
|
await loginWithJwt(
|
|
@@ -959,7 +995,7 @@ function handler(req, res) {
|
|
|
959
995
|
req.flash(
|
|
960
996
|
"error",
|
|
961
997
|
"You've made too many failed attempts in a short period of time, please try again " +
|
|
962
|
-
|
|
998
|
+
moment(req.rateLimit.resetTime).fromNow()
|
|
963
999
|
);
|
|
964
1000
|
res.redirect("/auth/login"); // brute force protection triggered, send them back to the login page
|
|
965
1001
|
}
|
|
@@ -992,6 +1028,7 @@ const userLimiter = rateLimit({
|
|
|
992
1028
|
});
|
|
993
1029
|
|
|
994
1030
|
/**
|
|
1031
|
+
* POST /auth/login
|
|
995
1032
|
* @name post/login
|
|
996
1033
|
* @function
|
|
997
1034
|
* @memberof module:auth/routes~routesRouter
|
|
@@ -1183,7 +1220,10 @@ const setLanguageForm = (req, user) =>
|
|
|
1183
1220
|
option(
|
|
1184
1221
|
{
|
|
1185
1222
|
value: locale,
|
|
1186
|
-
...(((user && user.language === locale) ||
|
|
1223
|
+
...(((user && user.language === locale) ||
|
|
1224
|
+
(user && !user.language && req.getLocale() === locale)) && {
|
|
1225
|
+
selected: true,
|
|
1226
|
+
}),
|
|
1187
1227
|
},
|
|
1188
1228
|
language
|
|
1189
1229
|
)
|
|
@@ -1223,7 +1263,7 @@ const userSettings = async ({ req, res, pwform, user }) => {
|
|
|
1223
1263
|
div(
|
|
1224
1264
|
user.api_token
|
|
1225
1265
|
? span({ class: "me-1" }, req.__("API token for this user: ")) +
|
|
1226
|
-
|
|
1266
|
+
code(user.api_token)
|
|
1227
1267
|
: req.__("No API token issued")
|
|
1228
1268
|
),
|
|
1229
1269
|
// button for reset or generate api token
|
|
@@ -1237,16 +1277,16 @@ const userSettings = async ({ req, res, pwform, user }) => {
|
|
|
1237
1277
|
),
|
|
1238
1278
|
// button for remove api token
|
|
1239
1279
|
user.api_token &&
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1280
|
+
div(
|
|
1281
|
+
{ class: "mt-4 ms-2 d-inline-block" },
|
|
1282
|
+
post_btn(
|
|
1283
|
+
`/auth/remove-api-token`,
|
|
1284
|
+
// TBD localization
|
|
1285
|
+
user.api_token ? req.__("Remove") : req.__("Generate"),
|
|
1286
|
+
req.csrfToken(),
|
|
1287
|
+
{ req: req, confirm: true }
|
|
1288
|
+
)
|
|
1289
|
+
),
|
|
1250
1290
|
],
|
|
1251
1291
|
};
|
|
1252
1292
|
return {
|
|
@@ -1257,13 +1297,13 @@ const userSettings = async ({ req, res, pwform, user }) => {
|
|
|
1257
1297
|
},
|
|
1258
1298
|
...(usersets
|
|
1259
1299
|
? [
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1300
|
+
{
|
|
1301
|
+
type: "card",
|
|
1302
|
+
class: "mt-0",
|
|
1303
|
+
title: userSetsName,
|
|
1304
|
+
contents: usersets,
|
|
1305
|
+
},
|
|
1306
|
+
]
|
|
1267
1307
|
: []),
|
|
1268
1308
|
{
|
|
1269
1309
|
type: "card",
|
|
@@ -1286,35 +1326,35 @@ const userSettings = async ({ req, res, pwform, user }) => {
|
|
|
1286
1326
|
},
|
|
1287
1327
|
...(show2FAPolicy
|
|
1288
1328
|
? [
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1329
|
+
{
|
|
1330
|
+
type: "card",
|
|
1331
|
+
title: req.__("Two-factor authentication"),
|
|
1332
|
+
contents: [
|
|
1333
|
+
div(
|
|
1334
|
+
user._attributes.totp_enabled
|
|
1335
|
+
? req.__("Two-factor authentication is enabled")
|
|
1336
|
+
: req.__("Two-factor authentication is disabled")
|
|
1337
|
+
),
|
|
1338
|
+
div(
|
|
1339
|
+
user._attributes.totp_enabled
|
|
1340
|
+
? a(
|
|
1341
|
+
{
|
|
1342
|
+
href: "/auth/twofa/disable/totp",
|
|
1343
|
+
class: "btn btn-danger mt-2",
|
|
1344
|
+
},
|
|
1345
|
+
req.__("Disable TWA")
|
|
1346
|
+
)
|
|
1347
|
+
: a(
|
|
1348
|
+
{
|
|
1349
|
+
href: "/auth/twofa/setup/totp",
|
|
1350
|
+
class: "btn btn-primary mt-2",
|
|
1351
|
+
},
|
|
1352
|
+
req.__("Enable TWA")
|
|
1353
|
+
)
|
|
1354
|
+
),
|
|
1355
|
+
],
|
|
1356
|
+
},
|
|
1357
|
+
]
|
|
1318
1358
|
: []),
|
|
1319
1359
|
...(apikeycard ? [apikeycard] : []),
|
|
1320
1360
|
],
|
|
@@ -1371,18 +1411,15 @@ router.post(
|
|
|
1371
1411
|
const newlang = available_languages[req.body.locale];
|
|
1372
1412
|
if (newlang && u) {
|
|
1373
1413
|
await u.set_language(req.body.locale);
|
|
1374
|
-
req.login(
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
req.flash("danger", err);
|
|
1382
|
-
res.redirect("/auth/settings");
|
|
1383
|
-
}
|
|
1414
|
+
req.login(u.session_object, function (err) {
|
|
1415
|
+
if (!err) {
|
|
1416
|
+
req.flash("success", req.__("Language changed to %s", newlang));
|
|
1417
|
+
res.redirect("/auth/settings");
|
|
1418
|
+
} else {
|
|
1419
|
+
req.flash("danger", err);
|
|
1420
|
+
res.redirect("/auth/settings");
|
|
1384
1421
|
}
|
|
1385
|
-
);
|
|
1422
|
+
});
|
|
1386
1423
|
} else {
|
|
1387
1424
|
req.flash("danger", req.__("Language not found"));
|
|
1388
1425
|
res.redirect("/auth/settings");
|
|
@@ -1479,19 +1516,16 @@ router.post(
|
|
|
1479
1516
|
const u = await User.findForSession({ id: req.user.id });
|
|
1480
1517
|
await u.update({ email: form.values.email });
|
|
1481
1518
|
u.email = form.values.email;
|
|
1482
|
-
req.login(
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
req.flash("danger", err);
|
|
1491
|
-
res.redirect("/");
|
|
1492
|
-
}
|
|
1519
|
+
req.login(u.session_object, function (err) {
|
|
1520
|
+
if (!err) {
|
|
1521
|
+
Trigger.emitEvent("Login", null, u);
|
|
1522
|
+
req.flash("success", req.__("Welcome, %s!", u.email));
|
|
1523
|
+
res.redirect("/");
|
|
1524
|
+
} else {
|
|
1525
|
+
req.flash("danger", err);
|
|
1526
|
+
res.redirect("/");
|
|
1493
1527
|
}
|
|
1494
|
-
);
|
|
1528
|
+
});
|
|
1495
1529
|
})
|
|
1496
1530
|
);
|
|
1497
1531
|
|
|
@@ -1608,8 +1642,9 @@ router.get(
|
|
|
1608
1642
|
// generate QR code for scanning into Google Authenticator
|
|
1609
1643
|
// reference: https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
|
|
1610
1644
|
const site_name = getState().getConfig("site_name");
|
|
1611
|
-
const otpUrl = `otpauth://totp/${
|
|
1612
|
-
|
|
1645
|
+
const otpUrl = `otpauth://totp/${
|
|
1646
|
+
user.email
|
|
1647
|
+
}?secret=${encodedKey}&period=30&issuer=${encodeURIComponent(site_name)}`;
|
|
1613
1648
|
const image = await qrcode.toDataURL(otpUrl);
|
|
1614
1649
|
res.sendWrap(req.__("Setup two-factor authentication"), {
|
|
1615
1650
|
type: "card",
|
|
@@ -1677,7 +1712,9 @@ router.post(
|
|
|
1677
1712
|
res.redirect("/auth/settings");
|
|
1678
1713
|
})
|
|
1679
1714
|
);
|
|
1680
|
-
|
|
1715
|
+
/**
|
|
1716
|
+
* GET /twofa/disable/totp
|
|
1717
|
+
*/
|
|
1681
1718
|
router.get(
|
|
1682
1719
|
"/twofa/disable/totp",
|
|
1683
1720
|
loggedIn,
|
|
@@ -1692,7 +1729,10 @@ router.get(
|
|
|
1692
1729
|
});
|
|
1693
1730
|
})
|
|
1694
1731
|
);
|
|
1695
|
-
|
|
1732
|
+
/**
|
|
1733
|
+
* POST /twofa/disable/totp
|
|
1734
|
+
* Disable TWA
|
|
1735
|
+
*/
|
|
1696
1736
|
router.post(
|
|
1697
1737
|
"/twofa/disable/totp",
|
|
1698
1738
|
loggedIn,
|
|
@@ -1726,6 +1766,12 @@ router.post(
|
|
|
1726
1766
|
res.redirect("/auth/settings");
|
|
1727
1767
|
})
|
|
1728
1768
|
);
|
|
1769
|
+
/**
|
|
1770
|
+
* totpForm (TWA)
|
|
1771
|
+
* @param {*} req
|
|
1772
|
+
* @param {*} action
|
|
1773
|
+
* @returns
|
|
1774
|
+
*/
|
|
1729
1775
|
const totpForm = (req, action) =>
|
|
1730
1776
|
new Form({
|
|
1731
1777
|
action: action || "/auth/twofa/setup/totp",
|
|
@@ -1738,7 +1784,11 @@ const totpForm = (req, action) =>
|
|
|
1738
1784
|
},
|
|
1739
1785
|
],
|
|
1740
1786
|
});
|
|
1741
|
-
|
|
1787
|
+
/**
|
|
1788
|
+
* Random key generation for totp (TWA)
|
|
1789
|
+
* @param {*} len
|
|
1790
|
+
* @returns
|
|
1791
|
+
*/
|
|
1742
1792
|
const randomKey = function (len) {
|
|
1743
1793
|
function getRandomInt(min, max) {
|
|
1744
1794
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
@@ -1753,7 +1803,9 @@ const randomKey = function (len) {
|
|
|
1753
1803
|
|
|
1754
1804
|
return buf.join("");
|
|
1755
1805
|
};
|
|
1756
|
-
|
|
1806
|
+
/**
|
|
1807
|
+
* GET /twofa/login/totp
|
|
1808
|
+
*/
|
|
1757
1809
|
router.get(
|
|
1758
1810
|
"/twofa/login/totp",
|
|
1759
1811
|
error_catcher(async (req, res) => {
|
|
@@ -1772,7 +1824,9 @@ router.get(
|
|
|
1772
1824
|
res.sendAuthWrap(req.__(`Two-factor authentication`), form, {});
|
|
1773
1825
|
})
|
|
1774
1826
|
);
|
|
1775
|
-
|
|
1827
|
+
/**
|
|
1828
|
+
* POST /twofa/login/totp
|
|
1829
|
+
*/
|
|
1776
1830
|
router.post(
|
|
1777
1831
|
"/twofa/login/totp",
|
|
1778
1832
|
passport.authenticate("totp", {
|