SECURITY: Fixed XSS in post-login page redirection. Reported by Secunia.
/*
* 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 );
/**
* Time to wait to focus the controls.
* @var int
*/
var AJAX_LOGIN_FOCUS_WAIT = aclDisableTransitionFX ? 0 : 750;
/**
* 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;
logindata.start_time = (new Date()).getTime();
// 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 = response.redirect_url;
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
var wait_time = AJAX_LOGIN_FOCUS_WAIT - ((new Date()).getTime() - logindata.start_time);
wait_time = Math.max(0, wait_time);
setTimeout(
function()
{
if ( logindata.loggedin_username )
document.getElementById('ajax_login_field_password').focus();
else
document.getElementById('ajax_login_field_username').focus();
}, wait_time);
// 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);
}