MeshCentral/views/login.handlebars

959 lines
62 KiB
Handlebars

<!DOCTYPE html>
<html dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head lang="en">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="format-detection" content="telephone=no" />
<meta name="robots" content="noindex,nofollow">
<link rel="manifest" href="{{{domainurl}}}manifest.json">
<link rel="shortcut icon" type="image/x-icon" href="{{{domainurl}}}favicon.ico" />
<link keeplink=1 type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/u2f-api{{min}}.js"></script>
<title>{{{title}}} - Login</title>
</head>
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" class="arg_hide login">
<div id=container>
<div id=masthead>
<div style="float:left">{{{titlehtml}}}</div>
<div class="title">{{{title1}}}</div>
<div class="title2">{{{title2}}}</div>
</div>
<div id=topbar class="noselect style3" style="height:24px">
<div id=uiMenuButton title="User interface selection" onclick="showUserInterfaceSelectMenu()">
&diams;
<div id=uiMenu style="display:none">
<div id=uiViewButton1 class=uiSelector onclick=userInterfaceSelectMenu(1) title="Left bar interface"><div class="uiSelector1"></div></div>
<div id=uiViewButton2 class=uiSelector onclick=userInterfaceSelectMenu(2) title="Top bar interface"><div class="uiSelector2"></div></div>
<div id=uiViewButton3 class=uiSelector onclick=userInterfaceSelectMenu(3) title="Fixed width interface"><div class="uiSelector3"></div></div>
<div id=uiViewButton4 class=uiSelector onclick=toggleNightMode() title="Toggle night mode"><div class="uiSelector4"></div></div>
</div>
</div>
</div>
<div id=column_l>
<h1>Welcome</h1>
<div id="welcomeText" style="display:none">Connect to your home or office devices from anywhere in the world using MeshCentral, the real time, open source remote monitoring and management web site. You will need to download and install a management agent on your computers. Once installed, computers will show up in the &quot;My Devices&quot; section of this web site and you will be able to monitor them and take control of them.</div>
<table id="centralTable" style="">
<tr>
<td id="welcomeimage">
<picture>
<img alt="" src=welcome.jpg style="border-radius:20px" />
</picture>
</td>
<td id="logincell">
<div id=loginpanel style="display:none">
<form method=post>
<input type=hidden name=action value=login />
<div id=message1></div>
<div id="loginuserpassdiv" style="display:none">
<div>
<b>Log In</b>
</div>
<table>
<tr>
<td id=loginusername align=right width=100>Username:</td>
<td><input id=username {{{autocomplete}}}="username" type=text maxlength=64 name=username onchange=validateLogin(1) onkeyup=validateLogin(1,event) /></td>
</tr>
<tr>
<td align=right>Password:</td>
<td><input id=password {{{autocomplete}}}="current-password" type=password maxlength=256 name=password onchange=validateLogin(2) onkeyup=validateLogin(2,event) /></td>
</tr>
<tr>
<td><div id=showPassHintLink style=display:none><a onclick="return showPassHint(event);" href="#" style="cursor:pointer">Show Hint</a></div></td>
<td align=right><input id=loginButton type=submit value="Log In" disabled="disabled" /></td>
</tr>
</table>
<div id="hrAccountDiv" style="display:none"><hr /></div>
<div id="resetAccountDiv" style="display:none;padding:2px">
<span id="resetAccountSpan">Forgot username/password?</span> <a onclick="return xgo(3,event);" href="#" style=cursor:pointer>Reset account</a>.
</div>
<div id="newAccountDiv" style="display:none;padding:2px">
Don&#39;t have an account? <a onclick="return xgo(2,event);" href="#" style=cursor:pointer>Create one</a>.
</div>
</div>
<input id=loginformargs name="urlargs" type="hidden" value="" />
<div id="authStrategies" style="display:none">
<hr id="loginuserpasshr" style="display:none" />
<div style="margin-bottom:8px">Log in using an existing account</div>
<a id="auth-twitter" href="auth-twitter" style="display:none"><img src="images/login/twitter32.png" srcset="images/login/twitter64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Sign-in using Twitter" /></a>
<a id="auth-google" href="auth-google" style="display:none"><img src="images/login/google32.png" srcset="images/login/google64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Sign-in using Google" /></a>
<a id="auth-github" href="auth-github" style="display:none"><img src="images/login/github32.png" srcset="images/login/github64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Sign-in using GitHub" /></a>
<a id="auth-azure" href="auth-azure" style="display:none"><img src="images/login/azure32.png" srcset="images/login/azure64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Sign-in using Azure" /></a>
<a id="auth-oidc" href="auth-oidc" style="display:none"><img src="images/login/oidc32.png" srcset="images/login/oidc64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Sign-in using OpenID Connect" /></a>
<a id="auth-oidc-azure" href="auth-oidc" style="display:none"><img src="images/login/azure32.png" srcset="images/login/azure64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Sign-in with Azure using OpenID Connect" /></a>
<a id="auth-oidc-google" href="auth-oidc" style="display:none"><img src="images/login/google32.png" srcset="images/login/google64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Sign-in with Google using OpenID Connect" /></a>
<a id="auth-jumpcloud" href="auth-jumpcloud" style="display:none"><img src="images/login/jumpcloud32.png" srcset="images/login/jumpcloud64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Sign-in using JumpCloud" /></a>
<a id="auth-intel" href="auth-intel" style="display:none"><img src="images/login/intel32.png" srcset="images/login/intel64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Sign-in using Intel" /></a>
<a id="auth-saml" href="auth-saml" style="display:none"><img src="images/login/generic32.png" srcset="images/login/generic64.png 2x" loading="lazy" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer" title="Single Sign-in" /></a>
</div>
</form>
</div>
<div id=createpanel style="display:none;position:relative">
<form method=post>
<input type=hidden name=action value=createaccount />
<div id=message2></div>
<div>
<b>Account Creation</b>
</div>
<div id="passwordPolicyCallout" style="display:none"></div>
<table>
<tr id="nuUserRow">
<td id="nuUser" align=right width=100>Username:</td>
<td><input id=ausername type=text {{{autocomplete}}}="username" name=username onchange=validateCreate(1) maxlength=64 onkeydown=haltReturn(event) onkeyup=validateCreate(1,event) /></td>
</tr>
<tr>
<td id="nuEmail" align=right width=100>Email:</td>
<td><input id=aemail type=text {{{autocomplete}}}="email" inputmode="email" name=email onchange=validateCreate(2) maxlength=256 onkeydown=haltReturn(event) onkeyup=validateCreate(2,event) /></td>
</tr>
<tr>
<td id="nuPass1" align=right>Password:</td>
<td><input id=apassword1 type=password name=password1 {{{autocomplete}}}="new-password" maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(3,event) onkeyup=validateCreate(3,event) /></td>
</tr>
<tr>
<td id="nuPass2" align=right>Password:</td>
<td><input id=apassword2 type=password name=password2 {{{autocomplete}}}="new-password" maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(4,event) onkeyup=validateCreate(4,event) /></td>
</tr>
<tr id="createPanelHint" style="display:none">
<td id="nuHint" align=right>Password Hint:</td>
<td><input id=apasswordhint type=text name=apasswordhint {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(5,event) onkeyup=validateCreate(5,event) /></td>
</tr>
<tr id=newAccountPass title="Enter the account creation token">
<td id="nuToken" align=right>Creation Token:</td>
<td><input id=anewaccountpass type=password name=anewaccountpass {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(6,event) onkeyup=validateCreate(6,event) /></td>
</tr>
<tr id=newAccountCaptchaImg title="CAPTCHA image">
<td></td>
<td colspan=2><img src="{{{newAccountCaptchaImage}}}" loading="lazy" /></td>
</tr>
<tr id=newAccountCaptcha title="Security check">
<td id="nuCaptcha" align=right>Security Check:</td>
<td><input id=anewaccountcaptcha type=text name=anewaccountcaptcha {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(7,event) onkeyup=validateCreate(7,event) /></td>
</tr>
<tr>
<td colspan=2>
<div style=float:right><input id=createButton type=submit value="Create Account" disabled="disabled" /></div>
<div id=passWarning style="padding-top:6px"></div>
</td>
</tr>
</table>
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
<input id=createformargs name="urlargs" type="hidden" value="" />
<input id=createformcaptcha name="captchaargs" type="hidden" value="{{{newAccountCaptcha}}}" />
</form>
</div>
<div id=resetpanel style="display:none">
<form method=post>
<input type=hidden name=action value=resetaccount />
<div id=message3></div>
<div>
<b>Account Reset</b>
</div>
<table>
<tr>
<td align=right width=100>Email:</td>
<td><input id=remail type=text inputmode="email" name=email maxlength=256 onchange=validateReset() onkeyup=validateReset(event) /></td>
</tr>
<tr>
<td colspan=2>
<div style=float:right><input id=eresetButton type=submit value="Reset Account" disabled="disabled" /></div>
<div id=passWarning style="padding-top:6px"></div>
</td>
</tr>
</table>
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
<input id=resetformargs name="urlargs" type="hidden" value="" />
</form>
</div>
<div id=tokenpanel style="display:none">
<form method=post {{{autocomplete}}}=off>
<input type=hidden name=action value=tokenlogin />
<input type=hidden name=hwstate value="{{{hwstate}}}" />
<div id=message4></div>
<table>
<tr>
<td align=right width=100>Login token:</td>
<td>
<input id=tokenInput {{{autocomplete}}}="one-time-code" inputmode="numeric" type=text name=token maxlength=50 onchange=checkToken(event) onpaste=checkToken(event) onkeyup=checkToken(event) onkeydown=checkToken(event) /><br />
<input id=hwtokenInput type=text name=hwtoken style="display:none" />
</td>
</tr>
<tr>
<td colspan="2" style="align-content:center">
<label id=tokenInputRememberLabel><input id=tokenInputRemember name=remembertoken type=checkbox /><span id=tokenInputRememberSpan></span></label>
</td>
</tr>
<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(1)" />
<input style="display:none;float:right" id=emailKeyButton type=button value="Email" onclick="useEmailToken(1)" />
<input style="display:none;float:right" id=smsKeyButton type=button value="SMS" onclick="useSMSToken(1)" />
<input style="display:none;float:right" id=msgKeyButton type=button value="Messaging" onclick="useMsgToken(1)" />
</div>
</td>
</tr>
</table>
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
<input id=tokenformargs name="urlargs" type="hidden" value="" />
</form>
</div>
<div id=resettokenpanel style="display:none">
<form method=post>
<input type=hidden name=action value=resetaccount />
<div id=message5></div>
<table>
<tr>
<td align=right width=100>Login token:</td>
<td>
<input id=resetTokenInput type=text name=token maxlength=50 onchange=resetCheckToken(event) onkeyup=resetCheckToken(event) onkeydown=resetCheckToken(event) />
<input id=resetHwtokenInput type=text name=hwtoken style="display:none" />
</td>
</tr>
<tr>
<td colspan=2>
<div style=float:right><input id=resetTokenOkButton type=submit value="Login" disabled="disabled" /></div>
<div style=float:right>
<input style="display:none;float:right" id=securityKeyButton2 type=button value="Use Security Key" onclick="useSecurityKey(2)" />
<input style="display:none;float:right" id=emailKeyButton2 type=button value="Email" onclick="useEmailToken(2)" />
<input style="display:none;float:right" id=smsKeyButton2 type=button value="SMS" onclick="useSMSToken(2)" />
<input style="display:none;float:right" id=msgKeyButton2 type=button value="Messaging" onclick="useMsgToken(2)" />
</div>
</td>
</tr>
</table>
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
<input id=resettokenformargs name="urlargs" type="hidden" value="" />
</form>
</div>
<div id=resetpasswordpanel style="display:none;position:relative">
<form method=post>
<input type=hidden name=action value=resetpassword />
<div id=message6></div>
<div id="rpasswordPolicyCallout" style="display:none"></div>
<table>
<tr>
<td id="rnuPass1" width=100 align=right>Password:</td>
<td><input id=rapassword1 type=password name=rpassword1 {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(3,event) onkeyup=validatePassReset(3,event) /></td>
</tr>
<tr>
<td id="rnuPass2" align=right>Password:</td>
<td><input id=rapassword2 type=password name=rpassword2 {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(4,event) onkeyup=validatePassReset(4,event) /></td>
</tr>
<tr id="resetpasswordpanelHint" style="display:none">
<td id="rnuHint" align=right>Password Hint:</td>
<td><input id=rapasswordhint type=text name=rpasswordhint {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(5,event) onkeyup=validatePassReset(5,event) /></td>
</tr>
<tr>
<td colspan=2>
<div style=float:right><input id=resetPassButton type=submit value="Reset Password" disabled="disabled" /></div>
<div id=rpassWarning style="padding-top:6px"></div>
</td>
</tr>
</table>
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
<input id=resetpasswordformargs name="urlargs" type="hidden" value="" />
</form>
</div>
<div id=checkemailpanel style="display:none;position:relative">
<form method=post>
<input type=hidden name=action value=checkemail />
<div id=message7></div>
<table id="checkCheckOperations" style="width:100%;display:none">
<tr>
<td>
<div id="unconfirmedEmail"></div><br />
</td>
</tr>
<tr>
<td>
<div>
<input id=changeEmailButton type=button value="Change Email Address" onclick="changeEmailAddress()" />
<input id=checkEmailButton2 type=button value="Resend Confirmation Email" onclick="resentEmailConfirmation()" />
<input id=checkEmailButton type=submit style="display:none" />
<input id=checkEmailVal name=email type="hidden" value="" />
</div>
</td>
</tr>
</table>
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
<input id=checkemailformargs name="urlargs" type="hidden" value="" />
</form>
</div>
</td>
</tr>
</table>
<br />
</div>
<div id="footer">
<div class="footer1">{{{footer}}}</div>
<div class="footer2">
{{{rootCertLink}}}
&nbsp;<a id="termsLinkFooter" href=terms>Terms &amp; Privacy</a>
</div>
</div>
</div>
<div id=dialog style="display:none">
<div id=dialogHeader>
<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>
<div style=width:100%;margin:6px></div>
</div>
<div id=dialogBody>
<div id=dialog1>
<div id=id_dialogMessage style=""></div>
</div>
<div id=dialog2 style="">
<div id=id_dialogOptions></div>
</div>
</div>
<div id="idx_dlgButtonBar" style="">
<input id="idx_dlgCancelButton" type="button" value="Cancel" style="" onclick="dialogclose(0)">
<input id="idx_dlgOkButton" type="button" value="OK" style="" onclick="dialogclose(1)">
</div>
</div>
<script>
'use strict';
var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
var passlogin = '{{{passlogin}}}';
var passhint = '{{{passhint}}}';
var loginMode = '{{{loginmode}}}';
var newAccount = '{{{newAccount}}}';
var newAccountPass = parseInt('{{{newAccountPass}}}');
var newAccountCaptcha = '{{{newAccountCaptcha}}}';
var emailCheck = '{{{emailcheck}}}';
var passRequirements = '{{{passRequirements}}}';
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } else { passRequirements = {}; }
var passRequirementsEx = ((passRequirements.min != null) || (passRequirements.max != null) || (passRequirements.upper != null) || (passRequirements.lower != null) || (passRequirements.numeric != null) || (passRequirements.nonalpha != null));
var features = parseInt('{{{features}}}');
var welcomeText = decodeURIComponent('{{{welcometext}}}');
var currentpanel = 0;
var uiMode = parseInt(getstore('uiMode', '1'));
var webPageFullScreen = true;
var nightMode = (getstore('_nightMode', '0') == '1');
var publicKeyCredentialRequestOptions = null;
var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true');
var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true');
var autofido = (decodeURIComponent('{{{autofido}}}') === 'true');
var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
var authStrategies = '{{{authStrategies}}}'.split(',');
var tokenTimeout = parseInt('{{{tokenTimeout}}}');
function startup() {
// Display the right server message
var i;
var messageid = parseInt('{{{messageid}}}');
var okmessages = ['', "If valid, reset mail sent.", "Email sent.", "Email verification required, check your mailbox and click the confirmation link.", "SMS sent.", "Sending notification...", "Message 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.", "Server under maintenance.", "Unable to send device notification.", "Invalid security check."];
if (messageid > 0) {
var msg = '';
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
if (msg != '') {
if (messageid >= 100) { msg = ('<span class="msg error"><b style=color:#8C001A>' + msg + '<b></span><br /><br />'); } else { msg = ('<span class="msg success"><b>' + msg + '</b></span><br /><br />'); }
for (i = 1; i < 8; i++) { QH('message' + i, msg); }
}
}
// Fix links if a loginKey if used
var urlargs = parseUriArgs();
if (urlargs.key) {
Q('termsLinkFooter').href += '?key=' + urlargs.key;
}
// Show user/pass login
passlogin = (passlogin == 'true');
if (urlargs.passlogin === 1) { passlogin = true; }
if (urlargs.passlogin === 0) { passlogin = false; }
QV('loginuserpassdiv', passlogin)
QV('loginuserpasshr', passlogin)
// Setup two factor cookie time
if (twoFactorCookieDays > 0) {
QV('tokenInputRememberLabel', true);
QH('tokenInputRememberSpan', format("Remember this device for {0} days.", twoFactorCookieDays));
} else {
QV('tokenInputRememberLabel', false);
}
// If URL arguments are provided, add them to form posts
if (window.location.href.indexOf('?') > 0) {
var xurlargs = window.location.href.substring(window.location.href.indexOf('?'));
Q('loginformargs').value = xurlargs;
Q('createformargs').value = xurlargs;
Q('resetformargs').value = xurlargs;
Q('tokenformargs').value = xurlargs;
Q('resettokenformargs').value = xurlargs;
Q('resetpasswordformargs').value = xurlargs;
Q('checkemailformargs').value = xurlargs;
}
//var webPageFullScreen = getstore('webPageFullScreen', true);
//if (webPageFullScreen == 'false') { webPageFullScreen = false; }
//if (webPageFullScreen == 'true') { webPageFullScreen = true; }
//toggleFullScreen();
if ((features & 32) == 0) {
// Guard against other site's top frames (web bugs).
var loc = null;
try { loc = top.location.toString().toLowerCase(); } catch (e) { }
if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
}
if (features & 0x200000) { // Email is username
QH('loginusername', "Email:");
QH('resetAccountSpan', "Forgot password?");
QV('nuUserRow', false);
}
if (nightMode) { QC('body').add('night'); }
QV('createPanelHint', passRequirements.hint === true);
QV('resetpasswordpanelHint', passRequirements.hint === true);
// Setup authentication strategies
if (authStrategies != '') {
QV('authStrategies', true);
if (authStrategies.indexOf('twitter') >= 0) { QV('auth-twitter', true); }
if (authStrategies.indexOf('google') >= 0) { QV('auth-google', true); }
if (authStrategies.indexOf('github') >= 0) { QV('auth-github', true); }
if (authStrategies.indexOf('azure') >= 0) { QV('auth-azure', true); }
if (authStrategies.indexOf('oidc') >= 0) { QV('auth-oidc', true); }
if (authStrategies.indexOf('oidc-azure') >= 0) { QV('auth-oidc-azure', true); }
if (authStrategies.indexOf('oidc-google') >= 0) { QV('auth-oidc-google', true); }
if (authStrategies.indexOf('jumpcloud') >= 0) { QV('auth-jumpcloud', true); }
if (authStrategies.indexOf('intel') >= 0) { QV('auth-intel', true); }
if (authStrategies.indexOf('saml') >= 0) { QV('auth-saml', true); }
}
// Display the welcome text
if (welcomeText) {
QH('welcomeText', welcomeText);
} else {
QH('welcomeText', addTextLink('MeshCentral', Q('welcomeText').innerHTML, 'http://www.meshcommander.com/meshcentral2'));
}
QV('welcomeText', true);
window.onresize = center;
center();
validateLogin();
validateCreate();
if (loginMode.length != 0) { go(parseInt(loginMode)); } else { go(1); }
QV('newAccountDiv', (newAccount === '1') || (newAccount === 'true')); // If new accounts are not allowed, don't display the new account link.
if ((passhint != null) && (passhint.length > 0)) { QV('showPassHintLink', true); }
QV('newAccountPass', (newAccountPass == 1));
QV('newAccountCaptcha', (newAccountCaptcha != ''));
QV('newAccountCaptchaImg', (newAccountCaptcha != ''));
QV('resetAccountDiv', (emailCheck == 'true'));
QV('hrAccountDiv', (emailCheck == 'true') || (newAccountPass == 1));
if (loginMode == '4') {
if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
QV('securityKeyButton', twofakey);
QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('smsKeyButton', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('msgKeyButton', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
// If hardware key is an option, trigger it now
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
}
if (loginMode == '5') {
if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
QV('securityKeyButton2', twofakey);
QV('emailKeyButton2', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('smsKeyButton2', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('msgKeyButton2', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
// If hardware key is an option, trigger it now
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); }
}
/*
if (loginMode == '5') {
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
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; }
publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
publicKeyCredentialRequestOptions.allowCredentials.push(
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'] }
);
}
// New WebAuthn hardware keys
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
function (rawAssertion) {
var assertion = {
id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
};
Q('resetHwtokenInput').value = JSON.stringify(assertion);
QE('resetTokenOkButton', true);
Q('resetTokenOkButton').click();
},
function (error) { console.log('credentials-get error', error); }
);
}
}
*/
// Setup the user interface in the right mode
userInterfaceSelectMenu();
}
// Use a hardware security key
function useSecurityKey(panelAction) {
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; }
publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
publicKeyCredentialRequestOptions.allowCredentials.push(
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'] }
);
}
// New WebAuthn hardware keys
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
function (rawAssertion) {
var assertion = {
id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
};
if (panelAction == 1) {
Q('hwtokenInput').value = JSON.stringify(assertion);
QE('tokenOkButton', true);
Q('tokenOkButton').click();
} else if (panelAction == 2) {
Q('resetHwtokenInput').value = JSON.stringify(assertion);
QE('resetTokenOkButton', true);
Q('resetTokenOkButton').click();
}
},
function (error) { console.log('credentials-get error', error); }
);
}
}
function useEmailToken(panelAction) {
if (otpemail != true) return;
setDialogMode(1, "Secure Login", 3, useEmailKeyEx, "Send token to registered email address?", panelAction);
}
function useEmailKeyEx(b, panelAction) {
if (panelAction == 1) {
Q('hwtokenInput').value = '**email**';
QE('tokenOkButton', true);
Q('tokenOkButton').click();
} else if (panelAction == 2) {
Q('resetHwtokenInput').value = '**email**';
QE('resetTokenOkButton', true);
Q('resetTokenOkButton').click();
}
}
function useSMSToken(panelAction) {
if (otpsms != true) return;
setDialogMode(1, "Secure Login", 3, useSMSTokenEx, "Send token to registered phone number?", panelAction);
}
function useSMSTokenEx(b, panelAction) {
if (panelAction == 1) {
Q('hwtokenInput').value = '**sms**';
QE('tokenOkButton', true);
Q('tokenOkButton').click();
} else if (panelAction == 2) {
Q('resetHwtokenInput').value = '**sms**';
QE('resetTokenOkButton', true);
Q('resetTokenOkButton').click();
}
}
function useMsgToken(panelAction) {
if (otpmsg != true) return;
setDialogMode(1, "Secure Login", 3, useMsgTokenEx, "Send token to messaging application?", panelAction);
}
function useMsgTokenEx(b, panelAction) {
if (panelAction == 1) {
Q('hwtokenInput').value = '**msg**';
QE('tokenOkButton', true);
Q('tokenOkButton').click();
} else if (panelAction == 2) {
Q('resetHwtokenInput').value = '**msg**';
QE('resetTokenOkButton', true);
Q('resetTokenOkButton').click();
}
}
function showPassHint(e) {
messagebox("Password Hint", passhint);
haltEvent(e);
return false;
}
function xgo(x, e) {
QV('message1', false);
QV('message2', false);
QV('message3', false);
QV('message4', false);
QV('message5', false);
QV('message6', false);
QV('message7', false);
go(x);
haltEvent(e);
return false;
}
function go(x) {
currentpanel = x;
setDialogMode(0);
QV('showPassHintLink', false);
QV('loginpanel', x == 1);
QV('createpanel', x == 2);
QV('resetpanel', x == 3);
QV('tokenpanel', x == 4);
QV('resettokenpanel', x == 5);
QV('resetpasswordpanel', x == 6);
QV('checkemailpanel', x == 7);
if (x == 1) { Q('username').focus(); }
if (x == 2) { if (features & 0x200000) { Q('aemail').focus(); } else { Q('ausername').focus(); } } // Email is username
if (x == 3) { Q('remail').focus(); }
if (x == 4) { Q('tokenInput').focus(); }
if (x == 5) { Q('resetTokenInput').focus(); }
if (x == 6) { Q('rapassword1').focus(); }
if (x == 7) {
QH('unconfirmedEmail', passhint);
QV('checkCheckOperations', Q('unconfirmedEmail').innerHTML != '');
QH('checkEmailVal', passhint);
}
}
function validateLogin(box, e) {
var ok = ((Q('username').value.length > 0) && (Q('username').value.indexOf(' ') == -1) && (Q('password').value.length > 0));
QE('loginButton', ok);
setDialogMode(0);
if ((e != null) && (e.keyCode == 13)) { if ((box == 1) && (Q('username').value != '')) { Q('password').focus(); } else if ((box == 2) && (Q('password').value != '')) { Q('loginButton').click(); } }
if (e != null) { haltEvent(e); }
}
function validateCreate(box, e) {
setDialogMode(0);
var userok = false;
if (features & 0x200000) { userok = true; } else { userok = (Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (Q('ausername').value.indexOf('"') == -1) && (Q('ausername').value.indexOf(',') == -1); }
var emailok = (validateEmail(Q('aemail').value) == true);
var pass1ok = (Q('apassword1').value.length > 0);
var pass2ok = (Q('apassword2').value.length > 0) && (Q('apassword2').value == Q('apassword1').value);
var newAccOk = (newAccountPass == 0) || (Q('anewaccountpass').value.length > 0);
var newCaptchaOk = (newAccountCaptcha == '') || (Q('anewaccountcaptcha').value.length > 0);
var ok = (userok && emailok && pass1ok && pass2ok && newAccOk);
// Color the fields
QS('nuUser').color = userok ? 'black' : '#7b241c';
QS('nuEmail').color = emailok ? 'black' : '#7b241c';
QS('nuPass1').color = pass1ok ? 'black' : '#7b241c';
QS('nuPass2').color = pass2ok ? 'black' : '#7b241c';
QS('nuToken').color = newAccOk ? 'black' : '#7b241c';
QS('nuCaptcha').color = newCaptchaOk ? 'black' : '#7b241c';
if (Q('apassword1').value == '') {
QH('passWarning', '');
QV('passwordPolicyCallout', false);
} else {
if (!passRequirementsEx) {
// No password requirements, display password strength
var passStrength = checkPasswordStrength(Q('apassword1').value);
if (passStrength >= 80) { QH('passWarning', '<span style=color:green><b>' + "Strong Password" + '</b><span>'); }
else if (passStrength >= 60) { QH('passWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
else { QH('passWarning', '<span style=color:red><b>' + "Weak Password" + '</b><span>'); }
} else {
// Password requirements provided, use that
var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
if (passReq == false) {
ok = false;
QS('nuPass1').color = '#7b241c';
QS('nuPass2').color = '#7b241c';
QH('passWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
QV('passwordPolicyCallout', true);
QH('passwordPolicyCallout', passwordPolicyText(Q('apassword1').value));
} else {
QH('passWarning', '');
QV('passwordPolicyCallout', false);
}
}
}
if ((e != null) && (e.keyCode == 13)) {
if ((box == 1) && userok) { Q('aemail').focus(); }
if ((box == 2) && emailok) { Q('apassword1').focus(); }
if ((box == 3) && pass1ok) { Q('apassword2').focus(); }
if ((box == 4) && pass2ok) { if (passRequirements.hint === true) { Q('apasswordhint').focus(); } else { box = 5; } }
if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { box = 6; } }
if (box == 6) { if (newAccountCaptcha != '') { Q('anewaccountcaptcha').focus(); } else { box = 7; } }
if (box == 7) { Q('createButton').click(); }
}
if (e != null) { haltEvent(e); }
QE('createButton', ok);
}
function validatePassReset(box, e) {
setDialogMode(0);
var pass1ok = (Q('rapassword1').value.length > 0);
var pass2ok = (Q('rapassword2').value.length > 0) && (Q('rapassword2').value == Q('rapassword1').value);
var ok = (pass1ok && pass2ok);
// Color the fields
QS('rnuPass1').color = pass1ok ? 'black' : '#7b241c';
QS('rnuPass2').color = pass2ok ? 'black' : '#7b241c';
if (Q('rapassword1').value == '') {
QH('rpassWarning', '');
QV('rpasswordPolicyCallout', false);
} else {
if (!passRequirementsEx) {
// No password requirements, display password strength
var passStrength = checkPasswordStrength(Q('rapassword1').value);
if (passStrength >= 80) { QH('rpassWarning', '<span style=color:green><b>' + "Strong Password" + '</b><span>'); }
else if (passStrength >= 60) { QH('rpassWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
else { QH('rpassWarning', '<span style=color:red><b>' + "Weak Password" + '</b><span>'); }
} else {
// Password requirements provided, use that
var passReq = checkPasswordRequirements(Q('rapassword1').value, passRequirements);
if (passReq == false) {
ok = false;
QS('rnuPass1').color = '#7b241c';
QS('rnuPass2').color = '#7b241c';
QH('rpassWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
QV('rpasswordPolicyCallout', true);
QH('rpasswordPolicyCallout', passwordPolicyText(Q('rapassword1').value));
} else {
QH('rpassWarning', '');
QV('rpasswordPolicyCallout', false);
}
}
}
if ((e != null) && (e.keyCode == 13)) {
if (box == 2) { Q('rapassword1').focus(); }
if (box == 3) { Q('rapassword2').focus(); }
if (box == 4) { Q('rapasswordhint').focus(); }
if (box == 6) { Q('resetPassButton').click(); }
}
if (e != null) { haltEvent(e); }
QE('resetPassButton', ok);
}
function passwordPolicyText(pass) {
var policy = '<div style=text-align:left>';
var counts = strCount(pass);
if (passRequirements.min && ((pass == null) || (pass.length < passRequirements.min))) { policy += format("Minimum length of {0}", passRequirements.min) + '<br />'; }
if (passRequirements.max && ((pass == null) || (pass.length > passRequirements.max))) { policy += format("Maximum length of {0}", passRequirements.max) + '<br />'; }
if (passRequirements.upper && ((pass == null) || (counts.upper < passRequirements.upper))) { policy += format("{0} upper case", passRequirements.upper) + '<br />'; }
if (passRequirements.lower && ((pass == null) || (counts.lower < passRequirements.lower))) { policy += format("{0} lower case", passRequirements.lower) + '<br />'; }
if (passRequirements.numeric && ((pass == null) || (counts.numeric < passRequirements.numeric))) { policy += format("{0} numeric", passRequirements.numeric) + '<br />'; }
if (passRequirements.nonalpha && ((pass == null) || (counts.nonalpha < passRequirements.nonalpha))) { policy += format("{0} non-alphanumeric", passRequirements.nonalpha) + '<br />'; }
policy += '</div>';
return policy;
}
function showPasswordPolicy() {
messagebox("Password Policy", passwordPolicyText());
}
function validateReset(e) {
setDialogMode(0);
var x = validateEmail(Q('remail').value);
QE('eresetButton', x);
if ((e != null) && (e.keyCode == 13) && (x == true)) {
Q('eresetButton').click();
}
if (e != null) { haltEvent(e); }
}
// Return a password strength score
function checkPasswordStrength(password) {
var r = 0, letters = {}, varCount = 0, variations = { digits: /\d/.test(password), lower: /[a-z]/.test(password), upper: /[A-Z]/.test(password), nonWords: /\W/.test(password) }
if (!password) return 0;
for (var i = 0; i < password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
return parseInt(r + (varCount - 1) * 10);
}
// Check password requirements
function checkPasswordRequirements(password, requirements) {
if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
if (requirements.min) { if (password.length < requirements.min) return false; }
if (requirements.max) { if (password.length > requirements.max) return false; }
var counts = strCount(password);
if (requirements.numeric && (counts.numeric < requirements.numeric)) return false;
if (requirements.lower && (counts.lower < requirements.lower)) return false;
if (requirements.upper && (counts.upper < requirements.upper)) return false;
if (requirements.nonalpha && (counts.nonalpha < requirements.nonalpha)) return false;
return true;
}
function strCount(password) {
var counts = { numeric: 0, lower: 0, upper: 0, nonalpha: 0 };
if (typeof password != 'string') return counts;
for (var i = 0; i < password.length; i++) {
if (/\d/.test(password[i])) { counts.numeric++; }
if (/[a-z]/.test(password[i])) { counts.lower++; }
if (/[A-Z]/.test(password[i])) { counts.upper++; }
if (/\W/.test(password[i])) { counts.nonalpha++; }
}
return counts;
}
function checkToken() {
var t1 = Q('tokenInput').value;
var t2 = t1.split(' ').join('');
if (t1 != t2) { Q('tokenInput').value = t2; }
QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8) || (Q('tokenInput').value.length == 44));
}
function resetCheckToken() {
var t1 = Q('resetTokenInput').value;
var t2 = t1.split(' ').join('');
if (t1 != t2) { Q('resetTokenInput').value = t2; }
QE('resetTokenOkButton', (Q('resetTokenInput').value.length == 6) || (Q('resetTokenInput').value.length == 8) || (Q('resetTokenInput').value.length == 44));
}
function changeEmailAddress() {
var email = Q('unconfirmedEmail').innerHTML;
var x = addHtmlValue("Email", '<input id=dp1email style=width:230px maxlength=256 value="' + email + '" {{{autocomplete}}}=off onchange=validateEmailAddress() onkeyup=validateEmailAddress() />');
setDialogMode(1, "Email Confirmation", 3, changeEmailAddressEx, x);
validateEmailAddress();
}
function validateEmailAddress() {
QE('idx_dlgOkButton', (validateEmail(Q('dp1email').value) == true));
}
function changeEmailAddressEx() {
Q('checkEmailVal').value = Q('dp1email').value;
QH('unconfirmedEmail', Q('dp1email').value);
Q('checkEmailButton').click();
}
function resentEmailConfirmation() {
Q('checkEmailVal').value = Q('unconfirmedEmail').innerHTML;
Q('checkEmailButton').click();
}
//
// POPUP DIALOG
//
// undefined = Hidden, 1 = Generic Message
var xxdialogMode;
var xxdialogFunc;
var xxdialogButtons;
var xxdialogTag;
var xxcurrentView = 0;
// Display a dialog box
// Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 2 only)
function setDialogMode(x, y, b, f, c, tag) {
xxdialogMode = x;
xxdialogFunc = f;
xxdialogButtons = b;
xxdialogTag = tag;
QE('idx_dlgOkButton', true);
QV('idx_dlgOkButton', b & 1);
QV('idx_dlgCancelButton', b & 2);
QV('id_dialogclose', (b & 2) || (b & 8));
QV('idx_dlgButtonBar', b & 7);
if (y) QH('id_dialogtitle', y);
for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
QV('dialog', x);
if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
}
function dialogclose(x) {
var f = xxdialogFunc;
var b = xxdialogButtons;
var t = xxdialogTag;
setDialogMode();
if (((b & 8) || x) && f) f(x, t);
}
// Toggle the web page to full screen
function toggleFullScreen(toggle) {
//if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
if (webPageFullScreen == false) {
// By adding body class, it will change a style of all ellements using CSS selector
// No need for JS anymore and it will be consistent style for all the templates.
QC('body').remove('fullscreen');
} else {
QC('body').add('fullscreen');
}
QV('body', true);
center();
}
// Toggle user interface menu
function showUserInterfaceSelectMenu() {
Q('uiViewButton1').classList.remove('uiSelectorSel');
Q('uiViewButton2').classList.remove('uiSelectorSel');
Q('uiViewButton3').classList.remove('uiSelectorSel');
try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
QV('uiMenu', (QS('uiMenu').display == 'none'));
if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
}
function userInterfaceSelectMenu(s) {
if (s) { uiMode = s; putstore('uiMode', uiMode); }
webPageFullScreen = (uiMode < 3);
//webPageStackMenu = (uiMode > 1);
toggleFullScreen(0);
//toggleStackMenu(0);
}
function toggleNightMode() {
nightMode = !nightMode;
if (nightMode) { QC('body').add('night'); } else { QC('body').remove('night'); }
putstore('_nightMode', (nightMode ? '1' : '0'));
}
function center() {
/* Now we use CSS media to achive the same effect as deleted JS */
if (webPageFullScreen == false) {
QS('centralTable')['margin-top'] = '';
} else {
var h = ((Q('column_l').clientHeight) / 2) - 220;
if (h < 0) h = 0;
QS('centralTable')['margin-top'] = h + '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; }
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); } // New version
function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
function addTextLink(subtext, text, link) { var i = text.toLowerCase().indexOf(subtext.toLowerCase()); if (i == -1) { return text; } return text.substring(0, i) + '<a href="' + link + '">' + subtext + '</a>' + text.substring(i + subtext.length); }
function addHtmlValue(t, v) { return '<table><td style=width:120px;text-align:left>' + t + '<td><b>' + v + '</b></table>'; }
</script>
</body>
</html>