diff --git a/application/config/autoload.php b/application/config/autoload.php
new file mode 100644
index 0000000..c765694
--- /dev/null
+++ b/application/config/autoload.php
@@ -0,0 +1,4 @@
+_public_key = $this->config->item('recaptcha_public_key', 'bitauth');
+ $this->_private_key = $this->config->item('recaptcha_private_key', 'bitauth');
+
$this->load->library('bitauth');
$this->load->helper('form');
@@ -102,12 +105,30 @@ public function index()
$this->load->view('example/users', array('bitauth' => $this->bitauth, 'users' => $this->bitauth->get_users()));
}
+ public function _recaptcha_check()
+ {
+ $resp = $this->recaptcha->recaptcha_check_answer($this->_private_key, $_SERVER["REMOTE_ADDR"], $this->input->post('recaptcha_challenge_field'), $this->input->post('recaptcha_response_field'));
+
+ if( ! $resp->is_valid)
+ {
+ $this->form_validation->set_message('_recaptcha_check', $this->lang->line('bitauth_recaptcha_error'));
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+ }
+
/**
* Example::register()
*
*/
public function register()
{
+ $this->load->library('Recaptcha');
+ $data['recaptcha'] = $this->recaptcha->recaptcha_get_html($this->_public_key);
+
if($this->input->post())
{
$this->form_validation->set_rules('username', 'Username', 'trim|required|bitauth_unique_username');
@@ -115,17 +136,60 @@ public function register()
$this->form_validation->set_rules('email', 'Email', 'trim|required|valid_email');
$this->form_validation->set_rules('password', 'Password', 'required|bitauth_valid_password');
$this->form_validation->set_rules('password_conf', 'Password Confirmation', 'required|matches[password]');
+ $this->form_validation->set_rules('recaptcha_response_field', 'Captcha code', 'required|callback__recaptcha_check');
if($this->form_validation->run() == TRUE)
{
- unset($_POST['submit'], $_POST['password_conf']);
+ unset($_POST['submit'], $_POST['password_conf'], $_POST['recaptcha_response_field'], $_POST['recaptcha_challenge_field']);
$this->bitauth->add_user($this->input->post());
redirect('example/login');
}
}
- $this->load->view('example/add_user', array('title' => 'Register'));
+ $this->load->view('example/register', array('title' => 'Register'));
+ }
+
+ public function forgot_password()
+ {
+ if($this->input->post())
+ {
+ $this->form_validation->set_rules('email', 'Email', 'trim|required|valid_email');
+
+ if($this->form_validation->run() == TRUE)
+ {
+ $this->bitauth->generate_forgot_code($this->form_validation->set_value('email'));
+ redirect('example');
+ }
+ }
+
+ $this->load->view('example/forgot_password');
+ }
+
+ /**
+ * Example::change_password()
+ *
+ */
+ public function change_password($code = '')
+ {
+ if( ! $user = $this->bitauth->get_user_by_forgot_code($code))
+ {
+ redirect('example');
+ }
+
+ if($this->input->post())
+ {
+ $this->form_validation->set_rules('password', 'Password', 'required|bitauth_valid_password');
+ $this->form_validation->set_rules('password_conf', 'Password Confirmation', 'required|matches[password]');
+
+ if($this->form_validation->run() == TRUE)
+ {
+ $this->bitauth->save_new_password($this->form_validation->set_value('password'), $code);
+ redirect('example');
+ }
+ }
+
+ $this->load->view('example/change_password', array('forgot_code' => $code));
}
/**
diff --git a/application/language/english/bitauth_lang.php b/application/language/english/bitauth_lang.php
index a0c9dba..faaf15b 100644
--- a/application/language/english/bitauth_lang.php
+++ b/application/language/english/bitauth_lang.php
@@ -62,3 +62,23 @@
$lang['bitauth_edit_group_failed'] = 'Updating group failed, please notify an administrator.';
$lang['bitauth_del_group_failed'] = 'Deleting group failed, please notify an administrator.';
$lang['bitauth_lang_not_found'] = '(No language entry for "%s" found!)';
+
+/**
+ * Email Activation Messages
+ */
+$lang['bitauth_activation_email_subject'] = 'Activation email';
+$lang['bitauth_activation_email_message'] = 'Dear User,
Please click on the following link to activate your account!
Your account details:Username: %s
Password: %s
%s
Thank you';
+$lang['bitauth_activation_email_send_error'] = 'Could not send activation email, please contact the webmaster';
+
+/**
+ * Forgot Password Messages
+ */
+$lang['bitauth_forgotpassword_email_subject'] = 'Forgotten password reset email';
+$lang['bitauth_forgotpassword_email_message'] = 'Dear User,
This is a password reset confirmation email.
%s';
+$lang['bitauth_forgotpassword_email_send_error'] = 'Could not send password reset email, please contact the webmaster';
+
+/**
+ * Recatpcha Messages
+ */
+$lang['bitauth_recaptcha_error'] = 'Incorrect captcha code entered. Try again!';
+$lang['bitauth_recaptcha_regenerate'] = 'I can\'t read, please generate new captcha!';
\ No newline at end of file
diff --git a/application/libraries/Bitauth.php b/application/libraries/Bitauth.php
index e130b9f..bf42d85 100644
--- a/application/libraries/Bitauth.php
+++ b/application/libraries/Bitauth.php
@@ -55,6 +55,11 @@ public function __construct()
$this->_remember_token_expires = $this->config->item('remember_token_expires', 'bitauth');
$this->_remember_token_updates = $this->config->item('remember_token_updates', 'bitauth');
$this->_require_user_activation = $this->config->item('require_user_activation', 'bitauth');
+ $this->_email_activation = $this->config->item('email_activation', 'bitauth');
+ $this->_activation_email_address = $this->config->item('activation_email_address', 'bitauth');
+ $this->_mailtype = $this->config->item('mailtype', 'bitauth');
+ $this->_locked_out_alert_message = $this->config->item('locked_out_alert_message', 'bitauth');
+ $this->_locked_out_notify_address = $this->config->item('locked_out_notify_address', 'bitauth');
$this->_pwd_max_age = $this->config->item('pwd_max_age', 'bitauth');
$this->_pwd_age_notification = $this->config->item('pwd_age_notification', 'bitauth');
$this->_pwd_min_length = $this->config->item('pwd_min_length', 'bitauth');
@@ -306,6 +311,23 @@ public function locked_out()
if($this->timestamp(strtotime($last->time), 'U') - $this->timestamp(strtotime($first->time), 'U') <= ($this->_mins_login_attempts * 60)
&& $this->timestamp(strtotime($last->time), 'U') >= $this->timestamp(strtotime($this->_mins_login_attempts.' minutes ago'), 'U'))
{
+ if($this->_locked_out_alert_message)
+ {
+ $config['useragent'] = 'bitauth';
+ $config['mailtype'] = $this->_mailtype;
+ $this->email_lib->initialize($config);
+ $this->email_lib->clear();
+ $this->email_lib->from($this->_locked_out_notify_address);
+ $this->email_lib->to($this->_locked_out_notify_address);
+ $this->email_lib->subject('Invalid login attempt on '.base_url());
+ $this->email_lib->message('User: '.$username.' IP address: '.$_SERVER['REMOTE_ADDR'].' Time: '.mdate("%Y-%m-%d %H:%i:%s", time()));
+
+ if( ! $this->email_lib->send())
+ {
+ log_message('error', 'Invalid login attempt email send failed.'.$this->email_lib->print_debugger());
+ }
+ }
+
return TRUE;
}
}
@@ -498,6 +520,11 @@ public function add_user($data, $require_activation = NULL)
if($require_activation)
{
$data['activation_code'] = $this->generate_code();
+
+ if($this->_email_activation)
+ {
+ $this->_send_email_activation($data['email'], $data['activation_code'], $data['username'], $data['password']);
+ }
}
// Just in case
@@ -840,6 +867,52 @@ public function forgot_password($user_id)
return FALSE;
}
+ /**
+ * Bitauth::generate_forgot_code()
+ *
+ * Sends a generated forgot code to the give email address
+ */
+ public function generate_forgot_code($email)
+ {
+ if( ! $user = $this->get_user_by_email($email))
+ {
+ return FALSE;
+ }
+ $forgot_code = $this->forgot_password($user->user_id);
+
+ $config['useragent'] = 'bitauth';
+ $config['mailtype'] = $this->_mailtype;
+ $this->email_lib->initialize($config);
+ $this->email_lib->clear();
+ $this->email_lib->from($this->_activation_email_address);
+ $this->email_lib->to($email);
+ $this->email_lib->subject($this->lang->line('bitauth_forgotpassword_email_subject'));
+ $this->email_lib->message(sprintf($this->lang->line('bitauth_forgotpassword_email_message'),
+ 'Click here to reset your password'));
+
+ if( ! $this->email_lib->send())
+ {
+ log_message('error', $this->email_lib->print_debugger());
+ show_error($this->lang->line('bitauth_forgotpassword_email_send_error'));
+ exit;
+ }
+ }
+
+ /**
+ * Bitauth::save_new_password()
+ *
+ * Saves a newly entered password, and delete forgot_code
+ */
+ public function save_new_password($password, $code)
+ {
+ if( ! $user = $this->get_user_by_forgot_code($code))
+ {
+ return FALSE;
+ }
+ $this->set_password($user->user_id, $password);
+ $this->update_user($user->user_id, array('forgot_code' => ''));
+ }
+
/**
* Bitauth::set_password()
*
@@ -1572,6 +1645,9 @@ public function _assign_libraries()
$CI->load->library('encrypt');
$this->encrypt = $CI->encrypt;
+ $CI->load->library('email');
+ $this->email_lib = $CI->email;
+
$this->load->database();
$this->db = $CI->db;
@@ -1593,4 +1669,31 @@ public function _assign_libraries()
}
+ /**
+ * Bitauth::_send_email_activation()
+ *
+ * Send activation email to activate user account
+ */
+ public function _send_email_activation($user_email, $activation_code, $username, $password)
+ {
+ $config['useragent'] = 'bitauth';
+ $config['mailtype'] = $this->_mailtype;
+ $this->email_lib->initialize($config);
+ $this->email_lib->clear();
+ $this->email_lib->from($this->_activation_email_address);
+ $this->email_lib->to($user_email);
+ $this->email_lib->subject($this->lang->line('bitauth_activation_email_subject'));
+ $this->email_lib->message(sprintf($this->lang->line('bitauth_activation_email_message'),
+ $username,
+ $password,
+ 'Click here to activate'));
+
+ if( ! $this->email_lib->send())
+ {
+ log_message('error', $this->email_lib->print_debugger());
+ show_error($this->lang->line('bitauth_activation_email_send_error'));
+ exit;
+ }
+ }
+
}
\ No newline at end of file
diff --git a/application/libraries/Recaptcha.php b/application/libraries/Recaptcha.php
new file mode 100644
index 0000000..b8f9d39
--- /dev/null
+++ b/application/libraries/Recaptcha.php
@@ -0,0 +1,292 @@
+ $value )
+ $req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
+
+ // Cut the last '&'
+ $req=substr($req,0,strlen($req)-1);
+ return $req;
+ }
+
+
+
+ /**
+ * Submits an HTTP POST to a reCAPTCHA server
+ * @param string $host
+ * @param string $path
+ * @param array $data
+ * @param int port
+ * @return array response
+ */
+ private function _recaptcha_http_post($host, $path, $data, $port = 80) {
+
+ $req = $this->_recaptcha_qsencode ($data);
+
+ $http_request = "POST $path HTTP/1.0\r\n";
+ $http_request .= "Host: $host\r\n";
+ $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
+ $http_request .= "Content-Length: " . strlen($req) . "\r\n";
+ $http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
+ $http_request .= "\r\n";
+ $http_request .= $req;
+
+ $response = '';
+ if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) {
+ die ('Could not open socket');
+ }
+
+ fwrite($fs, $http_request);
+
+ while ( !feof($fs) )
+ $response .= fgets($fs, 1160); // One TCP-IP packet
+ fclose($fs);
+ $response = explode("\r\n\r\n", $response, 2);
+
+ return $response;
+ }
+
+
+
+ /**
+ * Gets the challenge HTML (javascript and non-javascript version).
+ * This is called from the browser, and the resulting reCAPTCHA HTML widget
+ * is embedded within the HTML form it was called from.
+ * @param string $pubkey A public key for reCAPTCHA
+ * @param string $error The error given by reCAPTCHA (optional, default is null)
+ * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
+
+ * @return string - The HTML to be embedded in the user's form.
+ */
+ public function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false)
+ {
+ if ($pubkey == null || $pubkey == '') {
+ die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create");
+ }
+
+ if ($use_ssl) {
+ $server = RECAPTCHA_API_SECURE_SERVER;
+ } else {
+ $server = RECAPTCHA_API_SERVER;
+ }
+
+ $errorpart = "";
+ if ($error) {
+ $errorpart = "&error=" . $error;
+ }
+ return '
+
+ ';
+ }
+
+
+ /**
+ * Calls an HTTP POST function to verify if the user's guess was correct
+ * @param string $privkey
+ * @param string $remoteip
+ * @param string $challenge
+ * @param string $response
+ * @param array $extra_params an array of extra variables to post to the server
+ * @return ReCaptchaResponse
+ */
+ public function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array())
+ {
+ if ($privkey == null || $privkey == '') {
+ die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create");
+ }
+
+ if ($remoteip == null || $remoteip == '') {
+ die ("For security reasons, you must pass the remote ip to reCAPTCHA");
+ }
+
+
+
+ //discard spam submissions
+ if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
+ $recaptcha_response = new ReCaptchaResponse();
+ $recaptcha_response->is_valid = false;
+ $recaptcha_response->error = 'incorrect-captcha-sol';
+ return $recaptcha_response;
+ }
+
+ $response = $this->_recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify",
+ array (
+ 'privatekey' => $privkey,
+ 'remoteip' => $remoteip,
+ 'challenge' => $challenge,
+ 'response' => $response
+ ) + $extra_params
+ );
+
+ $answers = explode ("\n", $response [1]);
+ $recaptcha_response = new ReCaptchaResponse();
+
+ if (trim ($answers [0]) == 'true') {
+ $recaptcha_response->is_valid = true;
+ }
+ else {
+ $recaptcha_response->is_valid = false;
+ $recaptcha_response->error = $answers [1];
+ }
+ return $recaptcha_response;
+
+ }
+
+ /**
+ * gets a URL where the user can sign up for reCAPTCHA. If your application
+ * has a configuration page where you enter a key, you should provide a link
+ * using this function.
+ * @param string $domain The domain where the page is hosted
+ * @param string $appname The name of your application
+ */
+ public function recaptcha_get_signup_url ($domain = null, $appname = null) {
+ return "https://www.google.com/recaptcha/admin/create?" . $this->_recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname));
+ }
+
+ private function _recaptcha_aes_pad($val) {
+ $block_size = 16;
+ $numpad = $block_size - (strlen ($val) % $block_size);
+ return str_pad($val, strlen ($val) + $numpad, chr($numpad));
+ }
+
+ /* Mailhide related code */
+
+ private function _recaptcha_aes_encrypt($val,$ky) {
+ if (! function_exists ("mcrypt_encrypt")) {
+ die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
+ }
+ $mode=MCRYPT_MODE_CBC;
+ $enc=MCRYPT_RIJNDAEL_128;
+ $val=$this->_recaptcha_aes_pad($val);
+ return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
+ }
+
+
+ private function _recaptcha_mailhide_urlbase64 ($x) {
+ return strtr(base64_encode ($x), '+/', '-_');
+ }
+
+ /* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
+ public function recaptcha_mailhide_url($pubkey, $privkey, $email) {
+ if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
+ die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
+ "you can do so at http://www.google.com/recaptcha/mailhide/apikey");
+ }
+
+
+ $ky = pack('H*', $privkey);
+ $cryptmail = $this->_recaptcha_aes_encrypt ($email, $ky);
+
+ return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . $this->_recaptcha_mailhide_urlbase64 ($cryptmail);
+ }
+
+ /**
+ * gets the parts of the email to expose to the user.
+ * eg, given johndoe@example,com return ["john", "example.com"].
+ * the email is then displayed as john...@example.com
+ */
+ private function _recaptcha_mailhide_email_parts ($email) {
+ $arr = preg_split("/@/", $email );
+
+ if (strlen ($arr[0]) <= 4) {
+ $arr[0] = substr ($arr[0], 0, 1);
+ } else if (strlen ($arr[0]) <= 6) {
+ $arr[0] = substr ($arr[0], 0, 3);
+ } else {
+ $arr[0] = substr ($arr[0], 0, 4);
+ }
+ return $arr;
+ }
+
+ /**
+ * Gets html to display an email address given a public an private key.
+ * to get a key, go to:
+ *
+ * http://www.google.com/recaptcha/mailhide/apikey
+ */
+ public function recaptcha_mailhide_html($pubkey, $privkey, $email) {
+ $emailparts = $this->_recaptcha_mailhide_email_parts ($email);
+ $url = recaptcha_mailhide_url ($pubkey, $privkey, $email);
+
+ return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]);
+
+ }
+
+
+}
+
+/**
+ * A ReCaptchaResponse is returned from recaptcha_check_answer()
+ */
+class ReCaptchaResponse {
+ var $is_valid;
+ var $error;
+}
\ No newline at end of file
diff --git a/application/views/example/add_user.php b/application/views/example/add_user.php
index 41ab339..ce78bcd 100644
--- a/application/views/example/add_user.php
+++ b/application/views/example/add_user.php
@@ -15,6 +15,7 @@