master
handlebars 828 lines 55.4 KB
Raw
1 <!DOCTYPE html>
2 <html dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
3 <head lang="en">
4 <meta charset="utf-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
7 <meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
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" href="{{{domainurl}}}favicon.ico" />
12 <link rel="icon" type="image/png" sizes="16x16" href="{{{domainurl}}}favicon-16x16.png">
13 <link rel="icon" type="image/png" sizes="32x32" href="{{{domainurl}}}favicon-32x32.png">
14 <link rel="apple-touch-icon" href="/favicon-303x303.png" />
15 {{{customCSSTags}}}
16 <meta name="apple-mobile-web-app-capable" content="yes">
17 <meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
18 <meta name="apple-mobile-web-app-title" content="{{{title}}}">
19 <script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
20 {{{customJSTags}}}
21 <script type="text/javascript" src="scripts/u2f-api{{min}}.js"></script>
22 <meta name="msapplication-TileColor" content="#00aba9">
23 <meta name="theme-color" content="#ffffff">
24 <title>{{{title}}} - Login</title>
25 <style>
26 a {
27 color: #036;
28 text-decoration: underline;
29 }
30
31 #footer a {
32 color: #fff;
33 text-decoration: underline;
34 }
35
36 #footer a:hover {
37 color: #fff;
38 text-decoration: none;
39 }
40 </style>
41 </head>
42 <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">
43 <div id=container>
44 <div id=mastheadx></div>
45 <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">
46 <div style="float:left;height:66px;color:#c8c8c8;padding-left:10px;padding-top:6px">
47 <strong><font style="font-size:36px;font-family:Arial,Helvetica,sans-serif">{{{title}}}</font></strong>
48 </div>
49 <div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:10px">
50 <strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif">{{{title2}}}</font></strong>
51 </div>
52 </div>
53 <div id=page_content style="overflow-y:scroll;position:absolute;bottom:32px;top:50px;width:100%;display:flex;align-items:center">
54 <div id=column_l style="padding:10px;width:100%">
55 <table style="width:100%">
56 <tr>
57 <td align=center>
58 <div id=loginpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;display:none">
59 <form method=post>
60 <input type=hidden name=action value=login />
61 <div id=message1></div>
62 <div>
63 <b>Log In</b>
64 </div>
65 <table>
66 <tr>
67 <td id=loginusername align=right width=100>Username:</td>
68 <td><input id=username type=text autocomplete="username" maxlength=64 name=username required onchange=validateLogin(1) onkeyup=validateLogin(1,event) /></td>
69 </tr>
70 <tr>
71 <td align=right>Password:</td>
72 <td><input id=password type=password autocomplete="current-password" maxlength=256 name=password autocomplete=off required onchange=validateLogin(2) onkeyup=validateLogin(2,event) /></td>
73 </tr>
74 <tr>
75 <td><div id=showPassHintLink style=display:none><a onclick=showPassHint() style="cursor:pointer">Show Hint</a></div></td>
76 <td align=right><input id=loginButton type=submit value="Log In" /></td>
77 </tr>
78 </table>
79 <div id="hrAccountDiv" style="display:none"><hr /></div>
80 <div id="resetAccountDiv" style="display:none;padding:2px">
81 <span id="resetAccountSpan">Forgot user/password?</span> <a onclick=xgo(3) style=cursor:pointer>Reset account</a>.
82 </div>
83 <div id="newAccountDiv" style="display:none;padding:2px">
84 Don&#39;t have an account? <a onclick=xgo(2) style=cursor:pointer>Create one</a>.
85 </div>
86 <input id=loginformargs name="urlargs" type="hidden" value="" />
87 <div id="authStrategies" style="display:none">
88 <hr />
89 <div style="margin-bottom:8px">Log in using an existing account</div>
90 <a id="auth-twitter" href="auth-twitter" style="display:none"><img src="images/login/twitter32.png" 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>
91 <a id="auth-google" href="auth-google" style="display:none"><img src="images/login/google32.png" 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>
92 <a id="auth-github" href="auth-github" style="display:none"><img src="images/login/github32.png" 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>
93 <a id="auth-azure" href="auth-azure" style="display:none"><img src="images/login/azure32.png" 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>
94 <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>
95 <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>
96 <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>
97 <a id="auth-jumpcloud" href="auth-jumpcloud" style="display:none"><img src="images/login/jumpcloud32.png" 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>
98 <a id="auth-intel" href="auth-intel" style="display:none"><img src="images/login/intel32.png" 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>
99 <a id="auth-saml" href="auth-saml" style="display:none"><img src="images/login/generic32.png" 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>
100 </div>
101 </form>
102 </div>
103 <div id=createpanel style="display:none">
104 <div style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;position:relative">
105 <form method=post>
106 <input type=hidden name=action value=createaccount />
107 <div id=message2></div>
108 <div>
109 <b>Account Creation</b>
110 </div>
111 <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>
112 <table>
113 <tr id="nuUserRow">
114 <td align=right width=100>Username:</td>
115 <td><input id=ausername type=text autocomplete="username" name=username onchange=validateCreate(1) maxlength=64 onkeydown=haltReturn(event) onkeyup=validateCreate(1,event) /></td>
116 </tr>
117 <tr>
118 <td align=right width=100>Email:</td>
119 <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>
120 </tr>
121 <tr>
122 <td align=right>Password:</td>
123 <td><input id=apassword1 type=password autocomplete="current-password" name=password1 maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(3) onkeyup=validateCreate(3,event) /></td>
124 </tr>
125 <tr>
126 <td align=right>Password:</td>
127 <td><input id=apassword2 type=password autocomplete="current-password" name=password2 maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(4) onkeyup=validateCreate(4,event) /></td>
128 </tr>
129 <tr id="createPanelHint" style="display:none">
130 <td align=right>Pass Hint:</td>
131 <td><input id=apasswordhint type=text name=apasswordhint autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(5) onkeyup=validateCreate(5,event) /></td>
132 </tr>
133 <tr id=newAccountPass title="Enter the account creation token">
134 <td align=right>Creation Token:</td>
135 <td><input id=anewaccountpass type=password name=anewaccountpass autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(6) onkeyup=validateCreate(6,event) /></td>
136 </tr>
137 <tr id=newAccountCaptchaImg title="CAPTCHA image">
138 <td></td>
139 <td colspan=2><img src="{{{newAccountCaptchaImage}}}" /></td>
140 </tr>
141 <tr id=newAccountCaptcha title="Security check">
142 <td id="nuCaptcha" align=right>Security Check:</td>
143 <td><input id=anewaccountcaptcha type=text name=anewaccountcaptcha {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(7,event) onkeyup=validateCreate(7,event) /></td>
144 </tr>
145 <tr>
146 <td colspan=2>
147 <div style=float:right><input id=createButton type=submit value="Create Account" disabled="disabled" /></div>
148 <div id=passWarning style="padding-top:6px"></div>
149 </td>
150 </tr>
151 </table>
152 <hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
153 <input id=createformargs name="urlargs" type="hidden" value="" />
154 <input id=createformcaptcha name="captchaargs" type="hidden" value="{{{newAccountCaptcha}}}" />
155 </form>
156 </div>
157 </div>
158 <div id=resetpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
159 <form method=post>
160 <input type=hidden name=action value=resetaccount />
161 <div id=message3></div>
162 <div>
163 <b>Account Reset</b>
164 </div>
165 <table>
166 <tr>
167 <td align=right width=100>Email:</td>
168 <td><input id=remail type=text inputmode="email" autocomplete="email" name=email maxlength=256 onchange=validateReset() onkeyup=validateReset(event) /></td>
169 </tr>
170 <tr>
171 <td colspan=2>
172 <div style=float:right><input id=eresetButton type=submit value="Reset Account" disabled="disabled" /></div>
173 <div id=passWarning style="padding-top:6px"></div>
174 </td>
175 </tr>
176 </table>
177 <hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
178 <input id=resetformargs name="urlargs" type="hidden" value="" />
179 </form>
180 </div>
181 <div id=tokenpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
182 <form method=post autocomplete=off>
183 <input type=hidden name=action value=tokenlogin />
184 <input type=hidden name=hwstate value="{{{hwstate}}}" />
185 <div id=message4></div>
186 <table>
187 <tr>
188 <td align=right width=100>Login token:</td>
189 <td>
190 <input id=tokenInput type=text autocomplete="one-time-code" inputmode="numeric" name=token maxlength=50 onchange=checkToken(event) onkeyup=checkToken(event) onkeydown=checkToken(event) onfocus=checkTokenTimer(1) onblur=checkTokenTimer(0) />
191 <input id=hwtokenInput type=text name=hwtoken style="display:none" />
192 </td>
193 </tr>
194 <tr>
195 <td colspan="2" style="align-content:center">
196 <label id=tokenInputRememberLabel><input id=tokenInputRemember name=remembertoken type=checkbox /><span id=tokenInputRememberSpan></span></label>
197 </td>
198 </tr>
199 <tr>
200 <td colspan=2>
201 <div style=float:right><input id=tokenOkButton type=submit value="Login" disabled="disabled" /></div>
202 <div style=float:right>
203 <input style="display:none;float:right" id=securityKeyButton type=button value="Use Security Key" onclick="useSecurityKey()" />
204 <input style="display:none;float:right" id=emailKeyButton type=button value="Email" onclick="useEmailToken()" />
205 <input style="display:none;float:right" id=smsKeyButton type=button value="SMS" onclick="useSMSToken()" />
206 </div>
207 </td>
208 </tr>
209 </table>
210 <hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
211 <input id=tokenformargs name="urlargs" type="hidden" value="" />
212 </form>
213 </div>
214 <div id=resettokenpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
215 <form method=post autocomplete=off>
216 <input type=hidden name=action value=resetaccount />
217 <div id=message5></div>
218 <table>
219 <tr>
220 <td align=right width=100>Login token:</td>
221 <td>
222 <input id=resetTokenInput type=text name=token maxlength=50 onchange=resetCheckToken(event) onpaste=resetCheckToken(event) onkeyup=resetCheckToken(event) onkeydown=resetCheckToken(event) />
223 <input id=resetHwtokenInput type=text name=hwtoken style="display:none" />
224 </td>
225 </tr>
226 <tr>
227 <td colspan=2>
228 <div style=float:right><input id=resetTokenOkButton type=submit value="Login" disabled="disabled" /></div>
229 </td>
230 </tr>
231 </table>
232 <hr /><a onclick=xgo(1) 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="position:relative;background-color:#979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
237 <form method=post>
238 <input type=hidden name=action value=resetpassword />
239 <div id=message6></div>
240 <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>
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=xgo(1) 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="position:relative;background-color:#979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
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
291 </td>
292 </tr>
293 </table>
294 </div>
295 </div>
296 <div id=footer style="height:32px;width:100%;text-align:center;background-color:#113962;position:absolute;bottom:0px">
297 <table cellpadding=0 cellspacing=6 style=width:100%>
298 <tr>
299 <td style=text-align:left;color:white>{{{footer}}}</td>
300 <td style=text-align:right>{{{rootCertLink}}}&nbsp;<a href=terms>Terms &amp; Privacy</a></td>
301 </tr>
302 </table>
303 </div>
304 </div>
305 <div id=dialog style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:180px;width:300px;display:none">
306 <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
307 <div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
308 <div id=id_dialogtitle style=padding:5px></div>
309 <div style=width:100%;margin:6px></div>
310 </div>
311 <div style="margin-right:16px;margin-left:8px">
312 <div id=dialog1 style="margin:auto;text-align:center;margin:3px">
313 <div id=id_dialogMessage style="padding:10px"></div>
314 </div>
315 <div id=dialog2 style="margin:auto;margin:3px">
316 <div id=id_dialogOptions></div>
317 </div>
318 </div>
319 <div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:20px">
320 <input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
321 <input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
322 </div>
323 </div>
324 <script>
325 'use strict';
326 var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
327 var loginMode = '{{{loginmode}}}';
328 var newAccount = '{{{newAccount}}}';
329 var passhint = '{{{passhint}}}';
330 var newAccountPass = parseInt('{{{newAccountPass}}}');
331 var newAccountCaptcha = '{{{newAccountCaptcha}}}';
332 var emailCheck = '{{{emailcheck}}}';
333 var features = parseInt('{{{features}}}');
334 var passRequirements = '{{{passRequirements}}}';
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 hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
338 var publicKeyCredentialRequestOptions = null;
339 var currentpanel = 0;
340 var otpemail = ('{{{otpemail}}}' === 'true');
341 var otpsms = ('{{{otpsms}}}' === 'true');
342 var autofido = (decodeURIComponent('{{{autofido}}}') === 'true');
343 var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
344 var authStrategies = '{{{authStrategies}}}'.split(',');
345 var tokenTimeout = parseInt('{{{tokenTimeout}}}');
346
347 // Display the right server message
348 var messageid = parseInt('{{{messageid}}}');
349 var okmessages = ['', "If valid, reset mail sent.", "Email sent.", "Email verification required, check your mailbox and click the confirmation link.", "SMS sent."];
350 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."];
351 if (messageid > 0) {
352 var msg = '';
353 if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
354 else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
355 if (msg != '') {
356 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 />'); }
357 for (var i = 1; i < 8; i++) { QH('message' + i, msg); }
358 }
359 }
360
361 // Display flash error Messages
362 var flashErrors = JSON.parse('{{{flashErrors}}}');
363 if (flashErrors && (flashErrors.length > 0)) {
364 var msg = '';
365 for (i = 0; i < flashErrors.length; i++) {
366 if (flashErrors[i]) {
367 msg += '<span class="msg error"><b style=color:#8C001A>' + flashErrors[i] + '<b></span><br /><br />';
368 }
369 }
370 QH('message1', msg);
371 QV('message1', true);
372 }
373
374 // If URL arguments are provided, add them to form posts
375 if (window.location.href.indexOf('?') > 0) {
376 var urlargs = window.location.href.substring(window.location.href.indexOf('?'));
377 Q('loginformargs').value = urlargs;
378 Q('createformargs').value = urlargs;
379 Q('resetformargs').value = urlargs;
380 Q('tokenformargs').value = urlargs;
381 Q('resettokenformargs').value = urlargs;
382 Q('resetpasswordformargs').value = urlargs;
383 }
384
385 // Setup two factor cookie time
386 if (twoFactorCookieDays > 0) {
387 QV('tokenInputRememberLabel', true);
388 QH('tokenInputRememberSpan', format("Remember this device for {0} days.", twoFactorCookieDays));
389 } else {
390 QV('tokenInputRememberLabel', false);
391 }
392
393 function startup() {
394 if ((features & 32) == 0) {
395 // Guard against other site's top frames (web bugs).
396 var loc = null;
397 try { loc = top.location.toString().toLowerCase(); } catch (e) { }
398 if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
399 }
400
401 if (features & 0x200000) { // Email is username
402 QH('loginusername', "Email:");
403 QH('resetAccountSpan', "Forgot password?");
404 QV('nuUserRow', false);
405 }
406
407 QV('createPanelHint', passRequirements.hint === true);
408 QV('resetpasswordpanelHint', passRequirements.hint === true);
409
410 // Setup authentication strategies
411 if (authStrategies != '') {
412 QV('authStrategies', true);
413 if (authStrategies.indexOf('twitter') >= 0) { QV('auth-twitter', true); }
414 if (authStrategies.indexOf('google') >= 0) { QV('auth-google', true); }
415 if (authStrategies.indexOf('github') >= 0) { QV('auth-github', true); }
416 if (authStrategies.indexOf('azure') >= 0) { QV('auth-azure', true); }
417 if (authStrategies.indexOf('oidc') >= 0) { QV('auth-oidc', true); }
418 if (authStrategies.indexOf('oidc-azure') >= 0) { QV('auth-oidc-azure', true); }
419 if (authStrategies.indexOf('oidc-google') >= 0) { QV('auth-oidc-google', true); }
420 if (authStrategies.indexOf('jumpcloud') >= 0) { QV('auth-jumpcloud', true); }
421 if (authStrategies.indexOf('intel') >= 0) { QV('auth-intel', true); }
422 if (authStrategies.indexOf('saml') >= 0) { QV('auth-saml', true); }
423 // Override OIDC button title if custom text is configured by admin
424 var oidcButtonText = '{{{oidcButtonText}}}';
425 if (oidcButtonText != '') {
426 Q('auth-oidc').title = oidcButtonText;
427 Q('auth-oidc-azure').title = oidcButtonText;
428 Q('auth-oidc-google').title = oidcButtonText;
429 }
430 }
431
432 window.onresize = center;
433 center();
434 validateCreate();
435 if (loginMode.length != 0) { go(parseInt(loginMode)); } else { go(1); }
436 QV('newAccountDiv', (newAccount === '1') || (newAccount === 'true')); // If new accounts are not allowed, don't display the new account link.
437 if ((passRequirements.hint === true) && (passhint != null) && (passhint.length > 0)) { QV('showPassHintLink', true); }
438 QV('newAccountPass', (newAccountPass == 1));
439 QV('newAccountCaptcha', (newAccountCaptcha != ''));
440 QV('newAccountCaptchaImg', (newAccountCaptcha != ''));
441 QV('resetAccountDiv', (emailCheck == 'true'));
442 QV('hrAccountDiv', (emailCheck == 'true') || (newAccountPass == 1));
443
444 if (loginMode == '4') {
445 if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
446 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
447 var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
448 QV('securityKeyButton', twofakey);
449 QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4));
450 QV('smsKeyButton', otpsms && (messageid != 2) && (messageid != 4));
451
452 // If hardware key is an option, trigger it now
453 if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
454 }
455
456 if (loginMode == '5') {
457 if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
458 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
459 var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
460 if (twofakey) {
461 if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
462
463 publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
464 for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
465 publicKeyCredentialRequestOptions.allowCredentials.push(
466 { id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'], }
467 );
468 }
469
470 // New WebAuthn hardware keys
471 navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
472 function (rawAssertion) {
473 var assertion = {
474 id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
475 clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
476 userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
477 signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
478 authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
479 };
480 Q('resetHwtokenInput').value = JSON.stringify(assertion);
481 QE('resetTokenOkButton', true);
482 Q('resetTokenOkButton').click();
483 },
484 function (error) { console.log('credentials-get error', error); }
485 );
486 }
487 }
488 }
489
490 // Use a hardware security key
491 function useSecurityKey() {
492 if (xxdialogMode) return;
493 if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
494 if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
495
496 publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
497 for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
498 publicKeyCredentialRequestOptions.allowCredentials.push(
499 { id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'], }
500 );
501 }
502
503 // New WebAuthn hardware keys
504 navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
505 function (rawAssertion) {
506 var assertion = {
507 id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
508 clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
509 userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
510 signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
511 authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
512 };
513 Q('hwtokenInput').value = JSON.stringify(assertion);
514 QE('tokenOkButton', true);
515 Q('tokenOkButton').click();
516 },
517 function (error) { console.log('credentials-get error', error); }
518 );
519 }
520 }
521
522 function useEmailToken() {
523 if (xxdialogMode) return;
524 if (otpemail != true) return;
525 setDialogMode(1, "Secure Login", 3, useEmailKeyEx, "Send token to registed email address?");
526 }
527
528 function useEmailKeyEx() {
529 Q('hwtokenInput').value = '**email**';
530 QE('tokenOkButton', true);
531 Q('tokenOkButton').click();
532 }
533
534 function useSMSToken() {
535 if (otpsms != true) return;
536 setDialogMode(1, "Secure Login", 3, useSMSTokenEx, "Send token to registed phone number?");
537 }
538
539 function useSMSTokenEx() {
540 Q('hwtokenInput').value = '**sms**';
541 QE('tokenOkButton', true);
542 Q('tokenOkButton').click();
543 }
544
545 function showPassHint() {
546 if (passRequirements.hint === true) { messagebox("Password Hint", passhint); }
547 }
548
549 function xgo(x) {
550 QV('message1', false);
551 QV('message2', false);
552 QV('message3', false);
553 QV('message4', false);
554 QV('message5', false);
555 QV('message6', false);
556 QV('message7', false);
557 go(x);
558 }
559
560 function go(x) {
561 currentpanel = x;
562 setDialogMode(0);
563 QV('showPassHintLink', false);
564 QV('loginpanel', x == 1);
565 QV('createpanel', x == 2);
566 QV('resetpanel', x == 3);
567 QV('tokenpanel', x == 4);
568 QV('resettokenpanel', x == 5);
569 QV('resetpasswordpanel', x == 6);
570 QV('checkemailpanel', x == 7);
571 if (x == 1) { Q('username').focus(); }
572 if (x == 2) { if (features & 0x200000) { Q('aemail').focus(); } else { Q('ausername').focus(); } } // Email is username
573 if (x == 3) { Q('remail').focus(); }
574 if (x == 4) { Q('tokenInput').focus(); }
575 if (x == 5) { Q('resetTokenInput').focus(); }
576 if (x == 6) { Q('rapassword1').focus(); }
577 if (x == 7) {
578 QH('unconfirmedEmail', passhint);
579 QV('checkCheckOperations', Q('unconfirmedEmail').innerHTML != '');
580 QH('checkEmailVal', passhint);
581 }
582 }
583
584 function validateLogin(box, e) {
585 setTimeout(function(){
586 setDialogMode(0);
587 if ((e != null) && (e.keyCode == 13)) { if (box == 1) { Q('password').focus(); } else if (box == 2) { Q('loginButton').click(); } }
588 if (e != null) { haltEvent(e); }
589 }, 100);
590 }
591
592
593 function validateCreate(box,e) {
594 setDialogMode(0);
595 var ok = false;
596 if (features & 0x200000) { ok = true; } else { ok = (Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (Q('ausername').value.indexOf('"') == -1) && (Q('ausername').value.indexOf(',') == -1); }
597 ok &= ((validateEmail(Q('aemail').value) == true) && (Q('apassword1').value.length > 0) && (Q('apassword2').value == Q('apassword1').value));
598 if ((newAccountPass == 1) && (Q('anewaccountpass').value.length == 0)) { ok = false; }
599 if (Q('apassword1').value == '') {
600 QH('passWarning', '');
601 QV('passwordPolicyCallout', false);
602 } else {
603 if (!passRequirementsEx) {
604 // No password requirements, display password strength
605 var passStrength = checkPasswordStrength(Q('apassword1').value);
606 if (passStrength >= 80) { QH('passWarning', '<span style=color:green><b>' + "Strong Password" + '</b><span>'); }
607 else if (passStrength >= 60) { QH('passWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
608 else { QH('passWarning', '<span style=color:red><b>' + "Weak Password" + '</b><span>'); }
609 } else {
610 // Password requirements provided, use that
611 var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
612 if (passReq == false) {
613 ok = false;
614 //QS('nuPass1').color = '#7b241c';
615 //QS('nuPass2').color = '#7b241c';
616 QH('passWarning', '<span style=color:red><b>' + "Password Policy" + '</b><span>'); // TODO: Display problem hint
617 QV('passwordPolicyCallout', true);
618 QH('passwordPolicyCallout', passwordPolicyText(Q('apassword1').value));
619 } else {
620 QH('passWarning', '');
621 QV('passwordPolicyCallout', false);
622 }
623 }
624 }
625 QE('createButton', ok);
626 if ((e != null) && (e.keyCode == 13)) {
627 if (box == 1) { Q('aemail').focus(); }
628 if (box == 2) { Q('apassword1').focus(); }
629 if (box == 3) { Q('apassword2').focus(); }
630 if (box == 4) { Q('apasswordhint').focus(); }
631 if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { box = 6; } }
632 if (box == 6) { if (newAccountCaptcha != '') { Q('anewaccountcaptcha').focus(); } else { box = 7; } }
633 if (box == 7) { Q('createButton').click(); }
634 }
635 if (e != null) { haltEvent(e); }
636 }
637
638 function validatePassReset(box, e) {
639 setDialogMode(0);
640 var pass1ok = (Q('rapassword1').value.length > 0);
641 var pass2ok = (Q('rapassword2').value.length > 0) && (Q('rapassword2').value == Q('rapassword1').value);
642 var ok = (pass1ok && pass2ok);
643
644 // Color the fields
645 QS('rnuPass1').color = pass1ok ? 'black' : '#7b241c';
646 QS('rnuPass2').color = pass2ok ? 'black' : '#7b241c';
647
648 if (Q('rapassword1').value == '') {
649 QH('rpassWarning', '');
650 QV('rpasswordPolicyCallout', false);
651 } else {
652 if (!passRequirementsEx) {
653 // No password requirements, display password strength
654 var passStrength = checkPasswordStrength(Q('rapassword1').value);
655 if (passStrength >= 80) { QH('rpassWarning', '<span style=color:green><b>' + "Strong Password" + '</b><span>'); }
656 else if (passStrength >= 60) { QH('rpassWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
657 else { QH('rpassWarning', '<span style=color:red><b>' + "Weak Password" + '</b><span>'); }
658 } else {
659 // Password requirements provided, use that
660 var passReq = checkPasswordRequirements(Q('rapassword1').value, passRequirements);
661 if (passReq == false) {
662 ok = false;
663 QS('rnuPass1').color = '#7b241c';
664 QS('rnuPass2').color = '#7b241c';
665 QH('rpassWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
666 QV('rpasswordPolicyCallout', true);
667 QH('rpasswordPolicyCallout', passwordPolicyText(Q('rapassword1').value));
668 } else {
669 QH('rpassWarning', '');
670 QV('rpasswordPolicyCallout', false);
671 }
672 }
673 }
674 if ((e != null) && (e.keyCode == 13)) {
675 if (box == 2) { Q('rapassword1').focus(); }
676 if (box == 3) { Q('rapassword2').focus(); }
677 if (box == 4) { Q('rapasswordhint').focus(); }
678 if (box == 6) { Q('resetPassButton').click(); }
679 }
680 if (e != null) { haltEvent(e); }
681 QE('resetPassButton', ok);
682 }
683
684 function validateReset(e) {
685 setDialogMode(0);
686 var x = validateEmail(Q('remail').value);
687 QE('eresetButton', x);
688 if ((e != null) && (e.keyCode == 13) && (x == true)) { Q('eresetButton').click(); }
689 if (e != null) { haltEvent(e); }
690 }
691
692 function passwordPolicyText(pass) {
693 var policy = '<div style=text-align:left>';
694 var counts = strCount(pass);
695 if (passRequirements.min && ((pass == null) || (pass.length < passRequirements.min))) { policy += format("Minimum length of {0}", passRequirements.min) + '<br />'; }
696 if (passRequirements.max && ((pass == null) || (pass.length > passRequirements.max))) { policy += format("Maximum length of {0}", passRequirements.max) + '<br />'; }
697 if (passRequirements.upper && ((pass == null) || (counts.upper < passRequirements.upper))) { policy += format("{0} upper case", passRequirements.upper) + '<br />'; }
698 if (passRequirements.lower && ((pass == null) || (counts.lower < passRequirements.lower))) { policy += format("{0} lower case", passRequirements.lower) + '<br />'; }
699 if (passRequirements.numeric && ((pass == null) || (counts.numeric < passRequirements.numeric))) { policy += format("{0} numeric", passRequirements.numeric) + '<br />'; }
700 if (passRequirements.nonalpha && ((pass == null) || (counts.nonalpha < passRequirements.nonalpha))) { policy += format("{0} non-alphanumeric", passRequirements.nonalpha) + '<br />'; }
701 policy += '</div>';
702 return policy;
703 }
704
705 // Return a password strength score
706 function checkPasswordStrength(password) {
707 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) }
708 if (!password) return 0;
709 for (var i = 0; i< password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
710 for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
711 return parseInt(r + (varCount - 1) * 10);
712 }
713
714 // Check password requirements
715 function checkPasswordRequirements(password, requirements) {
716 if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
717 if (requirements.min) { if (password.length < requirements.min) return false; }
718 if (requirements.max) { if (password.length > requirements.max) return false; }
719 var counts = strCount(password);
720 if (requirements.numeric && (counts.numeric < requirements.numeric)) return false;
721 if (requirements.lower && (counts.lower < requirements.lower)) return false;
722 if (requirements.upper && (counts.upper < requirements.upper)) return false;
723 if (requirements.nonalpha && (counts.nonalpha < requirements.nonalpha)) return false;
724 return true;
725 }
726
727 function strCount(password) {
728 var counts = { numeric: 0, lower: 0, upper: 0, nonalpha: 0 };
729 if (typeof password != 'string') return counts;
730 for (var i = 0; i < password.length; i++) {
731 if (/\d/.test(password[i])) { counts.numeric++; }
732 if (/[a-z]/.test(password[i])) { counts.lower++; }
733 if (/[A-Z]/.test(password[i])) { counts.upper++; }
734 if (/\W/.test(password[i])) { counts.nonalpha++; }
735 }
736 return counts;
737 }
738
739 var xcheckTokenTimer = null;
740 function checkTokenTimer(enter) {
741 if ((enter == 0) && (xcheckTokenTimer != null)) { clearInterval(xcheckTokenTimer); xcheckTokenTimer = null; }
742 if ((enter == 1) && (xcheckTokenTimer == null)) { xcheckTokenTimer = setInterval(checkToken, 200); }
743 }
744
745 function checkToken() {
746 var t1 = Q('tokenInput').value, t2 = t1.split(' ').join('');
747 if (t1 != t2) { Q('tokenInput').value = t2; }
748 QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8) || (Q('tokenInput').value.length == 44));
749 }
750
751 function resetCheckToken() {
752 var t1 = Q('resetTokenInput').value, t2 = t1.split(' ').join('');
753 if (t1 != t2) { Q('resetTokenInput').value = t2; }
754 QE('resetTokenOkButton', (Q('resetTokenInput').value.length == 6) || (Q('resetTokenInput').value.length == 8) || (Q('resetTokenInput').value.length == 44));
755 }
756
757 function changeEmailAddress() {
758 var email = Q('unconfirmedEmail').innerHTML;
759 var x = addHtmlValue("Email", '<input id=dp1email style=width:230px maxlength=256 value="' + email + '" autocomplete=off onchange=validateEmailAddress() onkeyup=validateEmailAddress() />');
760 setDialogMode(1, "Email Confirmation", 3, changeEmailAddressEx, x);
761 validateEmailAddress();
762 }
763
764 function validateEmailAddress() {
765 QE('idx_dlgOkButton', (validateEmail(Q('dp1email').value) == true));
766 }
767
768 function changeEmailAddressEx() {
769 Q('checkEmailVal').value = Q('dp1email').value;
770 QH('unconfirmedEmail', Q('dp1email').value);
771 Q('checkEmailButton').click();
772 }
773
774 function resentEmailConfirmation() {
775 Q('checkEmailVal').value = Q('unconfirmedEmail').innerHTML;
776 Q('checkEmailButton').click();
777 }
778
779 //
780 // POPUP DIALOG
781 //
782
783 // undefined = Hidden, 1 = Generic Message
784 var xxdialogMode;
785 var xxdialogFunc;
786 var xxdialogButtons;
787 var xxdialogTag;
788 var xxcurrentView = 0;
789
790 // Display a dialog box
791 // 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)
792 function setDialogMode(x, y, b, f, c, tag) {
793 xxdialogMode = x;
794 xxdialogFunc = f;
795 xxdialogButtons = b;
796 xxdialogTag = tag;
797 QE('idx_dlgOkButton', true);
798 QV('idx_dlgOkButton', b & 1);
799 QV('idx_dlgCancelButton', b & 2);
800 QV('id_dialogclose', (b & 2) || (b & 8));
801 QV('idx_dlgButtonBar', b & 7);
802 if (y) QH('id_dialogtitle', y);
803 for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
804 QV('dialog', x);
805 if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
806 }
807
808 function dialogclose(x) {
809 var f = xxdialogFunc;
810 var b = xxdialogButtons;
811 var t = xxdialogTag;
812 setDialogMode();
813 if (((b & 8) || x) && f) f(x, t);
814 }
815
816 function center() { QS('dialog').left = ((((getDocWidth() - 300) / 2)) + 'px'); }
817 function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
818 function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
819 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; }
820 function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
821 function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
822 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
823 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; }); };
824 function addHtmlValue(t, v) { return '<table><td style=text-align:left>' + t + '<td><b>' + v + '</b></table>'; }
825
826 </script>
827 </body>
828 </html>