master
handlebars 997 lines 63.5 KB
Raw
1 <!DOCTYPE html>
2 <html dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
3 <head lang="en">
4 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
5 <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
6 <meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
7 <meta name="apple-mobile-web-app-capable" content="yes" />
8 <meta name="format-detection" content="telephone=no" />
9 <meta name="robots" content="noindex,nofollow">
10 <link rel="manifest" href="{{{domainurl}}}manifest.json">
11 <link rel="shortcut icon" type="image/x-icon" href="{{{domainurl}}}favicon.ico" />
12 <link keeplink=1 type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
13 {{{customCSSTags}}}
14 <link rel="apple-touch-icon" href="/favicon-303x303.png" />
15 <script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
16 {{{customJSTags}}}
17 <script keeplink=1 type="text/javascript" src="scripts/u2f-api{{min}}.js"></script>
18 <title>{{{title}}} - Login</title>
19 </head>
20 <body id="body" onload="if (typeof(startup) !== 'undefined') startup();" class="arg_hide login">
21 <div id=container>
22 <div id=masthead>
23 <div style="float:left">{{{titlehtml}}}</div>
24 <div class="title">{{{title1}}}</div>
25 <div class="title2">{{{title2}}}</div>
26 </div>
27 <div id=topbar class="noselect style3" style="height:24px">
28 <div id=uiMenuButton title="User interface selection" onclick="showUserInterfaceSelectMenu()">
29 &diams;
30 <div id=uiMenu style="display:none">
31 <div id=uiViewButton1 class=uiSelector onclick=userInterfaceSelectMenu(1) title="Left bar interface"><div class="uiSelector1"></div></div>
32 <div id=uiViewButton2 class=uiSelector onclick=userInterfaceSelectMenu(2) title="Top bar interface"><div class="uiSelector2"></div></div>
33 <div id=uiViewButton3 class=uiSelector onclick=userInterfaceSelectMenu(3) title="Fixed width interface"><div class="uiSelector3"></div></div>
34 <div id=uiViewButton4 class=uiSelector onclick=toggleNightMode() title="Toggle night mode"><div class="uiSelector4"></div></div>
35 </div>
36 </div>
37 </div>
38 <div id=column_l>
39 <h1>Welcome</h1>
40 <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>
41 <table id="centralTable" style="">
42 <tr>
43 <td id="welcomeimage">
44 <picture>
45 <img alt="" src=welcome.jpg style="border-radius:20px" />
46 </picture>
47 </td>
48 <td id="logincell">
49 <div id=loginpanel style="display:none">
50 <form method=post>
51 <input type=hidden name=action value=login />
52 <div id=message1></div>
53 <div id="loginuserpassdiv" style="display:none">
54 <div>
55 <b>Log In</b>
56 </div>
57 <table>
58 <tr>
59 <td id=loginusername align=right width=100>Username:</td>
60 <td><input id=username {{{autocomplete}}}="username" type=text maxlength=64 name=username required onchange=validateLogin(1) onkeyup=validateLogin(1,event) /></td>
61 </tr>
62 <tr>
63 <td align=right>Password:</td>
64 <td><input id=password {{{autocomplete}}}="current-password" type=password maxlength=256 name=password required onchange=validateLogin(2) onkeyup=validateLogin(2,event) /></td>
65 </tr>
66 <tr>
67 <td><div id=showPassHintLink style=display:none><a onclick="return showPassHint(event);" href="#" style="cursor:pointer">Show Hint</a></div></td>
68 <td align=right><input id=loginButton type=submit value="Log In" /></td>
69 </tr>
70 </table>
71 <div id="hrAccountDiv" style="display:none"><hr /></div>
72 <div id="resetAccountDiv" style="display:none;padding:2px">
73 <span id="resetAccountSpan">Forgot username/password?</span> <a onclick="return xgo(3,event);" href="#" style=cursor:pointer>Reset account</a>.
74 </div>
75 <div id="newAccountDiv" style="display:none;padding:2px">
76 Don&#39;t have an account? <a onclick="return xgo(2,event);" href="#" style=cursor:pointer>Create one</a>.
77 </div>
78 </div>
79 <input id=loginformargs name="urlargs" type="hidden" value="" />
80 <div id="authStrategies" style="display:none">
81 <hr id="loginuserpasshr" style="display:none" />
82 <div style="margin-bottom:8px">Log in using an existing account</div>
83 <a id="auth-twitter" href="auth-twitter" style="display:none"><img src="images/login/twitter32.png" srcset="images/login/twitter64.png 2x" 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>
84 <a id="auth-google" href="auth-google" style="display:none"><img src="images/login/google32.png" srcset="images/login/google64.png 2x" 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>
85 <a id="auth-github" href="auth-github" style="display:none"><img src="images/login/github32.png" srcset="images/login/github64.png 2x" 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>
86 <a id="auth-azure" href="auth-azure" style="display:none"><img src="images/login/azure32.png" srcset="images/login/azure64.png 2x" 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>
87 <a id="auth-oidc" href="auth-oidc" title="Sign-in using OpenID Connect" style="display:none"><img src="{{oidcButtonIcon}}" srcset="{{oidcButtonIcon2x}}" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:white" /></a>
88 <a id="auth-oidc-azure" href="auth-oidc" title="Sign-in with Azure using OpenID Connect" style="display:none"><img src="{{oidcButtonIcon}}" srcset="{{oidcButtonIcon2x}}" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:white" /></a>
89 <a id="auth-oidc-google" href="auth-oidc" title="Sign-in with Google using OpenID Connect" style="display:none"><img src="{{oidcButtonIcon}}" srcset="{{oidcButtonIcon2x}}" width="32" height="32" style="margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:white" /></a>
90 <a id="auth-jumpcloud" href="auth-jumpcloud" style="display:none"><img src="images/login/jumpcloud32.png" srcset="images/login/jumpcloud64.png 2x" 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>
91 <a id="auth-intel" href="auth-intel" style="display:none"><img src="images/login/intel32.png" srcset="images/login/intel64.png 2x" 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>
92 <a id="auth-saml" href="auth-saml" style="display:none"><img src="images/login/generic32.png" srcset="images/login/generic64.png 2x" 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>
93 </div>
94 </form>
95 </div>
96 <div id=createpanel style="display:none;position:relative">
97 <form method=post>
98 <input type=hidden name=action value=createaccount />
99 <div id=message2></div>
100 <div>
101 <b>Account Creation</b>
102 </div>
103 <div id="passwordPolicyCallout" style="display:none"></div>
104 <table>
105 <tr id="nuUserRow">
106 <td id="nuUser" align=right width=100>Username:</td>
107 <td><input id=ausername type=text {{{autocomplete}}}="username" name=username onchange=validateCreate(1) maxlength=64 onkeydown=haltReturn(event) onkeyup=validateCreate(1,event) /></td>
108 </tr>
109 <tr>
110 <td id="nuEmail" align=right width=100>Email:</td>
111 <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>
112 </tr>
113 <tr>
114 <td id="nuPass1" align=right>Password:</td>
115 <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>
116 </tr>
117 <tr>
118 <td id="nuPass2" align=right>Password:</td>
119 <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>
120 </tr>
121 <tr id="createPanelHint" style="display:none">
122 <td id="nuHint" align=right>Password Hint:</td>
123 <td><input id=apasswordhint type=text name=apasswordhint {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(5,event) onkeyup=validateCreate(5,event) /></td>
124 </tr>
125 <tr id=newAccountPass title="Enter the account creation token">
126 <td id="nuToken" align=right>Creation Token:</td>
127 <td><input id=anewaccountpass type=password name=anewaccountpass {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(6,event) onkeyup=validateCreate(6,event) /></td>
128 </tr>
129 <tr id=newAccountCaptchaImg title="CAPTCHA image">
130 <td></td>
131 <td colspan=2><img src="{{{newAccountCaptchaImage}}}" /></td>
132 </tr>
133 <tr id=newAccountCaptcha title="Security check">
134 <td id="nuCaptcha" align=right>Security Check:</td>
135 <td><input id=anewaccountcaptcha type=text name=anewaccountcaptcha {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(7,event) onkeyup=validateCreate(7,event) /></td>
136 </tr>
137 <tr>
138 <td colspan=2>
139 <div style=float:right><input id=createButton type=submit value="Create Account" disabled="disabled" /></div>
140 <div id=passWarning style="padding-top:6px"></div>
141 </td>
142 </tr>
143 </table>
144 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
145 <input id=createformargs name="urlargs" type="hidden" value="" />
146 <input id=createformcaptcha name="captchaargs" type="hidden" value="{{{newAccountCaptcha}}}" />
147 </form>
148 </div>
149 <div id=resetpanel style="display:none">
150 <form method=post>
151 <input type=hidden name=action value=resetaccount />
152 <div id=message3></div>
153 <div>
154 <b>Account Reset</b>
155 </div>
156 <table>
157 <tr>
158 <td align=right width=100>Email:</td>
159 <td><input id=remail type=text inputmode="email" name=email maxlength=256 onchange=validateReset() onkeyup=validateReset(event) /></td>
160 </tr>
161 <tr>
162 <td colspan=2>
163 <div style=float:right><input id=eresetButton type=submit value="Reset Account" disabled="disabled" /></div>
164 <div id=passWarning style="padding-top:6px"></div>
165 </td>
166 </tr>
167 </table>
168 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
169 <input id=resetformargs name="urlargs" type="hidden" value="" />
170 </form>
171 </div>
172 <div id=tokenpanel style="display:none">
173 <form method=post {{{autocomplete}}}=off>
174 <input type=hidden name=action value=tokenlogin />
175 <input type=hidden name=hwstate value="{{{hwstate}}}" />
176 <div id=message4></div>
177 <table>
178 <tr>
179 <td align=right width=100>Login token:</td>
180 <td>
181 <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 />
182 <input id=hwtokenInput type=text name=hwtoken style="display:none" />
183 </td>
184 </tr>
185 <tr>
186 <td colspan="2" style="align-content:center">
187 <label id=tokenInputRememberLabel><input id=tokenInputRemember name=remembertoken type=checkbox /><span id=tokenInputRememberSpan></span></label>
188 </td>
189 </tr>
190 <tr>
191 <td colspan=2>
192 <div style=float:right><input id=tokenOkButton type=submit value="Login" disabled="disabled" /></div>
193 <div style=float:right>
194 <input style="display:none;float:right" id=securityKeyButton type=button value="Use Security Key" onclick="useSecurityKey(1)" />
195 <input style="display:none;float:right" id=emailKeyButton type=button value="Email" onclick="useEmailToken(1)" />
196 <input style="display:none;float:right" id=smsKeyButton type=button value="SMS" onclick="useSMSToken(1)" />
197 <input style="display:none;float:right" id=msgKeyButton type=button value="Messaging" onclick="useMsgToken(1)" />
198 <input style="display:none;float:right" id=duoKeyButton type=button value="Duo" onclick="useDuoToken(1)" />
199 </div>
200 </td>
201 </tr>
202 </table>
203 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
204 <input id=tokenformargs name="urlargs" type="hidden" value="" />
205 </form>
206 </div>
207 <div id=resettokenpanel style="display:none">
208 <form method=post>
209 <input type=hidden name=action value=resetaccount />
210 <div id=message5></div>
211 <table>
212 <tr>
213 <td align=right width=100>Login token:</td>
214 <td>
215 <input id=resetTokenInput type=text name=token maxlength=50 onchange=resetCheckToken(event) onkeyup=resetCheckToken(event) onkeydown=resetCheckToken(event) />
216 <input id=resetHwtokenInput type=text name=hwtoken style="display:none" />
217 </td>
218 </tr>
219 <tr>
220 <td colspan=2>
221 <div style=float:right><input id=resetTokenOkButton type=submit value="Login" disabled="disabled" /></div>
222 <div style=float:right>
223 <input style="display:none;float:right" id=securityKeyButton2 type=button value="Use Security Key" onclick="useSecurityKey(2)" />
224 <input style="display:none;float:right" id=emailKeyButton2 type=button value="Email" onclick="useEmailToken(2)" />
225 <input style="display:none;float:right" id=smsKeyButton2 type=button value="SMS" onclick="useSMSToken(2)" />
226 <input style="display:none;float:right" id=msgKeyButton2 type=button value="Messaging" onclick="useMsgToken(2)" />
227 <input style="display:none;float:right" id=duoKeyButton2 type=button value="Duo" onclick="useDuoToken(2)" />
228 </div>
229 </td>
230 </tr>
231 </table>
232 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
233 <input id=resettokenformargs name="urlargs" type="hidden" value="" />
234 </form>
235 </div>
236 <div id=resetpasswordpanel style="display:none;position:relative">
237 <form method=post>
238 <input type=hidden name=action value=resetpassword />
239 <div id=message6></div>
240 <div id="rpasswordPolicyCallout" style="display:none"></div>
241 <table>
242 <tr>
243 <td id="rnuPass1" width=100 align=right>Password:</td>
244 <td><input id=rapassword1 type=password name=rpassword1 {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(3,event) onkeyup=validatePassReset(3,event) /></td>
245 </tr>
246 <tr>
247 <td id="rnuPass2" align=right>Password:</td>
248 <td><input id=rapassword2 type=password name=rpassword2 {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(4,event) onkeyup=validatePassReset(4,event) /></td>
249 </tr>
250 <tr id="resetpasswordpanelHint" style="display:none">
251 <td id="rnuHint" align=right>Password Hint:</td>
252 <td><input id=rapasswordhint type=text name=rpasswordhint {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(5,event) onkeyup=validatePassReset(5,event) /></td>
253 </tr>
254 <tr>
255 <td colspan=2>
256 <div style=float:right><input id=resetPassButton type=submit value="Reset Password" disabled="disabled" /></div>
257 <div id=rpassWarning style="padding-top:6px"></div>
258 </td>
259 </tr>
260 </table>
261 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
262 <input id=resetpasswordformargs name="urlargs" type="hidden" value="" />
263 </form>
264 </div>
265 <div id=checkemailpanel style="display:none;position:relative">
266 <form method=post>
267 <input type=hidden name=action value=checkemail />
268 <div id=message7></div>
269 <table id="checkCheckOperations" style="width:100%;display:none">
270 <tr>
271 <td>
272 <div id="unconfirmedEmail"></div><br />
273 </td>
274 </tr>
275 <tr>
276 <td>
277 <div>
278 <input id=changeEmailButton type=button value="Change Email Address" onclick="changeEmailAddress()" />
279 <input id=checkEmailButton2 type=button value="Resend Confirmation Email" onclick="resentEmailConfirmation()" />
280 <input id=checkEmailButton type=submit style="display:none" />
281 <input id=checkEmailVal name=email type="hidden" value="" />
282 </div>
283 </td>
284 </tr>
285 </table>
286 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
287 <input id=checkemailformargs name="urlargs" type="hidden" value="" />
288 </form>
289 </div>
290 </td>
291 </tr>
292 </table>
293 <br />
294 </div>
295 <div id="footer">
296 <div class="footer1">{{{footer}}}</div>
297 <div class="footer2">
298 {{{rootCertLink}}}
299 &nbsp;<a id="termsLinkFooter" href=terms>Terms &amp; Privacy</a>
300 </div>
301 </div>
302
303 </div>
304 <div id=dialog style="display:none">
305 <div id=dialogHeader>
306 <div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
307 <div id=id_dialogtitle style=padding:5px></div>
308 <div style=width:100%;margin:6px></div>
309 </div>
310 <div id=dialogBody>
311 <div id=dialog1>
312 <div id=id_dialogMessage style=""></div>
313 </div>
314 <div id=dialog2 style="">
315 <div id=id_dialogOptions></div>
316 </div>
317 </div>
318 <div id="idx_dlgButtonBar" style="">
319 <input id="idx_dlgCancelButton" type="button" value="Cancel" style="" onclick="dialogclose(0)">
320 <input id="idx_dlgOkButton" type="button" value="OK" style="" onclick="dialogclose(1)">
321 </div>
322 </div>
323 <script>
324 'use strict';
325 var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
326 var passlogin = '{{{passlogin}}}';
327 var passhint = '{{{passhint}}}';
328 var loginMode = '{{{loginmode}}}';
329 var newAccount = '{{{newAccount}}}';
330 var newAccountPass = parseInt('{{{newAccountPass}}}');
331 var newAccountCaptcha = '{{{newAccountCaptcha}}}';
332 var emailCheck = '{{{emailcheck}}}';
333 var passRequirements = '{{{passRequirements}}}';
334 var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
335 if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } else { passRequirements = {}; }
336 var passRequirementsEx = ((passRequirements.min != null) || (passRequirements.max != null) || (passRequirements.upper != null) || (passRequirements.lower != null) || (passRequirements.numeric != null) || (passRequirements.nonalpha != null));
337 var features = parseInt('{{{features}}}');
338 var welcomeText = decodeURIComponent('{{{welcometext}}}');
339 var currentpanel = 0;
340 var uiMode = parseInt(getstore('uiMode', '1'));
341 var webPageFullScreen = true;
342 var nightMode = (getstore('_nightMode', '0') == '1');
343 var publicKeyCredentialRequestOptions = null;
344 var otpduo = (decodeURIComponent('{{{otpduo}}}') === 'true');
345 var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true');
346 var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
347 var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true');
348 var autofido = (decodeURIComponent('{{{autofido}}}') === 'true');
349 var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
350 var authStrategies = '{{{authStrategies}}}'.split(',');
351 var tokenTimeout = parseInt('{{{tokenTimeout}}}');
352
353 function startup() {
354 // Display the right server message
355 var i;
356 var messageid = parseInt('{{{messageid}}}');
357 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."];
358 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."];
359 if (messageid > 0) {
360 var msg = '';
361 if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
362 else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
363 if (msg != '') {
364 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 />'); }
365 for (i = 1; i < 8; i++) { QH('message' + i, msg); }
366 }
367 }
368
369 // Display flash error Messages
370 var flashErrors = JSON.parse('{{{flashErrors}}}');
371 if (flashErrors && (flashErrors.length > 0)) {
372 var msg = '';
373 for (i = 0; i < flashErrors.length; i++) {
374 if (flashErrors[i]) {
375 msg += '<span class="msg error"><b style=color:#8C001A>' + flashErrors[i] + '<b></span><br /><br />';
376 }
377 }
378 QH('message1', msg);
379 QV('message1', true);
380 }
381
382 // Fix links if a loginKey if used
383 var urlargs = parseUriArgs();
384 if (urlargs.key) {
385 Q('termsLinkFooter').href += '?key=' + urlargs.key;
386 }
387
388 // Show user/pass login
389 passlogin = (passlogin == 'true');
390 if (urlargs.passlogin === 1) { passlogin = true; }
391 if (urlargs.passlogin === 0) { passlogin = false; }
392 QV('loginuserpassdiv', passlogin)
393 QV('loginuserpasshr', passlogin)
394
395 // Setup two factor cookie time
396 if (twoFactorCookieDays > 0) {
397 QV('tokenInputRememberLabel', true);
398 QH('tokenInputRememberSpan', format("Remember this device for {0} days.", twoFactorCookieDays));
399 } else {
400 QV('tokenInputRememberLabel', false);
401 }
402
403 // If URL arguments are provided, add them to form posts
404 if (window.location.href.indexOf('?') > 0) {
405 var xurlargs = window.location.href.substring(window.location.href.indexOf('?'));
406 Q('loginformargs').value = xurlargs;
407 Q('createformargs').value = xurlargs;
408 Q('resetformargs').value = xurlargs;
409 Q('tokenformargs').value = xurlargs;
410 Q('resettokenformargs').value = xurlargs;
411 Q('resetpasswordformargs').value = xurlargs;
412 Q('checkemailformargs').value = xurlargs;
413 }
414
415 //var webPageFullScreen = getstore('webPageFullScreen', true);
416 //if (webPageFullScreen == 'false') { webPageFullScreen = false; }
417 //if (webPageFullScreen == 'true') { webPageFullScreen = true; }
418 //toggleFullScreen();
419
420 if ((features & 32) == 0) {
421 // Guard against other site's top frames (web bugs).
422 var loc = null;
423 try { loc = top.location.toString().toLowerCase(); } catch (e) { }
424 if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
425 }
426
427 if (features & 0x200000) { // Email is username
428 QH('loginusername', "Email:");
429 QH('resetAccountSpan', "Forgot password?");
430 QV('nuUserRow', false);
431 }
432
433 if (nightMode) { QC('body').add('night'); }
434
435 QV('createPanelHint', passRequirements.hint === true);
436 QV('resetpasswordpanelHint', passRequirements.hint === true);
437
438 // Setup authentication strategies
439 if (authStrategies != '') {
440 QV('authStrategies', true);
441 if (authStrategies.indexOf('twitter') >= 0) { QV('auth-twitter', true); }
442 if (authStrategies.indexOf('google') >= 0) { QV('auth-google', true); }
443 if (authStrategies.indexOf('github') >= 0) { QV('auth-github', true); }
444 if (authStrategies.indexOf('azure') >= 0) { QV('auth-azure', true); }
445 if (authStrategies.indexOf('oidc') >= 0) { QV('auth-oidc', true); }
446 if (authStrategies.indexOf('oidc-azure') >= 0) { QV('auth-oidc-azure', true); }
447 if (authStrategies.indexOf('oidc-google') >= 0) { QV('auth-oidc-google', true); }
448 if (authStrategies.indexOf('jumpcloud') >= 0) { QV('auth-jumpcloud', true); }
449 if (authStrategies.indexOf('intel') >= 0) { QV('auth-intel', true); }
450 if (authStrategies.indexOf('saml') >= 0) { QV('auth-saml', true); }
451 // Override OIDC button title if custom text is configured by admin
452 var oidcButtonText = '{{{oidcButtonText}}}';
453 if (oidcButtonText != '') {
454 Q('auth-oidc').title = oidcButtonText;
455 Q('auth-oidc-azure').title = oidcButtonText;
456 Q('auth-oidc-google').title = oidcButtonText;
457 }
458 }
459
460 // Display the welcome text
461 if (welcomeText) {
462 QH('welcomeText', welcomeText);
463 } else {
464 QH('welcomeText', addTextLink('MeshCentral', Q('welcomeText').innerHTML, 'http://www.meshcommander.com/meshcentral2'));
465 }
466 QV('welcomeText', true);
467
468 window.onresize = center;
469 center();
470
471 validateCreate();
472 if (loginMode.length != 0) { go(parseInt(loginMode)); } else { go(1); }
473 QV('newAccountDiv', (newAccount === '1') || (newAccount === 'true')); // If new accounts are not allowed, don't display the new account link.
474 if ((passhint != null) && (passhint.length > 0)) { QV('showPassHintLink', true); }
475 QV('newAccountPass', (newAccountPass == 1));
476 QV('newAccountCaptcha', (newAccountCaptcha != ''));
477 QV('newAccountCaptchaImg', (newAccountCaptcha != ''));
478 QV('resetAccountDiv', (emailCheck == 'true'));
479 QV('hrAccountDiv', (emailCheck == 'true') || (newAccountPass == 1));
480
481 if (loginMode == '4') {
482 if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
483 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
484 var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
485 QV('securityKeyButton', twofakey);
486 QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
487 QV('smsKeyButton', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
488 QV('msgKeyButton', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
489 QV('duoKeyButton', otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6));
490
491 // If hardware key is an option, trigger it now
492 if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
493 }
494
495 if (loginMode == '5') {
496 if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
497 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
498 var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
499 QV('securityKeyButton2', twofakey);
500 QV('emailKeyButton2', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
501 QV('smsKeyButton2', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
502 QV('msgKeyButton2', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
503 QV('duoKeyButton2', otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6));
504
505 // If hardware key is an option, trigger it now
506 if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); }
507 }
508
509 /*
510 if (loginMode == '5') {
511 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
512 if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
513 if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
514
515 publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
516 for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
517 publicKeyCredentialRequestOptions.allowCredentials.push(
518 { id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'] }
519 );
520 }
521
522 // New WebAuthn hardware keys
523 navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
524 function (rawAssertion) {
525 var assertion = {
526 id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
527 clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
528 userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
529 signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
530 authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
531 };
532 Q('resetHwtokenInput').value = JSON.stringify(assertion);
533 QE('resetTokenOkButton', true);
534 Q('resetTokenOkButton').click();
535 },
536 function (error) { console.log('credentials-get error', error); }
537 );
538 }
539 }
540 */
541
542 // Setup the user interface in the right mode
543 userInterfaceSelectMenu();
544 }
545
546 // Use a hardware security key
547 function useSecurityKey(panelAction) {
548 if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
549 if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
550
551 publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout, userVerification: hardwareKeyChallenge.userVerification ? hardwareKeyChallenge.userVerification : 'preferred' }
552 for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
553 publicKeyCredentialRequestOptions.allowCredentials.push(
554 { id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'] }
555 );
556 }
557
558 // New WebAuthn hardware keys
559 navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
560 function (rawAssertion) {
561 var assertion = {
562 id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
563 clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
564 userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
565 signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
566 authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
567 };
568 if (panelAction == 1) {
569 Q('hwtokenInput').value = JSON.stringify(assertion);
570 QE('tokenOkButton', true);
571 Q('tokenOkButton').click();
572 } else if (panelAction == 2) {
573 Q('resetHwtokenInput').value = JSON.stringify(assertion);
574 QE('resetTokenOkButton', true);
575 Q('resetTokenOkButton').click();
576 }
577 },
578 function (error) { console.log('credentials-get error', error); }
579 );
580 }
581 }
582
583 function useEmailToken(panelAction) {
584 if (otpemail != true) return;
585 setDialogMode(1, "Secure Login", 3, useEmailKeyEx, "Send token to registered email address?", panelAction);
586 }
587
588 function useEmailKeyEx(b, panelAction) {
589 if (panelAction == 1) {
590 Q('hwtokenInput').value = '**email**';
591 QE('tokenOkButton', true);
592 Q('tokenOkButton').click();
593 } else if (panelAction == 2) {
594 Q('resetHwtokenInput').value = '**email**';
595 QE('resetTokenOkButton', true);
596 Q('resetTokenOkButton').click();
597 }
598 }
599
600 function useSMSToken(panelAction) {
601 if (otpsms != true) return;
602 setDialogMode(1, "Secure Login", 3, useSMSTokenEx, "Send token to registered phone number?", panelAction);
603 }
604
605 function useSMSTokenEx(b, panelAction) {
606 if (panelAction == 1) {
607 Q('hwtokenInput').value = '**sms**';
608 QE('tokenOkButton', true);
609 Q('tokenOkButton').click();
610 } else if (panelAction == 2) {
611 Q('resetHwtokenInput').value = '**sms**';
612 QE('resetTokenOkButton', true);
613 Q('resetTokenOkButton').click();
614 }
615 }
616
617 function useMsgToken(panelAction) {
618 if (otpmsg != true) return;
619 setDialogMode(1, "Secure Login", 3, useMsgTokenEx, "Send token to messaging application?", panelAction);
620 }
621
622 function useMsgTokenEx(b, panelAction) {
623 if (panelAction == 1) {
624 Q('hwtokenInput').value = '**msg**';
625 QE('tokenOkButton', true);
626 Q('tokenOkButton').click();
627 } else if (panelAction == 2) {
628 Q('resetHwtokenInput').value = '**msg**';
629 QE('resetTokenOkButton', true);
630 Q('resetTokenOkButton').click();
631 }
632 }
633
634 function useDuoToken(panelAction) {
635 if (panelAction == 1) {
636 Q('hwtokenInput').value = '**duo**';
637 QE('tokenOkButton', true);
638 Q('tokenOkButton').click();
639 } else if (panelAction == 2) {
640 Q('resetHwtokenInput').value = '**duo**';
641 QE('resetTokenOkButton', true);
642 Q('resetTokenOkButton').click();
643 }
644 }
645
646 function showPassHint(e) {
647 messagebox("Password Hint", passhint);
648 haltEvent(e);
649 return false;
650 }
651
652 function xgo(x, e) {
653 QV('message1', false);
654 QV('message2', false);
655 QV('message3', false);
656 QV('message4', false);
657 QV('message5', false);
658 QV('message6', false);
659 QV('message7', false);
660 go(x);
661 haltEvent(e);
662 return false;
663 }
664
665 function go(x) {
666 currentpanel = x;
667 setDialogMode(0);
668 QV('showPassHintLink', false);
669 QV('loginpanel', x == 1);
670 QV('createpanel', x == 2);
671 QV('resetpanel', x == 3);
672 QV('tokenpanel', x == 4);
673 QV('resettokenpanel', x == 5);
674 QV('resetpasswordpanel', x == 6);
675 QV('checkemailpanel', x == 7);
676 if (x == 1) { Q('username').focus(); }
677 if (x == 2) { if (features & 0x200000) { Q('aemail').focus(); } else { Q('ausername').focus(); } } // Email is username
678 if (x == 3) { Q('remail').focus(); }
679 if (x == 4) { Q('tokenInput').focus(); }
680 if (x == 5) { Q('resetTokenInput').focus(); }
681 if (x == 6) { Q('rapassword1').focus(); }
682 if (x == 7) {
683 QH('unconfirmedEmail', passhint);
684 QV('checkCheckOperations', Q('unconfirmedEmail').innerHTML != '');
685 QH('checkEmailVal', passhint);
686 }
687 }
688
689 function validateLogin(box, e) {
690 setTimeout(function(){
691 setDialogMode(0);
692 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(); } }
693 if (e != null) { haltEvent(e); }
694 }, 100);
695 }
696
697 function validateCreate(box, e) {
698 setDialogMode(0);
699 var userok = false;
700 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); }
701 var emailok = (validateEmail(Q('aemail').value) == true);
702 var pass1ok = (Q('apassword1').value.length > 0);
703 var pass2ok = (Q('apassword2').value.length > 0) && (Q('apassword2').value == Q('apassword1').value);
704 var newAccOk = (newAccountPass == 0) || (Q('anewaccountpass').value.length > 0);
705 var newCaptchaOk = (newAccountCaptcha == '') || (Q('anewaccountcaptcha').value.length > 0);
706 var ok = (userok && emailok && pass1ok && pass2ok && newAccOk);
707
708 // Color the fields
709 QS('nuUser').color = userok ? 'black' : '#7b241c';
710 QS('nuEmail').color = emailok ? 'black' : '#7b241c';
711 QS('nuPass1').color = pass1ok ? 'black' : '#7b241c';
712 QS('nuPass2').color = pass2ok ? 'black' : '#7b241c';
713 QS('nuToken').color = newAccOk ? 'black' : '#7b241c';
714 QS('nuCaptcha').color = newCaptchaOk ? 'black' : '#7b241c';
715
716 if (Q('apassword1').value == '') {
717 QH('passWarning', '');
718 QV('passwordPolicyCallout', false);
719 } else {
720 if (!passRequirementsEx) {
721 // No password requirements, display password strength
722 var passStrength = checkPasswordStrength(Q('apassword1').value);
723 if (passStrength >= 80) { QH('passWarning', '<span style=color:green><b>' + "Strong Password" + '</b><span>'); }
724 else if (passStrength >= 60) { QH('passWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
725 else { QH('passWarning', '<span style=color:red><b>' + "Weak Password" + '</b><span>'); }
726 } else {
727 // Password requirements provided, use that
728 var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
729 if (passReq == false) {
730 ok = false;
731 QS('nuPass1').color = '#7b241c';
732 QS('nuPass2').color = '#7b241c';
733 QH('passWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
734 QV('passwordPolicyCallout', true);
735 QH('passwordPolicyCallout', passwordPolicyText(Q('apassword1').value));
736 } else {
737 QH('passWarning', '');
738 QV('passwordPolicyCallout', false);
739 }
740 }
741 }
742 if ((e != null) && (e.keyCode == 13)) {
743 if ((box == 1) && userok) { Q('aemail').focus(); }
744 if ((box == 2) && emailok) { Q('apassword1').focus(); }
745 if ((box == 3) && pass1ok) { Q('apassword2').focus(); }
746 if ((box == 4) && pass2ok) { if (passRequirements.hint === true) { Q('apasswordhint').focus(); } else { box = 5; } }
747 if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { box = 6; } }
748 if (box == 6) { if (newAccountCaptcha != '') { Q('anewaccountcaptcha').focus(); } else { box = 7; } }
749 if (box == 7) { Q('createButton').click(); }
750 }
751 if (e != null) { haltEvent(e); }
752 QE('createButton', ok);
753 }
754
755 function validatePassReset(box, e) {
756 setDialogMode(0);
757 var pass1ok = (Q('rapassword1').value.length > 0);
758 var pass2ok = (Q('rapassword2').value.length > 0) && (Q('rapassword2').value == Q('rapassword1').value);
759 var ok = (pass1ok && pass2ok);
760
761 // Color the fields
762 QS('rnuPass1').color = pass1ok ? 'black' : '#7b241c';
763 QS('rnuPass2').color = pass2ok ? 'black' : '#7b241c';
764
765 if (Q('rapassword1').value == '') {
766 QH('rpassWarning', '');
767 QV('rpasswordPolicyCallout', false);
768 } else {
769 if (!passRequirementsEx) {
770 // No password requirements, display password strength
771 var passStrength = checkPasswordStrength(Q('rapassword1').value);
772 if (passStrength >= 80) { QH('rpassWarning', '<span style=color:green><b>' + "Strong Password" + '</b><span>'); }
773 else if (passStrength >= 60) { QH('rpassWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
774 else { QH('rpassWarning', '<span style=color:red><b>' + "Weak Password" + '</b><span>'); }
775 } else {
776 // Password requirements provided, use that
777 var passReq = checkPasswordRequirements(Q('rapassword1').value, passRequirements);
778 if (passReq == false) {
779 ok = false;
780 QS('rnuPass1').color = '#7b241c';
781 QS('rnuPass2').color = '#7b241c';
782 QH('rpassWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
783 QV('rpasswordPolicyCallout', true);
784 QH('rpasswordPolicyCallout', passwordPolicyText(Q('rapassword1').value));
785 } else {
786 QH('rpassWarning', '');
787 QV('rpasswordPolicyCallout', false);
788 }
789 }
790 }
791 if ((e != null) && (e.keyCode == 13)) {
792 if (box == 2) { Q('rapassword1').focus(); }
793 if (box == 3) { Q('rapassword2').focus(); }
794 if (box == 4) { Q('rapasswordhint').focus(); }
795 if (box == 6) { Q('resetPassButton').click(); }
796 }
797 if (e != null) { haltEvent(e); }
798 QE('resetPassButton', ok);
799 }
800
801 function passwordPolicyText(pass) {
802 var policy = '<div style=text-align:left>';
803 var counts = strCount(pass);
804 if (passRequirements.min && ((pass == null) || (pass.length < passRequirements.min))) { policy += format("Minimum length of {0}", passRequirements.min) + '<br />'; }
805 if (passRequirements.max && ((pass == null) || (pass.length > passRequirements.max))) { policy += format("Maximum length of {0}", passRequirements.max) + '<br />'; }
806 if (passRequirements.upper && ((pass == null) || (counts.upper < passRequirements.upper))) { policy += format("{0} upper case", passRequirements.upper) + '<br />'; }
807 if (passRequirements.lower && ((pass == null) || (counts.lower < passRequirements.lower))) { policy += format("{0} lower case", passRequirements.lower) + '<br />'; }
808 if (passRequirements.numeric && ((pass == null) || (counts.numeric < passRequirements.numeric))) { policy += format("{0} numeric", passRequirements.numeric) + '<br />'; }
809 if (passRequirements.nonalpha && ((pass == null) || (counts.nonalpha < passRequirements.nonalpha))) { policy += format("{0} non-alphanumeric", passRequirements.nonalpha) + '<br />'; }
810 policy += '</div>';
811 return policy;
812 }
813
814 function showPasswordPolicy() {
815 messagebox("Password Policy", passwordPolicyText());
816 }
817
818 function validateReset(e) {
819 setDialogMode(0);
820 var x = validateEmail(Q('remail').value);
821 QE('eresetButton', x);
822 if ((e != null) && (e.keyCode == 13) && (x == true)) {
823 Q('eresetButton').click();
824 }
825 if (e != null) { haltEvent(e); }
826 }
827
828 // Return a password strength score
829 function checkPasswordStrength(password) {
830 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) }
831 if (!password) return 0;
832 for (var i = 0; i < password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
833 for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
834 return parseInt(r + (varCount - 1) * 10);
835 }
836
837 // Check password requirements
838 function checkPasswordRequirements(password, requirements) {
839 if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
840 if (requirements.min) { if (password.length < requirements.min) return false; }
841 if (requirements.max) { if (password.length > requirements.max) return false; }
842 var counts = strCount(password);
843 if (requirements.numeric && (counts.numeric < requirements.numeric)) return false;
844 if (requirements.lower && (counts.lower < requirements.lower)) return false;
845 if (requirements.upper && (counts.upper < requirements.upper)) return false;
846 if (requirements.nonalpha && (counts.nonalpha < requirements.nonalpha)) return false;
847 return true;
848 }
849
850 function strCount(password) {
851 var counts = { numeric: 0, lower: 0, upper: 0, nonalpha: 0 };
852 if (typeof password != 'string') return counts;
853 for (var i = 0; i < password.length; i++) {
854 if (/\d/.test(password[i])) { counts.numeric++; }
855 if (/[a-z]/.test(password[i])) { counts.lower++; }
856 if (/[A-Z]/.test(password[i])) { counts.upper++; }
857 if (/\W/.test(password[i])) { counts.nonalpha++; }
858 }
859 return counts;
860 }
861
862 function checkToken() {
863 var t1 = Q('tokenInput').value;
864 var t2 = t1.split(' ').join('');
865 if (t1 != t2) { Q('tokenInput').value = t2; }
866 QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8) || (Q('tokenInput').value.length == 44));
867 }
868
869 function resetCheckToken() {
870 var t1 = Q('resetTokenInput').value;
871 var t2 = t1.split(' ').join('');
872 if (t1 != t2) { Q('resetTokenInput').value = t2; }
873 QE('resetTokenOkButton', (Q('resetTokenInput').value.length == 6) || (Q('resetTokenInput').value.length == 8) || (Q('resetTokenInput').value.length == 44));
874 }
875
876 function changeEmailAddress() {
877 var email = Q('unconfirmedEmail').innerHTML;
878 var x = addHtmlValue("Email", '<input id=dp1email style=width:230px maxlength=256 value="' + email + '" {{{autocomplete}}}=off onchange=validateEmailAddress() onkeyup=validateEmailAddress() />');
879 setDialogMode(1, "Email Confirmation", 3, changeEmailAddressEx, x);
880 validateEmailAddress();
881 }
882
883 function validateEmailAddress() {
884 QE('idx_dlgOkButton', (validateEmail(Q('dp1email').value) == true));
885 }
886
887 function changeEmailAddressEx() {
888 Q('checkEmailVal').value = Q('dp1email').value;
889 QH('unconfirmedEmail', Q('dp1email').value);
890 Q('checkEmailButton').click();
891 }
892
893 function resentEmailConfirmation() {
894 Q('checkEmailVal').value = Q('unconfirmedEmail').innerHTML;
895 Q('checkEmailButton').click();
896 }
897
898 //
899 // POPUP DIALOG
900 //
901
902 // undefined = Hidden, 1 = Generic Message
903 var xxdialogMode;
904 var xxdialogFunc;
905 var xxdialogButtons;
906 var xxdialogTag;
907 var xxcurrentView = 0;
908
909 // Display a dialog box
910 // 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)
911 function setDialogMode(x, y, b, f, c, tag) {
912 xxdialogMode = x;
913 xxdialogFunc = f;
914 xxdialogButtons = b;
915 xxdialogTag = tag;
916 QE('idx_dlgOkButton', true);
917 QV('idx_dlgOkButton', b & 1);
918 QV('idx_dlgCancelButton', b & 2);
919 QV('id_dialogclose', (b & 2) || (b & 8));
920 QV('idx_dlgButtonBar', b & 7);
921 if (y) QH('id_dialogtitle', y);
922 for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
923 QV('dialog', x);
924 if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
925 }
926
927 function dialogclose(x) {
928 var f = xxdialogFunc;
929 var b = xxdialogButtons;
930 var t = xxdialogTag;
931 setDialogMode();
932 if (((b & 8) || x) && f) f(x, t);
933 }
934
935 // Toggle the web page to full screen
936 function toggleFullScreen(toggle) {
937 //if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
938 if (webPageFullScreen == false) {
939 // By adding body class, it will change a style of all ellements using CSS selector
940 // No need for JS anymore and it will be consistent style for all the templates.
941 QC('body').remove('fullscreen');
942 } else {
943 QC('body').add('fullscreen');
944 }
945 QV('body', true);
946 center();
947 }
948
949 // Toggle user interface menu
950 function showUserInterfaceSelectMenu() {
951 Q('uiViewButton1').classList.remove('uiSelectorSel');
952 Q('uiViewButton2').classList.remove('uiSelectorSel');
953 Q('uiViewButton3').classList.remove('uiSelectorSel');
954 try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
955 QV('uiMenu', (QS('uiMenu').display == 'none'));
956 if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
957 }
958
959 function userInterfaceSelectMenu(s) {
960 if (s) { uiMode = s; putstore('uiMode', uiMode); }
961 webPageFullScreen = (uiMode < 3);
962 //webPageStackMenu = (uiMode > 1);
963 toggleFullScreen(0);
964 //toggleStackMenu(0);
965 }
966
967 function toggleNightMode() {
968 nightMode = !nightMode;
969 if (nightMode) { QC('body').add('night'); } else { QC('body').remove('night'); }
970 putstore('_nightMode', (nightMode ? '1' : '0'));
971 }
972
973 function center() {
974 /* Now we use CSS media to achive the same effect as deleted JS */
975 if (webPageFullScreen == false) {
976 QS('centralTable')['margin-top'] = '';
977 } else {
978 var h = ((Q('column_l').clientHeight) / 2) - 220;
979 if (h < 0) h = 0;
980 QS('centralTable')['margin-top'] = h + 'px';
981 }
982 }
983 function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
984 function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
985 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; }
986 function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
987 function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
988 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
989 function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
990 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; } }
991 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; }); };
992 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); }
993 function addHtmlValue(t, v) { return '<table><td style=width:120px;text-align:left>' + t + '<td><b>' + v + '</b></table>'; }
994
995 </script>
996 </body>
997 </html>