<commit_before><?php

if(!defined('BASEPATH')) {
    exit('No direct script access allowed');
}

/**
 * User account controller.
 * 
 * The workflow for a new user is as follows:
 *
 *   1. Enter the 'register' method.  This will first display the 'account_register'
 *      template for entering the needed information.  If the information is
 *      incomplete or faulty, the 'account_register_invalid' will be shown.  Else,
 *      a preliminary account with the role {@see \addventure\User::AwaitApproval} will be
 *      created an E-mail will be sent to the user which contains encrypted data
 *      for validation.
 * 
 *   2. The user receives his E-mail with the activation link, which points to
 *      the 'verify' method.  Here, the security token passed in the URL will
 *      be verified against the stored information, and if everything is OK,
 *      the account role will be set to {@see \addventure\User::Registered}.  But if
 *      something goes wrong, the 'account_register_invalid' will be shown.
 * 
 *   3. Now, the user has to login to create his session cookie and to store
 *      the session data in the database.  This could be done in the 'verify'
 *      step, but it ensures that the E-mail account isn't hijacked, because
 *      the user has to enter his password again.
 */
class Account extends CI_Controller {

    private static function _getVerificationCode($email) {
        return sha1($email . sha1(ADDVENTURE_KEY));
    }

    private static function _encodePassword($password) {
        return password_hash($password, PASSWORD_DEFAULT);
    }

    private function _createMessage($to, $subject) {
        $message = Swift_Message::newInstance();
        $message->setFrom(ADDVENTURE_EMAIL_ADDRESS, ADDVENTURE_EMAIL_NAME);
        $message->setTo($to);
        $message->setSubject($subject);
        return $message;
    }

    public function register() {
        $this->load->helper('smarty');
        $smarty = createSmarty();
        $email = $this->input->post('email', TRUE);
        $password = $this->input->post('password');
        if($email === FALSE || empty($email) || $password === FALSE || empty($password)) {
            $smarty->display('account_register.tpl');
            return;
        }
        $this->load->helper('email');
        if(!valid_email($email)) {
            $smarty->display('account_register_invalid.tpl');
            return;
        }

        try {
            $username = simplifyWhitespace($this->input->post('username', TRUE), 1000, false);
        }
        catch(\InvalidArgumentException $ex) {
            $smarty->display('account_register_invalid.tpl');
            return;
        }
        $user = new \addventure\User();
        $user->setUsername($username);
        $user->setEmail($email);
        $user->setRole(\addventure\UserRole::AwaitApproval);
        $this->load->library('encrypt');
        $user->setPassword(self::_encodePassword($password));
        $user->setRegisteredSince(new \DateTime());
        try {
            $this->load->library('em');
            $this->em->persistAndFlush($user);
        }
        catch(Exception $e) {
            $smarty->display('account_register_invalid.tpl');
            return;
        }

        $verify = site_url(array('account', 'verify')) . '?';
        $verify .= 'a=' . rawurlencode(self::_getVerificationCode($email));
        $verify .= '&b=' . rawurlencode(base64_encode($this->encrypt->encode($email)));
        /**
         * @todo Make it a smarty template and support HTML mails.
         */
        $message = $this->_createMessage($email, 'Addventure2 E-Mail Verification');
        $message->setBody(<<<"MSG"
Dear writer!

To verify your e-mail address, please open the following link in your browser:

{$verify}

Happy writing!
MSG
        );
        $transport = Swift_SendmailTransport::newInstance();
        $mailer = Swift_Mailer::newInstance($transport);
        if($mailer->send($message, $failures)) {
            $smarty->display('account_register_mail_sent.tpl');
            return;
        }
        $this->load->library('log');
        $this->log->crit('Could not send verification e-mail: ' . print_r($failures, true));
        show_error('Sorry, something bad happened; it\'s not your fault. Our dozens of monkey are probably working on it.');
    }

    public function verify() {
        $token = rawurldecode($this->input->get('a'));
        $email = rawurldecode($this->input->get('b'));

        $this->load->library('encrypt');
        $email = $this->encrypt->decode(base64_decode($email));

        $this->load->library('em');
        $user = $this->em->findUserByMail($email);
        
        $this->load->helper('smarty');
        $smarty = createSmarty();
        
        if($user && $user->getRole()->get() === \addventure\UserRole::AwaitApproval) {
            $diff = abs( (new \DateTime())->getTimestamp() - $user->getRegisteredSince()->getTimestamp() );
            if( $diff > ADDVENTURE_MAX_AWAITING_APPROVAL_HOURS * 60 * 60 ) {
                $this->em->getEntityManager()->remove($user);
                $this->em->getEntityManager()->flush();
                $smarty->display('account_verify_expired.tpl');
                return;
            }
        }

        if(!$user || $user->getRole()->get() !== \addventure\UserRole::AwaitApproval || $token !== self::_getVerificationCode($email)) {
            $smarty->display('account_verify_invalid.tpl');
            return;
        }

        $user->setRole(\addventure\UserRole::Registered);
        $user->setRegisteredSince(new \DateTime());
        $this->em->persistAndFlush($user);
        redirect(site_url());
    }

    public function login() {
        $this->load->library('encrypt');
        $name = $this->input->post('username', TRUE);
        $password = $this->input->post('password');
        $remember = $this->input->post('remember', TRUE);

        $this->load->library('em');
        $user = $this->em->findUserByName($name);

        $this->load->helper('smarty');
        $smarty = createSmarty();

        if(!$user || $user->getRole() < \addventure\UserRole::Registered || !password_verify($password, $user->getPassword())) {
            if($user) {
                $user->setFailedLogins($user->getFailedLogins() + 1);
                $this->em->persistAndFlush($user);
            }
            if($user->isLockedOut()) {
                $smarty->display('account_locked.tpl');
            }
            else {
                $smarty->display('account_login_invalid.tpl');
            }
            return;
        }

        // even a successful login cannot unlock a locked account
        if($user->isLockedOut()) {
            $smarty->display('account_locked.tpl');
            return;
        }
        
        $user->setFailedLogins(0);
        $this->em->persistAndFlush($user);
        
        $this->load->library('session');
        if(!isset($remember) || $remember !== 'yes') {
            $this->session->sess_expire_on_close = TRUE;
        }
        $this->session->set_userdata('userid', $user->getId());
        $this->load->helper('url');
        redirect(site_url());
    }
<commit_msg>fix user login checks<commit_after><?php

if(!defined('BASEPATH')) {
    exit('No direct script access allowed');
}

/**
 * User account controller.
 * 
 * The workflow for a new user is as follows:
 *
 *   1. Enter the 'register' method.  This will first display the 'account_register'
 *      template for entering the needed information.  If the information is
 *      incomplete or faulty, the 'account_register_invalid' will be shown.  Else,
 *      a preliminary account with the role {@see \addventure\User::AwaitApproval} will be
 *      created an E-mail will be sent to the user which contains encrypted data
 *      for validation.
 * 
 *   2. The user receives his E-mail with the activation link, which points to
 *      the 'verify' method.  Here, the security token passed in the URL will
 *      be verified against the stored information, and if everything is OK,
 *      the account role will be set to {@see \addventure\User::Registered}.  But if
 *      something goes wrong, the 'account_register_invalid' will be shown.
 * 
 *   3. Now, the user has to login to create his session cookie and to store
 *      the session data in the database.  This could be done in the 'verify'
 *      step, but it ensures that the E-mail account isn't hijacked, because
 *      the user has to enter his password again.
 */
class Account extends CI_Controller {

    private static function _getVerificationCode($email) {
        return sha1($email . sha1(ADDVENTURE_KEY));
    }

    private static function _encodePassword($password) {
        return password_hash($password, PASSWORD_DEFAULT);
    }

    private function _createMessage($to, $subject) {
        $message = Swift_Message::newInstance();
        $message->setFrom(ADDVENTURE_EMAIL_ADDRESS, ADDVENTURE_EMAIL_NAME);
        $message->setTo($to);
        $message->setSubject($subject);
        return $message;
    }

    public function register() {
        $this->load->helper('smarty');
        $smarty = createSmarty();
        $email = $this->input->post('email', TRUE);
        $password = $this->input->post('password');
        if($email === FALSE || empty($email) || $password === FALSE || empty($password)) {
            $smarty->display('account_register.tpl');
            return;
        }
        $this->load->helper('email');
        if(!valid_email($email)) {
            $smarty->display('account_register_invalid.tpl');
            return;
        }

        try {
            $username = simplifyWhitespace($this->input->post('username', TRUE), 1000, false);
        }
        catch(\InvalidArgumentException $ex) {
            $smarty->display('account_register_invalid.tpl');
            return;
        }
        $user = new \addventure\User();
        $user->setUsername($username);
        $user->setEmail($email);
        $user->setRole(\addventure\UserRole::AwaitApproval);
        $this->load->library('encrypt');
        $user->setPassword(self::_encodePassword($password));
        $user->setRegisteredSince(new \DateTime());
        try {
            $this->load->library('em');
            $this->em->persistAndFlush($user);
        }
        catch(Exception $e) {
            $smarty->display('account_register_invalid.tpl');
            return;
        }

        $verify = site_url(array('account', 'verify')) . '?';
        $verify .= 'a=' . rawurlencode(self::_getVerificationCode($email));
        $verify .= '&b=' . rawurlencode(base64_encode($this->encrypt->encode($email)));
        /**
         * @todo Make it a smarty template and support HTML mails.
         */
        $message = $this->_createMessage($email, 'Addventure2 E-Mail Verification');
        $message->setBody(<<<"MSG"
Dear writer!

To verify your e-mail address, please open the following link in your browser:

{$verify}

Happy writing!
MSG
        );
        $transport = Swift_SendmailTransport::newInstance();
        $mailer = Swift_Mailer::newInstance($transport);
        if($mailer->send($message, $failures)) {
            $smarty->display('account_register_mail_sent.tpl');
            return;
        }
        $this->load->library('log');
        $this->log->crit('Could not send verification e-mail: ' . print_r($failures, true));
        show_error('Sorry, something bad happened; it\'s not your fault. Our dozens of monkey are probably working on it.');
    }

    public function verify() {
        $token = rawurldecode($this->input->get('a'));
        $email = rawurldecode($this->input->get('b'));

        $this->load->library('encrypt');
        $email = $this->encrypt->decode(base64_decode($email));

        $this->load->library('em');
        $user = $this->em->findUserByMail($email);
        
        $this->load->helper('smarty');
        $smarty = createSmarty();
        
        if($user && $user->getRole()->get() === \addventure\UserRole::AwaitApproval) {
            $diff = abs( (new \DateTime())->getTimestamp() - $user->getRegisteredSince()->getTimestamp() );
            if( $diff > ADDVENTURE_MAX_AWAITING_APPROVAL_HOURS * 60 * 60 ) {
                $this->em->getEntityManager()->remove($user);
                $this->em->getEntityManager()->flush();
                $smarty->display('account_verify_expired.tpl');
                return;
            }
        }

        if(!$user || $user->getRole()->get() !== \addventure\UserRole::AwaitApproval || $token !== self::_getVerificationCode($email)) {
            $smarty->display('account_verify_invalid.tpl');
            return;
        }

        $user->setRole(\addventure\UserRole::Registered);
        $user->setRegisteredSince(new \DateTime());
        $this->em->persistAndFlush($user);
        redirect(site_url());
    }

    public function login() {
        $this->load->library('encrypt');
        $name = $this->input->post('username', TRUE);
        $password = $this->input->post('password');
        $remember = $this->input->post('remember', TRUE);

        $this->load->library('em');
        $user = $this->em->findUserByName($name);

        $this->load->helper('smarty');
        $smarty = createSmarty();

        if(!$user || $user->getRole()->get() < \addventure\UserRole::Registered || !password_verify($password, $user->getPassword())) {
            if($user) {
                $user->setFailedLogins($user->getFailedLogins() + 1);
                $this->em->persistAndFlush($user);
                if($user->isLockedOut()) {
                    $smarty->display('account_locked.tpl');
                    return;
                }
            }
            $smarty->display('account_login_invalid.tpl');
            return;
        }

        // even a successful login cannot unlock a locked account
        if($user->isLockedOut()) {
            $smarty->display('account_locked.tpl');
            return;
        }
        
        $user->setFailedLogins(0);
        $this->em->persistAndFlush($user);
        
        $this->load->library('session');
        if(!isset($remember) || $remember !== 'yes') {
            $this->session->sess_expire_on_close = TRUE;
        }
        $this->session->set_userdata('userid', $user->getId());
        $this->load->helper('url');
        redirect(site_url());
    }

{'fraction_non_alphanumeric': 0.15246356696738375, 'fraction_numerical': 0.002775850104094379, 'mean_word_length': 2.4550467513785663, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 222, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2869183', 'n_tokens_mistral': 4131, 'n_tokens_neox': 4042, 'n_words': 873}