Account actions
+ Manage phone number
Verify email
Enable web notifications
Localization Settings
@@ -1609,7 +1610,8 @@
// Update account actions
QV('p2AccountSecurity', ((features & 4) == 0) && (serverinfo.domainauth == false) && ((features & 4096) != 0)); // Hide Account Security if in single user mode, domain authentication to 2 factor auth not supported.
- QV('managePhoneNumber', features & 0x02000000);
+ QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000));
+ QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000));
QV('manageEmail2FA', features & 0x00800000);
QV('p2AccountPassActions', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide Account Actions if in single user mode or domain authentication
//QV('p2AccountImage', ((features & 4) == 0) && (serverinfo.domainauth == false)); // If account actions are not visible, also remove the image on that panel
@@ -1691,6 +1693,7 @@
// Check if none or at least 2 factors are enabled.
var authFactorCount = 0;
if ((features & 0x00800000) && (userinfo.otpekey == 1)) { authFactorCount += 1; }
+ if ((features & 0x02000000) && (features & 0x04000000) && (userinfo.phone != null)) { authFactorCount += 1; }
if (userinfo.otpkeys == 1) { authFactorCount += 1; }
if (userinfo.otpsecret == 1) { authFactorCount += 1; }
if (userinfo.otphkeys != null) { authFactorCount += userinfo.otphkeys; }
@@ -2189,7 +2192,7 @@
if (xxdialogMode && (xxdialogTag != 'verifyPhone')) return;
var x = '
';
+ x += '
' + "Verification code:" + '
';
setDialogMode(2, "Phone Notifications", 3, account_managePhoneConfirm, x, message.cookie);
Q('d2phoneCodeInput').focus();
account_managePhoneCodeValidate();
@@ -7896,17 +7899,17 @@
} else {
x = '
';
+ x += '
' + "Phone number:" + '
';
setDialogMode(2, "Phone Notifications", 3, account_managePhoneAdd, x, 'verifyPhone');
Q('d2phoneinput').focus();
account_managePhoneValidate();
}
}
- function isPhoneNumber(x) { return x.match(/^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/) }
+ function isPhoneNumber(x) { return x.match(/^\(?([0-9]{3,4})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/) }
function account_managePhoneValidate(x) { var ok = isPhoneNumber(Q('d2phoneinput').value); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
- function account_managePhoneCodeValidate(x) { var ok = Q('d2phoneCodeInput').value.match(/[0-9]/); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
- function account_managePhoneConfirm(b, tag) { meshserver.send({ action: 'confirmPhone', code: parseInt(Q('d2phoneCodeInput').value), cookie: tag }); }
+ function account_managePhoneCodeValidate(x) { var ok = (Q('d2phoneCodeInput').value.length == 6) && Q('d2phoneCodeInput').value.match(/[0-9]/); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
+ function account_managePhoneConfirm(b, tag) { meshserver.send({ action: 'confirmPhone', code: Q('d2phoneCodeInput').value, cookie: tag }); }
function account_managePhoneAdd() { if (isPhoneNumber(Q('d2phoneinput').value) == false) return; QE('d2phoneinput', false); meshserver.send({ action: 'verifyPhone', phone: Q('d2phoneinput').value }); }
function account_managePhoneRemove() { if (Q('d2delPhone').checked) { meshserver.send({ action: 'removePhone' }); } }
function account_managePhoneRemoveValidate() { QE('idx_dlgOkButton', Q('d2delPhone').checked); }
@@ -8159,7 +8162,7 @@
if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" tab to change and verify an email address."); return false; }
// Remind the user to add two factor authentication
- if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" tab and look at the \"Account Security\" section."); return false; }
+ if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || ((features & 0x02000000) && (features & 0x04000000) && (userinfo.phone != null)) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" tab and look at the \"Account Security\" section."); return false; }
// We are allowed, let's prompt to information
var x = "Create a new device group using the options below." + '
';
@@ -9798,10 +9801,9 @@
}
}
- if ((user.otpsecret > 0) || (user.otphkeys > 0)) { username += '
'; }
+ if ((user.otpsecret > 0) || (user.otphkeys > 0) || ((user.otpekey == 1) && (features & 0x00800000)) || ((user.phone != null) && (features & 0x04000000))) { username += '
'; }
if (user.phone != null) { username += '
'; }
if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += '
'; }
-
x += '
';
x += '';
x += '
';
@@ -9910,7 +9912,7 @@
function showSendSMS(userid) {
if (xxdialogMode) return;
- setDialogMode(2, "Send SMS", 3, showSendSMSEx, '
', decodeURIComponent(userid));
+ setDialogMode(2, "Send SMS", 3, showSendSMSEx, '', userid);
Q('d2smsText').focus();
showSendSMSValidate();
}
@@ -10661,6 +10663,7 @@
if (user.otpsecret > 0) { factors.push("Authentication App"); }
if (user.otphkeys > 0) { factors.push("Security Key"); }
if (user.otpkeys > 0) { factors.push("Backup Codes"); }
+ if ((user.phone != null) && (features & 0x04000000)) { factors.push("SMS"); }
x += addDeviceAttribute("Security", ' ' + factors.join(', '));
}
@@ -10668,7 +10671,7 @@
// Add action buttons
x += ' ';
- if (user.phone && (features & 0x02000000)) { x += ' '; }
+ if (user.phone && (features & 0x02000000)) { x += ' '; }
if (!self && (activeSessions > 0)) { x += ' '; }
// Setup the panel
@@ -11459,7 +11462,7 @@
x += ' = 0) ? 'checked' : '') + '>' + "MeshAgent update" + '
';
x += ' = 0) ? 'checked' : '') + '>' + "Server Certificate" + '
';
x += ' = 0) ? 'checked' : '') + '>' + "Server Database" + '
';
- x += ' = 0) ? 'checked' : '') + '>' + "Email Traffic" + '
';
+ x += ' = 0) ? 'checked' : '') + '>' + "Email/SMS Traffic" + '
';
x += '' + "Web Server" + '
';
x += ' = 0) ? 'checked' : '') + '>' + "Web Server" + '
';
x += ' = 0) ? 'checked' : '') + '>' + "Web Server Requests" + '
';
diff --git a/views/login.handlebars b/views/login.handlebars
index e7c1576b..95b5cb0e 100644
--- a/views/login.handlebars
+++ b/views/login.handlebars
@@ -163,6 +163,7 @@
+
@@ -299,10 +300,11 @@
var nightMode = (getstore('_nightMode', '0') == '1');
var publicKeyCredentialRequestOptions = null;
var otpemail = ('{{{otpemail}}}' === 'true');
+ var otpsms = ('{{{otpsms}}}' === 'true');
// Display the right server message
var messageid = parseInt('{{{messageid}}}');
- var okmessages = ['', "Hold on, reset mail sent.", "Email sent.", "Email verification required, check your mailbox and click the confirmation link."];
+ var okmessages = ['', "Hold on, reset mail sent.", "Email sent.", "Email verification required, check your mailbox and click the confirmation link.", "SMS 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 = '';
@@ -380,6 +382,7 @@
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));
+ QV('smsKeyButton', otpsms && (messageid != 2));
}
if (loginMode == '5') {
@@ -459,6 +462,17 @@
Q('tokenOkButton').click();
}
+ function useSMSToken() {
+ if (otpsms != true) return;
+ setDialogMode(1, "Secure Login", 3, useSMSTokenEx, "Send token to registed phone number?");
+ }
+
+ function useSMSTokenEx() {
+ Q('hwtokenInput').value = '**sms**';
+ QE('tokenOkButton', true);
+ Q('tokenOkButton').click();
+ }
+
function showPassHint(e) {
messagebox("Password Hint", passhint);
haltEvent(e);
diff --git a/webserver.js b/webserver.js
index 409fb7f9..061e46e5 100644
--- a/webserver.js
+++ b/webserver.js
@@ -598,8 +598,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}
}
+ // See if SMS 2FA is available
+ var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
+
// Check if a 2nd factor is present
- return ((parent.config.settings.no2factorauth !== true) && ((user.otpsecret != null) || ((user.email != null) && (user.emailVerified == true) && (parent.mailserver != null) && (user.otpekey != null)) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
+ return ((parent.config.settings.no2factorauth !== true) && (sms2fa || (user.otpsecret != null) || ((user.email != null) && (user.emailVerified == true) && (parent.mailserver != null) && (user.otpekey != null)) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
}
// Check the 2-step auth token
@@ -611,6 +614,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check if we can use OTP tokens with email
var otpemail = (parent.mailserver != null);
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
+ var otpsms = (parent.smsserver != null);
+ if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
// Check email key
if ((otpemail) && (user.otpekey != null) && (user.otpekey.d != null) && (user.otpekey.k === token)) {
@@ -624,6 +629,18 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}
}
+ // Check sms key
+ if ((otpsms) && (user.phone != null) && (user.otpsms != null) && (user.otpsms.d != null) && (user.otpsms.k === token)) {
+ var deltaTime = (Date.now() - user.otpsms.d);
+ if ((deltaTime > 0) && (deltaTime < 300000)) { // Allow 5 minutes to use the SMS token (10000 * 60 * 5).
+ delete user.otpsms;
+ obj.db.SetUser(user);
+ parent.debug('web', 'checkUserOneTimePassword: success (SMS).');
+ func(true);
+ return;
+ }
+ }
+
// Check hardware key
if (user.otphkeys && (user.otphkeys.length > 0) && (typeof (hwtoken) == 'string') && (hwtoken.length > 0)) {
var authResponse = null;
@@ -776,9 +793,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (userid) {
var user = obj.users[userid];
+ var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (parent.mailserver != null) && (user.email != null) && (user.emailVerified == true) && (user.otpekey != null));
+ var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
+
// 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)) {
+ if ((req.body.hwtoken == '**email**') && email2fa) {
user.otpekey = { k: obj.common.zeroPad(getRandomEightDigitInteger(), 8), d: Date.now() };
obj.db.SetUser(user);
parent.debug('web', 'Sending 2FA email to: ' + user.email);
@@ -789,6 +809,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
return;
}
+ if ((req.body.hwtoken == '**sms**') && sms2fa) {
+ // Cause a token to be sent to the user's phone number
+ user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() };
+ obj.db.SetUser(user);
+ parent.debug('web', 'Sending 2FA SMS to: ' + user.phone);
+ parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req));
+ // Ask for a login token & confirm sms was sent
+ req.session.messageid = 4; // "SMS 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;
@@ -809,6 +842,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
setTimeout(function () {
req.session.loginmode = '4';
req.session.tokenemail = ((user.email != null) && (user.emailVerified == true) && (parent.mailserver != null) && (user.otpekey != null));
+ req.session.tokensms = ((user.phone != null) && (parent.smsserver != null));
req.session.tokenusername = xusername;
req.session.tokenpassword = xpassword;
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
@@ -920,7 +954,6 @@ 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;
@@ -1140,7 +1173,6 @@ 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;
@@ -1839,6 +1871,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (parent.mailserver != null)) { features += 0x00800000; } // using email for 2FA is allowed
if (domain.agentinvitecodes == true) { features += 0x01000000; } // Support for agent invite codes
if (parent.smsserver != null) { features += 0x02000000; } // SMS messaging is supported
+ if ((parent.smsserver != null) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false))) { features += 0x04000000; } // SMS 2FA is allowed
// Create a authentication cookie
const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: cleanRemoteAddr(req.ip) }, obj.parent.loginCookieEncryptionKey);
@@ -1936,9 +1969,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check if we can use OTP tokens with email
var otpemail = (parent.mailserver != null) && (req.session != null) && (req.session.tokenemail != null);
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
+ var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tokensms != null);
+ if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
// Render the login page
- render(req, res, getRenderPage('login', req, domain), 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));
+ render(req, res, getRenderPage('login', req, domain), 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, otpsms: otpsms }, domain));
}
// Handle a post request on the root
@@ -4014,7 +4049,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (checkUserOneTimePasswordRequired(domain, user, req) == true) {
// Figure out if email 2FA is allowed
var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (parent.mailserver != null) && (user.otpekey != null));
- if ((typeof req.query.token != 'string') || (req.query.token == '**email**')) {
+ var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
+ if ((typeof req.query.token != 'string') || (req.query.token == '**email**') || (req.query.token == '**sms**')) {
if ((req.query.token == '**email**') && (email2fa == true)) {
// Cause a token to be sent to the user's registered email
user.otpekey = { k: obj.common.zeroPad(getRandomEightDigitInteger(), 8), d: Date.now() };
@@ -4023,6 +4059,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
parent.mailserver.sendAccountLoginMail(domain, user.email, user.otpekey.k, obj.getLanguageCodes(req));
// Ask for a login token & confirm email was sent
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa, email2fasent: true })); ws.close(); } catch (e) { }
+ } else if ((req.query.token == '**sms**') && (sms2fa == true)) {
+ // Cause a token to be sent to the user's phone number
+ user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() };
+ obj.db.SetUser(user);
+ parent.debug('web', 'Sending 2FA SMS to: ' + user.phone);
+ parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req));
+ // Ask for a login token & confirm sms was sent
+ try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', sms2fa: sms2fa, sms2fasent: true })); ws.close(); } catch (e) { }
} else {
// Ask for a login token
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa })); ws.close(); } catch (e) { }
@@ -4097,6 +4141,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (checkUserOneTimePasswordRequired(domain, user, req) == true) {
// Figure out if email 2FA is allowed
var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (parent.mailserver != null) && (user.otpekey != null));
+ var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
if (s.length != 3) {
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa })); ws.close(); } catch (e) { }
} else {
@@ -4110,6 +4155,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
parent.mailserver.sendAccountLoginMail(domain, user.email, user.otpekey.k, obj.getLanguageCodes(req));
// Ask for a login token & confirm email was sent
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa, email2fasent: true })); ws.close(); } catch (e) { }
+ } else if ((s[2] == '**sms**') && (sms2fa == true)) {
+ // Cause a token to be sent to the user's phone number
+ user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() };
+ obj.db.SetUser(user);
+ parent.debug('web', 'Sending 2FA SMS to: ' + user.phone);
+ parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req));
+ // Ask for a login token & confirm sms was sent
+ try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', sms2fa: sms2fa, sms2fasent: true })); ws.close(); } catch (e) { }
} else {
// Ask for a login token
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa })); ws.close(); } catch (e) { }
@@ -4621,6 +4674,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
delete user2.domain;
delete user2.subscriptions;
delete user2.passtype;
+ delete user2.otpsms;
if ((typeof user2.otpekey == 'object') && (user2.otpekey != 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.
@@ -4974,6 +5028,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// 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; }
+ function getRandomSixDigitInteger() { var bigInt; do { bigInt = parent.crypto.randomBytes(4).readUInt32BE(0); } while (bigInt >= 4200000000); return bigInt % 1000000; }
// 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; } }