author | Dan |
Tue, 30 Mar 2010 11:37:00 -0400 | |
changeset 1231 | 4797a4a88533 |
parent 1227 | bdac73ed481e |
child 1264 | 28c82f292a52 |
permissions | -rw-r--r-- |
/* * AJAX-based intelligent login interface */ /* * FRONTEND */ /** * Performs a logon as a regular member. */ window.ajaxLogonToMember = function() { // IE <6 pseudo-compatibility if ( KILL_SWITCH ) return true; if ( auth_level >= USER_LEVEL_MEMBER ) return true; ajaxLoginInit(function(k) { if ( on_main_page && main_page_members != physical_title ) { window.location = makeUrl(main_page_members); } else { window.location.reload(); } }, USER_LEVEL_MEMBER); } /** * Authenticates to the highest level the current user is allowed to go to. */ window.ajaxLogonToElev = function() { if ( auth_level == user_level ) return true; ajaxLoginInit(function(k) { ENANO_SID = k; var url = String(' ' + window.location).substr(1); url = append_sid(url); window.location = url; }, user_level); } /* * BACKEND */ /** * Holding object for various AJAX authentication information. * @var object */ var logindata = {}; /** * Path to the image used to indicate loading progress * @var string */ if ( !ajax_login_loadimg_path ) var ajax_login_loadimg_path = false; if ( !ajax_login_successimg_path ) var ajax_login_successimg_path = false; if ( !ajax_login_lockimg_path ) var ajax_login_lockimg_path = false; /** * Status variables * @var int */ var AJAX_STATUS_LOADING_KEY = 1; var AJAX_STATUS_GENERATING_KEY = 2; var AJAX_STATUS_LOGGING_IN = 3; var AJAX_STATUS_SUCCESS = 4; var AJAX_STATUS_ERROR = 5; var AJAX_STATUS_DESTROY = 65535; /** * State constants * @var int */ var AJAX_STATE_EARLY_INIT = 1; var AJAX_STATE_LOADING_KEY = 2; /** * Switch to decide if DiffieHellman shows a "browser incompatible" error * @var bool */ var ajax_login_prevent_dh = ( IE && !IE_8 ) || ( is_iPhone && !is_iPhone_3 ); /** * Performs the AJAX request to get an encryption key and from there spawns the login form. * @param function The function that will be called once authentication completes successfully. * @param int The security level to authenticate at - see http://docs.enanocms.org/Help:Appendix_B */ window.ajaxLoginInit = function(call_on_finish, user_level) { load_component(['messagebox', 'flyin', 'fadefilter', 'jquery', 'jquery-ui', 'l10n', 'crypto']); logindata = {}; var title = ( user_level > USER_LEVEL_MEMBER ) ? $lang.get('user_login_ajax_prompt_title_elev') : $lang.get('user_login_ajax_prompt_title'); logindata.mb_object = new MessageBox(MB_OKCANCEL | MB_ICONLOCK, title, ''); // // Cancel function: called when the "Cancel" button is clicked // logindata.mb_object.onclick['Cancel'] = function() { // Hide the error message, if any $('#ajax_login_error_box').remove(); // Hide the captcha, if any if ( document.getElementById('autoCaptcha') ) { var to = fly_out_top(document.getElementById('autoCaptcha'), false, true); setTimeout(function() { var d = document.getElementById('autoCaptcha'); d.parentNode.removeChild(d); }, to); } // Ask the server to delete the encryption key we're using ajaxLoginPerformRequest({ mode: 'clean_key', key_aes: logindata.key_aes, key_dh: logindata.key_dh }); }; // Clicking OK will not cause the box to destroy, as this function returns true. logindata.mb_object.onbeforeclick['OK'] = function() { // Just call the submitter and let it take care of everything ajaxLoginSubmitForm(); return true; } // Fetch the inner content area logindata.mb_inner = document.getElementById('messageBox').getElementsByTagName('div')[0]; // Initialize state logindata.showing_status = false; logindata.user_level = user_level; logindata.successfunc = call_on_finish; // Build the "loading" window ajaxLoginSetStatus(AJAX_STATUS_LOADING_KEY); // Request the key ajaxLoginPerformRequest({ mode: 'getkey' }); } /** * For compatibility only. Really, folks, it's ajaxLoginInit. If you need a * mnemonic device, use "two 'in's." */ window.ajaxLogonInit = function(call_on_finish, user_level) { return ajaxLoginInit(call_on_finish, user_level); } /** * Sets the contents of the AJAX login window to the appropriate status message. * @param int One of AJAX_STATUS_* constants */ window.ajaxLoginSetStatus = function(status) { if ( !logindata.mb_inner ) return false; if ( logindata.showing_status ) { var div = document.getElementById('ajax_login_status'); if ( div ) logindata.mb_inner.removeChild(div); } switch(status) { case AJAX_STATUS_LOADING_KEY: // Create the status div var div = document.createElement('div'); div.id = 'ajax_login_status'; div.style.marginTop = '10px'; div.style.textAlign = 'center'; // The circly ball ajaxy image + status message var status_msg = $lang.get('user_login_ajax_fetching_key'); // Insert the status message div.appendChild(document.createTextNode(status_msg)); // Append a br or two to space things properly div.appendChild(document.createElement('br')); div.appendChild(document.createElement('br')); var img = document.createElement('img'); img.src = ( ajax_login_loadimg_path ) ? ajax_login_loadimg_path : scriptPath + '/images/loading-big.gif'; div.appendChild(img); // Another coupla brs div.appendChild(document.createElement('br')); div.appendChild(document.createElement('br')); // The link to the full login form var small = document.createElement('small'); small.innerHTML = $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title) }); div.appendChild(small); // Insert the entire message into the login window logindata.mb_inner.innerHTML = ''; logindata.mb_inner.appendChild(div); break; case AJAX_STATUS_GENERATING_KEY: // Create the status div var div = document.createElement('div'); div.id = 'ajax_login_status'; div.style.marginTop = '10px'; div.style.textAlign = 'center'; // The circly ball ajaxy image + status message var status_msg = $lang.get('user_login_ajax_generating_key'); // Insert the status message div.appendChild(document.createTextNode(status_msg)); // Append a br or two to space things properly div.appendChild(document.createElement('br')); div.appendChild(document.createElement('br')); var img = document.createElement('img'); img.src = ( ajax_login_lockimg_path ) ? ajax_login_lockimg_path : scriptPath + '/images/lock48.png'; div.appendChild(img); // Another coupla brs div.appendChild(document.createElement('br')); div.appendChild(document.createElement('br')); // The link to the full login form var small = document.createElement('small'); small.innerHTML = $lang.get('user_login_ajax_link_fullform_dh', { link_full_form: makeUrlNS('Special', 'Login/' + title) }); div.appendChild(small); // Insert the entire message into the login window logindata.mb_inner.innerHTML = ''; logindata.mb_inner.appendChild(div); break; case AJAX_STATUS_LOGGING_IN: // Create the status div var div = document.createElement('div'); div.id = 'ajax_login_status'; div.style.marginTop = '10px'; div.style.textAlign = 'center'; // The circly ball ajaxy image + status message var status_msg = $lang.get('user_login_ajax_loggingin'); // Insert the status message div.appendChild(document.createTextNode(status_msg)); // Append a br or two to space things properly div.appendChild(document.createElement('br')); div.appendChild(document.createElement('br')); var img = document.createElement('img'); img.src = ( ajax_login_loadimg_path ) ? ajax_login_loadimg_path : scriptPath + '/images/loading-big.gif'; div.appendChild(img); // Insert the entire message into the login window logindata.mb_inner.innerHTML = ''; logindata.mb_inner.appendChild(div); break; case AJAX_STATUS_SUCCESS: // Create the status div var div = document.createElement('div'); div.id = 'ajax_login_status'; div.style.marginTop = '10px'; div.style.textAlign = 'center'; // The circly ball ajaxy image + status message var status_msg = $lang.get('user_login_success_short'); // Insert the status message div.appendChild(document.createTextNode(status_msg)); // Append a br or two to space things properly div.appendChild(document.createElement('br')); div.appendChild(document.createElement('br')); var img = document.createElement('img'); img.src = ( ajax_login_successimg_path ) ? ajax_login_successimg_path : scriptPath + '/images/check.png'; div.appendChild(img); // Insert the entire message into the login window logindata.mb_inner.innerHTML = ''; logindata.mb_inner.appendChild(div); break; case AJAX_STATUS_ERROR: // Create the status div var div = document.createElement('div'); div.id = 'ajax_login_status'; div.style.marginTop = '10px'; div.style.textAlign = 'center'; // The circly ball ajaxy image + status message var status_msg = $lang.get('user_login_ajax_err_crypto'); // Insert the status message div.appendChild(document.createTextNode(status_msg)); // Append a br or two to space things properly div.appendChild(document.createElement('br')); div.appendChild(document.createElement('br')); var img = document.createElement('img'); img.src = ( ajax_login_successimg_path ) ? ajax_login_successimg_path : scriptPath + '/images/checkbad.png'; div.appendChild(img); // Append a br or two to space things properly div.appendChild(document.createElement('br')); div.appendChild(document.createElement('br')); // The circly ball ajaxy image + status message var detail_msg = $lang.get('user_login_ajax_err_crypto_details'); var full_link = $lang.get('user_login_ajax_err_crypto_link'); var link = document.createElement('a'); link.href = makeUrlNS('Special', 'Login/' + title, 'level=' + logindata.user_level, true); link.appendChild(document.createTextNode(full_link)); var span = document.createElement('span'); span.style.fontSize = 'smaller'; // Insert the message span.appendChild(document.createTextNode(detail_msg + ' ')); span.appendChild(link); div.appendChild(span); // Insert the entire message into the login window logindata.mb_inner.innerHTML = ''; logindata.mb_inner.appendChild(div); break; default: eval(setHook('login_set_status')); break; case AJAX_STATUS_DESTROY: case null: case undefined: logindata.showing_status = false; return; break; } logindata.showing_status = true; } /** * Performs an AJAX logon request to the server and calls ajaxLoginProcessResponse() on the result. * @param object JSON packet to send * @param function Optional function to call on the response as well. */ window.ajaxLoginPerformRequest = function(json, _hookfunc) { json = toJSONString(json); json = ajaxEscape(json); var hookfunc = typeof(_hookfunc) == 'function' ? _hookfunc : false; ajaxPost(makeUrlNS('Special', 'Login/action.json'), 'r=' + json, function(ajax) { if ( ajax.readyState == 4 && ajax.status == 200 ) { // parse response var response = String(ajax.responseText + ''); if ( !check_json_response(response) ) { handle_invalid_json(response); return false; } response = parseJSON(response); ajaxLoginProcessResponse(response, hookfunc); } }, true); } /** * Processes a response from the login server * @param object JSON response */ window.ajaxLoginProcessResponse = function(response, hookfunc) { // Did the server send a plaintext error? if ( response.mode == 'error' ) { if ( logindata.mb_object ) { logindata.mb_object.destroy(); var error_msg = $lang.get('user_' + ( response.error.toLowerCase() )); new MessageBox(MB_ICONSTOP | MB_OK, $lang.get('user_err_login_generic_title'), error_msg); } else { alert(response.error); } return false; } // Main mode switch switch ( response.mode ) { case 'initial': // Rid ourselves of any loading windows ajaxLoginSetStatus(AJAX_STATUS_DESTROY); // show any errors ajaxLoginShowFriendlyError(response); // The server wants us to build the login form, all the information is there ajaxLoginBuildForm(response); break; case 'login_success': ajaxLoginSetStatus(AJAX_STATUS_SUCCESS); logindata.successfunc(response.key, response); break; case 'reset_pass_used': // We logged in with a temporary password. Prompt the user to go to the temp password page and // reset their real password. If they click no, treat it as a login failure, as no session key // is actually issued when this type of login is performed. var conf = confirm($lang.get('user_login_ajax_msg_used_temp_pass')); if ( conf ) { var url = makeUrlNS('Special', 'PasswordReset/stage2/' + response.user_id + '/' + response.temp_password); window.location = url; break; } // else, treat as a failure default: // Rid ourselves of any loading windows ajaxLoginSetStatus(AJAX_STATUS_DESTROY); document.getElementById('messageBox').style.backgroundColor = '#C0C0C0'; var mb_parent = document.getElementById('messageBox').parentNode; $(mb_parent).effect("shake", {}, 200); setTimeout(function() { document.getElementById('messageBox').style.backgroundColor = '#FFF'; console.debug(response); ajaxLoginShowFriendlyError(response); ajaxLoginBuildForm(response); }, 2500); break; case 'logout_success': if ( ENANO_SID ) { ajaxLoginReplaceSIDInline(false, ENANO_SID, USER_LEVEL_MEMBER); } break; case 'noop': break; } if ( hookfunc ) { hookfunc(response); } } /* * RESPONSE HANDLERS */ /** * Builds the login form. * @param object Metadata to build off of */ window.ajaxLoginBuildForm = function(data) { // let's hope this effectively preloads the image... var _1 = document.createElement('img'); _1.src = ( ajax_login_successimg_path ) ? ajax_login_successimg_path : scriptPath + '/images/check.png'; var _2 = document.createElement('img'); _2.src = ( ajax_login_lockimg_path ) ? ajax_login_lockimg_path : scriptPath + '/images/lock48.png'; var div = document.createElement('div'); div.id = 'ajax_login_form'; var show_captcha = ( data.lockout.active && data.lockout.policy == 'captcha' ) ? data.lockout.captcha : false; // text displayed on re-auth if ( logindata.user_level > USER_LEVEL_MEMBER ) { div.innerHTML += $lang.get('user_login_ajax_prompt_body_elev') + '<br /><br />'; } // Create the form var form = document.createElement('form'); form.action = 'javascript:void(ajaxLoginSubmitForm());'; form.onsubmit = function() { ajaxLoginSubmitForm(); return false; } if ( IE ) { form.style.marginTop = '-20px'; } // Using tables to wrap form elements because it results in a // more visually appealing form. Yes, tables suck. I don't really // care - they make forms look good. var table = document.createElement('table'); table.style.margin = '0 auto'; // Field - username var tr1 = document.createElement('tr'); var td1_1 = document.createElement('td'); td1_1.appendChild(document.createTextNode($lang.get('user_login_field_username') + ':')); tr1.appendChild(td1_1); var td1_2 = document.createElement('td'); var f_username = document.createElement('input'); f_username.id = 'ajax_login_field_username'; f_username.name = 'ajax_login_field_username'; f_username.type = 'text'; f_username.size = '25'; if ( data.username ) f_username.value = data.username; td1_2.appendChild(f_username); tr1.appendChild(td1_2); table.appendChild(tr1); // Field - password var tr2 = document.createElement('tr'); var td2_1 = document.createElement('td'); td2_1.appendChild(document.createTextNode($lang.get('user_login_field_password') + ':')); tr2.appendChild(td2_1); var td2_2 = document.createElement('td'); var f_password = document.createElement('input'); f_password.id = 'ajax_login_field_password'; f_password.name = 'ajax_login_field_username'; f_password.type = 'password'; f_password.size = '25'; if ( !show_captcha ) { f_password.onkeyup = function(e) { if ( !e ) e = window.event; if ( !e && IE ) return true; if ( e.keyCode == 13 ) { ajaxLoginSubmitForm(); } } } td2_2.appendChild(f_password); tr2.appendChild(td2_2); table.appendChild(tr2); // Field - captcha if ( show_captcha ) { var tr3 = document.createElement('tr'); var td3_1 = document.createElement('td'); td3_1.appendChild(document.createTextNode($lang.get('user_login_field_captcha') + ':')); tr3.appendChild(td3_1); var td3_2 = document.createElement('td'); var f_captcha = document.createElement('input'); f_captcha.id = 'ajax_login_field_captcha'; f_captcha.name = 'ajax_login_field_username'; f_captcha.type = 'text'; f_captcha.size = '25'; f_captcha.onkeyup = function(e) { if ( !e ) e = window.event; if ( !e.keyCode ) return true; if ( e.keyCode == 13 ) { ajaxLoginSubmitForm(); } } td3_2.appendChild(f_captcha); tr3.appendChild(td3_2); table.appendChild(tr3); } // ok, this is a compatibility hack data.locked_out = { locked_out: data.lockout.active }; // hook for the login form eval(setHook('login_build_form')); delete(data.locked_out); // Done building the main part of the form form.appendChild(table); // Checkbox container var boxen = document.createElement('div'); boxen.style.textAlign = 'center'; boxen.style.padding = '7px 0'; // Field: remember login if ( logindata.user_level <= USER_LEVEL_MEMBER ) { var lbl_remember = document.createElement('label'); lbl_remember.style.fontSize = 'smaller'; lbl_remember.style.textAlign = 'center'; // figure out what text to put in the "remember me" checkbox // infinite session length? if ( data.extended_time == 0 ) { // yes, infinite var txt_remember = $lang.get('user_login_ajax_check_remember_infinite'); } else { if ( data.extended_time % 7 == 0 ) { // number of days is a multiple of 7 // use weeks as our unit var sess_time = data.extended_time / 7; var unit = 'week'; } else { // use days as our unit var sess_time = data.extended_time; var unit = 'day'; } // more than one week or day? if ( sess_time != 1 ) unit += 's'; // assemble the string var txt_remember = $lang.get('user_login_ajax_check_remember', { session_length: sess_time, length_units: $lang.get('etc_unit_' + unit) }); } var check_remember = document.createElement('input'); check_remember.type = 'checkbox'; // this onclick attribute changes the cookie whenever the checkbox or label is clicked check_remember.setAttribute('onclick', 'var ck = ( this.checked ) ? "enable" : "disable"; createCookie("login_remember", ck, 3650);'); if ( readCookie('login_remember') != 'disable' ) check_remember.setAttribute('checked', 'checked'); check_remember.id = 'ajax_login_field_remember'; lbl_remember.appendChild(check_remember); lbl_remember.innerHTML += ' ' + txt_remember; boxen.appendChild(lbl_remember); } var bullet = document.createElement('span'); bullet.innerHTML = ' '; bullet.style.fontSize = '12pt'; bullet.style.borderRight = '1px solid #aaa'; bullet.style.margin = '0 6px 0 4px'; // Field: enable Diffie Hellman if ( ajax_login_prevent_dh ) { if ( logindata.user_level <= USER_LEVEL_MEMBER ) // only show this if both checkboxes are visible boxen.appendChild(bullet); var lbl_dh = document.createElement('span'); lbl_dh.style.fontSize = 'smaller'; lbl_dh.style.textAlign = 'center'; lbl_dh.innerHTML = $lang.get('user_login_ajax_check_dh_ie'); boxen.appendChild(lbl_dh); } else if ( !data.crypto.dh_enable ) { // create hidden control - server requested that DiffieHellman be disabled (usually means not supported) var check_dh = document.createElement('input'); check_dh.type = 'hidden'; check_dh.id = 'ajax_login_field_dh'; boxen.appendChild(check_dh); } else { if ( logindata.user_level <= USER_LEVEL_MEMBER ) // only show this if both checkboxes are visible boxen.appendChild(bullet); var lbl_dh = document.createElement('label'); lbl_dh.style.fontSize = 'smaller'; lbl_dh.style.textAlign = 'center'; var check_dh = document.createElement('input'); check_dh.type = 'checkbox'; // this onclick attribute changes the cookie whenever the checkbox or label is clicked check_dh.setAttribute('onclick', 'var ck = ( this.checked ) ? "enable" : "disable"; createCookie("diffiehellman_login", ck, 3650);'); if ( readCookie('diffiehellman_login') != 'disable' ) check_dh.setAttribute('checked', 'checked'); check_dh.id = 'ajax_login_field_dh'; lbl_dh.appendChild(check_dh); lbl_dh.innerHTML += ' ' + $lang.get('user_login_ajax_check_dh'); boxen.appendChild(lbl_dh); } form.appendChild(boxen); if ( IE ) { div.innerHTML += form.outerHTML; } else { div.appendChild(form); } // Diagnostic / help links // (only displayed in login, not in re-auth) if ( logindata.user_level == USER_LEVEL_MEMBER ) { var links = document.createElement('small'); links.style.display = 'block'; links.style.textAlign = 'center'; links.innerHTML = ''; if ( !show_captcha ) links.innerHTML += $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title) }) + ' • '; // Always shown links.innerHTML += $lang.get('user_login_ajax_link_forgotpass', { forgotpass_link: makeUrlNS('Special', 'PasswordReset') }) + ' • '; if ( !show_captcha ) links.innerHTML += $lang.get('user_login_ajax_createaccount_blurb', { reg_link: makeUrlNS('Special', 'Register') }); div.appendChild(links); } // Insert the entire form into the login window logindata.mb_inner.innerHTML = ''; logindata.mb_inner.appendChild(div); // Post operations: field focus setTimeout( function() { if ( logindata.loggedin_username ) document.getElementById('ajax_login_field_password').focus(); else document.getElementById('ajax_login_field_username').focus(); }, 750); // Post operations: show captcha window if ( show_captcha ) { ajaxShowCaptcha(show_captcha); } // Post operations: stash encryption keys and All That Jazz(TM) logindata.key_aes = data.crypto.aes_key; logindata.key_dh = data.crypto.dh_public_key; logindata.captcha_hash = show_captcha; logindata.loggedin_username = data.username; // If policy is lockout, also disable controls if ( data.lockout.policy == 'lockout' && data.lockout.active ) { f_username.setAttribute('disabled', 'disabled'); f_password.setAttribute('disabled', 'disabled'); } } window.ajaxLoginSubmitForm = function(real, username, password, captcha, remember) { // Perform AES test to make sure it's all working if ( !aes_self_test() ) { alert('BUG: AES self-test failed'); login_cache.mb_object.destroy(); return false; } // Hide the error message and captcha if ( document.getElementById('ajax_login_error_box') ) { document.getElementById('ajax_login_error_box').parentNode.removeChild(document.getElementById('ajax_login_error_box')); } if ( document.getElementById('autoCaptcha') ) { var to = fly_out_top(document.getElementById('autoCaptcha'), false, true); setTimeout(function() { var d = document.getElementById('autoCaptcha'); d.parentNode.removeChild(d); }, to); } // "Remember session" switch if ( typeof(remember) == 'boolean' ) { var remember_session = remember; } else { if ( document.getElementById('ajax_login_field_remember') ) { var remember_session = ( document.getElementById('ajax_login_field_remember').checked ) ? true : false; } else { var remember_session = false; } } // Encryption: preprocessor if ( real ) { var do_dh = true; } else if ( document.getElementById('ajax_login_field_dh') ) { var do_dh = document.getElementById('ajax_login_field_dh').checked; } else { if ( ajax_login_prevent_dh ) { // IE/MobileSafari doesn't have this control, continue silently IF the rest // of the login form is there if ( !document.getElementById('ajax_login_field_username') ) { return false; } } else { // The user probably clicked ok when the form wasn't in there. return false; } } if ( typeof(username) != 'string' ) { var username = document.getElementById('ajax_login_field_username').value; } if ( typeof(password) != 'string' ) { var password = document.getElementById('ajax_login_field_password').value; } if ( !captcha && document.getElementById('ajax_login_field_captcha') ) { var captcha = document.getElementById('ajax_login_field_captcha').value; } // Only run early submit hook once if ( !window.logindata.early_submit_run ) eval(setHook('login_submit_early')); window.logindata.early_submit_run = true; try { if ( do_dh ) { ajaxLoginSetStatus(AJAX_STATUS_GENERATING_KEY); if ( !real ) { // Wait while the browser updates the login window setTimeout(function() { ajaxLoginSubmitForm(true, username, password, captcha, remember_session); }, 20); return true; } var dh_start = (new Date()).getTime(); // Perform Diffie Hellman stuff var dh_priv = dh_gen_private(); var dh_pub = dh_gen_public(dh_priv); var secret = dh_gen_shared_secret(dh_priv, logindata.key_dh); // secret_hash is used to verify that the server guesses the correct secret var secret_hash = hex_sha1(secret); // crypt_key is the actual AES key var crypt_key = (hex_sha256(secret)).substr(0, (keySizeInBits / 4)); var dh_time = (new Date()).getTime() - dh_start; console.debug("DH: complete, time = %dms", dh_time); } else { var crypt_key = logindata.key_aes; } ajaxLoginSetStatus(AJAX_STATUS_LOGGING_IN); // Encrypt the password and username var userinfo = { username: username, password: password }; eval(setHook('login_build_userinfo')); userinfo = toJSONString(userinfo); var crypt_key_ba = hexToByteArray(crypt_key); userinfo = stringToByteArray(userinfo); userinfo = rijndaelEncrypt(userinfo, crypt_key_ba, 'ECB'); userinfo = byteArrayToHex(userinfo); // Encrypted username and password (serialized with JSON) are now in the userinfo string // Collect other needed information if ( logindata.captcha_hash ) { var captcha_hash = logindata.captcha_hash; var captcha_code = captcha; } else { var captcha_hash = false; var captcha_code = false; } // Ship it across the 'net if ( do_dh ) { var json_packet = { mode: 'login_dh', userinfo: userinfo, captcha_code: captcha_code, captcha_hash: captcha_hash, dh_public_key: logindata.key_dh, dh_client_key: dh_pub, dh_secret_hash: secret_hash, level: logindata.user_level, remember: remember_session } } else { var json_packet = { mode: 'login_aes', userinfo: userinfo, captcha_code: captcha_code, captcha_hash: captcha_hash, key_aes: hex_md5(crypt_key), level: logindata.user_level, remember: remember_session } } } catch(e) { ajaxLoginSetStatus(AJAX_STATUS_ERROR); console.error('Exception caught in login process; backtrace follows'); console.debug(e); return false; } // reset this... window.logindata.early_submit_run = false; ajaxLoginPerformRequest(json_packet); } window.ajaxLoginShowFriendlyError = function(response) { eval(setHook('ajax_login_process_error')); var text = ajaxLoginGetErrorText(response); if ( text == false ) return true; if ( document.getElementById('ajax_login_error_box') ) { // console.info('Reusing existing error-box'); document.getElementById('ajax_login_error_box').innerHTML = text; return true; } // console.info('Drawing new error-box'); // calculate position for the top of the box var mb_bottom = $dynano('messageBoxButtons').Top() + $dynano('messageBoxButtons').Height(); // if the box isn't done flying in yet, just estimate if ( mb_bottom < ( getHeight() / 2 ) ) { mb_bottom = ( getHeight() / 2 ) + 120; } var win_bottom = getHeight() + getScrollOffset(); var top = mb_bottom + ( ( win_bottom - mb_bottom ) / 2 ) - 32; // left position = 0.2 * window_width, seeing as the box is 60% width this works hackishly but nice and quick var left = getWidth() * 0.2; // create the div var errbox = document.createElement('div'); errbox.className = 'error-box-mini'; errbox.style.position = 'absolute'; errbox.style.width = '60%'; errbox.style.top = top + 'px'; errbox.style.left = left + 'px'; errbox.style.zIndex = getHighestZ(); errbox.innerHTML = text; errbox.id = 'ajax_login_error_box'; var body = document.getElementsByTagName('body')[0]; body.appendChild(errbox); } window.ajaxLoginGetErrorText = function(response) { if ( response.lockout ) { // set this pluralality thing response.lockout.plural = response.lockout.time_rem == 1 ? '' : $lang.get('meta_plural'); } if ( response.mode == 'initial' ) { // Just showing the box for the first time. If there's an error now, it's based on a preexisting lockout. if ( response.lockout.active ) { return $lang.get('user_err_locked_out_initial_' + response.lockout.policy, response.lockout); } return false; } else { // An attempt was made. switch(response.mode) { case 'login_failure': // Generic login user error. var error = '', x; if ( (x = $lang.get(response.error)) != response.error || ! (/^[a-z0-9_]+/).test(response.error) ) error = x; else error = $lang.get('user_err_' + response.error); if ( response.lockout.active && response.lockout.policy == 'lockout' ) { // Lockout enforcement was just activated. return $lang.get('user_err_locked_out_initial_' + response.lockout.policy, response.lockout); } else if ( response.lockout.policy != 'disable' && !response.lockout.active && response.lockout.fails > 0 ) { // Lockout is in a warning state. error += ' ' + $lang.get('user_err_invalid_credentials_' + response.lockout.policy, response.lockout); } return error; break; case 'api_error': // Error in the API. return $lang.get('user_err_login_generic_title') + ': ' + $lang.get('user_' + response.error.toLowerCase()); break; } } return typeof(response.error) == 'string' ? response.error : false; } window.ajaxShowCaptcha = function(code) { var mydiv = document.createElement('div'); mydiv.style.backgroundColor = '#FFFFFF'; mydiv.style.padding = '10px'; mydiv.style.position = 'absolute'; mydiv.style.top = '0px'; mydiv.id = 'autoCaptcha'; mydiv.style.zIndex = String( getHighestZ() + 1 ); var img = document.createElement('img'); img.onload = function() { if ( this.loaded ) return true; var mydiv = document.getElementById('autoCaptcha'); var width = getWidth(); var divw = $dynano(mydiv).Width(); var left = ( width / 2 ) - ( divw / 2 ); mydiv.style.left = left + 'px'; fly_in_top(mydiv, false, true); this.loaded = true; }; img.src = makeUrlNS('Special', 'Captcha/' + code); img.onclick = function() { this.src = this.src + '/a'; }; img.style.cursor = 'pointer'; mydiv.appendChild(img); domObjChangeOpac(0, mydiv); var body = document.getElementsByTagName('body')[0]; body.appendChild(mydiv); } window.ajaxInitLogout = function() { load_component(['messagebox', 'l10n', 'flyin', 'fadefilter', 'jquery', 'jquery-ui']); var title = $lang.get('user_logout_confirm_title'); var message = ( auth_level > USER_LEVEL_MEMBER ) ? $lang.get('user_logout_confirm_body_nelev') : $lang.get('user_logout_confirm_body_normal'); var buttons = []; buttons.push({ text: $lang.get('user_logout_confirm_btn_logout'), color: 'red', style: { fontWeight: 'bold' }, onclick: function() { miniPromptDestroy(this); window.location = makeUrlNS('Special', 'Logout/' + csrf_token + '/' + window.title); return false; } }); if ( auth_level > USER_LEVEL_MEMBER ) { buttons.push({ text: $lang.get('user_logout_confirm_btn_deauth'), color: 'blue', onclick: function() { var mp = miniPromptGetParent(this); var whitey = whiteOutMiniPrompt(mp); ajaxLoginPerformRequest({ mode: 'logout', level: auth_level, csrf_token: csrf_token }, function(response) { whiteOutReportSuccess(whitey); }); return false; } }); } buttons.push({ text: $lang.get('etc_cancel'), onclick: function() { miniPromptDestroy(this); return false; } }); miniPromptMessage({ title: title, message: message, buttons: buttons }); } window.mb_logout = function() { ajaxInitLogout(); } window.ajaxStartLogin = function() { ajaxLogonToMember(); } window.ajaxStartAdminLogin = function() { // IE <6 pseudo-compatibility if ( KILL_SWITCH ) return true; if ( auth_level < USER_LEVEL_ADMIN ) { ajaxLoginInit(function(k) { ENANO_SID = k; auth_level = USER_LEVEL_ADMIN; var loc = makeUrlNS('Special', 'Administration'); if ( (ENANO_SID + ' ').length > 1 ) window.location = loc; }, USER_LEVEL_ADMIN); return false; } var loc = makeUrlNS('Special', 'Administration'); window.location = loc; } window.ajaxAdminPage = function() { // IE <6 pseudo-compatibility if ( KILL_SWITCH ) return true; if ( auth_level < USER_LEVEL_ADMIN ) { ajaxPromptAdminAuth(function(k) { ENANO_SID = k; auth_level = USER_LEVEL_ADMIN; var loc = String(window.location + ''); window.location = append_sid(loc); var loc = makeUrlNS('Special', 'Administration', 'module=' + namespace_list['Admin'] + 'PageManager&source=ajax&page_id=' + ajaxEscape(title)); if ( (ENANO_SID + ' ').length > 1 ) window.location = loc; }, 9); return false; } var loc = makeUrlNS('Special', 'Administration', 'module=' + namespace_list['Admin'] + 'PageManager&source=ajax&page_id=' + ajaxEscape(title)); window.location = loc; } window.ajaxLoginNavTo = function(namespace, page_id, min_level, get) { // IE <6 pseudo-compatibility if ( KILL_SWITCH ) return true; void(namespace); void(page_id); get = get || false; if ( auth_level < min_level ) { ajaxPromptAdminAuth(function(k) { ENANO_SID = k; auth_level = min_level; var loc = makeUrlNS(namespace, page_id, get); if ( (ENANO_SID + ' ').length > 1 ) window.location = loc; }, min_level); return false; } var loc = makeUrlNS(namespace, page_id, get); window.location = loc; } window.ajaxAdminUser = function(username) { // IE <6 pseudo-compatibility if ( KILL_SWITCH ) return true; if ( auth_level < USER_LEVEL_ADMIN ) { ajaxPromptAdminAuth(function(k) { ENANO_SID = k; auth_level = USER_LEVEL_ADMIN; var loc = String(window.location + ''); window.location = append_sid(loc); var loc = makeUrlNS('Special', 'Administration', 'module=' + namespace_list['Admin'] + 'UserManager&src=get&user=' + ajaxEscape(username)); if ( (ENANO_SID + ' ').length > 1 ) window.location = loc; }, 9); return false; } var loc = makeUrlNS('Special', 'Administration', 'module=' + namespace_list['Admin'] + 'UserManager&src=get&user=' + ajaxEscape(username)); window.location = loc; } window.ajaxDynamicReauth = function(adminpage, level) { if ( auth_level < USER_LEVEL_MEMBER ) { ajaxStartLogin(); return false; } var old_sid = ENANO_SID; var targetpage = adminpage; if ( !level ) { level = USER_LEVEL_ADMIN; } ajaxLogonInit(function(k, response) { ajaxLoginReplaceSIDInline(k, old_sid, level); window.user_id = response.user_id; window.user_level = response.user_level; mb_current_obj.destroy(); if ( typeof(targetpage) == 'string' ) { ajaxPage(targetpage); } else if ( typeof(targetpage) == 'function' ) { targetpage(k); } }, level); if ( typeof(adminpage) == 'string' ) { ajaxLoginShowFriendlyError({ error_code: 'admin_session_timed_out', respawn_info: {} }); } } window.ajaxRenewSession = function() { ajaxDynamicReauth(false); } window.ajaxTrashElevSession = function() { load_component(['messagebox', 'fadefilter', 'l10n', 'flyin', 'jquery', 'jquery-ui']); miniPromptMessage({ title: $lang.get('user_logout_confirm_title_elev'), message: $lang.get('user_logout_confirm_body_elev'), buttons: [ { text: $lang.get('user_logout_confirm_btn_logout'), color: 'red', style: { fontWeight: 'bold' }, onclick: function() { ajaxLoginPerformRequest({ mode: 'logout', level: auth_level, csrf_token: csrf_token }); miniPromptDestroy(this); } }, { text: $lang.get('etc_cancel'), onclick: function() { miniPromptDestroy(this); } } ] }); } /** * Take an SID and patch all internal links on the page. * @param string New key. If false, removes keys from the page. * @param string Old key. If false, only appends the new SID (more work as it uses DOM, use when dynamically going up to elevated) * @param int New level, not a huge deal but sets auth_level. Try to specify it as some functions depend on it. */ window.ajaxLoginReplaceSIDInline = function(key, oldkey, level) { var host = String(window.location.hostname); var exp = new RegExp('^https?://' + host.replace('.', '\.') + contentPath.replace('.', '\.'), 'g'); var rexp = new RegExp('^https?://' + host.replace('.', '\.'), 'g'); if ( key ) { if ( oldkey ) { var body = document.getElementsByTagName('body')[0]; var replace = new RegExp(oldkey, 'g'); body.innerHTML = body.innerHTML.replace(replace, key); ENANO_SID = key; } else { // append SID to all internal links ENANO_SID = key; var links = document.getElementsByTagName('a'); for ( var i = 0; i < links.length; i++ ) { if ( links[i].href.match(exp, links[i]) && links[i].href.indexOf('#') == -1 ) { var newurl = (String(append_sid(links[i].href))).replace(rexp, ''); links[i].href = newurl; } } var forms = document.getElementsByTagName('form'); for ( var i = 0; i < forms.length; i++ ) { if ( forms[i].method.toLowerCase() == 'post' ) { if ( forms[i].action.match(exp, links[i]) ) { var newurl = (String(append_sid(forms[i].action))).replace(rexp, ''); forms[i].action = newurl; } } else { if ( !forms[i].auth ) { var auth = document.createElement('input'); auth.type = 'hidden'; auth.name = 'auth'; auth.value = key; forms[i].appendChild(auth); } else { forms[i].auth.value = key; } } } } if ( level ) { auth_level = level; } window.location.hash = '#auth:' + key; } else { auth_level = USER_LEVEL_MEMBER; ENANO_SID = false; if ( oldkey ) { var links = document.getElementsByTagName('a'); for ( var i = 0; i < links.length; i++ ) { if ( links[i].href.match(exp, links[i]) && links[i].href.indexOf('#') == -1 ) { links[i].href = links[i].href.replace(/\?auth=([a-f0-9]+)(&|#|$)/, '$2').replace(/&auth=([a-f0-9]+)/, '').replace(rexp, ''); } } } window.location.hash = '#auth:false'; } window.stdAjaxPrefix = append_sid(scriptPath + '/ajax.php?title=' + title); }