I have been working on my script which allows people to sign-up and login to my website. I was wondering if someone could review the code. It works but I can't help but feel it could be optimized.
forgot_password.php
<?php
// core configuration
require_once "config/core.php";
// include login checker
require_once "login_checker.php";
// include classes
require_once "config/database.php";
require_once 'objects/user.php';
require_once "libs/php/utils.php";
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
$utils = new Utils();
// header HTML
require_once "layout_head.php";
// if the login form was submitted
if ($_POST) {
echo "<div class='col-sm-12'>";
// check if username and password are in the database
$user->email = $_POST['email'];
if ($user->emailExists()) {
// update access code for user
$access_code = $utils->getToken();
$user->access_code = $access_code;
if ($user->updateAccessCode()) {
// send reset link
$body="Hi there.<br /><br />";
$body.="Please click the following link to reset your password: {$home_url}reset_password/?access_code={$access_code}";
$subject="Reset Password";
$send_to_email=$_POST['email'];
if ($utils->sendEmailViaPhpMail($send_to_email, $subject, $body)) {
echo "<div class='alert alert-info'>
Password reset link was sent to your email.
Click that link to reset your password.
</div>";
} else {
// message if unable to send email for password reset link
echo "<div class='alert alert-danger'>ERROR: Unable to send reset link.</div>";
}
} else {
// message if unable to update access code
echo "<div class='alert alert-danger'>ERROR: Unable to update access code.</div>";
}
} else {
// message if email does not exist
echo "<div class='alert alert-danger'>Your email cannot be found.</div>";
}
echo "</div>";
}
?>
<div class='col-md-4'></div>
<div class='col-md-4'>
<div class='account-wall'>
<div id='my-tab-content' class='tab-content'>
<div class='tab-pane active' id='login'>
<img class='profile-img' src='images/login-icon.png'>
<form class='form-signin' action='<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>' method='post'>
<input type='email' name='email' class='form-control' placeholder='Your email' required autofocus>
<input type='submit' class='btn btn-lg btn-primary btn-block' value='Send Reset Link' style='margin-top:1em;' />
</form>
</div>
</div>
</div>
</div>
<div class='col-md-4'></div>
<?php
// footer HTML and JavaScript codes
require_once "layout_foot.php";
?>
index.php
<?php
// core configuration
require_once "config/core.php";
// include login checker
$require_login = true;
require_once "login_checker.php";
// header HTML
require_once 'layout_head.php';
?>
<div class='col-md-12'>
<p>Hi, <?php echo $_SESSION['firstname']; ?></p>
</div>
<?php
// footer HTML and JavaScript codes
require 'layout_foot.php';
?>
login.php
<?php
// core configuration
require_once "config/core.php";
// include login checker
$require_login = false;
require_once "login_checker.php";
// default to false
$access_denied = false;
// if the login form was submitted
if ($_POST) {
// include classes
include_once "config/database.php";
include_once "objects/user.php";
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
// check if email and password are in the database
$user->email = $_POST['email'];
// check if email exists, also get user details using this emailExists() method
$email_exists = $user->emailExists();
// validate login
if ($email_exists && password_verify($_POST['password'], $user->password) && $user->status==1) {
// if it is, set the session value to true
$_SESSION['logged_in'] = true;
$_SESSION['user_id'] = $user->id;
$_SESSION['access_level'] = $user->access_level;
$_SESSION['firstname'] = htmlspecialchars($user->firstname, ENT_QUOTES, 'UTF-8');
$_SESSION['lastname'] = $user->lastname;
// if the access level detected is 'Administrator', redirect the user to the administrator section
if ($user->access_level=='Administrator') {
header("Location: {$home_url}admin/index.php");
} else {
// else, redirect only to 'Customer' section
header("Location: {$home_url}index.php");
}
} else {
// if username does not exist or password is wrong
$access_denied = true;
}
}
// header HTML
require_once "layout_head.php";
?>
<div class='col-sm-6 col-md-4 col-md-offset-4'>
<?php $action = isset($_GET['action']) ? $_GET['action'] : ""; ?>
<?php if ($action =='not_yet_logged_in') : ?>
<div class='alert alert-danger margin-top-40' role='alert'>
Please login.
</div>
<?php elseif ($action =='account_verified') : ?>
<div class='alert alert-info'>
Your account has been verified, you can now login.
</div>
<?php elseif ($action =='password_reset') : ?>
<div class='alert alert-info'>
Password was reset. Please login.
</div>
<?php elseif ($access_denied) : ?>
<div class='alert alert-danger margin-top-40' role='alert'>
Access Denied.<br /><br />
Your username or password maybe incorrect
</div>
<?php endif; ?>
<div class='account-wall'>
<div id='my-tab-content' class='tab-content'>
<div class='tab-pane active' id='login'>
<img class='profile-img' src='images/login-icon.png'>
<form class='form-signin' action='<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>' method='post'>
<input type='text' name='email' class='form-control' placeholder='Email' required autofocus />
<input type='password' name='password' class='form-control' placeholder='Password' required />
<input type='submit' class='btn btn-lg btn-primary btn-block' value='Log In' />
<div class='margin-1em-zero text-align-center'>
<a href='<?php echo $home_url; ?>forgot_password.php'>Forgot password?</a>
</div>
</form>
</div>
</div>
</div>
</div>
<?php
// footer HTML and JavaScript codes
require_once "layout_foot.php";
?>
login_checker.php
<?php
// login checker for the 'Customer' access level
// if the access level detected was not 'Administrator', redirect the user to the login page
if (isset($_SESSION['access_level']) && $_SESSION['access_level']=="Administrator") {
header("Location: {$home_url}admin/index.php");
}
// if $require_login was set and value is 'true'
else if (isset($require_login) && $require_login==true) {
// if user not yet logged in, redirect to login page
if (!isset($_SESSION['access_level'])) {
header("Location: {$home_url}login.php");
}
}
else if (isset($_SESSION['access_level']) && $_SESSION['access_level']=="Customer") {
header("Location: {$home_url}index.php");
}
else {
// no problem, stay on current page
}
?>
logout.php
<?php
// core configuration
require_once "config/core.php";
// destroy session, it will remove ALL session settings
session_destroy();
//redirect to login page
header("Location: {$home_url}login.php");
?>
register.php
<?php
// core configuration
require_once "config/core.php";
// include login checker
require_once "login_checker.php";
// include classes
require_once 'config/database.php';
require_once 'objects/user.php';
require_once "libs/php/utils.php";
// header HTML
require_once "layout_head.php";
?>
<div class='col-md-12'>
<?php
// if form was posted
if ($_POST) {
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
$utils = new Utils();
// set user email to detect if it already exists
$user->email = $_POST['email'];
// check if email already exists
if ($user->emailExists()) {
echo "<div class='alert alert-danger'>";
echo "The email you specified is already registered. Please try again or <a href='{$home_url}login'>login.</a>";
echo "</div>";
} else {
// set values to object properties
$user->firstname = $_POST['firstname'];
$user->lastname = $_POST['lastname'];
$user->contact_number = $_POST['contact_number'];
$user->address = $_POST['address'];
$user->password = $_POST['password'];
$user->access_level = 'Customer';
$user->status = 0;
// access code for email verification
$access_code = $utils->getToken();
$user->access_code = $access_code;
// create the user
if ($user->create()) {
// send confimation email
$send_to_email=$_POST['email'];
$body="Hi {$send_to_email}.<br /><br />";
$body.="Please click the following link to verify your email and login: {$home_url}verify/?access_code={$access_code}";
$subject="Verification Email";
if ($utils->sendEmailViaPhpMail($send_to_email, $subject, $body)) {
echo "<div class='alert alert-success'>
Verification link was sent to your email. Click that link to login.
</div>";
} else {
echo "<div class='alert alert-danger'>
User was created but unable to send verification email. Please contact admin.
</div>";
}
// empty posted values
$_POST = array();
} else {
echo "<div class='alert alert-danger' role='alert'>Unable to register. Please try again.</div>";
}
}
}
?>
<form action='register.php' method='post' id='register'>
<table class='table table-responsive'>
<tr>
<td class='width-30-percent'>Firstname</td>
<td><input type='text' name='firstname' class='form-control' required value="<?php echo isset($_POST['firstname']) ? htmlspecialchars($_POST['firstname'], ENT_QUOTES) : ""; ?>" /></td>
</tr>
<tr>
<td>Lastname</td>
<td><input type='text' name='lastname' class='form-control' required value="<?php echo isset($_POST['lastname']) ? htmlspecialchars($_POST['lastname'], ENT_QUOTES) : ""; ?>" /></td>
</tr>
<tr>
<td>Contact Number</td>
<td><input type='text' name='contact_number' class='form-control' required value="<?php echo isset($_POST['contact_number']) ? htmlspecialchars($_POST['contact_number'], ENT_QUOTES) : ""; ?>" /></td>
</tr>
<tr>
<td>Address</td>
<td><textarea name='address' class='form-control' required><?php echo isset($_POST['address']) ? htmlspecialchars($_POST['address'], ENT_QUOTES) : ""; ?></textarea></td>
</tr>
<tr>
<td>Email</td>
<td><input type='email' name='email' class='form-control' required value="<?php echo isset($_POST['email']) ? htmlspecialchars($_POST['email'], ENT_QUOTES) : ""; ?>" /></td>
</tr>
<tr>
<td>Password</td>
<td><input type='password' name='password' class='form-control' required /></td>
</tr>
<tr>
<td>Confirm Password</td>
<td>
<input type="password" name="confirm_password" class="form-control" required />
<p></p>
<div class="alert alert-danger">Passwords do not match!</div>
<p></p>
</td>
</tr>
<tr>
<td></td>
<td>
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-plus"></span>
Register
</button>
</td>
</tr>
</table>
</form>
</div>
<?php
// include page footer HTML
require_once "layout_foot.php";
?>
reset_password.php
<?php
// core configuration
require_once "config/core.php";
// include login checker
require_once "login_checker.php";
// include classes
require_once "config/database.php";
require_once "objects/user.php";
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
// header HTML
require_once "layout_head.php";
?>
<div class='col-sm-12'>
<?php
// get given access code
$access_code = isset($_GET['access_code']) ? $_GET['access_code'] : die("Access code not found.");
// check if access code exists
$user->access_code = $access_code;
if (!$user->accessCodeExists()) {
die('Access code not found.');
} else {
// if form was posted
if ($_POST) {
// set values to object properties
$user->password = $_POST['password'];
// reset password
if ($user->updatePassword()) {
header("Location: {$home_url}login.php?action=password_reset");
} else {
echo "<div class='alert alert-danger'>Unable to reset password.</div>";
}
}
?>
<form action='<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>?access_code=<?php echo $access_code; ?>' method='post'>
<table class='table table-hover table-responsive table-bordered'>
<tr>
<td>Password</td>
<td><input type='password' name='password' class='form-control' required></td>
</tr>
<tr>
<td></td>
<td><button type='submit' class='btn btn-primary'>Reset Password</button></td>
</tr>
</table>
</form>
</div>
<?php
}
// include page footer HTML
require_once "layout_foot.php";
?>
verify.php
<?php
// core configuration
require_once "config/core.php";
// include classes
require_once 'config/database.php';
require_once 'objects/user.php';
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
// set access code
$user->access_code = isset($_GET['access_code']) ? $_GET['access_code'] : "";
// verify if access code exists
if (!$user->accessCodeExists()) {
die("ERROR: Access code not found.");
} else {
// update status
$user->status = 1;
$user->updateStatusByAccessCode();
// and the redirect
header("Location: {$home_url}login.php?action=account_verified");
}
?>
user.php
<?php
// 'user' object
class User
{
// database connection and table name
private $conn;
private $table_name = "users";
// object properties
public $id;
public $firstname;
public $lastname;
public $email;
public $contact_number;
public $address;
public $password;
public $access_level;
public $access_code;
public $status;
public $created;
public $modified;
// constructor
public function __construct($db)
{
$this->conn = $db;
}
// check if given email exist in the database
function emailExists()
{
// query to check if email exists
$query = "SELECT id, firstname, lastname, access_level, password, status
FROM " . $this->table_name . "
WHERE email = ?
LIMIT 0,1";
// prepare the query
$stmt = $this->conn->prepare($query);
// sanitize
$this->email = htmlspecialchars(strip_tags($this->email));
// bind given email value
$stmt->bindParam(1, $this->email);
// execute the query
$stmt->execute();
// get number of rows
$num = $stmt->rowCount();
// if email exists, assign values to object properties for easy access and use for php sessions
if ($num>0) {
// get record details / values
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// assign values to object properties
$this->id = $row['id'];
$this->firstname = $row['firstname'];
$this->lastname = $row['lastname'];
$this->access_level = $row['access_level'];
$this->password = $row['password'];
$this->status = $row['status'];
// return true because email exists in the database
return true;
}
// return false if email does not exist in the database
return false;
}
// create new user record
function create()
{
// to get time stamp for 'created' field
$this->created = date('Y-m-d H:i:s');
// insert query
$query = "INSERT INTO " . $this->table_name . "
SET
firstname = :firstname,
lastname = :lastname,
email = :email,
contact_number = :contact_number,
address = :address,
password = :password,
access_level = :access_level,
access_code = :access_code,
status = :status,
created = :created";
// prepare the query
$stmt = $this->conn->prepare($query);
// sanitize
$this->firstname = htmlspecialchars(strip_tags($this->firstname));
$this->lastname = htmlspecialchars(strip_tags($this->lastname));
$this->email = htmlspecialchars(strip_tags($this->email));
$this->contact_number = htmlspecialchars(strip_tags($this->contact_number));
$this->address = htmlspecialchars(strip_tags($this->address));
$this->password = htmlspecialchars(strip_tags($this->password));
$this->access_level = htmlspecialchars(strip_tags($this->access_level));
$this->access_code = htmlspecialchars(strip_tags($this->access_code));
$this->status = htmlspecialchars(strip_tags($this->status));
// bind the values
$stmt->bindParam(':firstname', $this->firstname);
$stmt->bindParam(':lastname', $this->lastname);
$stmt->bindParam(':email', $this->email);
$stmt->bindParam(':contact_number', $this->contact_number);
$stmt->bindParam(':address', $this->address);
// hash the password before saving to database
$password_hash = password_hash($this->password, PASSWORD_BCRYPT);
$stmt->bindParam(':password', $password_hash);
$stmt->bindParam(':access_level', $this->access_level);
$stmt->bindParam(':access_code', $this->access_code);
$stmt->bindParam(':status', $this->status);
$stmt->bindParam(':created', $this->created);
// execute the query, also check if query was successful
if ($stmt->execute()) {
return true;
} else {
$this->showError($stmt);
return false;
}
}
public function showError($stmt)
{
echo "<pre>";
print_r($stmt->errorInfo());
echo "</pre>";
}
// read all user records
function readAll($from_record_num, $records_per_page)
{
// query to read all user records, with limit clause for pagination
$query = "SELECT
id,
firstname,
lastname,
email,
contact_number,
access_level,
created
FROM " . $this->table_name . "
ORDER BY id DESC
LIMIT ?, ?";
// prepare query statement
$stmt = $this->conn->prepare($query);
// bind limit clause variables
$stmt->bindParam(1, $from_record_num, PDO::PARAM_INT);
$stmt->bindParam(2, $records_per_page, PDO::PARAM_INT);
// execute query
$stmt->execute();
// return values
return $stmt;
}
// used for paging users
public function countAll()
{
// query to select all user records
$query = "SELECT id FROM " . $this->table_name . "";
// prepare query statement
$stmt = $this->conn->prepare($query);
// execute query
$stmt->execute();
// get number of rows
$num = $stmt->rowCount();
// return row count
return $num;
}
// used in email verification feature
function updateStatusByAccessCode()
{
// update query
$query = "UPDATE " . $this->table_name . "
SET status = :status
WHERE access_code = :access_code";
// prepare the query
$stmt = $this->conn->prepare($query);
// sanitize
$this->status = htmlspecialchars(strip_tags($this->status));
$this->access_code = htmlspecialchars(strip_tags($this->access_code));
// bind the values from the form
$stmt->bindParam(':status', $this->status);
$stmt->bindParam(':access_code', $this->access_code);
// execute the query
if ($stmt->execute()) {
return true;
}
return false;
}
// used in forgot password feature
function updateAccessCode()
{
// update query
$query = "UPDATE
" . $this->table_name . "
SET
access_code = :access_code
WHERE
email = :email";
// prepare the query
$stmt = $this->conn->prepare($query);
// sanitize
$this->access_code = htmlspecialchars(strip_tags($this->access_code));
$this->email = htmlspecialchars(strip_tags($this->email));
// bind the values from the form
$stmt->bindParam(':access_code', $this->access_code);
$stmt->bindParam(':email', $this->email);
// execute the query
if ($stmt->execute()) {
return true;
}
return false;
}
// check if given access_code exist in the database
function accessCodeExists()
{
// query to check if access_code exists
$query = "SELECT id
FROM " . $this->table_name . "
WHERE access_code = ?
LIMIT 0,1";
// prepare the query
$stmt = $this->conn->prepare($query);
// sanitize
$this->access_code = htmlspecialchars(strip_tags($this->access_code));
// bind given access_code value
$stmt->bindParam(1, $this->access_code);
// execute the query
$stmt->execute();
// get number of rows
$num = $stmt->rowCount();
// if access_code exists
if ($num>0) {
// return true because access_code exists in the database
return true;
}
// return false if access_code does not exist in the database
return false;
}
// used in forgot password feature
function updatePassword()
{
// update query
$query = "UPDATE " . $this->table_name . "
SET password = :password
WHERE access_code = :access_code";
// prepare the query
$stmt = $this->conn->prepare($query);
// sanitize
$this->password = htmlspecialchars(strip_tags($this->password));
$this->access_code = htmlspecialchars(strip_tags($this->access_code));
// bind the values from the form
$password_hash = password_hash($this->password, PASSWORD_BCRYPT);
$stmt->bindParam(':password', $password_hash);
$stmt->bindParam(':access_code', $this->access_code);
// execute the query
if ($stmt->execute()) {
return true;
}
return false;
}
}
?>
utils.php
<?php
class Utils
{
function getToken($length=32)
{
$token = "";
$codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
$codeAlphabet.= "0123456789";
for ($i = 0;$i<$length;$i++) {
$token .= $codeAlphabet[$this->crypto_rand_secure(0, strlen($codeAlphabet))];
}
return $token;
}
function crypto_rand_secure($min, $max)
{
$range = $max - $min;
if ($range < 0) {
return $min; // not so random...
}
$log = log($range, 2);
$bytes = (int) ($log / 8) + 1; // length in bytes
$bits = (int) $log + 1; // length in bits
$filter = (int) (1 << $bits) - 1; // set all lower bits to 1
do {
$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
$rnd = $rnd & $filter; // discard irrelevant bits
} while ($rnd >= $range);
return $min + $rnd;
}
// send email using built in php mailer
public function sendEmailViaPhpMail($send_to_email, $subject, $body)
{
$from_name = "";
$from_email = "";
$headers = "MIME-Version: 1.0\r\n";
$headers .= "Content-type: text/html; charset=iso-8859-1\r\n";
$headers .= "From: {$from_name} <{$from_email}> \n";
if (mail($send_to_email, $subject, $body, $headers)) {
return true;
} else {
return false;
}
}
}
?>
core.php
<?php
// show error reporting
error_reporting(E_ALL);
// start php session
session_start();
// set your default time-zone
date_default_timezone_set('Europe/London');
// home page url
$home_url = "http://localhost/";
?>
database.php
<?php
// used to get mysql database connection
class Database
{
// specify your own database credentials
private $host = "";
private $db_name = "";
private $username = "";
private $password = "";
public $conn;
// get the database connection
public function getConnection()
{
$this->conn = null;
try {
$this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
} catch (PDOException $exception) {
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
?>
admin/index.php
<?php
// core configuration
require_once "../config/core.php";
// check if logged in as administrator
require_once "login_checker.php";
// header HTML
require 'layout_head.php';
?>
<div class='col-md-12'>
<p>Hi, <?php echo $_SESSION['firstname']; ?></p>
</div>
<?php
// include page footer HTML
require_once 'layout_foot.php';
?>
admin/login_checker.php
<?php
// login checker for the 'Administrator' access level
// if the session value is empty, the user has not yet logged in, redirect the user to the login page
if (empty($_SESSION['logged_in'])) {
header("Location: {$home_url}login.php?action=not_yet_logged_in");
}
// if the access level detected was not 'Administrator', redirect the user to login page
else if ($_SESSION['access_level']!="Administrator") {
header("Location: {$home_url}login.php?action=not_admin");
}
else {
// no problem, stay on current page
}
?>