Login   Register  
PHP Classes
elePHPant
Icontem

File: auth.class.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of Cuonic  >  PHPAuth  >  auth.class.php  >  Download  
File: auth.class.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PHPAuth
Register and authenticate users stored in MySQL
Author: By
Last change: Spring cleaning... Big changes, take a look before you update.
Date: 13 days ago
Size: 46,008 bytes
 

Contents

Class file image Download
<?php

/*
* Auth class
* Functions with PHP 5.3.7 and above.
*/

class Auth
{
    private $dbh;
    private $config;

    /*
    * Initiates database connection
    */

    public function __construct(\PDO $dbh, $config)
    {
        $this->config = $config;
        $this->dbh = $dbh;

        if (version_compare(phpversion(), '5.5.0', '<')) {
            require("password.php");
        }
    }

    /*
    * Logs a user in
    * @param string $username
    * @param string $password
    * @param bool $remember
    * @return array $return
    */

    public function login($username, $password, $remember = 0)
    {
        $return = array();
        $return['code'] = 400;

        if ($this->isBlocked()) {
            $return['message'] = "user_blocked";

            return $return;
        } else {
            $validateUsername = $this->validateUsername($username);
            $validatePassword = $this->validatePassword($password);

            if ($validateUsername['error'] == 1) {
                $return['message'] = "username_password_invalid";

                $this->addAttempt();
                return $return;
            } elseif($validatePassword['error'] == 1) {
                $return['message'] = "username_password_invalid";

                $this->addAttempt();
                return $return;
            } elseif($remember != 0 && $remember != 1) {
                $return['message'] = "remember_me_invalid";

                $this->addAttempt();
                return $return;
            } else {
                $username = strtolower($username);
                $uid = $this->getUID($username);

                if($uid) {
                    $user = $this->getUser($uid);
                } else {
                    $user = false;
                }

                if($user) {
                    if (password_verify($password, $user['password'])) {
                        if ($user['isactive'] == 1) {
                            
                            $sessiondata = $this->addSession($user['uid'], $remember);

                            if($sessiondata == false) {
                                $return['message'] = "system_error";
                                return $return;
                            } else {
                                $return['code'] = 200;
                                $return['message'] = "logged_in";

                                $return['hash'] = $sessiondata['hash'];
                                $return['expire'] = $sessiondata['expiretime'];

                                return $return;
                            }

                        } else {
                            $this->addAttempt();

                            $this->addNewLog($uid, "LOGIN_ACCOUNT_INACTIVE", "");

                            $return['message'] = "account_inactive";
                            return $return;
                        }
                    } else {
                        $this->addAttempt();

                        $this->addNewLog(0, "LOGIN_PASSWORD_INCORRECT", "Username : {$username}");

                        $return['message'] = "username_password_incorrect";
                        return $return;
                    }
                } else {
                    $this->addAttempt();

                    $this->addNewLog(0, "LOGIN_USERNAME_INCORRECT", "Username : {$username}");

                    $return['message'] = "username_password_incorrect";
                    return $return;
                }
            }
        }
    }

    /*
    * Creates a new user, adds them to database
    * @param string $email
    * @param string $username
    * @param string $password
    * @param string $repeatpassword
    * @return array $return
    */

    public function register($email, $username, $password, $repeatpassword)
    {
        $return = array();
        $return['code'] = 400;

        if ($this->isBlocked()) {
            $return['code'] = 0;
            return $return;
        } else {
            $validateEmail = $this->validateEmail($email);
            $validateUsername = $this->validateUsername($username);
            $validatePassword = $this->validatePassword($password);

            if ($validateEmail['error'] == 1) {
                $return['message'] = $validateEmail['message'];
                return $return;
            } elseif ($validateUsername['error'] == 1) {
                $return['message'] = $validateUsername['message'];
                return $return;
            } elseif ($validatePassword['error'] == 1) {
                $return['message'] = $validatePassword['message'];
                return $return;
            } elseif($password !== $repeatpassword) {
                $return['message'] = "password_nomatch";
                return $return;
            } else {
                if (!$this->isEmailTaken($email)) {
                    if (!$this->isUsernameTaken($username)) {
                        $addUser = $this->addUser($email, $username, $password);

                        if($addUser['error'] == 0) {
                            $return['code'] = 200;
                            $return['message'] = "register_success";
                            return $return;
                        } else {
                            $return['message'] = $addUser['message'];
                            return $return;
                        }
                    } else {
                        $this->addAttempt();

                        $this->addNewLog("", "REGISTER_FAIL_USERNAME", "User attempted to register new account with the username : {$username} -> Username already in use");

                        $return['message'] = "username_taken";
                        return $return;
                    }
                } else {
                    $this->addAttempt();

                    $this->addNewLog("", "REGISTER_FAIL_EMAIL", "User attempted to register new account with the email : {$email} -> Email already in use");

                    $return['message'] = "email_taken";
                    return $return;
                }
            }
        }
    }

    /*
    * Activates a user's account
    * @param string $key
    * @return array $return
    */

    public function activate($key)
    {
        $return = array();
        $return['code'] = 400;

        if($this->isBlocked()) {
            $return['message'] = "user_blocked";
            return $return;
        } else {
            if(strlen($key) !== 20) {
                $this->addAttempt();

                $return['message'] = "key_invalid";
                return $return;
            } else {
                $getRequest = $this->getRequest($key, "activation");

                if($getRequest['error'] == 0) {
                    if($this->getUser($getRequest['uid'])['isactive'] == 0) {
                        $query = $this->dbh->prepare("UPDATE {$this->config->table_users} SET isactive = ? WHERE id = ?");
                        $query->execute(array(1, $getRequest['uid']));

                        $this->deleteRequest($getRequest['id']);

                        $this->addNewLog($getRequest['uid'], "ACTIVATE_SUCCESS", "");

                        $return['code'] = 200;
                        $return['message'] = "account_activated";
                        return $return;
                    } else {
                        $this->addAttempt();

                        $this->deleteRequest($getRequest['id']);

                        $this->addNewLog($getRequest['uid'], "ACTIVATE_FAIL_ALREADYACTIVE", "Key : {$key}");

                        $return['message'] = "system_error";
                        return $return;
                    }
                } else {
                    $return['message'] = $getRequest['message'];
                    return $return;
                }
            }
        }
    }

    /*
    * Creates a reset key for an email address and sends email
    * @param string $email
    * @return array $return
    */

    public function requestReset($email)
    {
        $return = array();
        $return['code'] = 400;

        if ($this->isBlocked()) {
            $return['message'] = "user_blocked";
            return $return;
        } else {
            $validateEmail = $this->validateEmail($email);

            if ($validateEmail['error'] == 1) {
                $return['message'] = "email_invalid";
                return $return;
            } else {
                $query = $this->dbh->prepare("SELECT id FROM {$this->config->table_users} WHERE email = ?");
                $query->execute(array($email));

                if ($query->rowCount() == 0) {
                    $this->addAttempt();

                    $this->addNewLog("", "REQUESTRESET_FAIL_EMAIL", "User attempted to reset the password for the email : {$email} -> Email doesn't exist in DB");

                    $return['message'] = "email_incorrect";
                    return $return;
                } else {
                    $row = $query->fetch(PDO::FETCH_ASSOC);

                    $addRequest = $this->addRequest($row['id'], $email, "reset");

                    if ($addRequest['error'] == 0) {
                        $this->addNewLog($row['id'], "REQUESTRESET_SUCCESS", "A reset request was sent to the email : {$email}");

                        $return['code'] = 200;
                        $return['message'] = "reset_requested";

                        return $return;
                    } else {
                        $this->addAttempt();

                        $this->addNewLog($row['id'], "REQUESTRESET_FAIL", "");

                        $return['message'] = $addRequest['message'];
                        return $return;
                    }
                }
            }
        }
    }

    /*
    * Logs out the session, identified by hash
    * @param string $hash
    * @return boolean
    */

    public function logout($hash)
    {
        if (strlen($hash) != 40) {
            return false;
        }

        $return = $this->deleteSession($hash);

        return $return;
    }

    /*
    * Hashes string using multiple hashing methods, for enhanced security
    * @param string $string
    * @param string $salt
    * @return string $hash
    */

    public function getHash($string, $salt)
    {
        return password_hash($string, PASSWORD_BCRYPT, ['salt' => $salt, 'cost' => $this->config->bcrypt_cost]);
    }

    /*
    * Gets user data for a given username and returns an array
    * @param string $username
    * @return array $data
    */

    public function getUID($username)
    {
        $query = $this->dbh->prepare("SELECT id FROM {$this->config->table_users} WHERE username = ?");
        $query->execute(array($username));

        if($query->rowCount() == 0) {
            return false;
        } else {
            $row = $query->fetch(PDO::FETCH_ASSOC);

            return $row['id'];
        }
    }

    /*
    * Creates a session for a specified user id
    * @param int $uid
    * @param boolean $remember
    * @return array $data
    */

    private function addSession($uid, $remember)
    {
        $ip = $this->getIp();

        if($user = $this->getUser($uid)) {

            $data['hash'] = sha1($user['salt'] . microtime());
            $agent = $_SERVER['HTTP_USER_AGENT'];

            $this->deleteExistingSessions($uid);

            if($remember == true) {
                $data['expire'] = date("Y-m-d H:i:s", strtotime($this->config->duration_remember));
                $data['expiretime'] = strtotime($data['expire']);
            } else {
                $data['expire'] = date("Y-m-d H:i:s", strtotime($this->config->duration_non_remember));
                $data['expiretime'] = 0;
            }

            $data['cookie_crc'] = sha1($data['hash'] . $this->config->sitekey);

            $query = $this->dbh->prepare("INSERT INTO {$this->config->table_sessions} (uid, hash, expiredate, ip, agent, cookie_crc) VALUES (?, ?, ?, ?, ?, ?)");
            
            if($query->execute(array($uid, $data['hash'], $data['expire'], $ip, $agent, $data['cookie_crc']))) {
                $data['expire'] = strtotime($data['expire']);

                return $data;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /*
    * Removes all existing sessions for a given UID
    * @param int $uid
    * @return boolean
    */

    private function deleteExistingSessions($uid)
    {
        $query = $this->dbh->prepare("DELETE FROM {$this->config->table_sessions} WHERE uid = ?");
        $return = $query->execute(array($uid));

        return $return;
    }

    /*
    * Removes a session based on hash
    * @param string $hash
    * @return boolean
    */

    private function deleteSession($hash)
    {
        $query = $this->dbh->prepare("DELETE FROM {$this->config->table_sessions} WHERE hash = ?");
        $return = $query->execute(array($hash));

        $this->addNewLog(0, "SESSION_DELETED", "Session deteted : {$hash}");

        return $return;
    }

    /*
    * Returns username based on session hash
    * @param string $hash
    * @return string $username
    */

    public function getSessionUID($hash)
    {
        $query = $this->dbh->prepare("SELECT uid FROM {$this->config->table_sessions} WHERE hash = ?");
        $query->execute(array($hash));

        if ($query->rowCount() == 0) {
            return false;
        } else {
            $row = $query->fetch(PDO::FETCH_ASSOC);

            return $row['uid'];
        }
    }

    /*
    * Function to add data to log table
    * @param string $uid
    * @param string $action
    * @param string $info
    * @param return boolean
    */

    public function addNewLog($uid = 0, $action, $info)
    {
        $ip = $this->getIp();

        if (strlen($action) == 0) {
            return false;
        } elseif (strlen($action) > 100) {
            return false;
        } elseif (strlen($info) == 0) {
            return false;
        } elseif (strlen($info) > 1000) {
            return false;
        } else {
            

            $query = $this->dbh->prepare("INSERT INTO {$this->config->table_log} (uid, action, info, ip) VALUES (?, ?, ?, ?)");
            $return = $query->execute(array(
                $uid,
                $action,
                $info,
                $ip));

            return $return;
        }
    }

    /*
    * Function to check if a session is valid
    * @param string $hash
    * @return boolean
    */

    public function checkSession($hash)
    {
        $ip = $this->getIp();

        if ($this->isBlocked()) {
            return false;
        } else {
            if (strlen($hash) != 40) {
                return false;
            }

            $query = $this->dbh->prepare("SELECT id, uid, expiredate, ip, agent, cookie_crc FROM {$this->config->table_sessions} WHERE hash = ?");
            $query->execute(array($hash));

            if ($query->rowCount() == 0) {
                $this->addNewLog(0, "CHECKSESSION_FAIL_NOEXIST", "Hash ({$hash}) doesn't exist in DB -> Cookie deleted");

                return false;
            } else {
                $row = $query->fetch(\PDO::FETCH_ASSOC);

                $sid = $row['id'];
                $uid = $row['uid'];
                $expiredate = $row['expiredate'];
                $db_ip = $row['ip'];
                $db_agent = $row['agent'];
                $db_cookie = $row['cookie_crc'];

                if ($ip != $db_ip) {
                    if ($_SERVER['HTTP_USER_AGENT'] != $db_agent) {
                        $this->deleteExistingSessions($uid);

                        $this->addNewLog($uid, "CHECKSESSION_FAIL_DIFF", "IP and User Agent Different ( DB : {$db_ip} / Current : " . $ip . " ) -> UID sessions deleted, cookie deleted");

                        return false;
                    } else {
                        $expiredate = strtotime($expiredate);
                        $currentdate = strtotime(date("Y-m-d H:i:s"));

                        if ($currentdate > $expiredate) {
                            $this->deleteExistingSessions($uid);

                            $this->addNewLog($uid, "CHECKSESSION_FAIL_EXPIRE", "Session expired ( Expire date : {$row['expiredate']} ) -> UID sessions deleted, cookie deleted");

                            return false;
                        } else {
                            return $this->updateSessionIp($sid, $ip);
                        }
                    }
                } else {
                    $expiredate = strtotime($expiredate);
                    $currentdate = strtotime(date("Y-m-d H:i:s"));

                    if ($currentdate > $expiredate) {
                        $this->deleteExistingSessions($uid);

                        $this->addNewLog($uid, "AUTH_CHECKSESSION_FAIL_EXPIRE", "Session expired ( Expire date : {$row['expiredate']} ) -> UID sessions deleted, cookie deleted");

                        return false;
                    } else {
                        $cookie_crc = sha1($hash . $this->config->sitekey);

                        if ($db_cookie == $cookie_crc) {
                            return true;
                        } else {
                            $this->addNewLog($uid, "AUTH_COOKIE_FAIL_BADCRC", "Cookie Integrity failed");

                            return false;
                        }
                    }
                }
            }
        }
    }

    /*
    * Updates the IP of a session (used if IP has changed, but agent has remained unchanged)
    * @param int $sid
    * @param string $ip
    * @return boolean
    */

    private function updateSessionIp($sid, $ip)
    {
        $query = $this->dbh->prepare("UPDATE {$this->config->table_sessions} SET ip = ? WHERE id = ?");
        return $query->execute(array($ip, $sid));
    }

    /*
    * Checks if an email is already in use
    * @param string $email
    * @return boolean
    */

    private function isEmailTaken($email)
    {
        $query = $this->dbh->prepare("SELECT * FROM {$this->config->table_users} WHERE email = ?");
        $query->execute(array($email));

        if ($query->rowCount() == 0) {
            return false;
        } else {
            return true;
        }
    }

    /*
    * Checks if a username is already in use
    * @param string $username
    * @return boolean
    */

    private function isUsernameTaken($username)
    {
        if($this->getUID($username)) {
            return true;
        } else {
            return false;
        }
    }

    /*
    * Adds a new user to database
    * @param string $email
    * @param string $username
    * @param string $password
    * @return int $uid
    */

    private function addUser($email, $username, $password)
    {
        $return = array();
        $return['error'] = 1;

        $query = $this->dbh->prepare("INSERT INTO {$this->config->table_users} VALUES ()");

        if($query->execute()) {
            $uid = $this->dbh->lastInsertId();
            $email = htmlentities($email);

            $addRequest = $this->addRequest($uid, $email, "activation");

            if($addRequest['error'] == 0) {
                $salt = substr(strtr(base64_encode(mcrypt_create_iv(22, MCRYPT_DEV_URANDOM)), '+', '.'), 0, 22);

                $username = htmlentities(strtolower($username));
                $password = $this->getHash($password, $salt);

                $query = $this->dbh->prepare("UPDATE {$this->config->table_users} SET username = ?, password = ?, email = ?, salt = ? WHERE id = ?");
                
                if($query->execute(array($username, $password, $email, $salt, $uid))) {
                    $return['error'] = 0;
                    return $return;
                } else {
                    // Database update failed. Delete user row.

                    $query = $this->dbh->prepare("DELETE FROM {$this->config->table_users} WHERE id = ?");
                    $query->execute(array($uid));

                    $return['message'] = "system_error";
                    return $return;
                }
            } else {
                // addRequest failed. Delete user row.

                $query = $this->dbh->prepare("DELETE FROM {$this->config->table_users} WHERE id = ?");
                $query->execute(array($uid));

                $return['message'] = $addRequest['message'];
                return $return;
            }
        } else {
            // Insert failed. Delete user row.

            $return['message'] = "system_error";
            return $return;
        }
    }

    /*
    * Gets user data for a given UID and returns an array
    * @param int $uid
    * @return array $data
    */

    public function getUser($uid)
    {
        $data = array();

        $query = $this->dbh->prepare("SELECT username, password, email, salt, isactive FROM {$this->config->table_users} WHERE id = ?");
        $query->execute(array($uid));

        if ($query->rowCount() == 0) {
            return false;
        } else {
            $data = $query->fetch(PDO::FETCH_ASSOC);

            if (!$data) {
                return false;
            } else {
                $data['uid'] = $uid;

                return $data;
            }
        }
    }

    /*
    * Allows a user to delete their account
    * @param int $uid
    * @param string $password
    * @return array $return
    */

    public function deleteUser($uid, $password) 
    {
        $return = array();
        $return['code'] = 400;

        if ($this->isBlocked()) {
            // User is locked out
            $return['message'] = "user_blocked";
            
            return $return;
        } else {
            $validatePassword = $this->validatePassword($password);
            
            if($validatePassword['error'] == 1) {
                $this->addAttempt();

                $return['message'] = $validatePassword['message'];      
                return $return;
            }
        }

        $getUser = $this->getUser($uid);

        if(password_verify($password, $getUser['password'])) {
            $query = $this->dbh->prepare("DELETE FROM {$this->config->table_users} WHERE id = ?");
            if($query->execute(array($uid))) {
                $query = $this->dbh->prepare("DELETE FROM {$this->config->table_sessions} WHERE uid = ?");
                if($query->execute(array($uid))) {
                    $query = $this->dbh->prepare("DELETE FROM {$this->config->table_requests} WHERE uid = ?");
                    if($query->execute(array($uid))) {
                        $this->addNewLog($uid, "ACCOUNT_DELETED", "Username : {$getUser['username']}");

                        $return['code'] = 200;
                        $return['message'] = "account_deleted";

                        return $return;
                    } else {
                        $return['message'] = "system_error";
                        return $return;
                    }
                } else {
                    $return['message'] = "system_error";
                    return $return;
                }
            } else {
                $return['message'] = "system_error";
                return $return;
            }
        } else {
            $this->addAttempt();

            $return['message'] = "password_incorrect";
            return $return;
        }
    }

    /*
    * Creates an activation entry and sends email to user
    * @param int $uid
    * @param string $email
    * @return boolean
    */

    private function addRequest($uid, $email, $type)
    {
        $return = array();
        $return['error'] = 1;

        if($type != "activation" && $type != "reset") {
            $return['message'] = "system_error";
            return $return;
        }

        $query = $this->dbh->prepare("SELECT id, expire FROM {$this->config->table_requests} WHERE uid = ? AND type = ?");
        $query->execute(array($uid, $type));

        if($query->rowCount() > 0) {
            $row = $query->fetch(PDO::FETCH_ASSOC);

            $expiredate = strtotime($row['expire']);
            $currentdate = strtotime(date("Y-m-d H:i:s"));

            if ($currentdate < $expiredate) {
                $return['message'] = "request_exists";
                return $return;
            } else {
                $this->deleteRequest($row['id']);
            }
        }

        if($type == "activation" && $this->getUser($uid)['isactive'] == 1) {
            $return['message'] = "already_activated";
            return $return;
        }

        $key = $this->getRandomKey(20);
        $expire = date("Y-m-d H:i:s", strtotime("+1 day"));

        $query = $this->dbh->prepare("INSERT INTO {$this->config->table_requests} (uid, rkey, expire, type) VALUES (?, ?, ?, ?)");

        if($query->execute(array($uid, $key, $expire, $type))) {
            if($type == "activation") {
                $message = "Account activation required : <strong><a href=\"{$this->config->authurl}/auth/#/activate/{$key}\">Activate my account</a></strong>";

                $headers  = 'MIME-Version: 1.0' . "\r\n";
                $headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
                $headers .= "From: {$this->config->fromemail}" . "\r\n";

                if(@mail($email, "{$this->config->sitename} - Account Activation", $message, $headers)) {
                    $return['error'] = 0;
                    return $return;
                } else {
                    $return['message'] = "system_error";
                }
            } else {
                $message = "Password reset request : <strong><a href=\"{$this->config->authurl}/auth/#/reset/{$key}\">Reset my password</a></strong>";

                $headers  = 'MIME-Version: 1.0' . "\r\n";
                $headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
                $headers .= "From: {$this->config->fromemail}" . "\r\n";

                if(@mail($email, "{$this->config->sitename} - Password Reset Request", $message, $headers)) {
                    $return['error'] = 0;
                    return $return;
                } else {
                    $return['message'] = "system_error";
                }
            }
        } else {
            $return['message'] = "system_error";
            return $return;
        }
    }

    /*
    * Returns request data if key is valid
    * @param string $key
    * @param string $type
    * @return array $return
    */

    private function getRequest($key, $type)
    {
        $return = array();
        $return['error'] = 1;

        $query = $this->dbh->prepare("SELECT id, uid, expire FROM {$this->config->table_requests} WHERE rkey = ? AND type = ?");
        $query->execute(array($key, $type));

        if ($query->rowCount() === 0) {
            $this->addAttempt();

            $return['message'] = "key_incorrect";
            return $return;
        }

        $row = $query->fetch();

        $expiredate = strtotime($row['expire']);
        $currentdate = strtotime(date("Y-m-d H:i:s"));

        if ($currentdate > $expiredate) {
            $this->addAttempt();

            $this->deleteRequest($row['id']);

            $return['message'] = "key_expired";
            return $return;
        } else {
            $return['error'] = 0;
            $return['id'] = $row['id'];
            $return['uid'] = $row['uid'];
            return $return;
        }
    }

    /*
    * Deletes request from database
    * @param int $id
    * @return boolean
    */

    private function deleteRequest($id)
    {
        $query = $this->dbh->prepare("DELETE FROM {$this->config->table_requests} WHERE id = ?");
        return $query->execute(array($id));
    }

    /*
    * Verifies that a username is valid
    * @param string $username
    * @return array $return
    */

    public function validateUsername($username) {
        $return = array();
        $return['error'] = 1;

        if (strlen($username) < 3) {
            $return['message'] = "username_short";
            $this->addNewLog(0, "USERNAME_SHORT", "Username : {$username}");
        } elseif (strlen($username) > 30) {
            $return['message'] = "username_long";
            $this->addNewLog(0, "USERNAME_LONG", "Username : {$username}");
        } elseif (!ctype_alnum($username)) {
            $return['message'] = "username_invalid";
            $this->addNewLog(0, "USERNAME_INVALID", "Username : {$username}");
        } else {
            $return['error'] = 0;
        }
    }

    /*
    * Verifies that a password is valid and respects security requirements
    * @param string $password
    * @return array $return
    */

    private function validatePassword($password) {
        $return = array();
        $return['error'] = 1;

        if (strlen($password) < 6) {
            $return['message'] = "password_short";
            $this->addNewLog(0, "PASSWORD_SHORT", "");
        } elseif (strlen($password) > 72) {
            $return['message'] = "password_long";
            $this->addNewLog(0, "PASSWORD_LONG", "");
        } elseif (!preg_match('@[A-Z]@', $password) || !preg_match('@[a-z]@', $password) || !preg_match('@[0-9]@', $password)) {
            $return['message'] = "password_invalid";
            $this->addNewLog(0, "PASSWORD_INVALID", "");
        } else {
            $return['error'] = 0;
        }

        return $return;
    }

    /*
    * Verifies that an email is valid
    * @param string $email
    * @return array $return
    */

    private function validateEmail($email) {
        $return = array();
        $return['error'] = 1;

        if (strlen($email) < 5) {
            $return['message'] = "email_short";
            $this->addNewLog(0, "EMAIL_SHORT", "Email : {$email}");
        } elseif (strlen($email) > 100) {
            $return['message'] = "email_long";
            $this->addNewLog(0, "EMAIL_LONG", "Email : {$email}");
        } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $return['message'] = "email_invalid";
            $this->addNewLog(0, "EMAIL_INVALID", "Email : {$email}");
        } else {
            $return['error'] = 0;
        }

        return $return;
    }

    /*
    * Allows a user to reset there password after requesting a reset key.
    * @param string $key
    * @param string $password
    * @param string $repeatpassword
    * @return array $return
    */

    public function resetPass($key, $password, $repeatpassword)
    {
        $return = array();
        $return['code'] = 400;

        if ($this->isBlocked()) {
            $return['message'] = "user_blocked";
            return $return;
        }

        if(strlen($key) != 20) {
            $return['message'] = "key_invalid";
            return $return;
        }

        $validatePassword = $this->validatePassword($password);

        if($validatePassword['error'] == 0) {

            if($password !== $repeatpassword) {
                // Passwords don't match
                $return['message'] = "newpassword_nomatch";
                return $return;
            }

            $data = $this->getRequest($key, "reset");

            if ($data['error'] == 0) {
                if ($user = $this->getUser($data['uid'])) {
                    if (password_verify($password, $user['password'])) {
                        
                        $this->addAttempt();
                        $this->addNewLog($data['uid'], "RESETPASS_FAIL_SAMEPASS", "User attempted to reset password with key : {$key} -> New password matches previous password !");
                        $this->deleteRequest($data['id']);

                        // Password matches previous password
                        $return['message'] = "newpassword_match";
                        return $return;
                    } else {
                        $password = $this->getHash($password, $user['salt']);

                        $query = $this->dbh->prepare("UPDATE {$this->config->table_users} SET password = ? WHERE id = ?");
                        $query->execute(array($password, $data['uid']));

                        if ($query->rowCount() == 0) {
                            // Error changing password
                            $return['message'] = "system_error";
                            return $return;
                        } else {
                            $this->addNewLog($data['uid'], "RESETPASS_SUCCESS","User attempted to reset password with key : {$key} -> Password changed, reset keys deleted !");

                            $this->deleteRequest($data['id']);

                            // Password changed successfully
                            $return['code'] = 200;
                            $return['message'] = "password_reset";
                            return $return;
                        }
                    }
                } else {
                    $this->addAttempt();

                    $this->deleteRequest($data['id']);

                    $this->addNewLog($data['uid'], "RESETPASS_FAIL_UID", "User attempted to reset password with key : {$key} -> User doesn't exist !");

                    // User no longer exists
                    $return['message'] = "system_error";
                    return $return;
                }
            } else {
                $return['message'] = $data['message'];
            }
        } else {
            $return['message'] = $validatePassword['message'];
        }
    }

    /*
    * Recreates activation email for a given email and sends
    * @param string $email
    * @return array $return
    */

    public function resendActivation($email)
    {
        $return = array();
        $return['code'] = 400;

        if ($this->isBlocked()) {
            $return['message'] = "user_blocked";
            return $return;
        } else {
            $validateEmail = $this->validateEmail($email);

            if($validateEmail['error'] == 1) {
                $return['message'] = $validateEmail['message'];
                return $return;
            } else {
                $query = $this->dbh->prepare("SELECT id FROM {$this->config->table_users} WHERE email = ?");
                $query->execute(array($email));

                if($query->rowCount() == 0) {
                    $this->addAttempt();

                    $this->addNewLog("", "RESENDACTIVATION_FAIL_EMAIL", "User attempted to resend activation email for the email : {$email} -> Email doesn't exist in DB !");

                    $return['message'] = "email_incorrect";
                    return $return;
                } else {
                    $row = $query->fetch(\PDO::FETCH_ASSOC);

                    if ($this->getUser($row['id'])['isactive'] == 1) {
                        $this->addAttempt();

                        $this->addNewLog($row['id'], "RESENDACTIVATION_FAIL_ACTIVATED","User attempted to resend activation email for the email : {$email} -> Account is already activated !");

                        $return['message'] = "already_activated";
                        return $return;
                    } else {
                        $addRequest = $this->addRequest($row['id'], $email, "activation");

                        if ($addRequest['error'] == 0) {
                            $this->addNewLog($row['id'], "RESENDACTIVATION_SUCCESS","Activation email was resent to the email : {$email}");

                            $return['code'] = 200;
                            $return['message'] = "activation_sent";
                            return $return;
                        } else {
                            $this->addAttempt();

                            $this->addNewLog($row['id'], "RESENDACTIVATION_FAIL_EXIST","User attempted to resend activation email for the email : {$email} -> Activation request already exists. 24 hour expire wait required !");

                            $return['message'] = $addRequest['message'];
                            return $return;
                        }
                    }
                }
            }
        }
    }

    /*
    * Gets UID from Session hash
    * @param string $hash
    * @return int $uid
    */

    public function sessionUID($hash)
    {
        if (strlen($hash) != 40) {
            return false;
        } else {
            $query = $this->dbh->prepare("SELECT uid FROM {$this->config->table_sessions} WHERE hash = ?");
            $query->execute(array($hash));
            
            if($query->rowCount() == 0) {
                return false;
            } else {
                $row = $query->fetch(\PDO::FETCH_ASSOC);
                return $row['uid'];
            }
        }
    }

    /*
    * Changes a user's password
    * @param int $uid
    * @param string $currpass
    * @param string $newpass
    * @return array $return
    */

    public function changePassword($uid, $currpass, $newpass, $repeatnewpass)
    {
        $return = array();
        $return['code'] = 400;

        if ($this->isBlocked()) {
            // User is locked out
            $return['message'] = "user_blocked";
			
            return $return;
        } else {
            $validatePassword = $this->validatePassword($currpass);
			
            if($validatePassword['error'] == 1) {
				$this->addAttempt();

                $return['message'] = $validatePassword['message'];		
				return $return;
			}

            $validatePassword = $this->validatePassword($newpass);

            if($validatePassword['error'] == 1) {
                $return['message'] = "new" . $validatePassword['message'];      
                return $return;
            } elseif($newpass !== $repeatnewpass) {
                // New passwords don't match
                $return['message'] = "newpassword_nomatch";
                return $return;
            }

			if ($user = $this->getUser($uid)) {
                $newpass = $this->getHash($newpass, $user['salt']);

				if ($currpass != $newpass) {
					if (password_verify($currpass, $user['password'])) {
						$query = $this->dbh->prepare("UPDATE {$this->config->table_users} SET password = ? WHERE id = ?");
						$query->execute(array($newpass, $uid));

						$this->addNewLog($uid, "CHANGEPASS_SUCCESS", "User changed the password for the UID : {$uid}");

						$return['code'] = 200;
                        $return['message'] = "password_changed";
						return $return;
					} else {
						$this->addAttempt();

						$this->addNewLog($uid, "CHANGEPASS_FAIL_PASSWRONG", "User attempted to change password for the UID : {$uid} -> Current password incorrect !");

						$return['message'] = "password_incorrect";
						return $return;
					}
				} else {
					$this->addAttempt();

					$this->addNewLog($uid, "CHANGEPASS_FAIL_PASSMATCH", "User attempted to change password for the UID : {$uid} -> New password matches current password !");

					$return['message'] = "newpassword_match";
					return $return;
				}
            } else {
                $this->addAttempt();

                $this->addNewLog($uid, "CHANGEPASS_FAIL_UID", "User attempted to change password for the UID : {$uid} -> UID doesn't exist !");

                // UID is incorrect
                $return['message'] = "system_error";
                return $return;
            }
		}
    }

    /*
    * Gets a user's email address by UID
    * @param int $uid
    * @return string $email
    */

    public function getEmail($uid)
    {
        $query = $this->dbh->prepare("SELECT email FROM {$this->config->table_users} WHERE id = ?");
        $query->execute(array($uid));
        $row = $query->fetch(\PDO::FETCH_ASSOC);

        if (!$row) {
            return false;
        } else {
            return $row['email'];
        }
    }

    /*
    * Changes a user's email
    * @param int $uid
    * @param string $currpass
    * @param string $newpass
    * @return array $return
    */

    public function changeEmail($uid, $email, $password)
    {
        $return = array();
        $return['code'] = 400;

        if ($this->isBlocked()) {
            // Locked out
            $return['message'] = "user_blocked";
            return $return;
        } else {
            $validateEmail = $this->validateEmail($email);
            $validatePassword = $this->validatePassword($password);

			if($validateEmail['error'] == 1)
			{
				$return['message'] = $validateEmail['message'];
				return $return;
			} elseif ($validatePassword['error'] == 1) {
                $return['message'] = "password_notvalid";
				return $return;
			} else {

				if ($user = $this->getUser($uid)) {
					if (password_verify($password, $user['password'])) {
						if ($email == $user['email']) {
							$this->addAttempt();

							$this->addNewLog($uid, "CHANGEEMAIL_FAIL_EMAILMATCH", "User attempted to change email for the UID : {$uid} -> New Email address matches current email !");

                            // New email matches current email
                            $return['message'] = "newemail_match";
							return $return;
						} else {
							$query = $this->dbh->prepare("UPDATE {$this->config->table_users} SET email = ? WHERE id = ?");
							$query->execute(array($email, $uid));

							if ($query->rowCount() == 0) {
                                $return['message'] = "system_error";
                                return $return;
                            }

							$this->addNewLog($uid, "CHANGEEMAIL_SUCCESS", "User changed email address for UID : {$uid}");

                            // Email changed
							$return['code'] = 200;
                            $return['message'] = "email_changed";
							return $return;
						}
					} else {
						$this->addAttempt();

						$this->addNewLog($uid, "CHANGEEMAIL_FAIL_PASS", "User attempted to change email for the UID : {$uid} -> Password is incorrect !");

                        // Password is incorrect
                        $return['message'] = "password_incorrect";
						return $return;
					}
				} else {
                    $this->addAttempt();

                    $this->addNewLog($uid, "CHANGEEMAIL_FAIL_UID", "User attempted to change email for the UID : {$uid} -> UID doesn't exist !");

                    // UID is incorrect
                    $return['message'] = "system_error";
                    return $return;
                }
			}
        }
    }

    /*
    * Informs if a user is locked out
    * @param string $ip
    * @return boolean
    */

    private function isBlocked()
    {
        $ip = $this->getIp();

        $query = $this->dbh->prepare("SELECT count, expiredate FROM {$this->config->table_attempts} WHERE ip = ?");
        $query->execute(array($ip));

        if($query->rowCount() == 0) {
            return false;
        }

        $row = $query->fetch(\PDO::FETCH_ASSOC);

        $expiredate = strtotime($row['expiredate']);
        $currentdate = strtotime(date("Y-m-d H:i:s"));

        if ($row['count'] == 5) {
            if ($currentdate < $expiredate) {
                return true;
            } else {
                $this->deleteAttempts($ip);
                return false;
            }
        } else {
            if ($currentdate > $expiredate) {
                $this->deleteAttempts($ip);
            }

            return false;
        }
    }

    public function getUsernameFromUID($uid)
    {
        $query = $this->dbh->prepare("SELECT username FROM users WHERE id = ?");
        $query->execute(array($uid));

        if ($query->rowCount() == 0) {
            return false;
        } else {
            $return = $query->fetch();
            return $return['username'];
        }
    }

    /*
    * Adds an attempt to database
    * @return boolean
    */

    private function addAttempt()
    {
        $ip = $this->getIp();

        $query = $this->dbh->prepare("SELECT count FROM {$this->config->table_attempts} WHERE ip = ?");
        $query->execute(array($ip));

        $row = $query->fetch(\PDO::FETCH_ASSOC);

        if (!$row) {
            $attempt_expiredate = date("Y-m-d H:i:s", strtotime("+30 minutes"));
            $attempt_count = 1;

            $query = $this->dbh->prepare("INSERT INTO {$this->config->table_attempts} (ip, count, expiredate) VALUES (?, ?, ?)");
            $return = $query->execute(array(
                $ip,
                $attempt_count,
                $attempt_expiredate));

            return $return;
        } else {
            $attempt_expiredate = date("Y-m-d H:i:s", strtotime("+30 minutes"));
            $attempt_count = $row['count'] + 1;

            $query = $this->dbh->prepare("UPDATE {$this->config->table_attempts} SET count=?, expiredate=? WHERE ip=?");
            $return = $query->execute(array(
                $attempt_count,
                $attempt_expiredate,
                $ip));

            return $return;
        }
    }

    /*
    * Deletes all attempts for a given IP from database
    * @param string $ip
    * @return boolean
    */

    private function deleteAttempts($ip)
    {
        $query = $this->dbh->prepare("DELETE FROM {$this->config->table_attempts} WHERE ip = ?");
        $return = $query->execute(array($ip));

        return $return;
    }

    /*
    * Returns a random string of a specified length
    * @param int $length
    * @return string $key
    */

    public function getRandomKey($length = 20)
    {
        $chars = "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6";
        $key = "";

        for ($i = 0; $i < $length; $i++) {
            $key .= $chars{mt_rand(0, strlen($chars) - 1)};
        }

        return $key;
    }

    /*
    * Returns IP address
    * @return string $ip
    */

    private function getIp()
    {
        return $_SERVER['REMOTE_ADDR'];
    }
}