Added CAPTCHA option when creating new accounts on login screen.
Ylian Saint-Hilaire committed
Jul 11, 2022 at 14:35 UTC
04fb1f2bf04a41c8cfe23ae94c30658691b1a157
8 files changed
+102
-22
authenticode.js
+2
-2
@@ -1235,7 +1235,7 @@ function createAuthenticodeHandler(path) {
1235
if (args.hash == 'sha512') { hashOid = forge.pki.oids.sha512; fileHash = obj.getHash('sha512'); }
1236
if (args.hash == 'sha224') { hashOid = forge.pki.oids.sha224; fileHash = obj.getHash('sha224'); }
1237
if (args.hash == 'md5') { hashOid = forge.pki.oids.md5; fileHash = obj.getHash('md5'); }
1238
- if (hashOid == null) { func(false); return; };
1238
+ if (hashOid == null) { func('Invalid signing hash: ' + args.hash); return; };
1239
1240
// Create the signature block
1241
var xp7 = forge.pkcs7.createSignedData();
@@ -1453,7 +1453,7 @@ function createAuthenticodeHandler(path) {
1453
// Open the output file
1454
var output = null;
1455
try { output = fs.openSync(args.out, 'w+'); } catch (ex) { }
1456
- if (output == null) { func(false); return; }
1456
+ if (output == null) { func('Unable to open output file: ' + args.out); return; }
1457
var tmp, written = 0, executableSize = obj.header.sigpos ? obj.header.sigpos : filesize;
1458
1459
// Compute pre-header length and copy that to the new file
meshcentral-config-schema.json
+1
@@ -343,6 +343,7 @@
343
"ipkvm": { "type": "boolean", "default": false, "description": "Set to true to enable IP KVM device support in this domain." },
344
"minify": { "type": "boolean", "default": false, "description": "When enabled, the server will send reduced sided web pages." },
345
"newAccounts": { "type": "boolean", "default": false, "description": "When set to true, allow new user accounts to be created from the login page." },
346
+ "newAccountsPass": { "type": "string", "default": null, "description": "When set this password will be required in order to create a new account from the login screen." },
347
"newAccountsUserGroups": { "type": "array", "uniqueItems": true, "items": { "type": "string" } },
348
"userNameIsEmail": { "type": "boolean", "default": false, "description": "When enabled, the username of each account is also the email address of the account." },
349
"newAccountEmailDomains": { "type": "array", "uniqueItems": true, "items": { "type": "string" } },
meshcentral.js
+4
@@ -3675,6 +3675,7 @@ function mainStart() {
3675
var wildleek = false;
3676
var nodemailer = false;
3677
var sendgrid = false;
3678
+ var captcha = false;
3679
if (require('os').platform() == 'win32') { for (var i in config.domains) { domainCount++; if (config.domains[i].auth == 'sspi') { sspi = true; } else { allsspi = false; } } } else { allsspi = false; }
3680
if (domainCount == 0) { allsspi = false; }
3681
for (var i in config.domains) {
@@ -3697,6 +3698,7 @@ function mainStart() {
3698
}
3699
if (config.domains[i].sessionrecording != null) { sessionRecording = true; }
3700
if ((config.domains[i].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; }
3701
+ if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = true; }
3702
}
3703
3704
// Build the list of required modules
@@ -3705,6 +3707,8 @@ function mainStart() {
3707
if (ldap == true) { modules.push('ldapauth-fork'); }
3708
if (ssh == true) { if (nodeVersion < 11) { addServerWarning('MeshCentral SSH support requires NodeJS 11 or higher.', 1); } else { modules.push('ssh2'); } }
3709
if (passport != null) { modules.push(...passport); }
3710
+ if (captcha == true) { modules.push('svg-captcha'); }
3711
+
3712
if (sessionRecording == true) { modules.push('image-size'); } // Need to get the remote desktop JPEG sizes to index the recodring file.
3713
if (config.letsencrypt != null) { modules.push('acme-client'); } // Add acme-client module
3714
if (config.settings.mqtt != null) { modules.push('aedes@0.39.0'); } // Add MQTT Modules
mpsserver.js
+1
-1
@@ -58,7 +58,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
58
obj.server.listen(args.mpsport, args.mpsportbind, function () {
59
console.log("MeshCentral Intel(R) AMT server running on " + certificates.AmtMpsName + ":" + args.mpsport + ((args.mpsaliasport != null) ? (", alias port " + args.mpsaliasport) : "") + ".");
60
obj.parent.authLog('mps', 'Server listening on ' + ((args.mpsportbind != null) ? args.mpsportbind : '0.0.0.0') + ' port ' + args.mpsport + '.');
61
- }).on("error", function (err) { console.error("ERROR: MeshCentral Intel(R) AMT server port " + args.mpsport + " is not available."); if (args.exactports) { process.exit(); } });
61
+ }).on("error", function (err) { console.error("ERROR: MeshCentral Intel(R) AMT server port " + args.mpsport + " is not available. Check if the MeshCentral is already running."); if (args.exactports) { process.exit(); } });
62
63
obj.server.on('tlsClientError', function (err, tlssocket) { if (args.mpsdebug) { var remoteAddress = tlssocket.remoteAddress; if (tlssocket.remoteFamily == 'IPv6') { remoteAddress = '[' + remoteAddress + ']'; } console.log('MPS:Invalid TLS connection from ' + remoteAddress + ':' + tlssocket.remotePort + '.'); } });
64
}
package.json
+12
-2
@@ -37,6 +37,8 @@
37
"sample-config-advanced.json"
38
],
39
"dependencies": {
40
+ "@crowdsec/express-bouncer": "^0.1.0",
41
+ "@yetzt/nedb": "^1.8.0",
42
"archiver": "^5.3.1",
43
"body-parser": "^1.19.0",
44
"cbor": "~5.2.0",
@@ -45,13 +47,21 @@
47
"express": "^4.17.0",
48
"express-handlebars": "^5.3.5",
49
"express-ws": "^4.0.0",
50
+ "image-size": "^1.0.1",
51
"ipcheck": "^0.1.0",
52
+ "loadavg-windows": "^1.1.1",
53
"minimist": "^1.2.5",
54
"multiparty": "^4.2.1",
51
- "@yetzt/nedb": "^1.8.0",
55
"node-forge": "^1.0.0",
56
+ "node-windows": "^0.1.4",
57
+ "otplib": "^10.2.3",
58
+ "pg": "^8.7.1",
59
+ "pgtools": "^0.3.2",
60
+ "ssh2": "^1.11.0",
61
+ "web-push": "^3.5.0",
62
"ws": "^5.2.3",
54
- "yauzl": "^2.10.0"
63
+ "yauzl": "^2.10.0",
64
+ "yubikeyotp": "^0.2.0"
65
},
66
"engines": {
67
"node": ">=10.0.0"
views/login.handlebars
+17
-3
@@ -122,6 +122,14 @@
122
<td id="nuToken" align=right>Creation Token:</td>
123
<td><input id=anewaccountpass type=password name=anewaccountpass {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(6,event) onkeyup=validateCreate(6,event) /></td>
124
</tr>
125
+ <tr id=newAccountCaptchaImg title="CAPTCHA image">
126
+ <td></td>
127
+ <td colspan=2><img src="{{{newAccountCaptchaImage}}}" loading="lazy" /></td>
128
+ </tr>
129
+ <tr id=newAccountCaptcha title="Security check">
130
+ <td id="nuCaptcha" align=right>Security Check:</td>
131
+ <td><input id=anewaccountcaptcha type=text name=anewaccountcaptcha {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(7,event) onkeyup=validateCreate(7,event) /></td>
132
+ </tr>
133
<tr>
134
<td colspan=2>
135
<div style=float:right><input id=createButton type=submit value="Create Account" disabled="disabled" /></div>
@@ -131,6 +139,7 @@
139
</table>
140
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
141
<input id=createformargs name="urlargs" type="hidden" value="" />
142
+ <input id=createformcaptcha name="captchaargs" type="hidden" value="{{{newAccountCaptcha}}}" />
143
</form>
144
</div>
145
<div id=resetpanel style="display:none">
@@ -311,6 +320,7 @@
320
var loginMode = '{{{loginmode}}}';
321
var newAccount = '{{{newAccount}}}';
322
var newAccountPass = parseInt('{{{newAccountPass}}}');
323
+ var newAccountCaptcha = '{{{newAccountCaptcha}}}';
324
var emailCheck = '{{{emailcheck}}}';
325
var passRequirements = '{{{passRequirements}}}';
326
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
@@ -335,7 +345,7 @@
345
var i;
346
var messageid = parseInt('{{{messageid}}}');
347
var okmessages = ['', "If valid, reset mail sent.", "Email sent.", "Email verification required, check your mailbox and click the confirmation link.", "SMS sent."];
338
- var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later.", "Server under maintenance."];
348
+ var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later.", "Server under maintenance.", "Unable to send device notification.", "Invalid security check."];
349
if (messageid > 0) {
350
var msg = '';
351
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
@@ -433,6 +443,8 @@
443
QV('newAccountDiv', (newAccount === '1') || (newAccount === 'true')); // If new accounts are not allowed, don't display the new account link.
444
if ((passhint != null) && (passhint.length > 0)) { QV('showPassHintLink', true); }
445
QV('newAccountPass', (newAccountPass == 1));
446
+ QV('newAccountCaptcha', (newAccountCaptcha != ''));
447
+ QV('newAccountCaptchaImg', (newAccountCaptcha != ''));
448
QV('resetAccountDiv', (emailCheck == 'true'));
449
QV('hrAccountDiv', (emailCheck == 'true') || (newAccountPass == 1));
450
@@ -627,6 +639,7 @@
639
var pass1ok = (Q('apassword1').value.length > 0);
640
var pass2ok = (Q('apassword2').value.length > 0) && (Q('apassword2').value == Q('apassword1').value);
641
var newAccOk = (newAccountPass == 0) || (Q('anewaccountpass').value.length > 0);
642
+ var newCaptchaOk = (newAccountCaptcha == '') || (Q('anewaccountcaptcha').value.length > 0);
643
var ok = (userok && emailok && pass1ok && pass2ok && newAccOk);
644
645
// Color the fields
@@ -635,6 +648,7 @@
648
QS('nuPass1').color = pass1ok ? 'black' : '#7b241c';
649
QS('nuPass2').color = pass2ok ? 'black' : '#7b241c';
650
QS('nuToken').color = newAccOk ? 'black' : '#7b241c';
651
+ QS('nuCaptcha').color = newCaptchaOk ? 'black' : '#7b241c';
652
653
if (Q('apassword1').value == '') {
654
QH('passWarning', '');
@@ -663,13 +677,13 @@
677
}
678
}
679
if ((e != null) && (e.keyCode == 13)) {
666
-
680
if ((box == 1) && userok) { Q('aemail').focus(); }
681
if ((box == 2) && emailok) { Q('apassword1').focus(); }
682
if ((box == 3) && pass1ok) { Q('apassword2').focus(); }
683
if ((box == 4) && pass2ok) { if (passRequirements.hint === true) { Q('apasswordhint').focus(); } else { box = 5; } }
684
if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { box = 6; } }
672
- if (box == 6) { Q('createButton').click(); }
685
+ if (box == 6) { if (newAccountCaptcha != '') { Q('anewaccountcaptcha').focus(); } else { box = 7; } }
686
+ if (box == 7) { Q('createButton').click(); }
687
}
688
if (e != null) { haltEvent(e); }
689
QE('createButton', ok);
views/login2.handlebars
+17
-3
@@ -144,6 +144,14 @@
144
<td id="nuToken" align=right>Creation Token:</td>
145
<td><input id=anewaccountpass type=password name=anewaccountpass {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(6,event) onkeyup=validateCreate(6,event) /></td>
146
</tr>
147
+ <tr id=newAccountCaptchaImg title="CAPTCHA image">
148
+ <td></td>
149
+ <td colspan=2><img src="{{{newAccountCaptchaImage}}}" loading="lazy" /></td>
150
+ </tr>
151
+ <tr id=newAccountCaptcha title="Security check">
152
+ <td id="nuCaptcha" align=right>Security Check:</td>
153
+ <td><input id=anewaccountcaptcha type=text name=anewaccountcaptcha {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(7,event) onkeyup=validateCreate(7,event) /></td>
154
+ </tr>
155
<tr>
156
<td colspan=2>
157
<div style=float:right><input id=createButton type="button" onclick="submitButtonClicked('createpanelform')" value="Create Account" disabled="disabled" /></div>
@@ -153,6 +161,7 @@
161
</table>
162
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
163
<input id=createformargs name="urlargs" type="hidden" value="" />
164
+ <input id=createformcaptcha name="captchaargs" type="hidden" value="{{{newAccountCaptcha}}}" />
165
</form>
166
</div>
167
<div id=resetpanel style="display:none">
@@ -368,6 +377,7 @@
377
var loginMode = '{{{loginmode}}}';
378
var newAccount = '{{{newAccount}}}';
379
var newAccountPass = parseInt('{{{newAccountPass}}}');
380
+ var newAccountCaptcha = '{{{newAccountCaptcha}}}';
381
var emailCheck = '{{{emailcheck}}}';
382
var passRequirements = '{{{passRequirements}}}';
383
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
@@ -405,7 +415,7 @@
415
var i;
416
var messageid = parseInt('{{{messageid}}}');
417
var okmessages = ['', "If valid, reset mail sent.", "Email sent.", "Email verification required, check your mailbox and click the confirmation link.", "SMS sent.", "Sending notification..."];
408
- var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later.", "Server under maintenance.", "Unable to send device notification."];
418
+ var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later.", "Server under maintenance.", "Unable to send device notification.", "Invalid security check."];
419
if (messageid > 0) {
420
var msg = '';
421
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
@@ -487,6 +497,8 @@
497
QV('newAccountDiv', (newAccount === '1') || (newAccount === 'true')); // If new accounts are not allowed, don't display the new account link.
498
if ((passhint != null) && (passhint.length > 0)) { QV('showPassHintLink', true); }
499
QV('newAccountPass', (newAccountPass == 1));
500
+ QV('newAccountCaptcha', (newAccountCaptcha != ''));
501
+ QV('newAccountCaptchaImg', (newAccountCaptcha != ''));
502
QV('resetAccountDiv', (emailCheck == 'true'));
503
QV('hrAccountDiv', (emailCheck == 'true') || (newAccountPass == 1));
504
@@ -712,6 +724,7 @@
724
var pass1ok = (Q('apassword1').value.length > 0);
725
var pass2ok = (Q('apassword2').value.length > 0) && (Q('apassword2').value == Q('apassword1').value);
726
var newAccOk = (newAccountPass == 0) || (Q('anewaccountpass').value.length > 0);
727
+ var newCaptchaOk = (newAccountCaptcha == '') || (Q('anewaccountcaptcha').value.length > 0);
728
var ok = (userok && emailok && pass1ok && pass2ok && newAccOk);
729
730
// Color the fields
@@ -720,6 +733,7 @@
733
QS('nuPass1').color = pass1ok ? 'black' : '#7b241c';
734
QS('nuPass2').color = pass2ok ? 'black' : '#7b241c';
735
QS('nuToken').color = newAccOk ? 'black' : '#7b241c';
736
+ QS('nuCaptcha').color = newCaptchaOk ? 'black' : '#7b241c';
737
738
if (Q('apassword1').value == '') {
739
QH('passWarning', '');
@@ -748,13 +762,13 @@
762
}
763
}
764
if ((e != null) && (e.keyCode == 13)) {
751
-
765
if ((box == 1) && userok) { Q('aemail').focus(); }
766
if ((box == 2) && emailok) { Q('apassword1').focus(); }
767
if ((box == 3) && pass1ok) { Q('apassword2').focus(); }
768
if ((box == 4) && pass2ok) { if (passRequirements.hint === true) { Q('apasswordhint').focus(); } else { box = 5; } }
769
if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { box = 6; } }
757
- if (box == 6) { Q('createButton').click(); }
770
+ if (box == 6) { if (newAccountCaptcha != '') { Q('anewaccountcaptcha').focus(); } else { box = 7; } }
771
+ if (box == 7) { Q('createButton').click(); }
772
}
773
if (e != null) { haltEvent(e); }
774
QE('createButton', ok);
webserver.js
+48
-11
@@ -1359,6 +1359,26 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
1359
// If the email is the username, set this here.
1360
if (domain.usernameisemail) { req.body.username = req.body.email; }
1361
1362
+ // Check if there is domain.newAccountToken, check if supplied token is valid
1363
+ if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
1364
+ parent.debug('web', 'handleCreateAccountRequest: Invalid account creation token');
1365
+ req.session.loginmode = 2;
1366
+ req.session.messageid = 103; // Invalid account creation token.
1367
+ if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1368
+ return;
1369
+ }
1370
+
1371
+ // If needed, check the new account creation CAPTCHA
1372
+ if ((domain.newaccountscaptcha != null) && (domain.newaccountscaptcha !== false)) {
1373
+ const c = parent.decodeCookie(req.body.captchaargs, parent.loginCookieEncryptionKey, 10); // 10 minute timeout
1374
+ if ((c == null) || (c.type != 'newAccount') || (typeof c.captcha != 'string') || (c.captcha.length < 5) || (c.captcha != req.body.anewaccountcaptcha)) {
1375
+ req.session.loginmode = 2;
1376
+ req.session.messageid = 117; // Invalid security check
1377
+ if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1378
+ return;
1379
+ }
1380
+ }
1381
+
1382
// Accounts that start with ~ are not allowed
1383
if ((typeof req.body.username != 'string') || (req.body.username.length < 1) || (req.body.username[0] == '~')) {
1384
parent.debug('web', 'handleCreateAccountRequest: unable to create account (0)');
@@ -1423,14 +1443,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
1443
req.session.messageid = 102; // Existing account with this email address.
1444
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1445
} else {
1426
- // Check if there is domain.newAccountToken, check if supplied token is valid
1427
- if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
1428
- parent.debug('web', 'handleCreateAccountRequest: Invalid account creation token');
1429
- req.session.loginmode = 2;
1430
- req.session.messageid = 103; // Invalid account creation token.
1431
- if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
1432
- return;
1433
- }
1446
// Check if user exists
1447
if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
1448
parent.debug('web', 'handleCreateAccountRequest: Username already exists');
@@ -3054,20 +3066,29 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3066
twoFactorTimeout = domain.passwordrequirements.twofactortimeout * 1000;
3067
}
3068
3069
+ // Setup CAPTCHA if needed
3070
+ var newAccountCaptcha = '', newAccountCaptchaImage = '';
3071
+ if ((domain.newaccountscaptcha != null) && (domain.newaccountscaptcha !== false)) {
3072
+ newAccountCaptcha = obj.parent.encodeCookie({ type: 'newAccount', captcha: require('svg-captcha').randomText(5) }, obj.parent.loginCookieEncryptionKey);
3073
+ newAccountCaptchaImage = 'newAccountCaptcha.ashx?x=' + newAccountCaptcha;
3074
+ }
3075
+
3076
// Render the login page
3077
render(req, res,
3078
getRenderPage((domain.sitestyle == 2) ? 'login2' : 'login', req, domain),
3079
getRenderArgs({
3080
loginmode: loginmode,
3081
rootCertLink: getRootCertLink(domain),
3063
- newAccount: newAccountsAllowed,
3064
- newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1),
3082
+ newAccount: newAccountsAllowed, // True if new accounts are allowed from the login page
3083
+ newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), // 1 if new account creation requires password
3084
+ newAccountCaptcha: newAccountCaptcha, // If new account creation requires a CAPTCHA, this string will not be empty
3085
+ newAccountCaptchaImage: newAccountCaptchaImage, // Set to the URL of the CAPTCHA image
3086
serverDnsName: obj.getWebServerName(domain),
3087
serverPublicPort: httpsPort,
3088
passlogin: (typeof domain.showpasswordlogin == 'boolean') ? domain.showpasswordlogin : true,
3089
emailcheck: emailcheck,
3090
features: features,
3070
- sessiontime: (args.sessiontime) ? args.sessiontime : 60,
3091
+ sessiontime: (args.sessiontime) ? args.sessiontime : 60, // Session time in minutes, 60 minutes is the default
3092
passRequirements: passRequirements,
3093
customui: customui,
3094
footer: (domain.loginfooter == null) ? '' : domain.loginfooter,
@@ -3195,6 +3216,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3216
}
3217
}
3218
3219
+ // Handle new account Captcha GET
3220
+ function handleNewAccountCaptchaRequest(req, res) {
3221
+ const domain = checkUserIpAddress(req, res);
3222
+ if (domain == null) { return; }
3223
+ if ((domain.newaccountscaptcha == null) || (domain.newaccountscaptcha === false) || (req.query.x == null)) { res.sendStatus(404); return; }
3224
+ const c = obj.parent.decodeCookie(req.query.x, obj.parent.loginCookieEncryptionKey);
3225
+ if ((c == null) || (c.type !== 'newAccount') || (typeof c.captcha != 'string')) { res.sendStatus(404); return; }
3226
+ res.type('svg');
3227
+ res.status(200).end(require('svg-captcha')(c.captcha, {}));
3228
+ }
3229
+
3230
// Handle Captcha GET
3231
function handleCaptchaGetRequest(req, res) {
3232
const domain = checkUserIpAddress(req, res);
@@ -6104,6 +6136,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
6136
obj.app.get(url + 'pluginHandler.js', obj.handlePluginJS);
6137
}
6138
6139
+ // New account CAPTCHA request
6140
+ if ((domain.newaccountscaptcha != null) && (domain.newaccountscaptcha !== false)) {
6141
+ obj.app.get(url + 'newAccountCaptcha.ashx', handleNewAccountCaptchaRequest);
6142
+ }
6143
+
6144
// Check CrowdSec Bounser if configured
6145
if (parent.crowdSecBounser != null) {
6146
obj.app.get(url + 'captcha.ashx', handleCaptchaGetRequest);