master
handlebars 1,069 lines 73.1 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 <script keeplink=1 type="text/javascript" src="scripts/u2f-api{{min}}.js"></script>
17 {{{customJSTags}}}
18 <title>{{{title}}} - Login</title>
19 <style>
20 #body {
21 background-color: cadetblue;
22 background: linear-gradient(to bottom right, #369, #036);
23 }
24
25 #flink a:link {
26 color: #c8c8c8;
27 }
28
29 #flink a:visited {
30 color: #c8c8c8;
31 }
32
33 #flink a:hover {
34 color: #c8c8c8;
35 }
36
37 #flink a:active {
38 color: #c8c8c8;
39 }
40
41 #welcomeText a:link {
42 color: #FFF;
43 }
44
45 #welcomeText a:visited {
46 color: #FFF;
47 }
48
49 #welcomeText a:hover {
50 color: #FFF;
51 }
52
53 #welcomeText a:active {
54 color: #FFF;
55 }
56 </style>
57 </head>
58 <body id="body" onload="if (typeof(startup) !== 'undefined') startup();" class="arg_hide login">
59 <div id="backgroundImage" style="position:absolute;left:0;bottom:0;z-index:-1;height:60%;width:100%;opacity:0.1;background-image:url('welcome.png');background-repeat:no-repeat;background-position:left bottom;background-size:contain"></div>
60 <table id="centralTable" class="container" style="height:100%;z-index:1">
61 <tr>
62 <td id="logincell">
63 {{{titlehtml}}}
64 <img id="loginPicture" />
65 <div style="font-size:46px;font-family:Arial,Helvetica,sans-serif;font-weight:bold;padding-bottom:10px;color:#c8c8c8;text-shadow: 2px 2px 2px #000;">{{{title1}}}<sup style="font-size:20px"> {{{title2}}}</sup></div>
66 <div id=loginpanel style="display:none;box-shadow:1px 1px 4px #000">
67 <form id=loginpanelform method=post>
68 <input type=hidden name=action value=login />
69 <div id=message1></div>
70 <div id="loginuserpassdiv" style="display:none">
71 <table style="width:100%">
72 <tr>
73 <td>
74 <input id=username title="Username" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC" {{{autocomplete}}}="username" placeholder="Username" type=text maxlength=64 name=username required onchange=validateLogin(1) onkeyup=validateLogin(1,event) />
75 </td>
76 </tr>
77 <tr>
78 <td>
79 <input id=password title="Password" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC" {{{autocomplete}}}="current-password" placeholder="Password" type=password maxlength=256 name=password required onchange=validateLogin(2) onkeyup=validateLogin(2,event) />
80 </td>
81 </tr>
82 <tr>
83 <td>
84 <div id=showPassHintLink title="Password Hint" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC;display:none"><a onclick="return showPassHint(event);" href="#" style="cursor:pointer">Show Hint</a></div>
85 </td>
86 </tr>
87 <tr>
88 <td>
89 <input id=loginButton style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:6px" onclick="submitButtonClicked('loginpanelform')" type=button value="Log In" />
90 </td>
91 </tr>
92 <tr id="topLanguageSelectRow" style="display:none"><td id="topLanguageSelect"></td></tr>
93 </table>
94 <div id="hrAccountDiv" style="display:none"><hr /></div>
95 <div id="resetAccountDiv" style="display:none;padding:2px">
96 <span id="resetAccountSpan">Forgot username/password?</span> <a onclick="return xgo(3,event);" href="#" style=cursor:pointer>Reset account</a>.
97 </div>
98 <div id="newAccountDiv" style="display:none;padding:2px;padding-top:10px">
99 Don&#39;t have an account? <a onclick="return xgo(2,event);" href="#" style=cursor:pointer>Create one</a>.
100 </div>
101 </div>
102 <input id=loginformargs name="urlargs" type="hidden" value="" />
103 <div id="authStrategies" style="display:none">
104 <hr id="loginuserpasshr" style="display:none" />
105 <div style="margin-bottom:8px">Log in using an existing account</div>
106 <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>
107 <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>
108 <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>
109 <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>
110 <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>
111 <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>
112 <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>
113 <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>
114 <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>
115 <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>
116 </div>
117 </form>
118 </div>
119 <div id=createpanel style="display:none;position:relative">
120 <form id=createpanelform method=post>
121 <input type=hidden name=action value=createaccount />
122 <div id=message2></div>
123 <div>
124 <b>Account Creation</b>
125 </div>
126 <div id="passwordPolicyCallout" style="display:none"></div>
127 <table>
128 <tr id="nuUserRow">
129 <td id="nuUser" align=right width=100>Username:</td>
130 <td><input id=ausername type=text {{{autocomplete}}}="username" name=username onchange=validateCreate(1) maxlength=64 onkeydown=haltReturn(event) onkeyup=validateCreate(1,event) /></td>
131 </tr>
132 <tr>
133 <td id="nuEmail" align=right width=100>Email:</td>
134 <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>
135 </tr>
136 <tr>
137 <td id="nuPass1" align=right>Password:</td>
138 <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>
139 </tr>
140 <tr>
141 <td id="nuPass2" align=right>Password:</td>
142 <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>
143 </tr>
144 <tr id="createPanelHint" style="display:none">
145 <td id="nuHint" align=right>Password Hint:</td>
146 <td><input id=apasswordhint type=text name=apasswordhint {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(5,event) onkeyup=validateCreate(5,event) /></td>
147 </tr>
148 <tr id=newAccountPass title="Enter the account creation token">
149 <td id="nuToken" align=right>Creation Token:</td>
150 <td><input id=anewaccountpass type=password name=anewaccountpass {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(6,event) onkeyup=validateCreate(6,event) /></td>
151 </tr>
152 <tr id=newAccountCaptchaImg title="CAPTCHA image">
153 <td></td>
154 <td colspan=2><img src="{{{newAccountCaptchaImage}}}" /></td>
155 </tr>
156 <tr id=newAccountCaptcha title="Security check">
157 <td id="nuCaptcha" align=right>Security Check:</td>
158 <td><input id=anewaccountcaptcha type=text name=anewaccountcaptcha {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(7,event) onkeyup=validateCreate(7,event) /></td>
159 </tr>
160 <tr>
161 <td colspan=2>
162 <div style=float:right><input id=createButton type="button" onclick="submitButtonClicked('createpanelform')" value="Create Account" disabled="disabled" /></div>
163 <div id=passWarning style="padding-top:6px"></div>
164 </td>
165 </tr>
166 </table>
167 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
168 <input id=createformargs name="urlargs" type="hidden" value="" />
169 <input id=createformcaptcha name="captchaargs" type="hidden" value="{{{newAccountCaptcha}}}" />
170 </form>
171 </div>
172 <div id=resetpanel style="display:none">
173 <form id=resetpanelform method=post>
174 <input type=hidden name=action value=resetaccount />
175 <div id=message3></div>
176 <div>
177 <b>Account Reset</b>
178 </div>
179 <table style="width:100%;margin-top:4px;margin-bottom:4px">
180 <tr>
181 <td>
182 <input id=remail title="Email" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC" {{{autocomplete}}}="username" placeholder="Email" type=text maxlength=256 name=email onchange=validateReset() onkeyup=validateReset(event) />
183 </td>
184 </tr>
185 <tr>
186 <td>
187 <input id=eresetButton style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:6px" type="button" onclick="submitButtonClicked('resetpanelform')" value="Reset Account" disabled="disabled" />
188 </td>
189 </tr>
190 </table>
191 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
192 <input id=resetformargs name="urlargs" type="hidden" value="" />
193 </form>
194 </div>
195 <div id=tokenpanel style="display:none">
196 <form id=tokenpanelform method=post {{{autocomplete}}}=off>
197 <input type=hidden name=action value=tokenlogin />
198 <input type=hidden name=hwstate value="{{{hwstate}}}" />
199 <div id=message4></div>
200 <table style="width:100%">
201 <tr>
202 <td>
203 <input id=tokenInput {{{autocomplete}}}="one-time-code" title="Token" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC" placeholder="Token" type=text maxlength=50 name=token onchange=checkToken(event) onpaste=checkToken(event) onkeyup=checkToken(event) onkeydown=checkToken(event) /><br />
204 <input id=hwtokenInput type=text name=hwtoken style="display:none" />
205 </td>
206 </tr>
207 <tr>
208 <td>
209 <div>
210 <input id=tokenOkButton style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:6px" type="button" onclick="submitButtonClicked('tokenpanelform')" value="Log In" disabled="disabled" />
211 </div>
212 </td>
213 </tr>
214 <tr>
215 <td style="align-content:center;padding-top:10px">
216 <label id=tokenInputRememberLabel><input id=tokenInputRemember name=remembertoken type=checkbox /><span id=tokenInputRememberSpan></span></label>
217 </td>
218 </tr>
219 <tr id="2farow">
220 <td>
221 <hr />
222 <div>
223 <img id=securityKeyButton src="images/login/2fa-key-48.png" srcset="images/login/2fa-key-96.png 2x" title="Use Security Key" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useSecurityKey(1)" />
224 <img id=smsKeyButton src="images/login/2fa-sms-48.png" srcset="images/login/2fa-sms-96.png 2x" title="SMS" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useSMSToken(1)" />
225 <img id=msgKeyButton src="images/login/2fa-messaging-48.png" srcset="images/login/2fa-messaging-96.png 2x" title="Messaging" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(1)" />
226 <img id=emailKeyButton src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(1)" />
227 <img id=pushKeyButton src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(1)" />
228 <img id=duoKeyButton src="images/login/2fa-duo-48.png" srcset="images/login/2fa-duo-96.png 2x" title="Duo Authentication" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useDuoToken(1)" />
229 </div>
230 </td>
231 </tr>
232 </table>
233 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
234 <input id=tokenformargs name="urlargs" type="hidden" value="" />
235 </form>
236 </div>
237 <div id=resettokenpanel style="display:none">
238 <form id=resettokenpanelform method=post>
239 <input type=hidden name=action value=resetaccount />
240 <div id=message5></div>
241 <table style="width:100%">
242 <tr>
243 <td>
244 <input id=resetTokenInput {{{autocomplete}}}="one-time-code" title="Token" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC" placeholder="Token" type=text maxlength=50 name=token onchange=resetCheckToken(event) onpaste=resetCheckToken(event) onkeyup=resetCheckToken(event) onkeydown=resetCheckToken(event) /><br />
245 <input id=resetHwtokenInput type=text name=hwtoken style="display:none" />
246 </td>
247 </tr>
248 <tr>
249 <td>
250 <div>
251 <input id=resetTokenOkButton style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:6px" type="button" onclick="submitButtonClicked('resettokenpanelform')" value="Log In" disabled="disabled" />
252 </div>
253 </td>
254 </tr>
255 <tr id="2farow2">
256 <td colspan=2>
257 <hr />
258 <div>
259 <img id=securityKeyButton2 src="images/login/2fa-key-48.png" srcset="images/login/2fa-key-96.png 2x" title="Use Security Key" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useSecurityKey(2)" />
260 <img id=smsKeyButton2 src="images/login/2fa-sms-48.png" srcset="images/login/2fa-sms-96.png 2x" title="SMS" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useSMSToken(2)" />
261 <img id=msgKeyButton2 src="images/login/2fa-messaging-48.png" srcset="images/login/2fa-messaging-96.png 2x" title="Messaging" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(2)" />
262 <img id=emailKeyButton2 src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(2)" />
263 <img id=pushKeyButton2 src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(2)" />
264 <img id=duoKeyButton2 src="images/login/2fa-duo-48.png" srcset="images/login/2fa-duo-96.png 2x" title="Duo Authentication" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useDuoToken(2)" />
265 </div>
266 </td>
267 </tr>
268 </table>
269 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
270 <input id=resettokenformargs name="urlargs" type="hidden" value="" />
271 </form>
272 </div>
273 <div id=resetpasswordpanel style="display:none;position:relative">
274 <form id=resetpasswordpanelform method=post>
275 <input type=hidden name=action value=resetpassword />
276 <div id=message6></div>
277 <div id="rpasswordPolicyCallout" style="display:none"></div>
278 <table>
279 <tr>
280 <td id="rnuPass1" width=100 align=right>Password:</td>
281 <td><input id=rapassword1 type=password name=rpassword1 {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(3,event) onkeyup=validatePassReset(3,event) /></td>
282 </tr>
283 <tr>
284 <td id="rnuPass2" align=right>Password:</td>
285 <td><input id=rapassword2 type=password name=rpassword2 {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(4,event) onkeyup=validatePassReset(4,event) /></td>
286 </tr>
287 <tr id="resetpasswordpanelHint" style="display:none">
288 <td id="rnuHint" align=right>Password Hint:</td>
289 <td><input id=rapasswordhint type=text name=rpasswordhint {{{autocomplete}}}=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(5,event) onkeyup=validatePassReset(5,event) /></td>
290 </tr>
291 <tr>
292 <td colspan=2>
293 <div style=float:right><input id=resetPassButton type="button" onclick="submitButtonClicked('resetpasswordpanelform')" value="Reset Password" disabled="disabled" /></div>
294 <div id=rpassWarning style="padding-top:6px"></div>
295 </td>
296 </tr>
297 </table>
298 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
299 <input id=resetpasswordformargs name="urlargs" type="hidden" value="" />
300 </form>
301 </div>
302 <div id=checkemailpanel style="display:none;position:relative">
303 <form id=checkemailpanelform method=post>
304 <input type=hidden name=action value=checkemail />
305 <div id=message7></div>
306 <table id="checkCheckOperations" style="width:100%;display:none">
307 <tr>
308 <td>
309 <div id="unconfirmedEmail"></div><br />
310 </td>
311 </tr>
312 <tr>
313 <td>
314 <div>
315 <input id=changeEmailButton type=button value="Change Email Address" onclick="changeEmailAddress()" />
316 <input id=checkEmailButton2 type=button value="Resend Confirmation Email" onclick="resentEmailConfirmation()" />
317 <input id=checkEmailButton type="button" onclick="submitButtonClicked('checkemailpanelform')" style="display:none" />
318 <input id=checkEmailVal name=email type="hidden" value="" />
319 </div>
320 </td>
321 </tr>
322 </table>
323 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
324 <input id=checkemailformargs name="urlargs" type="hidden" value="" />
325 </form>
326 </div>
327 <div id=waitpushpanel style="display:none">
328 <form id=waitpushpanelform method=post>
329 <input type=hidden name=action value=pushlogin />
330 <div id=message8></div>
331 <table style="width:100%">
332 <tr>
333 <td style="align-content:center;padding-top:10px">
334 <img id="waitpushpanelimage" src="images/login/push-150.png" srcset="images/login/push-300.png 2x" style="opacity:0.3" width="265" height="150" />
335 </td>
336 </tr>
337 <tr>
338 <td style="align-content:center;padding-top:10px">
339 <label id=tokenInputRememberLabel2><input id=tokenInputRemember2 name=remembertoken type=checkbox /><span id=tokenInputRememberSpan2></span></label>
340 </td>
341 </tr>
342 </table>
343 <hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
344 <input id=pushtokenformargs name="urlargs" type="hidden" value="" />
345 <input id=pushtokenInput name="hwstate" type="hidden" value="" />
346 <input id=pushOkButton onclick="submitButtonClicked('waitpushpanelform')" style="display:none" />
347 </form>
348 </div>
349 </td>
350 </tr>
351 <tr id="welcomeTextRow"><td><div id="welcomeText" style="color:white;text-align:center;margin-left:20px;margin-right:20px"></div></td></tr>
352 <tr id="bottomLanguageSelectRow" style="display:none;text-align:center"><td id="bottomLanguageSelect"></td></tr>
353 <tr style="height:20px">
354 <td>
355 <div>
356 <div id="flink" style="float:right;margin-right:4px;color:#c8c8c8">{{{rootCertLink}}}&nbsp;<a href=terms>Terms &amp; Privacy</a></div>
357 <div id="flink" style="margin-left:4px;color:#c8c8c8">{{{footer}}}</div>
358 </div>
359 </td>
360 </tr>
361 </table>
362 <div id=dialog style="display:none">
363 <div id=dialogHeader>
364 <div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
365 <div id=id_dialogtitle style=padding:5px></div>
366 <div style=width:100%;margin:6px></div>
367 </div>
368 <div id=dialogBody>
369 <div id=dialog1>
370 <div id=id_dialogMessage style=""></div>
371 </div>
372 <div id=dialog2 style="">
373 <div id=id_dialogOptions></div>
374 </div>
375 </div>
376 <div id="idx_dlgButtonBar" style="">
377 <input id="idx_dlgCancelButton" type="button" value="Cancel" style="" onclick="dialogclose(0)">
378 <input id="idx_dlgOkButton" type="button" value="OK" style="" onclick="dialogclose(1)">
379 </div>
380 </div>
381 <script>
382 'use strict';
383 var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
384 var welcomePictureFullScreen = (decodeURIComponent('{{{welcomePictureFullScreen}}}') === 'true');
385 var passlogin = '{{{passlogin}}}';
386 var passhint = '{{{passhint}}}';
387 var loginMode = '{{{loginmode}}}';
388 var newAccount = '{{{newAccount}}}';
389 var newAccountPass = parseInt('{{{newAccountPass}}}');
390 var newAccountCaptcha = '{{{newAccountCaptcha}}}';
391 var emailCheck = '{{{emailcheck}}}';
392 var passRequirements = '{{{passRequirements}}}';
393 var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
394 if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } else { passRequirements = {}; }
395 var passRequirementsEx = ((passRequirements.min != null) || (passRequirements.max != null) || (passRequirements.upper != null) || (passRequirements.lower != null) || (passRequirements.numeric != null) || (passRequirements.nonalpha != null));
396 var features = parseInt('{{{features}}}');
397 var welcomeText = decodeURIComponent('{{{welcometext}}}');
398 var currentpanel = 0;
399 var publicKeyCredentialRequestOptions = null;
400 var otpduo = (decodeURIComponent('{{{otpduo}}}') === 'true');
401 var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true');
402 var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
403 var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true');
404 var otppush = (decodeURIComponent('{{{otppush}}}') === 'true');
405 var autofido = (decodeURIComponent('{{{autofido}}}') === 'true');
406 var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
407 var authStrategies = '{{{authStrategies}}}'.split(',');
408 var tokenTimeout = parseInt('{{{tokenTimeout}}}');
409 var websocket = null;
410 var formSubmitted = false;
411 var serverLangs = '{{{renderLanguages}}}'.split(',');
412 var loclist = { 'af': "Afrikaans", 'sq': "Albanian", 'ar': "Arabic (Standard)", 'ar-dz': "Arabic (Algeria)", 'ar-bh': "Arabic (Bahrain)", 'ar-eg': "Arabic (Egypt)", 'ar-iq': "Arabic (Iraq)", 'ar-jo': "Arabic (Jordan)", 'ar-kw': "Arabic (Kuwait)", 'ar-lb': "Arabic (Lebanon)", 'ar-ly': "Arabic (Libya)", 'ar-ma': "Arabic (Morocco)", 'ar-om': "Arabic (Oman)", 'ar-qa': "Arabic (Qatar)", 'ar-sa': "Arabic (Saudi Arabia)", 'ar-sy': "Arabic (Syria)", 'ar-tn': "Arabic (Tunisia)", 'ar-ae': "Arabic (U.A.E.)", 'ar-ye': "Arabic (Yemen)", 'an': "Aragonese", 'hy': "Armenian", 'as': "Assamese", 'ast': "Asturian", 'az': "Azerbaijani", 'eu': "Basque", 'bg': "Bulgarian", 'be': "Belarusian", 'bn': "Bengali", 'bs': "Bosnian", 'br': "Breton", 'my': "Burmese", 'ca': "Catalan", 'ch': "Chamorro", 'ce': "Chechen", 'zh': "Chinese", 'zh-hk': "Chinese (Hong Kong)", 'zh-cn': "Chinese (PRC)", 'zh-sg': "Chinese (Singapore)", 'zh-tw': "Chinese (Taiwan)", 'cv': "Chuvash", 'co': "Corsican", 'cr': "Cree", 'hr': "Croatian", 'cs': "Czech", 'da': "Danish", 'nl': "Dutch (Standard)", 'nl-be': "Dutch (Belgian)", 'en': "English", 'en-au': "English (Australia)", 'en-bz': "English (Belize)", 'en-ca': "English (Canada)", 'en-ie': "English (Ireland)", 'en-jm': "English (Jamaica)", 'en-nz': "English (New Zealand)", 'en-ph': "English (Philippines)", 'en-za': "English (South Africa)", 'en-tt': "English (Trinidad & Tobago)", 'en-gb': "English (United Kingdom)", 'en-us': "English (United States)", 'en-zw': "English (Zimbabwe)", 'eo': "Esperanto", 'et': "Estonian", 'fo': "Faeroese", 'fa': "Farsi (Persian)", 'fj': "Fijian", 'fi': "Finnish", 'fr': "French (Standard)", 'fr-be': "French (Belgium)", 'fr-ca': "French (Canada)", 'fr-fr': "French (France)", 'fr-lu': "French (Luxembourg)", 'fr-mc': "French (Monaco)", 'fr-ch': "French (Switzerland)", 'fy': "Frisian", 'fur': "Friulian", 'gd': "Gaelic (Scots)", 'gd-ie': "Gaelic (Irish)", 'gl': "Galacian", 'ka': "Georgian", 'de': "German (Standard)", 'de-at': "German (Austria)", 'de-de': "German (Germany)", 'de-li': "German (Liechtenstein)", 'de-lu': "German (Luxembourg)", 'de-ch': "German (Switzerland)", 'el': "Greek", 'gu': "Gujurati", 'ht': "Haitian", 'he': "Hebrew", 'hi': "Hindi", 'hu': "Hungarian", 'is': "Icelandic", 'id': "Indonesian", 'iu': "Inuktitut", 'ga': "Irish", 'it': "Italian (Standard)", 'it-ch': "Italian (Switzerland)", 'ja': "Japanese", 'kn': "Kannada", 'ks': "Kashmiri", 'kk': "Kazakh", 'km': "Khmer", 'ky': "Kirghiz", 'tlh': "Klingon", 'ko': "Korean", 'ko-kp': "Korean (North Korea)", 'ko-kr': "Korean (South Korea)", 'la': "Latin", 'lv': "Latvian", 'lt': "Lithuanian", 'lb': "Luxembourgish", 'mk': "FYRO Macedonian", 'ms': "Malay", 'ml': "Malayalam", 'mt': "Maltese", 'mi': "Maori", 'mr': "Marathi", 'mo': "Moldavian", 'nv': "Navajo", 'ng': "Ndonga", 'ne': "Nepali", 'no': "Norwegian", 'nb': "Norwegian (Bokmal)", 'nn': "Norwegian (Nynorsk)", 'oc': "Occitan", 'or': "Oriya", 'om': "Oromo", 'fa-ir': "Persian/Iran", 'pl': "Polish", 'pt': "Portuguese", 'pt-br': "Portuguese (Brazil)", 'pa': "Punjabi", 'pa-in': "Punjabi (India)", 'pa-pk': "Punjabi (Pakistan)", 'qu': "Quechua", 'rm': "Rhaeto-Romanic", 'ro': "Romanian", 'ro-mo': "Romanian (Moldavia)", 'ru': "Russian", 'ru-mo': "Russian (Moldavia)", 'sz': "Sami (Lappish)", 'sg': "Sango", 'sa': "Sanskrit", 'sc': "Sardinian", 'sd': "Sindhi", 'si': "Singhalese", 'sr': "Serbian", 'sk': "Slovak", 'sl': "Slovenian", 'so': "Somani", 'sb': "Sorbian", 'es': "Spanish", 'es-ar': "Spanish (Argentina)", 'es-bo': "Spanish (Bolivia)", 'es-cl': "Spanish (Chile)", 'es-co': "Spanish (Colombia)", 'es-cr': "Spanish (Costa Rica)", 'es-do': "Spanish (Dominican Republic)", 'es-ec': "Spanish (Ecuador)", 'es-sv': "Spanish (El Salvador)", 'es-gt': "Spanish (Guatemala)", 'es-hn': "Spanish (Honduras)", 'es-mx': "Spanish (Mexico)", 'es-ni': "Spanish (Nicaragua)", 'es-pa': "Spanish (Panama)", 'es-py': "Spanish (Paraguay)", 'es-pe': "Spanish (Peru)", 'es-pr': "Spanish (Puerto Rico)", 'es-es': "Spanish (Spain)", 'es-uy': "Spanish (Uruguay)", 'es-ve': "Spanish (Venezuela)", 'sx': "Sutu", 'sw': "Swahili", 'sv': "Swedish", 'sv-fi': "Swedish (Finland)", 'sv-sv': "Swedish (Sweden)", 'ta': "Tamil", 'tt': "Tatar", 'te': "Teluga", 'th': "Thai", 'tig': "Tigre", 'ts': "Tsonga", 'tn': "Tswana", 'tr': "Turkish", 'tk': "Turkmen", 'uk': "Ukrainian", 'hsb': "Upper Sorbian", 'ur': "Urdu", 've': "Venda", 'vi': "Vietnamese", 'vo': "Volapuk", 'wa': "Walloon", 'cy': "Welsh", 'xh': "Xhosa", 'ji': "Yiddish", 'zu': "Zulu" };
413 var loclistex = { 'zh-chs': "Chinese (Simplified)", 'zh-cht': "Chinese (Traditional)" };
414 var showLanguageSelect = '{{{showLanguageSelect}}}';
415
416 function startup() {
417 if (decodeURIComponent('{{{loginpicture}}}') == 'true') { Q('loginPicture').src = 'loginlogo.png'; }
418
419 QV('welcomeTextRow', welcomeText != '');
420 QH('welcomeText', welcomeText);
421
422 // Change the background image to full screen
423 if (welcomePictureFullScreen) {
424 QS('backgroundImage').height = '100%';
425 QS('backgroundImage').opacity = '1';
426 QS('backgroundImage')['background-position'] = 'center center';
427 QS('backgroundImage')['background-size'] = 'cover';
428 }
429
430 // Display the right server message
431 var i;
432 var messageid = parseInt('{{{messageid}}}');
433 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."];
434 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."];
435 if (messageid > 0) {
436 var msg = '';
437 if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
438 else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
439 if (msg != '') {
440 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 />'); }
441 for (i = 1; i < 9; i++) { QH('message' + i, msg); }
442 }
443 }
444
445 // Display flash error Messages
446 var flashErrors = JSON.parse('{{{flashErrors}}}');
447 if (flashErrors && (flashErrors.length > 0)) {
448 var msg = '';
449 for (i = 0; i < flashErrors.length; i++) {
450 if (flashErrors[i]) {
451 msg += '<span class="msg error"><b style=color:#8C001A>' + flashErrors[i] + '<b></span><br /><br />';
452 }
453 }
454 QH('message1', msg);
455 QV('message1', true);
456 }
457
458 // Fix links if a loginKey if used
459 var urlargs = parseUriArgs();
460 //if (urlargs.key) { Q('termsLinkFooter').href += '?key=' + urlargs.key; }
461
462 // Show Language Select Box if needed
463 if (showLanguageSelect === 'top' || showLanguageSelect === 'bottom') {
464 var x = '<select id=d2langselect style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px" onChange="changeLanguage()">';
465 x += '<option value="*">' + "Use Browser Language" + '</option>';
466 for (var i in serverLangs) {
467 var lang = serverLangs[i];
468 x += '<option value="' + lang + '"' + ((urlargs.lang == lang)?' selected':'') + '>' + lang + ' - ' + (loclist[lang]?loclist[lang]:loclistex[lang]) + '</option>';
469 }
470 QH(showLanguageSelect+'LanguageSelect', x);
471 if (showLanguageSelect === 'top') QS(showLanguageSelect+'LanguageSelect')['padding-top'] = '5px';
472 QV(showLanguageSelect+'LanguageSelectRow', true);
473 }
474
475 // Show user/pass login
476 passlogin = (passlogin == 'true');
477 if (urlargs.passlogin === 1) { passlogin = true; }
478 if (urlargs.passlogin === 0) { passlogin = false; }
479 QV('loginuserpassdiv', passlogin)
480 QV('loginuserpasshr', passlogin)
481
482 // Setup two factor cookie time
483 if (twoFactorCookieDays > 0) {
484 QV('tokenInputRememberLabel', true);
485 QH('tokenInputRememberSpan', format("Remember this device for {0} days.", twoFactorCookieDays));
486 QV('tokenInputRememberLabel2', true);
487 QH('tokenInputRememberSpan2', format("Remember this device for {0} days.", twoFactorCookieDays));
488 } else {
489 QV('tokenInputRememberLabel', false);
490 QV('tokenInputRememberLabel2', false);
491 }
492
493 // If URL arguments are provided, add them to form posts
494 if (window.location.href.indexOf('?') > 0) {
495 var xurlargs = window.location.href.substring(window.location.href.indexOf('?'));
496 Q('loginformargs').value = xurlargs;
497 Q('createformargs').value = xurlargs;
498 Q('resetformargs').value = xurlargs;
499 Q('tokenformargs').value = xurlargs;
500 Q('pushtokenformargs').value = xurlargs;
501 Q('resettokenformargs').value = xurlargs;
502 Q('resetpasswordformargs').value = xurlargs;
503 Q('checkemailformargs').value = xurlargs;
504 }
505
506 if ((features & 32) == 0) {
507 // Guard against other site's top frames (web bugs).
508 var loc = null;
509 try { loc = top.location.toString().toLowerCase(); } catch (e) { }
510 if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
511 }
512
513 if (features & 0x200000) { // Email is username
514 Q('username').placeholder = "Email";
515 QH('resetAccountSpan', "Forgot password?");
516 QV('nuUserRow', false);
517 }
518
519 QV('createPanelHint', passRequirements.hint === true);
520 QV('resetpasswordpanelHint', passRequirements.hint === true);
521
522 // Setup authentication strategies
523 if (authStrategies != '') {
524 QV('authStrategies', true);
525 if (authStrategies.indexOf('twitter') >= 0) { QV('auth-twitter', true); }
526 if (authStrategies.indexOf('google') >= 0) { QV('auth-google', true); }
527 if (authStrategies.indexOf('github') >= 0) { QV('auth-github', true); }
528 if (authStrategies.indexOf('azure') >= 0) { QV('auth-azure', true); }
529 if (authStrategies.indexOf('oidc') >= 0) { QV('auth-oidc', true); }
530 if (authStrategies.indexOf('oidc-azure') >= 0) { QV('auth-oidc-azure', true); }
531 if (authStrategies.indexOf('oidc-google') >= 0) { QV('auth-oidc-google', true); }
532 if (authStrategies.indexOf('jumpcloud') >= 0) { QV('auth-jumpcloud', true); }
533 if (authStrategies.indexOf('intel') >= 0) { QV('auth-intel', true); }
534 if (authStrategies.indexOf('saml') >= 0) { QV('auth-saml', true); }
535 // Override OIDC button title if custom text is configured by admin
536 var oidcButtonText = '{{{oidcButtonText}}}';
537 if (oidcButtonText != '') {
538 Q('auth-oidc').title = oidcButtonText;
539 Q('auth-oidc-azure').title = oidcButtonText;
540 Q('auth-oidc-google').title = oidcButtonText;
541 }
542 }
543
544 validateCreate();
545 if (loginMode.length != 0) { go(parseInt(loginMode)); } else { go(1); }
546 QV('newAccountDiv', (newAccount === '1') || (newAccount === 'true')); // If new accounts are not allowed, don't display the new account link.
547 if ((passhint != null) && (passhint.length > 0)) { QV('showPassHintLink', true); }
548 QV('newAccountPass', (newAccountPass == 1));
549 QV('newAccountCaptcha', (newAccountCaptcha != ''));
550 QV('newAccountCaptchaImg', (newAccountCaptcha != ''));
551 QV('resetAccountDiv', (emailCheck == 'true'));
552 QV('hrAccountDiv', (emailCheck == 'true') || (newAccountPass == 1));
553
554 if (loginMode == '4') {
555 if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
556 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
557 var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
558 var emailkey = otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6);
559 var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6);
560 var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6);
561 var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6);
562 var duokey = otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6);
563 QV('securityKeyButton', twofakey);
564 QV('emailKeyButton', emailkey);
565 QV('smsKeyButton', smskey);
566 QV('msgKeyButton', msgkey);
567 QV('pushKeyButton', pushkey);
568 QV('duoKeyButton', duokey);
569 QV('2farow', twofakey || emailkey || smskey || msgkey || pushkey || duokey);
570
571 // If hardware key is an option, trigger it now
572 if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
573 }
574
575 if (loginMode == '5') {
576 if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
577 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
578 var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
579 var emailkey = otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6);
580 var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6);
581 var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6);
582 var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6);
583 var duokey = otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6);
584 QV('securityKeyButton2', twofakey);
585 QV('emailKeyButton2', emailkey);
586 QV('smsKeyButton2', smskey);
587 QV('msgKeyButton2', msgkey);
588 QV('pushKeyButton2', pushkey);
589 QV('duoKeyButton2', duokey);
590 QV('2farow2', twofakey || emailkey || smskey || msgkey || pushkey || duokey);
591
592 // If hardware key is an option, trigger it now
593 if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); }
594 }
595
596 if (loginMode == '8') {
597 // Perform websocket connection to server to wait for device authentication
598 websocket = new WebSocket(passhint);
599 websocket.onopen = function (e) { QS('waitpushpanelimage')['opacity'] = '1'; }
600 websocket.onmessage = function (e) {
601 if (typeof e.data != 'string') { this.close(); }
602 var r = null;
603 try { r = JSON.parse(e.data); } catch (ex) { }
604 if (r.sent === true) {
605 // Request was sent
606 QH('message8', '<span class="msg success"><b>' + format("Request sent, {0}.", r.code) + '</b></span><br /><br />');
607 } else if (r.sent === false) {
608 // Request failed to send
609 QH('message8', '<span class="msg error"><b style=color:#8C001A>' + "Failed to send request." + '<b></span><br /><br />');
610 QV('tokenInputRememberLabel2', false);
611 this.close();
612 } else if (r.approved === true) {
613 // Request approved
614 this.close();
615 QV('tokenInputRememberLabel2', false);
616 QH('message8', '<span class="msg success"><b>' + "Request Accepted." + '</b></span><br /><br />');
617 Q('pushtokenInput').value = r.token;
618 Q('pushOkButton').click();
619 } else {
620 // Request rejected
621 QH('message8', '<span class="msg error"><b style=color:#8C001A>' + "Access Rejected." + '<b></span><br /><br />');
622 QV('tokenInputRememberLabel2', false);
623 this.close();
624 }
625 }
626 websocket.onclose = function (e) { QS('waitpushpanelimage')['opacity'] = '0.3'; }
627 websocket.onerror = function (e) {
628 QH('message8', '<span class="msg error"><b style=color:#8C001A>' + "Connection Error" + '<b></span><br /><br />');
629 QS('waitpushpanelimage')['opacity'] = '0.5';
630 }
631 }
632 }
633
634 function changeLanguage() {
635 var lang = Q('d2langselect').value;
636 var urlParams = new URLSearchParams(window.location.search);
637 if(lang=='*'){ urlParams.delete('lang') } else { urlParams.set('lang', lang); }
638 var newURL = window.location.pathname + '?' + urlParams.toString();
639 window.location.href = newURL;
640 }
641
642 // Use a hardware security key
643 function useSecurityKey(panelAction) {
644 if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
645 if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
646
647 publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout, userVerification: hardwareKeyChallenge.userVerification ? hardwareKeyChallenge.userVerification : 'preferred' }
648 for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
649 publicKeyCredentialRequestOptions.allowCredentials.push(
650 { id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'] }
651 );
652 }
653
654 // New WebAuthn hardware keys
655 navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
656 function (rawAssertion) {
657 var assertion = {
658 id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
659 clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
660 userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
661 signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
662 authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
663 };
664 if (panelAction == 1) {
665 Q('hwtokenInput').value = JSON.stringify(assertion);
666 QE('tokenOkButton', true);
667 Q('tokenOkButton').click();
668 } else if (panelAction == 2) {
669 Q('resetHwtokenInput').value = JSON.stringify(assertion);
670 QE('resetTokenOkButton', true);
671 Q('resetTokenOkButton').click();
672 }
673 },
674 function (error) { console.log('credentials-get error', error); }
675 );
676 }
677 }
678
679 function useEmailToken(panelAction) {
680 if (otpemail != true) return;
681 setDialogMode(1, "Secure Login", 3, useEmailKeyEx, "Send token to registered email address?", panelAction);
682 }
683
684 function useEmailKeyEx(b, panelAction) {
685 if (panelAction == 1) {
686 Q('hwtokenInput').value = '**email**';
687 QE('tokenOkButton', true);
688 Q('tokenOkButton').click();
689 } else if (panelAction == 2) {
690 Q('resetHwtokenInput').value = '**email**';
691 QE('resetTokenOkButton', true);
692 Q('resetTokenOkButton').click();
693 }
694 }
695
696 function useSMSToken(panelAction) {
697 if (otpsms != true) return;
698 setDialogMode(1, "Secure Login", 3, useSMSTokenEx, "Send token to registered phone number?", panelAction);
699 }
700
701 function useSMSTokenEx(b, panelAction) {
702 if (panelAction == 1) {
703 Q('hwtokenInput').value = '**sms**';
704 QE('tokenOkButton', true);
705 Q('tokenOkButton').click();
706 } else if (panelAction == 2) {
707 Q('resetHwtokenInput').value = '**sms**';
708 QE('resetTokenOkButton', true);
709 Q('resetTokenOkButton').click();
710 }
711 }
712
713 function useMsgToken(panelAction) {
714 if (otpmsg != true) return;
715 setDialogMode(1, "Secure Login", 3, useMsgTokenEx, "Send token to messaging application?", panelAction);
716 }
717
718 function useMsgTokenEx(b, panelAction) {
719 if (panelAction == 1) {
720 Q('hwtokenInput').value = '**msg**';
721 QE('tokenOkButton', true);
722 Q('tokenOkButton').click();
723 } else if (panelAction == 2) {
724 Q('resetHwtokenInput').value = '**msg**';
725 QE('resetTokenOkButton', true);
726 Q('resetTokenOkButton').click();
727 }
728 }
729
730 function usePushToken(panelAction) {
731 if (panelAction == 1) {
732 Q('hwtokenInput').value = '**push**';
733 QE('tokenOkButton', true);
734 Q('tokenOkButton').click();
735 } else if (panelAction == 2) {
736 Q('resetHwtokenInput').value = '**push**';
737 QE('resetTokenOkButton', true);
738 Q('resetTokenOkButton').click();
739 }
740 }
741
742 function useDuoToken(panelAction) {
743 if (panelAction == 1) {
744 Q('hwtokenInput').value = '**duo**';
745 QE('tokenOkButton', true);
746 Q('tokenOkButton').click();
747 } else if (panelAction == 2) {
748 Q('resetHwtokenInput').value = '**duo**';
749 QE('resetTokenOkButton', true);
750 Q('resetTokenOkButton').click();
751 }
752 }
753
754 function showPassHint(e) {
755 messagebox("Password Hint", passhint);
756 haltEvent(e);
757 return false;
758 }
759
760 function xgo(x, e) {
761 QV('message1', false);
762 QV('message2', false);
763 QV('message3', false);
764 QV('message4', false);
765 QV('message5', false);
766 QV('message6', false);
767 QV('message7', false);
768 go(x);
769 haltEvent(e);
770 return false;
771 }
772
773 function go(x) {
774 currentpanel = x;
775 setDialogMode(0);
776 QV('showPassHintLink', false);
777 QV('loginpanel', x == 1);
778 QV('createpanel', x == 2);
779 QV('resetpanel', x == 3);
780 QV('tokenpanel', x == 4);
781 QV('resettokenpanel', x == 5);
782 QV('resetpasswordpanel', x == 6);
783 QV('checkemailpanel', x == 7);
784 QV('waitpushpanel', x == 8);
785 if (x == 1) { Q('username').focus(); }
786 if (x == 2) { if (features & 0x200000) { Q('aemail').focus(); } else { Q('ausername').focus(); } } // Email is username
787 if (x == 3) { Q('remail').focus(); }
788 if (x == 4) { Q('tokenInput').focus(); }
789 if (x == 5) { Q('resetTokenInput').focus(); }
790 if (x == 6) { Q('rapassword1').focus(); }
791 if (x == 7) {
792 QH('unconfirmedEmail', passhint);
793 QV('checkCheckOperations', Q('unconfirmedEmail').innerHTML != '');
794 QH('checkEmailVal', passhint);
795 }
796 }
797
798 function submitButtonClicked(v) {
799 if (!formSubmitted) { formSubmitted = true; document.getElementById(v).submit(); }
800 }
801
802 function validateLogin(box, e) {
803 setTimeout(function(){
804 setDialogMode(0);
805 if ((e != null) && (e.keyCode == 13)) {
806 if ((box == 1) && (Q('username').value != '')) { Q('password').focus(); }
807 else if ((box == 2) && (Q('password').value != '')) { Q('loginButton').click(); }
808 }
809 if (e != null) { haltEvent(e); }
810 }, 100);
811 }
812
813 function validateCreate(box, e) {
814 setDialogMode(0);
815 var userok = false;
816 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); }
817 var emailok = (validateEmail(Q('aemail').value) == true);
818 var pass1ok = (Q('apassword1').value.length > 0);
819 var pass2ok = (Q('apassword2').value.length > 0) && (Q('apassword2').value == Q('apassword1').value);
820 var newAccOk = (newAccountPass == 0) || (Q('anewaccountpass').value.length > 0);
821 var newCaptchaOk = (newAccountCaptcha == '') || (Q('anewaccountcaptcha').value.length > 0);
822 var ok = (userok && emailok && pass1ok && pass2ok && newAccOk);
823
824 // Color the fields
825 QS('nuUser').color = userok ? 'black' : '#7b241c';
826 QS('nuEmail').color = emailok ? 'black' : '#7b241c';
827 QS('nuPass1').color = pass1ok ? 'black' : '#7b241c';
828 QS('nuPass2').color = pass2ok ? 'black' : '#7b241c';
829 QS('nuToken').color = newAccOk ? 'black' : '#7b241c';
830 QS('nuCaptcha').color = newCaptchaOk ? 'black' : '#7b241c';
831
832 if (Q('apassword1').value == '') {
833 QH('passWarning', '');
834 QV('passwordPolicyCallout', false);
835 } else {
836 if (!passRequirementsEx) {
837 // No password requirements, display password strength
838 var passStrength = checkPasswordStrength(Q('apassword1').value);
839 if (passStrength >= 80) { QH('passWarning', '<span style=color:green><b>' + "Strong Password" + '</b><span>'); }
840 else if (passStrength >= 60) { QH('passWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
841 else { QH('passWarning', '<span style=color:red><b>' + "Weak Password" + '</b><span>'); }
842 } else {
843 // Password requirements provided, use that
844 var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
845 if (passReq == false) {
846 ok = false;
847 QS('nuPass1').color = '#7b241c';
848 QS('nuPass2').color = '#7b241c';
849 QH('passWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
850 QV('passwordPolicyCallout', true);
851 QH('passwordPolicyCallout', passwordPolicyText(Q('apassword1').value));
852 } else {
853 QH('passWarning', '');
854 QV('passwordPolicyCallout', false);
855 }
856 }
857 }
858 if ((e != null) && (e.keyCode == 13)) {
859 if ((box == 1) && userok) { Q('aemail').focus(); }
860 if ((box == 2) && emailok) { Q('apassword1').focus(); }
861 if ((box == 3) && pass1ok) { Q('apassword2').focus(); }
862 if ((box == 4) && pass2ok) { if (passRequirements.hint === true) { Q('apasswordhint').focus(); } else { box = 5; } }
863 if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { box = 6; } }
864 if (box == 6) { if (newAccountCaptcha != '') { Q('anewaccountcaptcha').focus(); } else { box = 7; } }
865 if (box == 7) { Q('createButton').click(); }
866 }
867 if (e != null) { haltEvent(e); }
868 QE('createButton', ok);
869 }
870
871 function validatePassReset(box, e) {
872 setDialogMode(0);
873 var pass1ok = (Q('rapassword1').value.length > 0);
874 var pass2ok = (Q('rapassword2').value.length > 0) && (Q('rapassword2').value == Q('rapassword1').value);
875 var ok = (pass1ok && pass2ok);
876
877 // Color the fields
878 QS('rnuPass1').color = pass1ok ? 'black' : '#7b241c';
879 QS('rnuPass2').color = pass2ok ? 'black' : '#7b241c';
880
881 if (Q('rapassword1').value == '') {
882 QH('rpassWarning', '');
883 QV('rpasswordPolicyCallout', false);
884 } else {
885 if (!passRequirementsEx) {
886 // No password requirements, display password strength
887 var passStrength = checkPasswordStrength(Q('rapassword1').value);
888 if (passStrength >= 80) { QH('rpassWarning', '<span style=color:green><b>' + "Strong Password" + '</b><span>'); }
889 else if (passStrength >= 60) { QH('rpassWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
890 else { QH('rpassWarning', '<span style=color:red><b>' + "Weak Password" + '</b><span>'); }
891 } else {
892 // Password requirements provided, use that
893 var passReq = checkPasswordRequirements(Q('rapassword1').value, passRequirements);
894 if (passReq == false) {
895 ok = false;
896 QS('rnuPass1').color = '#7b241c';
897 QS('rnuPass2').color = '#7b241c';
898 QH('rpassWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
899 QV('rpasswordPolicyCallout', true);
900 QH('rpasswordPolicyCallout', passwordPolicyText(Q('rapassword1').value));
901 } else {
902 QH('rpassWarning', '');
903 QV('rpasswordPolicyCallout', false);
904 }
905 }
906 }
907 if ((e != null) && (e.keyCode == 13)) {
908 if (box == 2) { Q('rapassword1').focus(); }
909 if (box == 3) { Q('rapassword2').focus(); }
910 if (box == 4) { Q('rapasswordhint').focus(); }
911 if (box == 6) { Q('resetPassButton').click(); }
912 }
913 if (e != null) { haltEvent(e); }
914 QE('resetPassButton', ok);
915 }
916
917 function passwordPolicyText(pass) {
918 var policy = '<div style=text-align:left>';
919 var counts = strCount(pass);
920 if (passRequirements.min && ((pass == null) || (pass.length < passRequirements.min))) { policy += format("Minimum length of {0}", passRequirements.min) + '<br />'; }
921 if (passRequirements.max && ((pass == null) || (pass.length > passRequirements.max))) { policy += format("Maximum length of {0}", passRequirements.max) + '<br />'; }
922 if (passRequirements.upper && ((pass == null) || (counts.upper < passRequirements.upper))) { policy += format("{0} upper case", passRequirements.upper) + '<br />'; }
923 if (passRequirements.lower && ((pass == null) || (counts.lower < passRequirements.lower))) { policy += format("{0} lower case", passRequirements.lower) + '<br />'; }
924 if (passRequirements.numeric && ((pass == null) || (counts.numeric < passRequirements.numeric))) { policy += format("{0} numeric", passRequirements.numeric) + '<br />'; }
925 if (passRequirements.nonalpha && ((pass == null) || (counts.nonalpha < passRequirements.nonalpha))) { policy += format("{0} non-alphanumeric", passRequirements.nonalpha) + '<br />'; }
926 policy += '</div>';
927 return policy;
928 }
929
930 function showPasswordPolicy() {
931 messagebox("Password Policy", passwordPolicyText());
932 }
933
934 function validateReset(e) {
935 setDialogMode(0);
936 var x = validateEmail(Q('remail').value);
937 QE('eresetButton', x);
938 if ((e != null) && (e.keyCode == 13) && (x == true)) {
939 Q('eresetButton').click();
940 }
941 if (e != null) { haltEvent(e); }
942 }
943
944 // Return a password strength score
945 function checkPasswordStrength(password) {
946 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) }
947 if (!password) return 0;
948 for (var i = 0; i < password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
949 for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
950 return parseInt(r + (varCount - 1) * 10);
951 }
952
953 // Check password requirements
954 function checkPasswordRequirements(password, requirements) {
955 if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
956 if (requirements.min) { if (password.length < requirements.min) return false; }
957 if (requirements.max) { if (password.length > requirements.max) return false; }
958 var counts = strCount(password);
959 if (requirements.numeric && (counts.numeric < requirements.numeric)) return false;
960 if (requirements.lower && (counts.lower < requirements.lower)) return false;
961 if (requirements.upper && (counts.upper < requirements.upper)) return false;
962 if (requirements.nonalpha && (counts.nonalpha < requirements.nonalpha)) return false;
963 return true;
964 }
965
966 function strCount(password) {
967 var counts = { numeric: 0, lower: 0, upper: 0, nonalpha: 0 };
968 if (typeof password != 'string') return counts;
969 for (var i = 0; i < password.length; i++) {
970 if (/\d/.test(password[i])) { counts.numeric++; }
971 if (/[a-z]/.test(password[i])) { counts.lower++; }
972 if (/[A-Z]/.test(password[i])) { counts.upper++; }
973 if (/\W/.test(password[i])) { counts.nonalpha++; }
974 }
975 return counts;
976 }
977
978 function checkToken(e) {
979 var t1 = Q('tokenInput').value;
980 var t2 = t1.split(' ').join('');
981 if (t1 != t2) { Q('tokenInput').value = t2; }
982 var enabled = (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8) || (Q('tokenInput').value.length == 44);
983 QE('tokenOkButton', enabled);
984 if (enabled && (e != null) && (e.keyCode == 13)) { submitButtonClicked('tokenpanelform'); }
985 }
986
987 function resetCheckToken(e) {
988 var t1 = Q('resetTokenInput').value;
989 var t2 = t1.split(' ').join('');
990 if (t1 != t2) { Q('resetTokenInput').value = t2; }
991 var enabled = (Q('resetTokenInput').value.length == 6) || (Q('resetTokenInput').value.length == 8) || (Q('resetTokenInput').value.length == 44);
992 QE('resetTokenOkButton', enabled);
993 if (enabled && (e != null) && (e.keyCode == 13)) { submitButtonClicked('resettokenpanelform'); }
994 }
995
996 function changeEmailAddress() {
997 var email = Q('unconfirmedEmail').innerHTML;
998 var x = addHtmlValue("Email", '<input id=dp1email style=width:230px maxlength=256 value="' + email + '" {{{autocomplete}}}=off onchange=validateEmailAddress() onkeyup=validateEmailAddress() />');
999 setDialogMode(1, "Email Confirmation", 3, changeEmailAddressEx, x);
1000 validateEmailAddress();
1001 }
1002
1003 function validateEmailAddress() {
1004 QE('idx_dlgOkButton', (validateEmail(Q('dp1email').value) == true));
1005 }
1006
1007 function changeEmailAddressEx() {
1008 Q('checkEmailVal').value = Q('dp1email').value;
1009 QH('unconfirmedEmail', Q('dp1email').value);
1010 Q('checkEmailButton').click();
1011 }
1012
1013 function resentEmailConfirmation() {
1014 Q('checkEmailVal').value = Q('unconfirmedEmail').innerHTML;
1015 Q('checkEmailButton').click();
1016 }
1017
1018 //
1019 // POPUP DIALOG
1020 //
1021
1022 // undefined = Hidden, 1 = Generic Message
1023 var xxdialogMode;
1024 var xxdialogFunc;
1025 var xxdialogButtons;
1026 var xxdialogTag;
1027 var xxcurrentView = 0;
1028
1029 // Display a dialog box
1030 // 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)
1031 function setDialogMode(x, y, b, f, c, tag) {
1032 xxdialogMode = x;
1033 xxdialogFunc = f;
1034 xxdialogButtons = b;
1035 xxdialogTag = tag;
1036 QE('idx_dlgOkButton', true);
1037 QV('idx_dlgOkButton', b & 1);
1038 QV('idx_dlgCancelButton', b & 2);
1039 QV('id_dialogclose', (b & 2) || (b & 8));
1040 QV('idx_dlgButtonBar', b & 7);
1041 if (y) QH('id_dialogtitle', y);
1042 for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
1043 QV('dialog', x);
1044 if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
1045 }
1046
1047 function dialogclose(x) {
1048 var f = xxdialogFunc;
1049 var b = xxdialogButtons;
1050 var t = xxdialogTag;
1051 setDialogMode();
1052 if (((b & 8) || x) && f) f(x, t);
1053 }
1054
1055 function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
1056 function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
1057 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; }
1058 function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
1059 function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
1060 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
1061 function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
1062 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; } }
1063 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; }); };
1064 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); }
1065 function addHtmlValue(t, v) { return '<table><td style=width:120px;text-align:left>' + t + '<td><b>' + v + '</b></table>'; }
1066
1067 </script>
1068 </body>
1069 </html>