Email based 2FA almost completed.

This commit is contained in:
Ylian Saint-Hilaire 2020-03-13 20:39:21 -07:00
parent cee4e44c18
commit 59f1463a3f
6 changed files with 121 additions and 17 deletions

View File

@ -43,11 +43,13 @@ module.exports.CreateMeshMail = function (parent) {
// Set default mail templates
// You can override these by placing a file with the same name in "meshcentral-data/mail"
// If the server hash many domains, just add the domainid to the file like this: 'account-check-customer1.html', 'mesh-invite-customer1.txt'.
obj.mailTemplates['account-login.html'] = '<title>[[[SERVERNAME]]] - Account Login</title>\r\n<div style="font-family:Arial,Helvetica,sans-serif"><table style="background-color:#003366;color:lightgray;width:100%" cellpadding=8><tr><td><b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Account Login</b></td></tr></table><p>Your login token is: [[[TOKEN]]]</p><p>This token can only be used once and is valid for 5 minutes.</p></div>';
obj.mailTemplates['account-invite.html'] = '<title>[[[SERVERNAME]]] - Account Invitation</title>\r\n<div style="font-family:Arial,Helvetica,sans-serif"><table style="background-color:#003366;color:lightgray;width:100%" cellpadding=8><tr><td><b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Account Invitation</b></td></tr></table><p>An account was created for you on server <a href="[[[SERVERURL]]]">[[[SERVERNAME]]]</a>, you can access it now with:</p><p>&nbsp;&nbsp;&nbsp;Username: <b>[[[ACCOUNTNAME]]]</b><br />&nbsp;&nbsp;&nbsp;Password: <b>[[[PASSWORD]]]</b></p>Best regards,<br>[[[USERNAME]]]<br></div>';
obj.mailTemplates['account-check.html'] = '<title>[[[SERVERNAME]]] - Email Verification</title>\r\n<div style="font-family:Arial,Helvetica,sans-serif"><table style="background-color:#003366;color:lightgray;width:100%" cellpadding=8><tr><td><b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Verification</b></td></tr></table><p>Hi [[[USERNAME]]], <a href="[[[SERVERURL]]]">[[[SERVERNAME]]]</a> is requesting email verification, click on the following link to complete the process.</p><p style="margin-left:30px"><a href="[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]]">Click here to verify your e-mail address.</a></p>If you did not initiate this request, please ignore this mail.</div>';
obj.mailTemplates['account-reset.html'] = '<title>[[[SERVERNAME]]] - Account Reset</title>\r\n<div style="font-family:Arial,Helvetica,sans-serif"><table style="background-color:#003366;color:lightgray;width:100%" cellpadding=8><tr><td><b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Verification</b></td></tr></table><p>Hi [[[USERNAME]]], <a href="[[[SERVERURL]]]">[[[SERVERNAME]]]</a> is requesting an account password reset, click on the following link to complete the process.</p><p style="margin-left:30px"><a href="[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]]">Click here to reset your account password.</a></p>If you did not initiate this request, please ignore this mail.</div>';
obj.mailTemplates['mesh-invite.html'] = '<title>[[[SERVERNAME]]] - Invitation</title>\r\n<div style="font-family:Arial,Helvetica,sans-serif"><table style="background-color:#003366;color:lightgray;width:100%" cellpadding=8><tr><td><b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Agent Installation</b></td></tr></table>[[[AREA-NAME]]]<p>Hello [[[NAME]]],</p>[[[/AREA-NAME]]]<p>User [[[USERNAME]]] on server <a href="[[[SERVERURL]]]">[[[SERVERNAME]]]</a> is requesting you to install software to start a remote control session.</p>[[[AREA-MSG]]]<p>Message: <b>[[[MSG]]]</b></p>[[[/AREA-MSG]]][[[AREA-WINDOWS]]]<p style="margin-left:30px"><a href="[[[SERVERURL]]]/meshagents?id=3&meshid=[[[MESHIDHEX]]]&tag=mailto:[[[EMAIL]]]&installflags=[[[INSTALLFLAGS]]]">Click here to download the MeshAgent for Windows.</a></p>[[[/AREA-WINDOWS]]][[[AREA-OSX]]]<p style="margin-left:30px"><a href="[[[SERVERURL]]]/meshagents?id=16&meshid=[[[MESHIDHEX]]]&tag=mailto:[[[EMAIL]]]&installflags=[[[INSTALLFLAGS]]]">Click here to download the MeshAgent for Apple OSX.</a></p>[[[/AREA-OSX]]][[[AREA-LINUX]]]<p>For Linux, cut & paste the following in a terminal to install the agent:<br /><pre style="margin-left:30px">wget -q "[[[SERVERURL]]]/meshagents?script=1" --no-check-certificate -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh [[[SERVERURL]]] \'[[[MESHIDHEX]]]\'</pre></p>[[[/AREA-LINUX]]][[[AREA-LINK]]]<p>To install the software, <a href="[[[SERVERURL]]][[[LINKURL]]]">click here</a> and follow the instructions.</p>[[[/AREA-LINK]]]<p>If you did not initiate this request, please ignore this mail.</p>Best regards,<br>[[[USERNAME]]]<br></div>';
obj.mailTemplates['account-login.txt'] = '[[[SERVERNAME]]] - Account Login\r\nYour login token is: [[[TOKEN]]]\r\n\r\nThis token can only be used once and is valid for 5 minutes.';
obj.mailTemplates['account-invite.txt'] = '[[[SERVERNAME]]] - Account Invitation\r\nAn account was created for you on server [[[SERVERNAME]]] ([[[SERVERURL]]]/), you can access it now with username \"[[[ACCOUNTNAME]]]\" and password \"[[[PASSWORD]]]\".\r\n\r\nBest regards,\r\n[[[USERNAME]]]';
obj.mailTemplates['account-check.txt'] = '[[[SERVERNAME]]] - Email Verification\r\nHi [[[USERNAME]]], [[[SERVERNAME]]] ([[[SERVERURL]]]) is performing an e-mail verification. Nagivate to the following link to complete the process:\r\n\r\n[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]]\r\n\r\nIf you did not initiate this request, please ignore this mail.\r\n';
obj.mailTemplates['account-reset.txt'] = '[[[SERVERNAME]]] - Account Reset\r\nHi [[[USERNAME]]], [[[SERVERNAME]]] ([[[SERVERURL]]]) is requesting an account password reset. Nagivate to the following link to complete the process:\r\n\r\n[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]]\r\n\r\nIf you did not initiate this request, please ignore this mail.';
@ -133,6 +135,20 @@ module.exports.CreateMeshMail = function (parent) {
sendNextMail();
};
// Send account login mail / 2 factor token
obj.sendAccountLoginMail = function (domain, email, token) {
var template = getTemplateEx('account-login', domain);
if ((template == null) || (template.htmlSubject == null) || (template.txtSubject == null) || (parent.certificates == null) || (parent.certificates.CommonName == null) || (parent.certificates.CommonName.indexOf('.') == -1)) return; // If the server name is not set, invitation not possible.
// Set all the options.
var options = { email: email, servername: domain.title ? domain.title : 'MeshCentral', token: token };
// Send the email
console.log(options);
obj.pendingMails.push({ to: email, from: parent.config.smtp.from, subject: mailReplacements(template.htmlSubject, domain, options), text: mailReplacements(template.txt, domain, options), html: mailReplacements(template.html, domain, options) });
sendNextMail();
};
// Send account invitation mail
obj.sendAccountInviteMail = function (domain, username, accountname, email, password) {
var template = getTemplateEx('account-invite', domain);

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.5.0-e",
"version": "0.5.0-f",
"keywords": [
"Remote Management",
"Intel AMT",

View File

@ -507,10 +507,9 @@ module.exports.pluginHandler = function (parent) {
obj.removePlugin = function (id, func) {
parent.db.getPlugin(id, function (err, docs) {
var plugin = docs[0];
var rimraf = null;
try { rimraf = require('rimraf'); } catch (ex) { }
var rimraf = require('rimraf');
let pluginPath = obj.parent.path.join(obj.pluginPath, plugin.shortName);
if (rimraf) rimraf.sync(pluginPath);
rimraf.sync(pluginPath);
parent.db.deletePlugin(id, func);
delete obj.plugins[plugin.shortName];
});

View File

@ -164,7 +164,10 @@
<tr>
<td colspan=2>
<div style=float:right><input id=tokenOkButton type=submit value="Login" disabled="disabled" /></div>
<div style=float:right><input style="display:none;float:right" id=securityKeyButton type=button value="Use Security Key" onclick="useSecurityKey()" /></div>
<div style=float:right>
<input style="display:none;float:right" id=securityKeyButton type=button value="Use Security Key" onclick="useSecurityKey()" />
<input style="display:none;float:right" id=emailKeyButton type=button value="Email" onclick="useEmailToken()" />
</div>
</td>
</tr>
</table>
@ -238,7 +241,7 @@
</table>
</div>
</div>
<div id=dialog style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:180px;width:400px;display:none">
<div id=dialog style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:180px;width:300px;display:none">
<div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
<div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
<div id=id_dialogtitle style=padding:5px></div>
@ -271,10 +274,11 @@
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
var publicKeyCredentialRequestOptions = null;
var currentpanel = 0;
var otpemail = ('{{{otpemail}}}' === 'true');
// Display the right server message
var messageid = parseInt('{{{messageid}}}');
var okmessages = ['', "Hold on, reset mail sent."];
var okmessages = ['', "Hold on, reset mail sent.", "Email sent."];
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."];
if (messageid > 0) {
var msg = '';
@ -328,6 +332,7 @@
if (loginMode == '4') {
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
QV('securityKeyButton', (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'));
QV('emailKeyButton', otpemail && (messageid != 2)); // TODO
}
if (loginMode == '5') {
@ -364,6 +369,7 @@
// Use a hardware security key
function useSecurityKey() {
if (xxdialogMode) return;
if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
@ -393,6 +399,18 @@
}
}
function useEmailToken() {
if (xxdialogMode) return;
if (otpemail != true) return;
setDialogMode(1, "Secure Login", 3, useEmailKeyEx, "Send token to registed email address?");
}
function useEmailKeyEx() {
Q('hwtokenInput').value = '**email**';
QE('tokenOkButton', true);
Q('tokenOkButton').click();
}
function showPassHint() {
if (passRequirements.hint === true) { messagebox("Password Hint", passhint); }
}
@ -633,7 +651,7 @@
if (((b & 8) || x) && f) f(x, t);
}
function center() { QS('dialog').left = ((((getDocWidth() - 400) / 2)) + 'px'); }
function center() { QS('dialog').left = ((((getDocWidth() - 300) / 2)) + 'px'); }
function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
function getDocWidth() { if (window.innerWidth) return window.innerWidth; if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientWidth != 0) return document.documentElement.clientWidth; return document.getElementsByTagName('body')[0].clientWidth; }

View File

@ -160,7 +160,10 @@
<tr>
<td colspan=2>
<div style=float:right><input id=tokenOkButton type=submit value="Login" disabled="disabled" /></div>
<div style=float:right><input style="display:none;float:right" id=securityKeyButton type=button value="Use Security Key" onclick="useSecurityKey()" /></div>
<div style=float:right>
<input style="display:none;float:right" id=securityKeyButton type=button value="Use Security Key" onclick="useSecurityKey()" />
<input style="display:none;float:right" id=emailKeyButton type=button value="Email" onclick="useEmailToken()" />
</div>
</td>
</tr>
</table>
@ -270,10 +273,11 @@
var webPageFullScreen = true;
var nightMode = (getstore('_nightMode', '0') == '1');
var publicKeyCredentialRequestOptions = null;
var otpemail = ('{{{otpemail}}}' === 'true');
// Display the right server message
var messageid = parseInt('{{{messageid}}}');
var okmessages = ['', "Hold on, reset mail sent."];
var okmessages = ['', "Hold on, reset mail sent.", "Email sent."];
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."];
if (messageid > 0) {
var msg = '';
@ -349,6 +353,7 @@
if (loginMode == '4') {
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
QV('securityKeyButton', (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'));
QV('emailKeyButton', otpemail && (messageid != 2));
}
if (loginMode == '5') {
@ -417,6 +422,17 @@
}
}
function useEmailToken() {
if (otpemail != true) return;
setDialogMode(1, "Secure Login", 3, useEmailKeyEx, "Send token to registed email address?");
}
function useEmailKeyEx() {
Q('hwtokenInput').value = '**email**';
QE('tokenOkButton', true);
Q('tokenOkButton').click();
}
function showPassHint(e) {
messagebox("Password Hint", passhint);
haltEvent(e);

View File

@ -541,7 +541,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}
// Check if a 2nd factor is present
return ((parent.config.settings.no2factorauth !== true) && ((user.otpsecret != null) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
return ((parent.config.settings.no2factorauth !== true) && ((user.otpsecret != null) || (user.otpekey != null) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
}
// Check the 2-step auth token
@ -550,6 +550,22 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true) && (parent.config.settings.no2factorauth !== true));
if (twoStepLoginSupported == false) { parent.debug('web', 'checkUserOneTimePassword: not supported.'); func(true); return; };
// Check if we can use OTP tokens with email
var otpemail = (parent.mailserver != null);
if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.email2factor == false)) { otpemail = false; }
// Check email key
if ((otpemail) && (user.otpekey != null) && (user.otpekey.d != null) && (user.otpekey.k === token)) {
var deltaTime = (Date.now() - user.otpekey.d);
if ((deltaTime > 0) && (deltaTime < 300000)) { // Allow 5 minutes to use the email token (10000 * 60 * 5).
user.otpekey = {};
obj.db.SetUser(user);
parent.debug('web', 'checkUserOneTimePassword: success (email).');
func(true);
return;
}
}
// Check hardware key
if (user.otphkeys && (user.otphkeys.length > 0) && (typeof (hwtoken) == 'string') && (hwtoken.length > 0)) {
var authResponse = null;
@ -595,10 +611,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Update the hardware key counter and accept the 2nd factor
webAuthnKey.counter = webauthnResponse.counter;
obj.db.SetUser(user);
parent.debug('web', 'checkUserOneTimePassword: success.');
parent.debug('web', 'checkUserOneTimePassword: success (hardware).');
func(true);
} else {
parent.debug('web', 'checkUserOneTimePassword: fail.');
parent.debug('web', 'checkUserOneTimePassword: fail (hardware).');
func(false);
}
return;
@ -610,12 +626,21 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check Google Authenticator
const otplib = require('otplib')
otplib.authenticator.options = { window: 2 }; // Set +/- 1 minute window
if (user.otpsecret && (typeof (token) == 'string') && (token.length == 6) && (otplib.authenticator.check(token, user.otpsecret) == true)) { func(true); return; };
if (user.otpsecret && (typeof (token) == 'string') && (token.length == 6) && (otplib.authenticator.check(token, user.otpsecret) == true)) {
parent.debug('web', 'checkUserOneTimePassword: success (authenticator).');
func(true);
return;
};
// Check written down keys
if ((user.otpkeys != null) && (user.otpkeys.keys != null) && (typeof (token) == 'string') && (token.length == 8)) {
var tokenNumber = parseInt(token);
for (var i = 0; i < user.otpkeys.keys.length; i++) { if ((tokenNumber === user.otpkeys.keys[i].p) && (user.otpkeys.keys[i].u === true)) { user.otpkeys.keys[i].u = false; func(true); return; } }
for (var i = 0; i < user.otpkeys.keys.length; i++) {
if ((tokenNumber === user.otpkeys.keys[i].p) && (user.otpkeys.keys[i].u === true)) {
parent.debug('web', 'checkUserOneTimePassword: success (one-time).');
user.otpkeys.keys[i].u = false; func(true); return;
}
}
}
// Check OTP hardware key
@ -631,7 +656,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var yubikeyotp = require('yubikeyotp');
var request = { otp: token, id: domain.yubikey.id, key: domain.yubikey.secret, timestamp: true }
if (domain.yubikey.proxy) { request.requestParams = { proxy: domain.yubikey.proxy }; }
yubikeyotp.verifyOTP(request, function (err, results) { func((results != null) && (results.status == 'OK')); });
yubikeyotp.verifyOTP(request, function (err, results) {
if ((results != null) && (results.status == 'OK')) {
parent.debug('web', 'checkUserOneTimePassword: success (Yubikey).');
func(true);
} else {
parent.debug('web', 'checkUserOneTimePassword: fail (Yubikey).');
func(false);
}
});
return;
}
}
@ -687,6 +720,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check if this user has 2-step login active
if ((req.session.loginmode != '6') && checkUserOneTimePasswordRequired(domain, user, req)) {
if ((req.body.hwtoken == '**email**') && (user.email != null) && (user.emailVerified == true) && (parent.mailserver != null) && (user.otpekey != null)) {
user.otpekey = { k: obj.common.zeroPad(getRandomEightDigitInteger(), 8), d: Date.now() };
obj.db.SetUser(user);
parent.debug('web', 'Sending 2FA email to: ' + user.email);
parent.mailserver.sendAccountLoginMail(domain, user.email, user.otpekey.k);
req.session.messageid = 2; // "Email sent" message
req.session.loginmode = '4';
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
return;
}
checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) {
if (result == false) {
var randomWaitTime = 0;
@ -706,6 +750,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Wait and redirect the user
setTimeout(function () {
req.session.loginmode = '4';
req.session.tokenemail = ((user.email != null) && (user.emailVerified == true) && (parent.mailserver != null) && (user.otpekey != null));
req.session.tokenusername = xusername;
req.session.tokenpassword = xpassword;
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
@ -793,6 +838,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
//req.session.regenerate(function () {
// Store the user's primary key in the session store to be retrieved, or in this case the entire user object
delete req.session.loginmode;
delete req.session.tokenemail;
delete req.session.tokenusername;
delete req.session.tokenpassword;
delete req.session.tokenemail;
@ -1008,6 +1054,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Failed, error out.
parent.debug('web', 'handleResetPasswordRequest: failed authenticate()');
delete req.session.loginmode;
delete req.session.tokenemail;
delete req.session.tokenusername;
delete req.session.tokenpassword;
delete req.session.resettokenusername;
@ -1672,8 +1719,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var hwstate = null;
if (hardwareKeyChallenge) { hwstate = obj.parent.encodeCookie({ u: req.session.tokenusername, p: req.session.tokenpassword, c: req.session.u2fchallenge }, obj.parent.loginCookieEncryptionKey) }
// Check if we can use OTP tokens with email
var otpemail = (parent.mailserver != null) && (req.session.tokenemail);
if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.email2factor == false)) { otpemail = false; }
// Render the login page
render(req, res, getRenderPage('login', req), getRenderArgs({ loginmode: loginmode, rootCertLink: getRootCertLink(), newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge), messageid: msgid, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate }, domain));
render(req, res, getRenderPage('login', req), getRenderArgs({ loginmode: loginmode, rootCertLink: getRootCertLink(), newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge), messageid: msgid, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate, otpemail: otpemail }, domain));
}
// Handle a post request on the root
@ -4223,6 +4274,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
delete user2.domain;
delete user2.subscriptions;
delete user2.passtype;
if ((typeof user2.otpkeys == 'object') && (user2.otpkeys != null)) { user2.otpekey = 1; } // Indicates that email 2FA is enabled.
if ((typeof user2.otpsecret == 'string') && (user2.otpsecret != null)) { user2.otpsecret = 1; } // Indicates a time secret is present.
if ((typeof user2.otpkeys == 'object') && (user2.otpkeys != null)) { user2.otpkeys = 0; if (user.otpkeys != null) { for (var i = 0; i < user.otpkeys.keys.length; i++) { if (user.otpkeys.keys[i].u == true) { user2.otpkeys = 1; } } } } // Indicates the number of one time backup codes that are active.
if ((typeof user2.otphkeys == 'object') && (user2.otphkeys != null)) { user2.otphkeys = user2.otphkeys.length; } // Indicates the number of hardware keys setup
@ -4510,6 +4562,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function getRandomPassword() { return Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); }
function getRandomLowerCase(len) { var r = '', random = obj.crypto.randomBytes(len); for (var i = 0; i < len; i++) { r += String.fromCharCode(97 + (random[i] % 26)); } return r; }
// Generate a 8 digit integer with even random probability for each value.
function getRandomEightDigitInteger() { var bigInt; do { bigInt = parent.crypto.randomBytes(4).readUInt32BE(0); } while (bigInt >= 4200000000); return bigInt % 100000000; }
// Clean a IPv6 address that encodes a IPv4 address
function cleanRemoteAddr(addr) { if (typeof addr != 'string') { return null; } if (addr.indexOf('::ffff:') == 0) { return addr.substring(7); } else { return addr; } }