620 lines
39 KiB
Handlebars
620 lines
39 KiB
Handlebars
<!DOCTYPE html>
|
|
<html dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<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" />
|
|
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
|
|
<script type="text/javascript" src="scripts/u2f-api.js"></script>
|
|
<title>MeshCentral - Login</title>
|
|
<style>
|
|
a {
|
|
color: #036;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
#footer a {
|
|
color: #fff;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
#footer a:hover {
|
|
color: #fff;
|
|
text-decoration: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;color:black;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
|
|
<div id=container>
|
|
<div id=mastheadx></div>
|
|
<div id=masthead style="background:url(logo.png) 0px 0px;background-size:341px 50px;background-color:#036;background-repeat:no-repeat;height:50px;width:100%;overflow:hidden">
|
|
<div style="float:left;height:66px;color:#c8c8c8;padding-left:10px;padding-top:6px">
|
|
<strong><font style="font-size:36px;font-family:Arial,Helvetica,sans-serif">{{{title}}}</font></strong>
|
|
</div>
|
|
<div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:10px">
|
|
<strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif">{{{title2}}}</font></strong>
|
|
</div>
|
|
</div>
|
|
<div id=page_content style="overflow-y:scroll;position:absolute;bottom:32px;top:50px;width:100%;display:flex;align-items:center">
|
|
<div id=column_l style="padding:10px;width:100%">
|
|
<table style="width:100%">
|
|
<tr>
|
|
<td align=center>
|
|
<div id=loginpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;display:none">
|
|
<form action="login" method=post>
|
|
<div id=message1>
|
|
{{{message}}}
|
|
</div>
|
|
<div>
|
|
<b>Log In</b>
|
|
</div>
|
|
<table>
|
|
<tr>
|
|
<td align=right width=100>Username:</td>
|
|
<td><input id=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 type=password maxlength=256 name=password autocomplete=off onchange=validateLogin(2) onkeyup=validateLogin(2,event) /></td>
|
|
</tr>
|
|
<tr>
|
|
<td><div id=showPassHintLink style=display:none><a onclick=showPassHint() 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">
|
|
Forgot user/password? <a onclick=xgo(3) style=cursor:pointer>Reset account</a>.
|
|
</div>
|
|
<div id="newAccountDiv" style="display:none;padding:2px">
|
|
Don't have an account? <a onclick=xgo(2) style=cursor:pointer>Create one</a>.
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div id=createpanel style="position:relative;display:none">
|
|
<div style="background-color: #979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both">
|
|
<form action=createaccount method=post>
|
|
<div id=message2>
|
|
{{{message}}}
|
|
</div>
|
|
<div>
|
|
<b>Account Creation</b>
|
|
</div>
|
|
<div id="passwordPolicyCallout" style="left:-5px;top:10px;width:100px;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></div>
|
|
<table>
|
|
<tr>
|
|
<td align=right width=100>Username:</td>
|
|
<td><input id=ausername type=text name=username onchange=validateCreate(1) maxlength=64 onkeydown=haltReturn(event) onkeyup=validateCreate(1,event) /></td>
|
|
</tr>
|
|
<tr>
|
|
<td align=right width=100>Email:</td>
|
|
<td><input id=aemail type=text name=email onchange=validateCreate(2) maxlength=256 onkeydown=haltReturn(event) onkeyup=validateCreate(2,event) /></td>
|
|
</tr>
|
|
<tr>
|
|
<td align=right>Password:</td>
|
|
<td><input id=apassword1 type=password name=password1 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(3) onkeyup=validateCreate(3,event) /></td>
|
|
</tr>
|
|
<tr>
|
|
<td align=right>Password:</td>
|
|
<td><input id=apassword2 type=password name=password2 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(4) onkeyup=validateCreate(4,event) /></td>
|
|
</tr>
|
|
<tr id="createPanelHint" style="display:none">
|
|
<td align=right>Pass Hint:</td>
|
|
<td><input id=apasswordhint type=text name=apasswordhint autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(5) onkeyup=validateCreate(5,event) /></td>
|
|
</tr>
|
|
<tr id=newAccountPass title="Enter the account creation token">
|
|
<td align=right>Creation Token:</td>
|
|
<td><input id=anewaccountpass type=password name=anewaccountpass autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(6) onkeyup=validateCreate(6,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=xgo(1) style=cursor:pointer>Back to login</a>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div id=resetpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
|
|
<form action=resetaccount method=post>
|
|
<div id=message3>
|
|
{{{message}}}
|
|
</div>
|
|
<div>
|
|
<b>Account Reset</b>
|
|
</div>
|
|
<table>
|
|
<tr>
|
|
<td align=right width=100>Email:</td>
|
|
<td><input id=remail type=text 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=xgo(1) style=cursor:pointer>Back to login</a>
|
|
</form>
|
|
</div>
|
|
<div id=tokenpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
|
|
<form action=tokenlogin method=post autocomplete=off>
|
|
<div id=message4>
|
|
{{{message}}}
|
|
</div>
|
|
<table>
|
|
<tr>
|
|
<td align=right width=100>Login token:</td>
|
|
<td>
|
|
<input id=tokenInput type=text name=token maxlength=50 onchange=checkToken(event) onkeyup=checkToken(event) onkeydown=checkToken(event) onfocus=checkTokenTimer(1) onblur=checkTokenTimer(0) />
|
|
<input id=hwtokenInput type=text name=hwtoken style="display:none" />
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan=2>
|
|
<div style=float:right><input id=tokenOkButton type=submit value="Login" disabled="disabled" /></div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
|
|
</form>
|
|
</div>
|
|
|
|
<div id=resettokenpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
|
|
<form action=resetaccount method=post autocomplete=off>
|
|
<div id=message5>
|
|
{{{message}}}
|
|
</div>
|
|
<table>
|
|
<tr>
|
|
<td align=right width=100>Login token:</td>
|
|
<td>
|
|
<input id=resetTokenInput type=text name=token maxlength=50 onchange=resetCheckToken(event) onpaste=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>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
|
|
</form>
|
|
</div>
|
|
|
|
<div id=resetpasswordpanel style="position:relative;background-color: #979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
|
|
<form action=resetpassword method=post>
|
|
<div id=message6>
|
|
{{{message}}}
|
|
</div>
|
|
<div id="rpasswordPolicyCallout" style="left:-10px;width:100px;display:none;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></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=xgo(1) style=cursor:pointer>Back to login</a>
|
|
</form>
|
|
</div>
|
|
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div id=footer style="height:32px;width:100%;text-align:center;background-color:#113962;position:absolute;bottom:0px">
|
|
<table cellpadding=0 cellspacing=6 style=width:100%>
|
|
<tr>
|
|
<td style=text-align:left;color:white>{{{footer}}}</td>
|
|
<td style=text-align:right>{{{rootCertLink}}} <a href=terms>Terms & Privacy</a></td>
|
|
</tr>
|
|
</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 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>
|
|
<div style=width:100%;margin:6px></div>
|
|
</div>
|
|
<div style="margin-right:16px;margin-left:8px">
|
|
<div id=dialog1 style="margin:auto;text-align:center;margin:3px">
|
|
<div id=id_dialogMessage style="padding:10px"></div>
|
|
</div>
|
|
<div id=dialog2 style="margin:auto;margin:3px">
|
|
<div id=id_dialogOptions></div>
|
|
</div>
|
|
</div>
|
|
<div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:20px">
|
|
<input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
|
|
<input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
|
|
</div>
|
|
</div>
|
|
<script>
|
|
'use strict';
|
|
var passhint = "{{{passhint}}}";
|
|
var newAccountPass = parseInt('{{{newAccountPass}}}');
|
|
var emailCheck = ('{{{emailcheck}}}' == 'true');
|
|
var features = parseInt('{{{features}}}');
|
|
var passRequirements = "{{{passRequirements}}}";
|
|
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 hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
|
|
var currentpanel = 0;
|
|
|
|
function startup() {
|
|
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; }
|
|
}
|
|
|
|
QV('createPanelHint', passRequirements.hint === true);
|
|
QV('resetpasswordpanelHint', passRequirements.hint === true);
|
|
|
|
window.onresize = center;
|
|
center();
|
|
validateLogin();
|
|
validateCreate();
|
|
if ('{{loginmode}}' != '') { go(parseInt('{{loginmode}}')); } else { go(1); }
|
|
QV('newAccountDiv', ('{{{newAccount}}}' != '0') && ('{{{newAccount}}}' != 'false')); // If new accounts are not allowed, don't display the new account link.
|
|
if ((passRequirements.hint === true) && (passhint != null) && (passhint.length > 0)) { QV("showPassHintLink", true); }
|
|
QV("newAccountPass", (newAccountPass == 1));
|
|
QV("resetAccountDiv", (emailCheck == true));
|
|
QV("hrAccountDiv", (emailCheck == true) || (newAccountPass == 1));
|
|
|
|
if ('{{loginmode}}' == '4') {
|
|
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
|
|
if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
|
|
hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), c => c.charCodeAt(0)).buffer;
|
|
|
|
const 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]), c => c.charCodeAt(0)), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }
|
|
);
|
|
}
|
|
|
|
// 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('hwtokenInput').value = JSON.stringify(assertion);
|
|
QE('tokenOkButton', true);
|
|
Q('tokenOkButton').click();
|
|
},
|
|
function (error) { console.log('credentials-get error', error); }
|
|
);
|
|
} else if ((hardwareKeyChallenge != null) && u2fSupported()) {
|
|
// Old U2F hardware keys
|
|
window.u2f.sign(hardwareKeyChallenge.appId, hardwareKeyChallenge.challenge, hardwareKeyChallenge.registeredKeys, function (authResponse) {
|
|
if ((currentpanel == 4) && authResponse.signatureData) {
|
|
Q('hwtokenInput').value = JSON.stringify(authResponse);
|
|
QE('tokenOkButton', true);
|
|
Q('tokenOkButton').click();
|
|
}
|
|
}, hardwareKeyChallenge.timeoutSeconds);
|
|
}
|
|
}
|
|
|
|
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')) {
|
|
hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), c => c.charCodeAt(0)).buffer;
|
|
|
|
const 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]), c => c.charCodeAt(0)), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }
|
|
);
|
|
}
|
|
|
|
// 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); }
|
|
);
|
|
} else if ((hardwareKeyChallenge != null) && u2fSupported()) {
|
|
// Old U2F hardware keys
|
|
window.u2f.sign(hardwareKeyChallenge.appId, hardwareKeyChallenge.challenge, hardwareKeyChallenge.registeredKeys, function (authResponse) {
|
|
if ((currentpanel == 5) && authResponse.signatureData) {
|
|
Q('resetHwtokenInput').value = JSON.stringify(authResponse);
|
|
QE('resetTokenOkButton', true);
|
|
Q('resetTokenOkButton').click();
|
|
}
|
|
}, hardwareKeyChallenge.timeoutSeconds);
|
|
}
|
|
}
|
|
}
|
|
|
|
function showPassHint() {
|
|
if (passRequirements.hint === true) { messagebox("Password Hint", passhint); }
|
|
}
|
|
|
|
function xgo(x) {
|
|
QV('message1', false);
|
|
QV('message2', false);
|
|
QV('message3', false);
|
|
QV('message4', false);
|
|
QV('message5', false);
|
|
QV('message6', false);
|
|
go(x);
|
|
}
|
|
|
|
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);
|
|
if (x == 1) { Q('username').focus(); }
|
|
if (x == 2) { Q('ausername').focus(); }
|
|
if (x == 3) { Q('remail').focus(); }
|
|
if (x == 4) { Q('tokenInput').focus(); }
|
|
if (x == 5) { Q('resetTokenInput').focus(); }
|
|
if (x == 6) { Q('rapassword1').focus(); }
|
|
}
|
|
|
|
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('password').focus(); } else if (box == 2) { Q('loginButton').click(); } }
|
|
if (e != null) { haltEvent(e); }
|
|
}
|
|
|
|
function validateCreate(box,e) {
|
|
setDialogMode(0);
|
|
var ok = ((Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (validateEmail(Q('aemail').value) == true) && (Q('apassword1').value.length > 0) && (Q('apassword2').value == Q('apassword1').value));
|
|
if ((newAccountPass == 1) && (Q('anewaccountpass').value.length == 0)) { ok = false; }
|
|
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', '<span style=color:red><b>Password Policy</b><span>'); // TODO: Display problem hint
|
|
QV('passwordPolicyCallout', true);
|
|
QH('passwordPolicyCallout', passwordPolicyText(Q('apassword1').value));
|
|
} else {
|
|
QH('passWarning', '');
|
|
QV('passwordPolicyCallout', false);
|
|
}
|
|
}
|
|
}
|
|
QE('createButton', ok);
|
|
if ((e != null) && (e.keyCode == 13)) {
|
|
if (box == 1) { Q('aemail').focus(); }
|
|
if (box == 2) { Q('apassword1').focus(); }
|
|
if (box == 3) { Q('apassword2').focus(); }
|
|
if (box == 4) { Q('apasswordhint').focus(); }
|
|
if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { Q('createButton').click(); } }
|
|
if (box == 6) { Q('createButton').click(); }
|
|
}
|
|
if (e != null) { haltEvent(e); }
|
|
}
|
|
|
|
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 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); }
|
|
}
|
|
|
|
function passwordPolicyText(pass) {
|
|
var policy = '<div style=text-align:left>';
|
|
var counts = strCount(pass);
|
|
if (passRequirements.min && ((pass == null) || (pass.length < passRequirements.min))) { policy += 'Minimum length of ' + passRequirements.min + '<br />'; }
|
|
if (passRequirements.max && ((pass == null) || (pass.length > passRequirements.max))) { policy += 'Maximum length of ' + passRequirements.max + '<br />'; }
|
|
if (passRequirements.upper && ((pass == null) || (counts.upper < passRequirements.upper))) { policy += '' + passRequirements.upper + ' upper case<br />'; }
|
|
if (passRequirements.lower && ((pass == null) || (counts.lower < passRequirements.lower))) { policy += '' + passRequirements.lower + ' lower case<br />'; }
|
|
if (passRequirements.numeric && ((pass == null) || (counts.numeric < passRequirements.numeric))) { policy += '' + passRequirements.numeric + ' numeric<br />'; }
|
|
if (passRequirements.nonalpha && ((pass == null) || (counts.nonalpha < passRequirements.nonalpha))) { policy += passRequirements.nonalpha + ' non-alphanumeric<br />'; }
|
|
policy += '</div>';
|
|
return policy;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
var xcheckTokenTimer = null;
|
|
function checkTokenTimer(enter) {
|
|
if ((enter == 0) && (xcheckTokenTimer != null)) { clearInterval(xcheckTokenTimer); xcheckTokenTimer = null; }
|
|
if ((enter == 1) && (xcheckTokenTimer == null)) { xcheckTokenTimer = setInterval(checkToken, 200); }
|
|
}
|
|
|
|
function checkToken() {
|
|
var t1 = Q('tokenInput').value, 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, 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));
|
|
}
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
function center() { QS('dialog').left = ((((getDocWidth() - 400) / 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; }
|
|
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 u2fSupported() { return (window.u2f && ((navigator.userAgent.indexOf('Chrome/') > 0) || (navigator.userAgent.indexOf('Firefox/') > 0) || (navigator.userAgent.indexOf('Opera/') > 0) || (navigator.userAgent.indexOf('Safari/') > 0))); }
|
|
|
|
</script>
|
|
</body>
|
|
</html> |