1
\$\begingroup\$

I would like tips on my CODE, map structure (How I should seperate the code, more files, etc). What I can do about the func.php file as a file with just a bunch of functions, doesn't seem to be the best approach for it. And generally just a code review how I can improve on it.

createaccount.php

<?php
require_once 'init.php';

if (isset($_POST['submit'])) {
    if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token']) {

        $errors = array();

        if (!empty($_POST['username'])) {

            if (strlen($_POST['username']) < 3 || strlen($_POST['username']) > 20) {
                $errors[] = 'The username must be between 3 and 20 characters long';
            }

            if (!preg_match("/^[_a-zA-Z0-9]+$/", $_POST['username'])) {
                $errors[] = 'The username must contain only letters and numbers and _';
            }

            $STH = $DBH->prepare("SELECT username FROM users WHERE username = ?");
            $STH->execute(array($_POST['username']));

            if ($STH->rowCount() > 0) {
                $errors[] = 'The username is already taken';
            }

        } else {
            $errors[] = 'The username field is required';
        }

       if (!empty($_POST['password'])) {

           if (strlen($_POST['password']) < 4) {
               $errors[] = 'The password must be at least 4 characters long';
           }

           if ($_POST['password'] != $_POST['passconf']) {
               $errors[] = 'The passwords must match';
           }

       } else {
           $errors[] = 'The password field is required';
       }

       if (!empty($_POST['email'])) {

            if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
                $errors[] = 'Must be a valid email adress';
            } else {

                $STH = $DBH->prepare("SELECT email FROM users WHERE email = ?");
                $STH->execute(array($_POST['email']));

                if ($STH->rowCount() > 0) {
                    $errors[] = 'The email adress is already taken';
                }
            }

       } else {
           $errors[] = 'The email adress is required';
       }

        $resp = recaptcha_check_answer (PRIVATE_KEY,
                        $_SERVER["REMOTE_ADDR"],
                        $_POST["recaptcha_challenge_field"],
                        $_POST["recaptcha_response_field"]);

        if (!$resp->is_valid) {
            $errors[] = 'The reCAPTCHA wasn\'t entered correctly. Go back and try it again.';
        }

        if (empty($errors)) {
            $salt = salt();
            $password = sha1(sha1($_POST['password'] . $salt));
            $activation_key = md5($_POST['username'] . $_POST['email']);

            $STH = $DBH->prepare("INSERT INTO users (username, password, salt, email, activation_key, created) VALUES(:username, :password, :salt, :email, :activation_key, :created)");
            $STH->bindParam(':username', $_POST['username']);
            $STH->bindParam(':password', $password);
            $STH->bindParam(':salt', $salt);
            $STH->bindParam(':email', $_POST['email']);
            $STH->bindParam(':activation_key', $activation_key);
            $STH->bindParam(':created', time());
            $STH->execute();

            if ($DBH->lastInsertId()) {
                header("Location: activate.php");
                exit;
            } else {
                // Log
            }
        }

    }
}

$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token;

?>

<!DOCTYPE html>
<html>
    <head>
        <title>Heroes of Legacy - Create Account</title>
    </head>
    <body>
        <?php
        if (isset($errors)) {
            foreach ($errors as $error) {
                echo $error . "<br />\n";
            }
        }
        ?>
        <form method="post" action="createaccount.php">
            <input type="hidden" name="token" value="<?php echo $token; ?>" />
            Username <input type="text" name="username" value="" />
            Password <input type="password" name="password" value="" />
            Confirm Password <input type="password" name="passconf" value="" />
            Email Adress <input type="email" name="email" value="" />
            <?php echo recaptcha_get_html(PUBLIC_KEY); ?>
            <input type="submit" name="submit" value="Create Account" />
        </form>
    </body>
</html>

init.php

<?php
session_start();

require_once 'config.php';
require_once 'func.php';

try {
  $DBH = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);
}
catch(PDOException $e) {
    echo $e->getMessage();
}

function salt() {
    mt_srand(microtime(true)*100000 + memory_get_usage(true));
    return md5(uniqid(mt_rand(), true));
}

require_once 'lib/recaptchalib.php';

define('PUBLIC_KEY', '');
define('PRIVATE_KEY', '');

config.php

<?php

$hostname = 'localhost';
$username = 'root';
$password = '';
$dbname = 'hol';

login.php

<?php
require_once 'init.php';

if (logged_in()) {
    header("Location: index.php");
    exit;
}

if (isset($_POST['submit'])) {
    if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token']) {

        $errors = array();

        if (!empty($_POST['username']) && !empty($_POST['password'])) {

            $STH = $DBH->prepare("SELECT id, salt, password, banned FROM users WHERE username = ?");
            $STH->execute(array($_POST['username']));

            if ($STH->rowCount() > 0) {
                $user = $STH->fetch();

                $passconf = sha1(sha1($_POST['password'] . $user['salt']));

                if ($passconf != $user['password']) {
                    $errors[] = 'The password you entered is incorrect';
                    unset($user);
                } else {

                    session_regenerate_id();
                    $_SESSION['userid'] = $user['id'];
                    $_SESSION['hash'] = sha1($_SESSION['userid'] . $_SERVER['HTTP_USER_AGENT']);
                    unset($user);
                    header("Location: index.php");
                    exit;
                }

            } else {
                $errors[] = 'The username is incorrect';
            }

        } else {
            $errors[] = 'You must enter a username and password!';
        }

    }
}

$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token;

?>

<!DOCTYPE html>
<html>
    <head>
        <title>Heroes of Legacy - Log in</title>
    </head>
    <body>
        <?php
        if (isset($errors)) {
            foreach ($errors as $error) {
                echo $error . "<br />\n";
            }
        }
        ?>
        <form method="post" action="login.php">
            <input type="hidden" name="token" value="<?php echo $token; ?>" />
            Username <input type="text" name="username" value="" />
            Password <input type="password" name="password" value="" />
            <input type="submit" name="submit" value="Log in" />
        </form>
    </body>
</html>

func.php

<?php
function logged_in() {
    if (isset($_SESSION['userid']) 
        && isset($_SESSION['hash'])) {

        $session_check = sha1($_SESSION['userid'] . $_SERVER['HTTP_USER_AGENT']);

        if ($_SESSION['hash'] == $session_check)
            return true;
    }
    return false;
}

function logout() {
    session_unset();
    session_destroy();
    header("Location: index.php");
    exit;
}

That's about it.

-- UPDATE:

<?php
//Temporary solution
$request = trim(strtolower($_REQUEST['username']));
usleep(150000);

//Let's say this is data grabbed from a database...
$users = array('asdf', 'Peter', 'Peter2', 'George');
$valid = 'true';

foreach($users as $user) {
    if( strtolower($user) == $request )
        $valid = 'false';
}
echo $valid;
?>
\$\endgroup\$
5
  • \$\begingroup\$ Too much code without context. Tell us what you're trying to do... \$\endgroup\$
    – yannis
    Commented Nov 30, 2011 at 10:54
  • \$\begingroup\$ It's an simple authentication system... Read my context in the top, and ull see what im looking for, cheers \$\endgroup\$
    – John
    Commented Nov 30, 2011 at 11:02
  • \$\begingroup\$ Well not what I meant, I can see it's a simple authentication system and I can read what you're looking for. I meant context for your code, some of the stuff you're doing are extremely weird, and their purpose may be outside the code you've provided. Does it work, have you actually tested it? - and more specifically can a user login with her password, after login out? It doesn't seem so... \$\endgroup\$
    – yannis
    Commented Nov 30, 2011 at 11:09
  • \$\begingroup\$ The code is working? What is weird? Yes, the user can login .. when the user logged out? \$\endgroup\$
    – John
    Commented Nov 30, 2011 at 11:14
  • \$\begingroup\$ Sorry I didn't get that you are using individual salts at first. That's weird, highly unconventional and potentially dangerous, see my answer. \$\endgroup\$
    – yannis
    Commented Nov 30, 2011 at 11:33

1 Answer 1

2
\$\begingroup\$

There seems to be a fair amount of paranoia in your code:

function salt() {
    mt_srand(microtime(true)*100000 + memory_get_usage(true));
    return md5(uniqid(mt_rand(), true));
}

here mt_srand(microtime(true)*100000 + memory_get_usage(true)); is unused. If you consider using it, you should simplify it, you don't get a better salt that way.

$password = sha1(sha1($_POST['password'] . $salt));

Here you hash the password + salt twice. That doesn't provide for any more security, than simply hashing it once. Not to mention that your salt is already hashed, which is also redundant.

And what's with the individual salts? That's actually not a very good idea, you shouldn't store the hashed password and the salt in the same storage. If someone get's access to your database, she has everything she needs for a common brute force attack. It would be a little more secure if you simply had a variable somewhere with a common salt for every password. Although I'm not advocating security through obscurity, separating hash from salt would be saner.

if (strlen($_POST['username']) < 3 || strlen($_POST['username']) > 20) 

It would be better to do something like:

$lengthUsername = $_POST['username'];
if (lengthUsername < 3 || lengthUsername > 20) {

to not call the function twice. The performance issue is minuscule, but you should avoid repeating code anyway. The if (isset($_POST['submit'])) { ... } is too long. You could move some of the functionality (let's say the database stuff) into a function and just call the function in the if block.

What I can do about the func.php file as a file with just a bunch of functions, doesn't seem to be the best approach for it.

It depends. If you use everything that's in it everywhere, then it's ok. If not you should brake it to smaller function collection files and include as needed. But since you are doing this the procedural way, it's ok to have one or more function collections. Have you considered an OO approach? I'm not saying you should, just curious.

Other than that, your code looks fine. Kudos for using PDO and prepared statements, that's a wise decision. Also good that you are calling exit after location(), location() will fail with a notice if headers already sent.


As for the update:

  • Why sleep at all?
  • The code is fairly small, it would be an overkill to rewrite in OO

Unless you already have a User model, in which case you could just add the functionality there:

class User {

    function usernameExists($username) {
        $username  = trim(strtolower($username);        
        $usernames = $this->getUsernames();

        foreach($users as $user) {
            if(strtolower($user) == $request) {
                return false
            }
        }

        return true;
    }

    function getUsernames() {
        // return list of usernames from db
    }

}

and you would call in in the ajax script as:

include("User.php");

$user = new User();

if($user->usernameExists($_REQUEST['username'])) {
    echo "false";
    exit;
}

echo "true"

The code is a lot more than with the procedural style, but you can use User::usernameExists() everywhere now, if you need it. Also note that I've pushed strtolower into the class, if you use it elsewhere such comparison normalizations and possible validations should be in the class method not in the calling code.

\$\endgroup\$
3
  • \$\begingroup\$ I have always thought that a individual salt was more appropriated, I guess not. About OO, yes, I've considered that, but I'm quite unsure of how I should do it. A User class, I assume, but should I put the validation in the method, or have a method for validation or a class for validation? \$\endgroup\$
    – John
    Commented Nov 30, 2011 at 11:52
  • \$\begingroup\$ Also, If u look at my updated question - How would I treat the following code there in OO? As, that "file" is called from an AJAX request, to check the username.. \$\endgroup\$
    – John
    Commented Nov 30, 2011 at 11:54
  • \$\begingroup\$ If you are unsure, you shouldn't try and convert it. Study OO concepts a bit and then start coding. The bad thing with OO is that there isn't a "one fits all" solution. You could have a Session class (for token etc), and a User class... And a validation class... it depends on what the concerns & responsibilities are (en.wikipedia.org/wiki/Single_responsibility_principle)... As for the individual salt, you can post a question at crypto.stackexchange.com for more educated answers, mine was mostly empirical. \$\endgroup\$
    – yannis
    Commented Nov 30, 2011 at 11:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.