view resume | view portfolio | view code samples | contact | about
Login class(PHP4) | MySQL class(PHP5) | session handler(PHP5) | python pinger(Python) | Qmail popper(PHP4)
<?php
/**
/**
* @note Login class::PHP4::login-class-php
* @author Stefan Antonowicz
*
* I use the excellent PHPMailer class by Brent R. Matzelle for handling the mailings.
* The MySQL SQL statements to create the appropriate tables are in the comments at the bottom of the page.
* I'm including a session handling class - this is the same session handler linked to above
*/
require_once( 'dbSession.inc.php' );
require_once( 'PHPMailer.inc.php' );
class Login {
var $u, $p, $uid;
var $errarr = array( );
var $_sql, $_aurl;
var $_crypt = ''; // add private salt
var $dsn = ''; // mysql dsn connection
var $webroot = "http://localhost/login";
var $_captcha = NULL; // Optional captcha
var $mail_tpl_path = ''; // Mail template path
/* Constructor. Build the DB connection and start the session */
function Login( )
{
extract( parse_url( $this->dsn ) );
$conn = mysql_connect( $host, $user, $pass, true );
$path = str_replace( '/', '', $path );
mysql_select_db( $path, $conn );
$this->_db = $conn;
$this->_sess = session_start( );
}
/* Process a login request. If successful, set the user_id in the session */
function process( $username, $password, $remember = FALSE )
{
$this->logout( FALSE );
$this->u = $this->clean( $username );
$this->p = $this->clean( $password );
$this->errarr = array( );
if( $this->_check( ) )
{
$_SESSION['uid'] = $this->uid;
$_SESSION['uname'] = $this->u;
$this->_sess->make_crypt( $this->u );
setcookie( 'login', '', time() - 3600);
if( $remember )
{
setcookie( 'login', $this->u, time()+60*60*24*365 );
}
return( true );
}
return( false );
}
/* Check to see if someone is logged in. You can add roles management in
this method, as well */
function check( )
{
if(! $_SESSION['uid'] || ! $_SESSION['uname'] ) return( false );
if(! $this->_sess->verify( $_SESSION['uname'] ) ) return( false );
return( true );
}
/* Wrapper that returns the error array, if it exists */
function error( )
{
if( $this->errarr ) return( $this->errarr );
return( false );
}
function remember( )
{
if( $_COOKIE['login'] )
{
return( $_COOKIE['login'] );
}
return( false );
}
/* Logout method; kills the session and any cookies. I added a "full"
handler in there to allow for a partial logout without destroying the
session, like in process() and create()
*/
function logout( $full = TRUE )
{
$_SESSION = array( );
if( $full )
{
setcookie( 'user', '', time() - 42000, '/' );
setcookie( 'PHPSESSID', '', time() - 42000, '/' );
session_destroy( );
}
return( true );
}
/* Processes a request from an activation email */
function activate( $str )
{
parse_str( $str );
$this->uid = ( int ) $i;
$stamp = ( int ) $t;
$this->_sql = "SELECT a.activity_id, u.user_name, a.activity_hash FROM users u
LEFT JOIN activity a ON u.user_id = a.user_id
WHERE
u.user_id = $this->uid
AND activity_desc = 'Activation'
AND activity_expired = 0
ORDER BY activity_ts DESC
LIMIT 1
";
$res = $this->_query( );
if(! mysql_num_rows( $res ) )
{
$this->_error( LOGIN_FAIL );
return( false );
}
$user = mysql_fetch_assoc( $res );
if( strcmp( md5( $user['user_name'] . $stamp ), $user['activity_hash'] ) )
{
$this->_error( BAD_ACTIVATION );
return( false );
}
$this->_sql = "UPDATE users SET user_confirmed = 1 WHERE user_id = $this->uid LIMIT 1";
$this->_query();
$this->_sql = "UPDATE activity SET activity_expired = 1 WHERE activity_id = {$user['activity_id']}";
$this->_query();
return( true );
}
/* Resend a request for an activation email */
function request_reactivate( $u='', $e='' )
{
// we have a username
if( $u )
{
$this->u = $this->clean( $u );
$this->_sql = "SELECT user_id, user_name, user_email FROM users WHERE user_name= '$this->u' LIMIT 1";
} elseif( $e )
{
$this->email = $this->clean( $user_email );
$this->_sql = "SELECT user_id, user_name, user_email FROM users WHERE user_email = '$this->email' LIMIT 1";
}
$res = $this->_query( );
if(! mysql_num_rows( $res ) )
{
$this->_error( LOGIN_FAIL );
return( false );
}
$row = mysql_fetch_assoc( $res );
$this->u = $row['user_name'];
$this->uid = $row['user_id'];
$this->email = $row['user_email'];
// Kill all old versions, so there's no confusion
$this->_sql = "UPDATE activity SET activity_expired = 1 WHERE activity_desc = 'Activation' AND
user_id = $this->uid";
$this->_query( );
// Insert a record into the activity table
$code_creation_time = time( );
$this->_generate_code( $code_creation_time );
$this->_sql = "INSERT INTO activity ( user_id, activity_ts, activity_hash, activity_desc) VALUES
( $this->uid, $code_creation_time, '$this->_code', 'Activation')";
$this->_query( );
// Mail it out
$this->_mailIt( 'reactivate.tpl' );
}
/* Resend a request for an activation email */
function request_password( $email )
{
$this->email = $this->clean( $email );
$this->_sql = "SELECT user_id, user_name, user_email FROM users WHERE user_email = '$this->email' LIMIT 1";
$res = $this->_query( );
if(! mysql_num_rows( $res ) )
{
$this->_error( LOGIN_FAIL );
return( false );
}
$row = mysql_fetch_assoc( $res );
$this->u = $row['user_name'];
$this->uid = $row['user_id'];
$this->email = $row['user_email'];
// Expire other requests for password reset, so only one is active at any given time
$this->_sql = "UPDATE activity SET activity_expired = 1 WHERE user_id = $this->uid AND activity_desc = 'Password Reset'";
$this->_query( );
// Insert a new record into the db.
$code_creation_time = time( );
$this->_generate_code( $code_creation_time );
$this->_sql = "INSERT INTO activity ( user_id, activity_ts, activity_hash, activity_desc) VALUES
( $this->uid, $code_creation_time, '$this->_code', 'Password Reset')";
$this->_query( );
// Mail it out
$this->_mailIt( 'password.tpl' );
}
function reset_password( $str )
{
parse_str( $str );
$this->uid = ( int ) $i;
$stamp = ( int ) $t;
$this->_sql = "SELECT u.user_name, a.activity_hash FROM users u
LEFT JOIN activity a ON u.user_id = a.user_id
WHERE
u.user_id = $this->uid
AND activity_desc = 'Password Reset'
AND activity_expired = 0
ORDER BY activity_ts DESC
LIMIT 1
";
$res = $this->_query( );
if(! mysql_num_rows( $res ) )
{
$this->_error( LOGIN_FAIL );
return( false );
}
$user = mysql_fetch_assoc( $res );
if( strcmp( md5( $user['user_name'] . $stamp ), $user['activity_hash'] ) )
{
$this->_error( BAD_PASSWORD );
return( false );
}
return( true );
}
/* Creates a new user. Plenty of error checking in here */
function create( $post_array )
{
$clean = array( );
if( $_SESSION['captcha'] ) $this->_captcha = $_SESSION['captcha'];
$this->logout( FALSE );
// username
$clean['u'] = $this->clean( $post_array['user_name'] );
if( strlen( $clean['u'] ) < 6 )
{
$this->_error( U_TOO_SHORT );
} elseif( strlen( $clean['u'] ) > 15 )
{
$this->_error( U_TOO_LONG );
} elseif( $this->_exists( 'user', $clean['u'] ) )
{
$this->_error( U_ALREADY_EXISTS );
} else
{
$this->u = $clean['u'];
}
// email
$clean['e'] = $this->clean( $post_array['email'] );
if(! eregi( "^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $clean['e'] ) )
{
$this->_error( BAD_EMAIL );
} elseif( $this->_exists( 'email', $clean['e'] ) )
{
$this->_error( E_ALREADY_EXISTS );
} else
{
$this->email = $clean['e'];
}
// password
$clean['p'] = $post_array['password'];
$clean['v'] = $post_array['verify'];
if( strlen( $clean['p'] ) < 6 )
{
$this->_error( P_TOO_SHORT );
} elseif( strcmp( $clean['p'], $clean['v'] ) )
{
$this->_error( P_NO_MATCH );
} else
{
$this->p = $clean['p'];
}
// captcha, if set
if( $this->_captcha != '' )
{
$clean['cap'] = $this->clean( $post_array['captcha'] );
if( strcmp( $clean['cap'], $this->_captcha ) )
{
$this->_error( 'BAD_CAPTCHA' );
}
}
if( $this->error( ) ) return( false );
// You made it! Create this guy...
$this->_create( );
return( true );
}
/* A method to clean strings of illegal characters */
function clean( $str )
{
// Remove invalid characters: ',",(),&,|
$illegal_characters = array( "'", '\\', '"', '(', ')', '&', '|' );
$str = strip_tags( $str );
$str = str_replace( $illegal_characters, '', $str );
return( $str );
}
function save( $post_array )
{
if(! $_SESSION['uid'] ) return( false );
$clean = array( );
// acceptable things to update in users table using this method
$accept = array( 'user_email', 'user_password' );
foreach( $accept as $field )
{
switch( $field )
{
case 'user_email':
$clean['e'] = $this->clean( $post_array['email'] );
$clean['ce'] = $this->clean( $post_array['email_confirm'] );
if( $clean['e'] && $clean['ce'] )
{
if(! eregi( "^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $clean['e'] ) )
{
$this->_error( BAD_EMAIL );
} elseif( $this->_exists( 'email', $clean['e'] ) )
{
$this->_error( E_ALREADY_EXISTS );
} elseif( strcmp( $clean['e'], $clean['ce'] ) )
{
$this->_error( BAD_EMAIL );
} else
{
$arr[] = "user_email='{$clean['e']}'";
}
}
break;
case 'user_password':
$clean['p'] = $post_array['password'];
$clean['v'] = $post_array['verify'];
if( $clean['p'] || $clean['v'] )
{
if( strlen( $clean['p'] ) < 6 )
{
$this->_error( P_TOO_SHORT );
} elseif( strcmp( $clean['p'], $clean['v'] ) )
{
$this->_error( P_NO_MATCH );
} else
{
$arr[] = "user_password = AES_ENCRYPT( '{clean['p']}', '" . $this->u . $this->_crypt . "')";
}
}
break;
}
}
if( $arr )
{
$this->_sql = "UPDATE users SET " . implode( ',', $arr ) . " WHERE user_id = " . $_SESSION['uid'];
$this->_query();
}
return( true );
}
/* Generate the captcha code. The captcha itself is used on
a different page
*/
function captcha( $width='120',$height='40',$characters='6' )
{
// Generate the code first
$possible = '23456789bcdfghjkmnpqrstvwxyz';
$this->_captcha = '';
$i = 0;
while ($i < $characters) {
$this->_captcha .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
$i++;
}
$_SESSION['captcha'] = $this->_captcha;
}
function update_password( $pass, $uid )
{
$this->uid = ( int ) $uid;
$this->_sql = "SELECT user_name FROM users WHERE user_id = $this->uid";
$res = $this->_query( );
$user = mysql_fetch_assoc( $res );
$this->u = $user['user_name'];
$this->_sql = "UPDATE users SET user_pass = AES_ENCRYPT('$pass', '" . $this->u . $this->_crypt . "' ) WHERE user_id = $this->uid LIMIT 1";
$this->_query( );
$this->_sql = "UPDATE activity SET activity_expired = 1 WHERE user_id = $this->uid AND activity_desc = 'Password Reset'";
$this->_query( );
return( true );
}
function login_by_id( $pass )
{
if(! $this->uid ) return( false );
$this->logout( false );
$this->_sql = "SELECT user_name FROM users WHERE user_id = '$this->uid' LIMIT 1";
$res = $this->_query( );
if(! mysql_num_rows( $res ) ) return( false );
$name = mysql_fetch_assoc( $res );
$this->u = $name['user_name'];
$this->p = $pass;
return( $this->process( $this->u, $this->p, 0 ) );
}
/* PRIVATE METHODS */
/* Reset a single var or all vars to their original attribute definitions */
function _reset( $var = '' )
{
$class_vars = get_class_vars( get_class( $this ) );
if( $var )
{
$this->$var = $class_vars[$name];
} else {
foreach( $class_vars as $name=>$value )
{
$this->$name = $value;
}
}
}
/* Called by the check() public method, checks for the existance of a user in the DB
with the given username and password
*/
function _check( )
{
$this->_sql = "SELECT user_id, user_confirmed FROM users WHERE users.user_name = '$this->u'
AND users.user_pass = AES_ENCRYPT('$this->p', '" . $this->u . $this->_crypt . "' )
LIMIT 1";
$res = $this->_query();
if(! mysql_num_rows( $res ) )
{
$this->_error( LOGIN_FAIL );
return( false );
} else
{
$row = mysql_fetch_assoc( $res );
if(! $row['user_confirmed'] )
{
$this->_error( LOGIN_NOT_CONFIRMED );
return( false );
} else
{
$this->uid = $row['user_id'];
$this->update_saved_products( );
return( true );
}
}
}
/* MySQL query wrapper */
function _query( )
{
if ( empty( $this->_sql ) ) die ( "The SQL query you passed was empty" );
if(! ( $result = mysql_query( $this->_sql, $this->_db ) ) )
die( mysql_errno($this->_db ).' ) '. mysql_error( $this->_db ).'<hr />query was: ' . $this->_sql . ' on DB '. $this->_db );
$this->_reset( '_sql' );
return( $result );
}
/* Big ol' error method, pushes errors into the errarr stack for display by the public error() method */
function _error( $type )
{
switch( $type )
{
case LOGIN_FAIL:
$this->errarr[] = 'This user was not found';
break;
case U_TOO_SHORT:
$this->errarr[] = 'Your username must be at least 6 characters long';
break;
case U_TOO_LONG:
$this->errarr[] = 'Your username must be less than 16 characters long';
break;
case P_NO_MATCH:
$this->errarr[] = 'Your passwords do not match';
break;
case BAD_BIRTHDATE:
$this->errarr[] = "Your birthday never happened...";
break;
case BAD_EMAIL:
$this->errarr[] = "You must provide a proper email address";
break;
case P_TOO_SHORT:
$this->errarr[] = "Your password must be at least 6 characters long";
break;
case NO_BIRTHDATE:
$this->errarr[] = "Sorry! You have to enter your birthdate";
break;
case LATE_BIRTHDATE:
$this->errarr[] = "Not even Stephen Hawking could be born after today...";
break;
case U_ALREADY_EXISTS:
$this->errarr[] = "There is already a user with this username. Sorry!";
break;
case E_ALREADY_EXISTS:
$this->errarr[] = "There is already someone with this email address. Please try again.";
break;
case BAD_CAPTCHA:
$this->errarr[] = "Whoops! Looks like you didn't enter the image text correctly. Please try again.";
break;
case BAD_ACTIVATION:
$this->errarr[] = "This activation e-mail is either badly formed or expired. Please <a href=\"login?reactivate=1&u=$this->u\">click here</a> and we'll send you a new email";
break;
case LOGIN_NOT_CONFIRMED:
$this->errarr[] = "You haven't confirmed your login yet. Please check your email and follow the link inside. If you never received this email, <a href=\"login?reactivate=1&u=$this->u\">click here</a> and we'll resend it to you";
break;
}
}
/* Generate a code to be used for activation. Takes a timestamp from
the calling method and truncates it to the username
*/
function _generate_code( $time )
{
$this->_code = md5( $this->u . $time );
$this->_aurl = $this->webroot . '?a=1&t=' . $time . '&i=' . $this->uid;
}
function check_for_user( $username )
{
$username = $this->clean( $username );
$this->_sql = "SELECT 1 FROM users WHERE user_name = '$username' LIMIT 1";
$res = $this->_query( );
if( mysql_num_rows( $res ) ) return( true );
return( false );
}
function _create( )
{
$this->_sql = "INSERT INTO users (user_name, user_pass, user_email, user_birthdate, user_newsletter, user_confirmed, user_dtecreated ) VALUES ( '$this->u', AES_ENCRYPT('$this->p', '" . $this->u . $this->_crypt . "'), '$this->email', '$this->bdate', $this->news, 0, now())";
$this->_query( );
$this->uid = mysql_insert_id( $this->_db );
// Update the activity table - they'll need to activate their account
$code_creation_time = time( );
$this->_generate_code( $code_creation_time );
$this->_sql = "INSERT INTO activity ( user_id, activity_ts, activity_hash, activity_desc) VALUES
( $this->uid, $code_creation_time, '$this->_code', 'Activation')";
$this->_query( );
// And finally put a record in the profile table
$this->_sql = "INSERT INTO profiles (user_id, profile_private, profile_products_page )
VALUES ($this->uid, $this->mr, 10)";
$this->_query( );
$this->_mailIt( 'activate.tpl' );
return( true );
}
/* Check to see if an email address or user_name already exists. Note
that this is being passed cleaned values by the create() method
*/
function _exists( $type, $value='' )
{
switch( $type )
{
case 'user':
$this->_sql = "SELECT 1 FROM users WHERE user_name = '" . $value . "'";
$res = $this->_query( );
return( mysql_num_rows( $res ) );
break;
case 'email':
$this->_sql = "SELECT 1 FROM users WHERE user_email = '" . $value . "'";
$res = $this->_query( );
return( mysql_num_rows( $res ) );
break;
default:
return( false );
break;
}
}
function _mailIt( $tpl )
{
$mail =& new PHPMailer();
$mail->IsMail();
$mail->From = "security@somewhere.com";
$mail->FromName = "Somewhere Security";
$mail->AddAddress( $this->email, $this->u );
include( $this->mail_tpl_path . $tpl );
$mail->Subject = $subject;
$mail->Body = $body;
$mail->Send( );
}
}
/*
CREATE TABLE `users` (
`user_id` int(11) NOT NULL auto_increment,
`user_name` varchar(255) NOT NULL default '',
`user_pass` blob NOT NULL,
`user_email` varchar(255) NOT NULL default '',
`user_birthdate` datetime NOT NULL default '0000-00-00 00:00:00',
`user_newsletter` tinyint(4) NOT NULL default '1',
`user_confirmed` tinyint(1) NOT NULL default '0',
`user_dtecreated` datetime NOT NULL default '0000-00-00 00:00:00',
`user_wize_points` int(11) NOT NULL default '0',
`user_role` enum('user','merchant','admin','deity') NOT NULL default 'user',
`user_views` int(11) NOT NULL default '0',
PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `activity` (
`activity_id` int(11) NOT NULL auto_increment,
`user_id` int(11) NOT NULL default '0',
`activity_ts` int(11) NOT NULL default '0',
`activity_hash` varchar(255) default NULL,
`activity_desc` enum('None','Activation','Password Reset') NOT NULL default 'None',
`activity_expired` int(11) NOT NULL default '0',
PRIMARY KEY (`activity_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `sessions` (
`session_id` varchar(32) NOT NULL default '',
`session_data` longtext NOT NULL,
`session_time` int(11) NOT NULL default '0',
`session_ip` varchar(32) NOT NULL default '0',
`session_crypt` blob NOT NULL,
PRIMARY KEY (`session_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
*/
?>