# HG changeset patch # User Dan # Date 1190089843 14400 # Node ID af0f6ec48de3df875f8f60a4a263bd533d757696 # Parent 0ae1b281a8842e5eae9a12c5c7a95a0e80a51592 Fully implemented password complexity enforcement; added encryption for passwords on registration form; some baby steps taken towards supporting international usernames - this is not working very well, we might need a hackish fix; TODO: implement password strength meter into installer UI and get international usernames 100% working diff -r 0ae1b281a884 -r af0f6ec48de3 includes/clientside/static/ajax.js --- a/includes/clientside/static/ajax.js Mon Sep 17 11:52:58 2007 -0400 +++ b/includes/clientside/static/ajax.js Tue Sep 18 00:30:43 2007 -0400 @@ -44,6 +44,16 @@ function ajaxEscape(text) { + /* + text = escape(text); + text = text.replace(/\+/g, '%2B', text); + */ + text = window.encodeURIComponent(text); + return text; +} + +function ajaxAltEscape(text) +{ text = escape(text); text = text.replace(/\+/g, '%2B', text); return text; diff -r 0ae1b281a884 -r af0f6ec48de3 includes/clientside/static/enano-lib-basic.js --- a/includes/clientside/static/enano-lib-basic.js Mon Sep 17 11:52:58 2007 -0400 +++ b/includes/clientside/static/enano-lib-basic.js Tue Sep 18 00:30:43 2007 -0400 @@ -282,6 +282,7 @@ 'dynano.js', 'flyin.js', 'paginate.js', + 'pwstrength.js', 'loader.js' ]; diff -r 0ae1b281a884 -r af0f6ec48de3 includes/clientside/static/misc.js --- a/includes/clientside/static/misc.js Mon Sep 17 11:52:58 2007 -0400 +++ b/includes/clientside/static/misc.js Tue Sep 18 00:30:43 2007 -0400 @@ -446,8 +446,11 @@ 'level' : ajax_auth_level_cache }; + window.console.debug(json_data); json_data = toJSONString(json_data); - json_data = ajaxEscape(json_data); + window.console.debug(json_data); + json_data = ajaxAltEscape(json_data); + window.console.debug(json_data); var loading_win = '
\

Logging in...

\ diff -r 0ae1b281a884 -r af0f6ec48de3 includes/render.php --- a/includes/render.php Mon Sep 17 11:52:58 2007 -0400 +++ b/includes/render.php Tue Sep 18 00:30:43 2007 -0400 @@ -72,7 +72,7 @@ $chartag = $row['char_tag']; unset($row); // Free some memory - if ( preg_match('#^\#redirect \[\[(.+?)\]\]#', $message, $m) && $redir && !isset($_GET['redirect']) || ( isset($_GET['redirect']) && $_GET['redirect'] != 'no' ) ) + if ( preg_match("#^\#redirect \[\[([^\]\r\n\a\t]+?)\]\]#", $message, $m) && $redir && ( !isset($_GET['redirect']) || ( isset($_GET['redirect']) && $_GET['redirect'] != 'no' ) ) ) { dc_here('render: looks like a redirect page to me...'); $old = $paths->cpage; diff -r 0ae1b281a884 -r af0f6ec48de3 includes/sessions.php --- a/includes/sessions.php Mon Sep 17 11:52:58 2007 -0400 +++ b/includes/sessions.php Tue Sep 18 00:30:43 2007 -0400 @@ -150,7 +150,8 @@ * @var string */ - var $valid_username = '([A-Za-z0-9 \!\@\(\)-]+)'; + //var $valid_username = '([A-Za-z0-9 \!\@\(\)-]+)'; + var $valid_username = '([^<>_&\?\'"%\n\r\t\a]+)'; /** * What we're allowed to do as far as permissions go. This changes based on the value of the "auth" URI param. @@ -576,10 +577,21 @@ // Initialize our success switch $success = false; + // Escaped username + $db_username = $this->prepare_text(strtolower($username)); + // Select the user data from the table, and decrypt that so we can verify the password - $this->sql('SELECT password,old_encryption,user_id,user_level,theme,style,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$this->prepare_text(strtolower($username)).'\';'); + $this->sql('SELECT password,old_encryption,user_id,user_level,theme,style,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$db_username.'\' OR username=\'' . $db_username . '\';'); if($db->numrows() < 1) - return 'The username and/or password is incorrect.'; + { + // This wasn't logged in <1.0.2, dunno how it slipped through + if($level > USER_LEVEL_MEMBER) + $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); + else + $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); + + return "The username and/or password is incorrect."; + } $row = $db->fetchrow(); // Check to see if we're logging in using a temporary password @@ -1392,24 +1404,51 @@ $username = $this->prepare_text($username); $email = $this->prepare_text($email); $real_name = $this->prepare_text($real_name); - $password = $aes->encrypt($password, $this->private_key, ENC_HEX); $nameclause = ( $real_name != '' ) ? ' OR real_name=\''.$real_name.'\'' : ''; $q = $this->sql('SELECT * FROM '.table_prefix.'users WHERE lcase(username)=\''.strtolower($username).'\' OR email=\''.$email.'\''.$nameclause.';'); - if($db->numrows() > 0) { + if($db->numrows() > 0) + { $r = 'The '; $i=0; $row = $db->fetchrow(); // Wow! An error checker that actually speaks English with the properest grammar! :-P - if($row['username'] == $username) { $r .= 'username'; $i++; } - if($row['email'] == $email) { if($i) $r.=', '; $r .= 'e-mail address'; $i++; } - if($row['real_name'] == $real_name && $real_name != '') { if($i) $r.=', and '; $r .= 'real name'; $i++; } + if ( $row['username'] == $username ) + { + $r .= 'username'; + $i++; + } + if ( $row['email'] == $email ) + { + if($i) $r.=', '; + $r .= 'e-mail address'; + $i++; + } + if ( $row['real_name'] == $real_name && $real_name != '' ) + { + if($i) $r.=', and '; + $r .= 'real name'; + $i++; + } $r .= ' that you entered '; $r .= ( $i == 1 ) ? 'is' : 'are'; $r .= ' already in use by another user.'; return $r; } + // Is the password strong enough? + if ( getConfig('pw_strength_enable') ) + { + $min_score = intval( getConfig('pw_strength_minimum') ); + $pass_score = password_score($password); + if ( $pass_score < $min_score ) + { + return 'The password you entered did not meet the complexity requirements for this site. Please choose a stronger password.'; + } + } + + $password = $aes->encrypt($password, $this->private_key, ENC_HEX); + // Require the account to be activated? switch(getConfig('account_activation')) { diff -r 0ae1b281a884 -r af0f6ec48de3 includes/template.php --- a/includes/template.php Mon Sep 17 11:52:58 2007 -0400 +++ b/includes/template.php Tue Sep 18 00:30:43 2007 -0400 @@ -1146,7 +1146,7 @@ $text_parser = $this->makeParserText($tplvars['sidebar_button']); - preg_match_all('#\[\[([a-zA-Z0-9 -_]*?)\]\]#is', $message, $il); + preg_match_all("#\[\[([^\|\]\n\a\r\t]*?)\]\]#is", $message, $il); for($i=0;$irun(), $message); } - preg_match_all('#\[\[([a-zA-Z0-9 -_]*?)\|([a-zA-Z0-9!@\#\$%\^&\*\(\)\{\} -_]*?)\]\]#is', $message, $il); + preg_match_all('#\[\[([^\|\]\n\a\r\t]*?)\|([^\]\r\n\a\t]*?)\]\]#is', $message, $il); for($i=0;$i= -10 && $strength <= 30 ) + { + $strength = strval($strength); + setConfig('pw_strength_minimum', $strength); + } + echo '
Your changes to the site configuration have been saved.

'; } @@ -339,6 +350,28 @@ + Password strength + + + + Enable password strength analysis
+ This should be enabled in most cases. When this is enabled, a strength meter and a numerical score will be displayed wherever a password can be changed. + + + + + + + + + Minimum strength score
+ This is the lowest score a password will be allowed to have. -10 will allow any password. A score of under -3 is considered weak, under 1 is fair, under 4 is good, under 10 is strong, and 10 and above are very strong. The scale is open-ended. This only has an effect if the meter is enabled above. + + + + + + E-mail sent from the site @@ -946,12 +979,15 @@ else { $disabled = ( $r['user_id'] == $session->user_id ) ? ' disabled="disabled" ' : ''; + $evt_get_score = ( getConfig('pw_strength_enable') == '1' ) ? 'onkeyup="password_score_field(this);" style="margin-right: 7px;" ' : ''; + $meter = ( getConfig('pw_strength_enable') == '1' ) ? '

Password complexity requirements are not enforced here.

' : ''; echo('

Edit User Info

- + + '.$meter.' ' . ( ( !empty($disabled) ) ? '' : '' ) . ' diff -r 0ae1b281a884 -r af0f6ec48de3 plugins/SpecialUserFuncs.php --- a/plugins/SpecialUserFuncs.php Mon Sep 17 11:52:58 2007 -0400 +++ b/plugins/SpecialUserFuncs.php Tue Sep 18 00:30:43 2007 -0400 @@ -333,6 +333,12 @@ function page_Special_Register() { global $db, $session, $paths, $template, $plugins; // Common objects + + // form field trackers + $username = ''; + $email = ''; + $realname = ''; + if(getConfig('account_activation') == 'disable' && ( ( $session->user_level >= USER_LEVEL_ADMIN && !isset($_GET['IWannaPlayToo']) ) || $session->user_level < USER_LEVEL_ADMIN || !$session->user_logged_in )) { $s = ($session->user_level >= USER_LEVEL_ADMIN) ? '

Oops...it seems that you are the administrator...hehe...you can also force account registration to work.

' : ''; @@ -360,9 +366,38 @@ else { $coppa = ( isset($_POST['coppa']) && $_POST['coppa'] == 'yes' ); + $s = false; + + // decrypt password + // as with the change pass form, we aren't going to bother checking the confirmation code because if the passwords didn't match + // and yet the password got encrypted, that means the user screwed with the code, and if the user screwed with the code and thus + // forgot his password, that's his problem. + + if ( $_POST['use_crypt'] == 'yes' ) + { + $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE); + $crypt_key = $session->fetch_public_key($_POST['crypt_key']); + if ( !$crypt_key ) + { + $s = 'Couldn\'t look up public encryption key'; + } + else + { + $data = $_POST['crypt_data']; + $bin_key = hexdecode($crypt_key); + //die("Decrypting with params: key $crypt_key, data $data"); + $password = $aes->decrypt($data, $bin_key, ENC_HEX); + } + } + else + { + $password = $_POST['password']; + } // CAPTCHA code was correct, create the account - $s = $session->create_user($_POST['username'], $_POST['password'], $_POST['email'], $_POST['real_name'], $coppa); + // ... and check for errors returned from the crypto API + if ( !$s ) + $s = $session->create_user($_POST['username'], $password, $_POST['email'], $_POST['real_name'], $coppa); } } if($s == 'success' && !$coppa) @@ -387,6 +422,9 @@ $str = 'However, in compliance with the Childrens\' Online Privacy Protection Act, you must have your parent or legal guardian activate your account. Please ask them to check their e-mail for further information.'; die_friendly('Registration successful', '

Thank you for registering, your user account has been created. '.$str.'

'); } + $username = htmlspecialchars($_POST['username']); + $email = htmlspecialchars($_POST['email']); + $realname = htmlspecialchars($_POST['real_name']); } $template->header(); echo 'A user account enables you to have greater control over your browsing experience.'; @@ -396,9 +434,13 @@ $coppa = ( isset($_GET['coppa']) && $_GET['coppa'] == 'yes' ); $session->kill_captcha(); $captchacode = $session->make_captcha(); + + $pubkey = $session->rijndael_genkey(); + $challenge = $session->dss_rand(); + ?>

Create a user account

- +
Username:
New Password:
New Password:
E-mail:
Real Name:
To change your e-mail address, password, or real name, please use the user control panel.
@@ -412,7 +454,7 @@ - - @@ -436,10 +481,20 @@ + + + + + + + + @@ -493,7 +548,7 @@ @@ -501,7 +556,7 @@ @@ -511,6 +566,79 @@ $val = ( $coppa ) ? 'yes' : 'no'; echo ''; ?> + + + + + @@ -525,7 +653,9 @@ // Username if(!namegood) { - if(frm.username.value.match(/^([A-z0-9 \!@\-\(\)]+){2,}$/ig)) + //if(frm.username.value.match(/^([A-z0-9 \!@\-\(\)]+){2,}$/ig)) + var regex = new RegExp('^([^<>_&\?]+){2,}$', 'ig'); + if ( frm.username.value.match(regex) ) { document.getElementById('s_username').src='/images/unknown.gif'; document.getElementById('e_username').innerHTML = ''; // '
Checking availability...'; @@ -616,10 +746,13 @@ } function regenCaptcha() { - var frm = document.forms.regform; document.getElementById('captchaimg').src = ''+frm.captchahash.value+'/'+Math.floor(Math.random() * 100000); return false; } + + var frm = document.forms.regform; + password_score_field(frm.password); + validateForm(); setTimeout('checkUsername();', 1000); // ]]> @@ -898,9 +1031,9 @@ $row = $db->fetchrow(); $db->free_result(); - if ( ( intval($row['temp_password_time']) + 3600 * 24 ) < time() ) + if ( ( intval($row['temp_password_time']) + ( 3600 * 24 ) ) < time() ) { - echo '

Password has expired

'; + echo '

Your temporary password has expired. Please request another one.

'; $template->footer(); return false; } @@ -949,6 +1082,18 @@ $template->footer(); return false; } + if ( getConfig('pw_strength_enable') == '1' ) + { + $min_score = intval(getConfig('pw_strength_minimum')); + $inp_score = password_score($data); + if ( $inp_score < $min_score ) + { + $url = makeUrl($paths->fullpage); + echo "

ERROR: Your password did not pass the complexity score requirement. You need $min_score points to pass; your password received a score of $inp_score. Go back

"; + $template->footer(); + return false; + } + } $encpass = $aes->encrypt($data, $session->private_key, ENC_HEX); $q = $db->sql_query('UPDATE '.table_prefix.'users SET password=\'' . $encpass . '\',temp_password=\'\',temp_password_time=0 WHERE user_id='.$user_id.';'); @@ -969,14 +1114,19 @@ // Password reset form $pubkey = $session->rijndael_genkey(); + $evt_get_score = ( getConfig('pw_strength_enable') == '1' ) ? 'onkeyup="password_score_field(this);" ' : ''; + $pw_meter = ( getConfig('pw_strength_enable') == '1' ) ? '
' : ''; + $pw_blurb = ( getConfig('pw_strength_enable') == '1' && intval(getConfig('pw_strength_minimum')) > -10 ) ? '
Your password needs to have a score of at least '.getConfig('pw_strength_minimum').'.' : ''; + ?>
Please tell us a little bit about yourself.
- + Good/bad icon @@ -421,14 +463,17 @@
+ Password: + -10 ): ?> + It needs to score at least for your registration to be accepted. + - + Loading... + Good/bad icon
- Enter your password again to confirm. + Enter your password again to confirm.
+
+
@@ -455,7 +510,7 @@ ?> - + Good/bad icon @@ -469,7 +524,7 @@ Giving your real name is totally optional. If you choose to provide your real name, it will be used to provide attribution for any edits or contributions you may make to this site. - +
Code: - +
- +
Password strength rating:
- + +
Reset password
Password:
Password:/>
Confirm:
diff -r 0ae1b281a884 -r af0f6ec48de3 plugins/SpecialUserPrefs.php --- a/plugins/SpecialUserPrefs.php Mon Sep 17 11:52:58 2007 -0400 +++ b/plugins/SpecialUserPrefs.php Tue Sep 18 00:30:43 2007 -0400 @@ -214,7 +214,14 @@ { // Perform checks if ( strlen($newpass) < 6 ) - $errors .= '
Password must be at least 6 characters. You hacked my script, darn you!
'; + $errors .= '
Password must be at least 6 characters. You hacked my script, darn you!
'; + if ( getConfig('pw_strength_enable') == '1' ) + { + $score_inp = password_score($newpass); + $score_min = intval( getConfig('pw_strength_minimum') ); + if ( $score_inp < $score_min ) + $errors .= '
Your password did not meet the complexity score requirement for this site. Your password scored '. $score_inp .', while a score of at least '. $score_min .' is needed.
'; + } // Encrypt new password if ( empty($errors) ) { @@ -304,6 +311,12 @@ break; case 'EmailPassword': + $errors = trim($errors); + if ( !empty($errors) ) + { + echo $errors; + } + echo ''; // Password change form @@ -312,20 +325,22 @@ echo '
Change password Type a new password:
- + ' . ( getConfig('pw_strength_enable') == '1' ? ' Loading...' : '' ) . '

Type the password again to confirm:
- + + ' . ( getConfig('pw_strength_enable') == '1' ? '

+ Your password needs to score at least '.getConfig('pw_strength_minimum').' in order to be accepted.' : '' ) . '

Change e-mail address New e-mail address:
- +

Confirm e-mail address:
- +
@@ -338,6 +353,9 @@ // ENCRYPTION CODE ?>