Page 2 of 2

Re: Check my Code

Posted: Sat Dec 10, 2011 4:02 pm
by ibnclaudius
Ohh, got it. But i'm not sure about how to use verifyCookie($token). Could you show me an example?

Another thing, what exists() does?

Re: Check my Code

Posted: Sat Dec 10, 2011 4:14 pm
by Celauran
exists() checks whether the user exists in the database. Well, technically, that's what validate() does, but validate() is a private function.

I must have been half asleep when I wrote verifyCookie(). The idea is to check for the existence of a 'token' cookie, pass it to the verifyCookie() function, and get back the userID or FALSE if no match is found. Trouble is, you'd need to instantiate the User object, which you can't do without a username. Like I said, I must have been half asleep. It needs rewriting. And rethinking.

Re: Check my Code

Posted: Sat Dec 10, 2011 4:32 pm
by ibnclaudius
I am not sure why have three functions doing basically the same thing: login(), validate(), exists()

About the problem with the cookie, you have an idea about how to fix it?

This is what I have:

login.php

Code: Select all

<?php

session_start();

include 'class.php';

if (isset($_POST['username']) && isset($_POST['password'])) {
	$user  = new User($_POST['username']);
	if ($user->exists()) {
		$login = $user->login($_POST['password']);
		if ($login) {
			$_SESSION['user_id'] = $login;
		
	session_write_close();
		} else {
			echo "Login failed.";
		}
	} else {
		header("Location: register.php");
	}
}

?>

<!DOCTYPE html>
<html>
	<head>
		<title>Login Form</title>
	</head>
	<body>
		<form action="" method="post">
			<label for="username">Username: </label>
			<input type="text" name="username" /><br />
			<label for="password">Password: </label>
			<input type="password" name="password" /><br />
			<input type="submit" value="Submit" />
		</form>
	</body>
</html>
class.php

Code: Select all

<?php

class User {
	protected $id;
	protected $username;
	protected $email;
	protected $sql;
	
	private $exists = FALSE;

	public function __construct($username) {
		if (empty($username)) {
			throw new Exception('Username cannot be blank.');
		}

		$this->username = $username;
		$this->sql      = new PDO(DSN, DBUSER, DBPASS);
		$this->exists   = $this->validate();
	}

	private function createLoginToken($id) {
		$token   = $id . md5(microtime());
		$expires = new DateTime();

		$expires->add(new DateInterval('P30D'));

		$query = "INSERT INTO sessions (userID, token, expires)
			  VALUES (:id, :token, :expires)";
		$stmt  = $this->sql->prepare($query);

		$stmt->execute(array(':id'      => $id,
				     ':token'   => $token,
				     ':expires' => $expires->format('Y-m-d H:i:s')));

		setcookie('token', $token, $expires->getTimestamp(), '/');
	}

	private function hashPassword($password, $salt) {
		$string = PASSWORD_SALT . $password . md5($salt);
		$hashed = crypt($string, '$2a$12$' . substr(md5($salt), 0, 22));

		return $hashed;
	}

	private function validate() {
		$query = "SELECT COUNT(id)
			  FROM users
			  WHERE username = :username";
		$stmt  = $this->sql->prepare($query);

		$stmt->execute(array(':username' => $this->username));
		$count = $stmt->fetchColumn();

		return ($count > 0) ? TRUE : FALSE;
	}

	public function exists() {
		return $this->exists;
	}

	public function login($password, $remember = FALSE) {
		$query = "SELECT id, password, UNIX_TIMESTAMP(created) AS salt
			  FROM users
			  WHERE username = :username";
		$stmt  = $this->sql->prepare($query);

		$stmt->execute(array(':username' => $this->username));

		$row = $stmt->fetch(PDO::FETCH_OBJ);

		$hashed = $this->hashPassword($password, $row->salt);

		if ($row->password == $hashed) {
			if ($remember) {
				$this->createLoginToken($row->id);
			}
			return $row->id;
		}

		return FALSE;
	}

	public function registerUser($password, $email) {
		if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
			throw new Exception('Email does not appear to be valid.');
		}
		$this->email = $email;

		$date   = new DateTime();
		$hashed = $this->hashPassword($password, $date->getTimestamp());

		$query = "INSERT INTO users (username, password, email, created)
			  VALUES (:username, :password, :email, :created)";
		$stmt  = $this->sql->prepare($query);

		$success = $stmt->execute(array(':username' => $this->username,
						':password' => $hashed,
						':email'    => $email,
						':created'  => $date->format('Y-m-d H:i:s')));

		return ($success === TRUE) ? $this->sql->lastInsertId() : FALSE;
	}

	public function verifyCookie($token) {
		$query = "SELECT userID
			  FROM sessions
			  WHERE token = :token
			  AND expires > NOW()";
		$stmt  = $this->sql->prepare($query);

		$stmt->execute(array(':token' => $token));

		return $stmt->fetchColumn();
	}
}

?>
db.sql

Code: Select all

CREATE TABLE IF NOT EXISTS `users` (
	`id` int(11) DEFAULT NULL AUTO_INCREMENT,
	`username` varchar(30) DEFAULT NULL,
	`password` varchar(60) DEFAULT NULL,
	`email` varchar(100) DEFAULT NULL UNIQUE,
	`created` datetime DEFAULT NULL,
	PRIMARY KEY (`id`)
) ENGINE=MyISAM;

CREATE TABLE IF NOT EXISTS `sessions` (
	`id` int(11) DEFAULT NULL AUTO_INCREMENT,
	`userID` int(10) DEFAULT NULL,
	`token` varchar(50) DEFAULT NULL,
	`expires` datetime DEFAULT NULL,
	KEY `userID` (`id`,`userID`,`token`,`expires`),
	KEY `token` (`id`,`userID`,`token`,`expires`),
	PRIMARY KEY (`id`)
) ENGINE=MyISAM;

I'm not sure about how hashPassword() works. Each user will have a different $salt and it will be the created date?

Re: Check my Code

Posted: Sat Dec 10, 2011 5:24 pm
by Celauran
ibnclaudius wrote:I am not sure why have three functions doing basically the same thing: login(), validate(), exists()
They don't do the same thing at all. Validate checks once whether the user exists in the database and stores that in the exists property, which needs to be private. The exists() method returns the value of the exists property; you can see the value but you can't change it. Login authenticates the user.
ibnclaudius wrote:About the problem with the cookie, you have an idea about how to fix it?
Haven't even had time to give it much thought. I suppose storing the username in the cookie could work.

You might be better off using InnoDB over MyISAM so you can cascade deletes. No point keeping a user's logins stored if the user no longer exists.

Re: Check my Code

Posted: Sat Dec 10, 2011 6:06 pm
by ibnclaudius
Finally got the login(), validate(), exists() function..

About the cookie, I'm in the waiting.

Thanks!