includes/clientside/static/login.js
changeset 436 242353360e37
child 460 3a1c99845ca8
equal deleted inserted replaced
435:a434d60e525d 436:242353360e37
       
     1 /*
       
     2  * AJAX-based intelligent login interface
       
     3  */
       
     4 
       
     5 /*
       
     6  * FRONTEND
       
     7  */
       
     8 
       
     9 /**
       
    10  * Performs a logon as a regular member.
       
    11  */
       
    12 
       
    13 function ajaxLogonToMember()
       
    14 {
       
    15   // IE <6 pseudo-compatibility
       
    16   if ( KILL_SWITCH )
       
    17     return true;
       
    18   if ( auth_level >= USER_LEVEL_MEMBER )
       
    19     return true;
       
    20   ajaxLoginInit(function(k)
       
    21     {
       
    22       window.location.reload();
       
    23     }, USER_LEVEL_MEMBER);
       
    24 }
       
    25 
       
    26 /**
       
    27  * Authenticates to the highest level the current user is allowed to go to.
       
    28  */
       
    29 
       
    30 function ajaxLogonToElev()
       
    31 {
       
    32   if ( auth_level == user_level )
       
    33     return true;
       
    34   
       
    35   ajaxLoginInit(function(k)
       
    36     {
       
    37       ENANO_SID = k;
       
    38       var url = String(' ' + window.location).substr(1);
       
    39       url = append_sid(url);
       
    40       window.location = url;
       
    41     }, user_level);
       
    42 }
       
    43 
       
    44 /*
       
    45  * BACKEND
       
    46  */
       
    47 
       
    48 /**
       
    49  * Holding object for various AJAX authentication information.
       
    50  * @var object
       
    51  */
       
    52 
       
    53 var logindata = {};
       
    54 
       
    55 /**
       
    56  * Path to the image used to indicate loading progress
       
    57  * @var string
       
    58  */
       
    59 
       
    60 if ( !ajax_login_loadimg_path )
       
    61   var ajax_login_loadimg_path = false;
       
    62 
       
    63 if ( !ajax_login_successimg_path )
       
    64   var ajax_login_successimg_path = false;
       
    65 
       
    66 /**
       
    67  * Status variables
       
    68  * @var int
       
    69  */
       
    70 
       
    71 var AJAX_STATUS_LOADING_KEY = 1;
       
    72 var AJAX_STATUS_GENERATING_KEY = 2;
       
    73 var AJAX_STATUS_LOGGING_IN = 3;
       
    74 var AJAX_STATUS_SUCCESS = 4;
       
    75 var AJAX_STATUS_DESTROY = 65535;
       
    76 
       
    77 /**
       
    78  * State constants
       
    79  * @var int
       
    80  */
       
    81 
       
    82 var AJAX_STATE_EARLY_INIT = 1;
       
    83 var AJAX_STATE_LOADING_KEY = 2;
       
    84 
       
    85 /**
       
    86  * Performs the AJAX request to get an encryption key and from there spawns the login form.
       
    87  * @param function The function that will be called once authentication completes successfully.
       
    88  * @param int The security level to authenticate at - see http://docs.enanocms.org/Help:Appendix_B
       
    89  */
       
    90 
       
    91 function ajaxLoginInit(call_on_finish, user_level)
       
    92 {
       
    93   logindata = {};
       
    94   
       
    95   var title = ( user_level > USER_LEVEL_MEMBER ) ? $lang.get('user_login_ajax_prompt_title_elev') : $lang.get('user_login_ajax_prompt_title');
       
    96   logindata.mb_object = new messagebox(MB_OKCANCEL | MB_ICONLOCK, title, '');
       
    97   
       
    98   logindata.mb_object.onclick['Cancel'] = function()
       
    99   {
       
   100     // Hide the error message and captcha
       
   101     if ( document.getElementById('ajax_login_error_box') )
       
   102     {
       
   103       document.getElementById('ajax_login_error_box').parentNode.removeChild(document.getElementById('ajax_login_error_box'));
       
   104     }
       
   105     if ( document.getElementById('autoCaptcha') )
       
   106     {
       
   107       var to = fly_out_top(document.getElementById('autoCaptcha'), false, true);
       
   108       setTimeout(function() {
       
   109           var d = document.getElementById('autoCaptcha');
       
   110           d.parentNode.removeChild(d);
       
   111         }, to);
       
   112     }
       
   113   };
       
   114   
       
   115   logindata.mb_object.onbeforeclick['OK'] = function()
       
   116   {
       
   117     ajaxLoginSubmitForm();
       
   118     return true;
       
   119   }
       
   120   
       
   121   // Fetch the inner content area
       
   122   logindata.mb_inner = document.getElementById('messageBox').getElementsByTagName('div')[0];
       
   123   
       
   124   // Initialize state
       
   125   logindata.showing_status = false;
       
   126   logindata.user_level = user_level;
       
   127   logindata.successfunc = call_on_finish;
       
   128   
       
   129   // Build the "loading" window
       
   130   ajaxLoginSetStatus(AJAX_STATUS_LOADING_KEY);
       
   131   
       
   132   // Request the key
       
   133   ajaxLoginPerformRequest({ mode: 'getkey' });
       
   134 }
       
   135 
       
   136 /**
       
   137  * Sets the contents of the AJAX login window to the appropriate status message.
       
   138  * @param int One of AJAX_STATUS_*
       
   139  */
       
   140 
       
   141 function ajaxLoginSetStatus(status)
       
   142 {
       
   143   if ( !logindata.mb_inner )
       
   144     return false;
       
   145   if ( logindata.showing_status )
       
   146   {
       
   147     var div = document.getElementById('ajax_login_status');
       
   148     if ( div )
       
   149       logindata.mb_inner.removeChild(div);
       
   150   }
       
   151   switch(status)
       
   152   {
       
   153     case AJAX_STATUS_LOADING_KEY:
       
   154       
       
   155       // Create the status div
       
   156       var div = document.createElement('div');
       
   157       div.id = 'ajax_login_status';
       
   158       div.style.marginTop = '10px';
       
   159       div.style.textAlign = 'center';
       
   160       
       
   161       // The circly ball ajaxy image + status message
       
   162       var status_msg = $lang.get('user_login_ajax_fetching_key');
       
   163       
       
   164       // Insert the status message
       
   165       div.appendChild(document.createTextNode(status_msg));
       
   166       
       
   167       // Append a br or two to space things properly
       
   168       div.appendChild(document.createElement('br'));
       
   169       div.appendChild(document.createElement('br'));
       
   170       
       
   171       var img = document.createElement('img');
       
   172       img.src = ( ajax_login_loadimg_path ) ? ajax_login_loadimg_path : scriptPath + '/images/loading-big.gif';
       
   173       div.appendChild(img);
       
   174       
       
   175       // Another coupla brs
       
   176       div.appendChild(document.createElement('br'));
       
   177       div.appendChild(document.createElement('br'));
       
   178       
       
   179       // The link to the full login form
       
   180       var small = document.createElement('small');
       
   181       small.innerHTML = $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title) });
       
   182       div.appendChild(small);
       
   183       
       
   184       // Insert the entire message into the login window
       
   185       logindata.mb_inner.innerHTML = '';
       
   186       logindata.mb_inner.appendChild(div);
       
   187       
       
   188       break;
       
   189     case AJAX_STATUS_GENERATING_KEY:
       
   190       
       
   191       // Create the status div
       
   192       var div = document.createElement('div');
       
   193       div.id = 'ajax_login_status';
       
   194       div.style.marginTop = '10px';
       
   195       div.style.textAlign = 'center';
       
   196       
       
   197       // The circly ball ajaxy image + status message
       
   198       var status_msg = $lang.get('user_login_ajax_generating_key');
       
   199       
       
   200       // Insert the status message
       
   201       div.appendChild(document.createTextNode(status_msg));
       
   202       
       
   203       // Append a br or two to space things properly
       
   204       div.appendChild(document.createElement('br'));
       
   205       div.appendChild(document.createElement('br'));
       
   206       
       
   207       var img = document.createElement('img');
       
   208       img.src = ( ajax_login_loadimg_path ) ? ajax_login_loadimg_path : scriptPath + '/images/loading-big.gif';
       
   209       div.appendChild(img);
       
   210       
       
   211       // Another coupla brs
       
   212       div.appendChild(document.createElement('br'));
       
   213       div.appendChild(document.createElement('br'));
       
   214       
       
   215       // The link to the full login form
       
   216       var small = document.createElement('small');
       
   217       small.innerHTML = $lang.get('user_login_ajax_link_fullform_dh', { link_full_form: makeUrlNS('Special', 'Login/' + title) });
       
   218       div.appendChild(small);
       
   219       
       
   220       // Insert the entire message into the login window
       
   221       logindata.mb_inner.innerHTML = '';
       
   222       logindata.mb_inner.appendChild(div);
       
   223       
       
   224       break;
       
   225     case AJAX_STATUS_LOGGING_IN:
       
   226       
       
   227       // Create the status div
       
   228       var div = document.createElement('div');
       
   229       div.id = 'ajax_login_status';
       
   230       div.style.marginTop = '10px';
       
   231       div.style.textAlign = 'center';
       
   232       
       
   233       // The circly ball ajaxy image + status message
       
   234       var status_msg = $lang.get('user_login_ajax_loggingin');
       
   235       
       
   236       // Insert the status message
       
   237       div.appendChild(document.createTextNode(status_msg));
       
   238       
       
   239       // Append a br or two to space things properly
       
   240       div.appendChild(document.createElement('br'));
       
   241       div.appendChild(document.createElement('br'));
       
   242       
       
   243       var img = document.createElement('img');
       
   244       img.src = ( ajax_login_loadimg_path ) ? ajax_login_loadimg_path : scriptPath + '/images/loading-big.gif';
       
   245       div.appendChild(img);
       
   246       
       
   247       // Insert the entire message into the login window
       
   248       logindata.mb_inner.innerHTML = '';
       
   249       logindata.mb_inner.appendChild(div);
       
   250       
       
   251       break;
       
   252     case AJAX_STATUS_SUCCESS:
       
   253       
       
   254       // Create the status div
       
   255       var div = document.createElement('div');
       
   256       div.id = 'ajax_login_status';
       
   257       div.style.marginTop = '10px';
       
   258       div.style.textAlign = 'center';
       
   259       
       
   260       // The circly ball ajaxy image + status message
       
   261       var status_msg = $lang.get('user_login_success_short');
       
   262       
       
   263       // Insert the status message
       
   264       div.appendChild(document.createTextNode(status_msg));
       
   265       
       
   266       // Append a br or two to space things properly
       
   267       div.appendChild(document.createElement('br'));
       
   268       div.appendChild(document.createElement('br'));
       
   269       
       
   270       var img = document.createElement('img');
       
   271       img.src = ( ajax_login_successimg_path ) ? ajax_login_successimg_path : scriptPath + '/images/check.png';
       
   272       div.appendChild(img);
       
   273       
       
   274       // Insert the entire message into the login window
       
   275       logindata.mb_inner.innerHTML = '';
       
   276       logindata.mb_inner.appendChild(div);
       
   277       
       
   278     case AJAX_STATUS_DESTROY:
       
   279     case null:
       
   280     case undefined:
       
   281       logindata.showing_status = false;
       
   282       return null;
       
   283       break;
       
   284   }
       
   285   logindata.showing_status = true;
       
   286 }
       
   287 
       
   288 /**
       
   289  * Performs an AJAX logon request to the server and calls ajaxLoginProcessResponse() on the result.
       
   290  * @param object JSON packet to send
       
   291  */
       
   292 
       
   293 function ajaxLoginPerformRequest(json)
       
   294 {
       
   295   json = toJSONString(json);
       
   296   json = ajaxEscape(json);
       
   297   ajaxPost(makeUrlNS('Special', 'Login/action.json'), 'r=' + json, function()
       
   298     {
       
   299       if ( ajax.readyState == 4 && ajax.status == 200 )
       
   300       {
       
   301         // parse response
       
   302         var response = String(ajax.responseText + '');
       
   303         if ( response.substr(0, 1) != '{' )
       
   304         {
       
   305           handle_invalid_json(response);
       
   306           return false;
       
   307         }
       
   308         response = parseJSON(response);
       
   309         ajaxLoginProcessResponse(response);
       
   310       }
       
   311     }, true);
       
   312 }
       
   313 
       
   314 /**
       
   315  * Processes a response from the login server
       
   316  * @param object JSON response
       
   317  */
       
   318 
       
   319 function ajaxLoginProcessResponse(response)
       
   320 {
       
   321   // Did the server send a plaintext error?
       
   322   if ( response.mode == 'error' )
       
   323   {
       
   324     logindata.mb_object.destroy();
       
   325     new messagebox(MB_ICONSTOP | MB_OK, 'FIXME L10N: There was an error in the login process', 'The following error code came from the server:<br />' + response.error);
       
   326     return false;
       
   327   }
       
   328   // Rid ourselves of any loading windows
       
   329   ajaxLoginSetStatus(AJAX_STATUS_DESTROY);
       
   330   // Main mode switch
       
   331   switch ( response.mode )
       
   332   {
       
   333     case 'build_box':
       
   334       // The server wants us to build the login form, all the information is there
       
   335       ajaxLoginBuildForm(response);
       
   336       break;
       
   337     case 'login_success':
       
   338       ajaxLoginSetStatus(AJAX_STATUS_SUCCESS);
       
   339       logindata.successfunc(response.key);
       
   340       break;
       
   341     case 'login_failure':
       
   342       document.getElementById('messageBox').style.backgroundColor = '#C0C0C0';
       
   343       var mb_parent = document.getElementById('messageBox').parentNode;
       
   344       new Spry.Effect.Shake(mb_parent, {duration: 1500}).start();
       
   345       setTimeout(function()
       
   346         {
       
   347           document.getElementById('messageBox').style.backgroundColor = '#FFF';
       
   348           ajaxLoginBuildForm(response.respawn_info);
       
   349           ajaxLoginShowFriendlyError(response);
       
   350         }, 2500);
       
   351       break;
       
   352   }
       
   353 }
       
   354 
       
   355 /*
       
   356  * RESPONSE HANDLERS
       
   357  */
       
   358 
       
   359 /**
       
   360  * Builds the login form.
       
   361  * @param object Metadata to build off of
       
   362  */
       
   363 
       
   364 function ajaxLoginBuildForm(data)
       
   365 {
       
   366   // let's hope this effectively preloads the image...
       
   367   var _ = document.createElement('img');
       
   368   _.src = ( ajax_login_successimg_path ) ? ajax_login_successimg_path : scriptPath + '/images/check.png';
       
   369   
       
   370   var div = document.createElement('div');
       
   371   div.id = 'ajax_login_form';
       
   372   
       
   373   var show_captcha = ( data.locked_out && data.lockout_info.lockout_policy == 'captcha' ) ? data.lockout_info.captcha : false;
       
   374   
       
   375   // text displayed on re-auth
       
   376   if ( logindata.user_level > USER_LEVEL_MEMBER )
       
   377   {
       
   378     div.innerHTML += $lang.get('user_login_ajax_prompt_body_elev') + '<br /><br />';
       
   379   }
       
   380   
       
   381   // Create the form
       
   382   var form = document.createElement('form');
       
   383   form.action = 'javascript:void(ajaxLoginSubmitForm());';
       
   384   form.onsubmit = function()
       
   385   {
       
   386     ajaxLoginSubmitForm();
       
   387     return false;
       
   388   }
       
   389   
       
   390   // Using tables to wrap form elements because it results in a
       
   391   // more visually appealing form. Yes, tables suck. I don't really
       
   392   // care - they make forms look good.
       
   393   
       
   394   var table = document.createElement('table');
       
   395   table.style.margin = '0 auto';
       
   396   
       
   397   // Field - username
       
   398   var tr1 = document.createElement('tr');
       
   399   var td1_1 = document.createElement('td');
       
   400   td1_1.appendChild(document.createTextNode($lang.get('user_login_field_username') + ':'));
       
   401   tr1.appendChild(td1_1);
       
   402   var td1_2 = document.createElement('td');
       
   403   var f_username = document.createElement('input');
       
   404   f_username.id = 'ajax_login_field_username';
       
   405   f_username.name = 'ajax_login_field_username';
       
   406   f_username.type = 'text';
       
   407   f_username.size = '25';
       
   408   if ( data.username )
       
   409     f_username.value = data.username;
       
   410   td1_2.appendChild(f_username);
       
   411   tr1.appendChild(td1_2);
       
   412   table.appendChild(tr1);
       
   413   
       
   414   // Field - password
       
   415   var tr2 = document.createElement('tr');
       
   416   var td2_1 = document.createElement('td');
       
   417   td2_1.appendChild(document.createTextNode($lang.get('user_login_field_password') + ':'));
       
   418   tr2.appendChild(td2_1);
       
   419   var td2_2 = document.createElement('td');
       
   420   var f_password = document.createElement('input');
       
   421   f_password.id = 'ajax_login_field_password';
       
   422   f_password.name = 'ajax_login_field_username';
       
   423   f_password.type = 'password';
       
   424   f_password.size = '25';
       
   425   if ( !show_captcha )
       
   426   {
       
   427     f_password.onkeyup = function(e)
       
   428     {
       
   429       if ( !e.keyCode )
       
   430         e = window.event;
       
   431       if ( !e.keyCode )
       
   432         return true;
       
   433       if ( e.keyCode == 13 )
       
   434       {
       
   435         ajaxLoginSubmitForm();
       
   436       }
       
   437     }
       
   438   }
       
   439   td2_2.appendChild(f_password);
       
   440   tr2.appendChild(td2_2);
       
   441   table.appendChild(tr2);
       
   442   
       
   443   // Field - captcha
       
   444   if ( show_captcha )
       
   445   {
       
   446     var tr3 = document.createElement('tr');
       
   447     var td3_1 = document.createElement('td');
       
   448     td3_1.appendChild(document.createTextNode($lang.get('user_login_field_captcha') + ':'));
       
   449     tr3.appendChild(td3_1);
       
   450     var td3_2 = document.createElement('td');
       
   451     var f_captcha = document.createElement('input');
       
   452     f_captcha.id = 'ajax_login_field_captcha';
       
   453     f_captcha.name = 'ajax_login_field_username';
       
   454     f_captcha.type = 'text';
       
   455     f_captcha.size = '25';
       
   456     f_captcha.onkeyup = function(e)
       
   457     {
       
   458       if ( !e )
       
   459         e = window.event;
       
   460       if ( !e.keyCode )
       
   461         return true;
       
   462       if ( e.keyCode == 13 )
       
   463       {
       
   464         ajaxLoginSubmitForm();
       
   465       }
       
   466     }
       
   467     td3_2.appendChild(f_captcha);
       
   468     tr3.appendChild(td3_2);
       
   469     table.appendChild(tr3);
       
   470   }
       
   471   
       
   472   // Done building the main part of the form
       
   473   form.appendChild(table);
       
   474   
       
   475   // Field: enable Diffie Hellman
       
   476   var lbl_dh = document.createElement('label');
       
   477   lbl_dh.style.fontSize = 'smaller';
       
   478   lbl_dh.style.display = 'block';
       
   479   lbl_dh.style.textAlign = 'center';
       
   480   var check_dh = document.createElement('input');
       
   481   check_dh.type = 'checkbox';
       
   482   // this onclick attribute changes the cookie whenever the checkbox or label is clicked
       
   483   check_dh.setAttribute('onclick', 'var ck = ( this.checked ) ? "enable" : "disable"; createCookie("diffiehellman_login", ck, 3650);');
       
   484   if ( readCookie('diffiehellman_login') != 'disable' )
       
   485     check_dh.setAttribute('checked', 'checked');
       
   486   check_dh.id = 'ajax_login_field_dh';
       
   487   lbl_dh.appendChild(check_dh);
       
   488   lbl_dh.innerHTML += $lang.get('user_login_ajax_check_dh');
       
   489   form.appendChild(lbl_dh);
       
   490   
       
   491   div.appendChild(form);
       
   492   
       
   493   // Diagnostic / help links
       
   494   // (only displayed in login, not in re-auth)
       
   495   if ( logindata.user_level == USER_LEVEL_MEMBER )
       
   496   {
       
   497     form.style.marginBottom = '10px';
       
   498     var links = document.createElement('small');
       
   499     links.style.display = 'block';
       
   500     links.style.textAlign = 'center';
       
   501     links.innerHTML = '';
       
   502     if ( !show_captcha )
       
   503       links.innerHTML += $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title) }) + '<br />';
       
   504     // Always shown
       
   505     links.innerHTML += $lang.get('user_login_ajax_link_forgotpass', { forgotpass_link: makeUrlNS('Special', 'PasswordReset') }) + '<br />';
       
   506     if ( !show_captcha )
       
   507       links.innerHTML += $lang.get('user_login_createaccount_blurb', { reg_link: makeUrlNS('Special', 'Register') });
       
   508     div.appendChild(links);
       
   509   }
       
   510   
       
   511   // Insert the entire form into the login window
       
   512   logindata.mb_inner.innerHTML = '';
       
   513   logindata.mb_inner.appendChild(div);
       
   514   
       
   515   // Post operations: field focus
       
   516   if ( data.username )
       
   517     f_password.focus();
       
   518   else
       
   519     f_username.focus();
       
   520   
       
   521   // Post operations: show captcha window
       
   522   if ( show_captcha )
       
   523     ajaxShowCaptcha(show_captcha);
       
   524   
       
   525   // Post operations: stash encryption keys and All That Jazz(TM)
       
   526   logindata.key_aes = data.aes_key;
       
   527   logindata.key_dh = data.dh_public_key;
       
   528   logindata.captcha_hash = show_captcha;
       
   529   
       
   530   // Are we locked out? If so simulate an error and disable the controls
       
   531   if ( data.lockout_info.lockout_policy == 'lockout' && data.locked_out )
       
   532   {
       
   533     f_username.setAttribute('disabled', 'disabled');
       
   534     f_password.setAttribute('disabled', 'disabled');
       
   535     var fake_packet = {
       
   536       error_code: 'locked_out',
       
   537       respawn_info: data
       
   538     };
       
   539     ajaxLoginShowFriendlyError(fake_packet);
       
   540   }
       
   541 }
       
   542 
       
   543 function ajaxLoginSubmitForm(real, username, password, captcha)
       
   544 {
       
   545   // Perform AES test to make sure it's all working
       
   546   if ( !aes_self_test() )
       
   547   {
       
   548     alert('BUG: AES self-test failed');
       
   549     login_cache.mb_object.destroy();
       
   550     return false;
       
   551   }
       
   552   // Hide the error message and captcha
       
   553   if ( document.getElementById('ajax_login_error_box') )
       
   554   {
       
   555     document.getElementById('ajax_login_error_box').parentNode.removeChild(document.getElementById('ajax_login_error_box'));
       
   556   }
       
   557   if ( document.getElementById('autoCaptcha') )
       
   558   {
       
   559     var to = fly_out_top(document.getElementById('autoCaptcha'), false, true);
       
   560     setTimeout(function() {
       
   561         var d = document.getElementById('autoCaptcha');
       
   562         d.parentNode.removeChild(d);
       
   563       }, to);
       
   564   }
       
   565   // Encryption: preprocessor
       
   566   if ( real )
       
   567   {
       
   568     var do_dh = true;
       
   569   }
       
   570   else if ( document.getElementById('ajax_login_field_dh') )
       
   571   {
       
   572     var do_dh = document.getElementById('ajax_login_field_dh').checked;
       
   573   }
       
   574   else
       
   575   {
       
   576     // The user probably clicked ok when the form wasn't in there.
       
   577     return false;
       
   578   }
       
   579   if ( !username )
       
   580   {
       
   581     var username = document.getElementById('ajax_login_field_username').value;
       
   582   }
       
   583   if ( !password )
       
   584   {
       
   585     var password = document.getElementById('ajax_login_field_password').value;
       
   586   }
       
   587   if ( !captcha && document.getElementById('ajax_login_field_captcha') )
       
   588   {
       
   589     var captcha = document.getElementById('ajax_login_field_captcha').value;
       
   590   }
       
   591   
       
   592   if ( do_dh )
       
   593   {
       
   594     ajaxLoginSetStatus(AJAX_STATUS_GENERATING_KEY);
       
   595     if ( !real )
       
   596     {
       
   597       // Wait while the browser updates the login window
       
   598       setTimeout(function()
       
   599         {
       
   600           ajaxLoginSubmitForm(true, username, password, captcha);
       
   601         }, 200);
       
   602       return true;
       
   603     }
       
   604     // Perform Diffie Hellman stuff
       
   605     var dh_priv = dh_gen_private();
       
   606     var dh_pub = dh_gen_public(dh_priv);
       
   607     var secret = dh_gen_shared_secret(dh_priv, logindata.key_dh);
       
   608     // secret_hash is used to verify that the server guesses the correct secret
       
   609     var secret_hash = hex_sha1(secret);
       
   610     // crypt_key is the actual AES key
       
   611     var crypt_key = (hex_sha256(secret)).substr(0, (keySizeInBits / 4));
       
   612   }
       
   613   else
       
   614   {
       
   615     var crypt_key = logindata.key_aes;
       
   616   }
       
   617   
       
   618   ajaxLoginSetStatus(AJAX_STATUS_LOGGING_IN);
       
   619   
       
   620   // Encrypt the password and username
       
   621   var userinfo = toJSONString({
       
   622       username: username,
       
   623       password: password
       
   624     });
       
   625   var crypt_key_ba = hexToByteArray(crypt_key);
       
   626   userinfo = stringToByteArray(userinfo);
       
   627   
       
   628   userinfo = rijndaelEncrypt(userinfo, crypt_key_ba, 'ECB');
       
   629   userinfo = byteArrayToHex(userinfo);
       
   630   // Encrypted username and password (serialized with JSON) are now in the userinfo string
       
   631   
       
   632   // Collect other needed information
       
   633   if ( logindata.captcha_hash )
       
   634   {
       
   635     var captcha_hash = logindata.captcha_hash;
       
   636     var captcha_code = captcha;
       
   637   }
       
   638   else
       
   639   {
       
   640     var captcha_hash = false;
       
   641     var captcha_code = false;
       
   642   }
       
   643   
       
   644   // Ship it across the 'net
       
   645   if ( do_dh )
       
   646   {
       
   647     var json_packet = {
       
   648       mode: 'login_dh',
       
   649       userinfo: userinfo,
       
   650       captcha_code: captcha_code,
       
   651       captcha_hash: captcha_hash,
       
   652       dh_public_key: logindata.key_dh,
       
   653       dh_client_key: dh_pub,
       
   654       dh_secret_hash: secret_hash,
       
   655       level: logindata.user_level
       
   656     }
       
   657   }
       
   658   else
       
   659   {
       
   660     var json_packet = {
       
   661       mode: 'login_aes',
       
   662       userinfo: userinfo,
       
   663       captcha_code: captcha_code,
       
   664       captcha_hash: captcha_hash,
       
   665       key_aes: hex_md5(crypt_key),
       
   666       level: logindata.user_level
       
   667     }
       
   668   }
       
   669   ajaxLoginPerformRequest(json_packet);
       
   670 }
       
   671 
       
   672 function ajaxLoginShowFriendlyError(response)
       
   673 {
       
   674   if ( !response.respawn_info )
       
   675     return false;
       
   676   if ( !response.error_code )
       
   677     return false;
       
   678   var text = ajaxLoginGetErrorText(response);
       
   679   if ( document.getElementById('ajax_login_error_box') )
       
   680   {
       
   681     // console.info('Reusing existing error-box');
       
   682     document.getElementById('ajax_login_error_box').innerHTML = text;
       
   683     return true;
       
   684   }
       
   685   
       
   686   // console.info('Drawing new error-box');
       
   687   
       
   688   // calculate position for the top of the box
       
   689   var mb_bottom = $('messageBoxButtons').Top() + $('messageBoxButtons').Height();
       
   690   // if the box isn't done flying in yet, just estimate
       
   691   if ( mb_bottom < ( getHeight() / 2 ) )
       
   692   {
       
   693     mb_bottom = ( getHeight() / 2 ) + 120;
       
   694   }
       
   695   var win_bottom = getHeight() + getScrollOffset();
       
   696   var top = mb_bottom + ( ( win_bottom - mb_bottom ) / 2 ) - 32;
       
   697   // left position = 0.2 * window_width, seeing as the box is 60% width this works hackishly but nice and quick
       
   698   var left = getWidth() * 0.2;
       
   699   
       
   700   // create the div
       
   701   var errbox = document.createElement('div');
       
   702   errbox.className = 'error-box-mini';
       
   703   errbox.style.position = 'absolute';
       
   704   errbox.style.width = '60%';
       
   705   errbox.style.top = top + 'px';
       
   706   errbox.style.left = left + 'px';
       
   707   errbox.innerHTML = text;
       
   708   errbox.id = 'ajax_login_error_box';
       
   709   
       
   710   var body = document.getElementsByTagName('body')[0];
       
   711   body.appendChild(errbox);
       
   712 }
       
   713 
       
   714 function ajaxLoginGetErrorText(response)
       
   715 {
       
   716   switch ( response.error_code )
       
   717   {
       
   718     default:
       
   719       return $lang.get('user_err_' + response.error_code);
       
   720       break;
       
   721     case 'locked_out':
       
   722       if ( response.respawn_info.lockout_info.lockout_policy == 'lockout' )
       
   723       {
       
   724         return $lang.get('user_err_locked_out', { 
       
   725                   lockout_threshold: response.respawn_info.lockout_info.lockout_threshold,
       
   726                   lockout_duration: response.respawn_info.lockout_info.lockout_duration,
       
   727                   time_rem: response.respawn_info.lockout_info.time_rem,
       
   728                   plural: ( response.respawn_info.lockout_info.time_rem == 1 ) ? '' : $lang.get('meta_plural'),
       
   729                   captcha_blurb: ''
       
   730                 });
       
   731         break;
       
   732       }
       
   733     case 'invalid_credentials':
       
   734       var base = $lang.get('user_err_invalid_credentials');
       
   735       if ( response.respawn_info.locked_out )
       
   736       {
       
   737         base += ' ';
       
   738         var captcha_blurb = '';
       
   739         switch(response.respawn_info.lockout_info.lockout_policy)
       
   740         {
       
   741           case 'captcha':
       
   742             captcha_blurb = $lang.get('user_err_locked_out_captcha_blurb');
       
   743             break;
       
   744           case 'lockout':
       
   745             break;
       
   746           default:
       
   747             base += 'WTF? Shouldn\'t be locked out with lockout policy set to disable.';
       
   748             break;
       
   749         }
       
   750         base += $lang.get('user_err_locked_out', { 
       
   751                   captcha_blurb: captcha_blurb,
       
   752                   lockout_threshold: response.respawn_info.lockout_info.lockout_threshold,
       
   753                   lockout_duration: response.respawn_info.lockout_info.lockout_duration,
       
   754                   time_rem: response.respawn_info.lockout_info.time_rem,
       
   755                   plural: ( response.respawn_info.lockout_info.time_rem == 1 ) ? '' : $lang.get('meta_plural')
       
   756                 });
       
   757       }
       
   758       else if ( response.respawn_info.lockout_info.lockout_policy == 'lockout' || response.respawn_info.lockout_info.lockout_policy == 'captcha' )
       
   759       {
       
   760         // if we have a lockout policy of captcha or lockout, then warn the user
       
   761         switch ( response.respawn_info.lockout_info.lockout_policy )
       
   762         {
       
   763           case 'captcha':
       
   764             base += $lang.get('user_err_invalid_credentials_lockout', { 
       
   765                 fails: response.respawn_info.lockout_info.lockout_fails,
       
   766                 lockout_threshold: response.respawn_info.lockout_info.lockout_threshold,
       
   767                 lockout_duration: response.respawn_info.lockout_info.lockout_duration
       
   768               });
       
   769             break;
       
   770           case 'lockout':
       
   771             break;
       
   772         }
       
   773       }
       
   774       return base;
       
   775       break;
       
   776   }
       
   777 }
       
   778