Allowing users to create passwords is critical. But what happens when they want to change their password, or worse yet a user forgets their password. Will they be forever banned, unable to remember that hastily typed string?! Allowing users to send password reset tickets to their original email is a pretty good way to solve this, and is seen throughout the web. Its a snap in CakePHP as well. Here’s how it works.
To manage these tasks we’ll rely on the users model and our new tickets model. First off let me be clear. I don’t use Auth component. Just a matter of taste. So in my example I will be interacting directly with my users model, and sessions for any authentication points. Second I use my own email component as well, but again you can easily change those areas to meet your needs.
You should already have this, so we’ll just look at the new actions.
/**
* This sweet controller was written by
* @author Edward A Webb edwardawebb.com
*
*/
class UsersController extends AppController {
var $name = 'Users';
var $uses =array('User','Ticket');
var $helpers = array('Html', 'Form');
var $components =array('Email','Ticketmaster');
function resetpassword($email=null){
//grab a fresh botcheck question from the db
// for this example youll need to static code these, my botcheck article is coming soon though // $bc=$this->Botcheck->getFreshBotcheck(); $this->whatWeAsk=“Is water a liquid at room temperature?"; $this->humanWouldType=array(‘Yes’, ‘of course’); $this->set(‘botQuestion’,$this->whatWeAsk); if(empty($this->data)){ $this->data[‘User’][‘email’]=$email; //show form }else{ //already entered email $botcheck = $this->data[‘User’][‘check’]; //set email to passed variable if present if(!$email) $email=$this->data[‘User’][‘email’]; // make sure whave email and a check if(!$email){ $this->User->invalidate(‘email’); }elseif(!in_array(strtolower($botcheck),$this->humanWouldType)){ $this->User->invalidate(‘check’); }else{ //email entered, check for it $account=$this->User->findByEmail($email); if($account[‘User’][‘isBanned’]){ //banned user, tell em where to go $this->Session->setFlash('
‘); $this->redirect('/'); } if(!isset($account[‘User’][‘email’])){ $this->Session->setFlash(’
‘); $this->redirect('/');
}
$hashyToken=md5(date('mdY').rand(4000000,4999999));
$message = $this->Ticketmaster->createMessage($hashyToken);
$this->Email->useremail($email,$account['User']['username'],$message);
$data['Ticket']['hash']=$hashyToken;
$data['Ticket']['data']=$email;
$data['Ticket']['expires']=$this->Ticketmaster->getExpirationDate();
if ($this->Ticket->save($data)){
$this->Session->setFlash('An email has been sent with instructions to reset your password');
$this->redirect('/');
}else{
$this->Session->setFlash('Ticket could not be issued');
$this->redirect('/');
}
}
}
}
function useticket($hash){
//purge all expired tickets
//built into check
$results=$this->Ticketmaster->checkTicket($hash);
if($results){
//now pull up mine IF still present
$passTicket=$this->User->findByEmail($results['Ticket']['data']);
$this->Ticketmaster->voidTicket($hash);
$this->Session->write('tokenreset',$passTicket['User']['id']);
$this->Session->setFlash('Enter your new password below');
$this->redirect('/users/newpassword/'.$passTicket['User']['id']);
}else{
$this->Session->setFlash('Your ticket is lost or expired.');
$this->redirect('/');
}
}
function newpassword($id = null) {
if($this->Session->check('tokenreset')){
//user is not logged in, BUT has TOKEN in hand
}else{
// But you only want authenticated users to access this action.
//lines like the one below ‘checkSession are authentication code, so you can ignore these or use Auth $this->checkSession(1,'/users/edit/’.$id);
//But youll need to read the user info somehow, and only the user who owns the profile
$attempter=$this->Session->read('User');
//make sure its the admin or the rigth user
if($attempter['User']['id']!=$id && $attempter['Role']['rights']<4)
{
//not the user, not the admin and not a reset request via toekns
/*
* SHAME
*/
$this->Userban->banuser('Edit Anothers Password');
$this->Session->setFlash('Your account has been banned');
$this->redirect('/');
}
}
if (empty($this->data)) {
if($this->Session->check('tokenreset')) $id=$this->Session->read('tokenreset');
if (!$id) {
$this->Session->setFlash('Invalid id for User');
$this->redirect('/users/index');
}
$this->data = $this->User->read(null, $id);
} else {
$this->data['User']['password']=md5($this->data['User']['password']);
if ($this->User->save($this->data,true,array('password'))) {
//delkete session token and dlete used ticket from table
$this->Session->delete('tokenreset');
$this->Session->setFlash('The User\'s Password has been updated');
$this->redirect('/');
} else {
$this->Session->setFlash('Please correct errors below.');
}
}
}
}
CREATE TABLE IF NOT EXISTS prefix_tickets
(
id
int(11) NOT NULL auto_increment,
hash
varchar(255) default NULL,
data
varchar(255) default NULL,
created
datetime default NULL,
expires
datetime default NULL,
PRIMARY KEY (id
),
UNIQUE KEY hash
(hash
)
) ;
This manages tickets, creation, validation and destruction.
controller =& $controller; } function getExpirationDate(){ $date=strftime('%c’); $date=strtotime($date); $date+=($this->hours6060); $expired=date(‘Y-m-d H:i:s’,$date); return $expired;
}
function createMessage($token){
$ms='';
$ms='Your email has been used in a password reset request at '.$this->sitename.'<br?>';
$ms.='If you did not initiate this request, then ignore this message.
‘; $ms.=’ Copy the link below into your browser to reset your password. ‘; $ms.='Reset Password'; $ms.='';
$ms=wordwrap($ms,70);
return $ms;
}
function purgeTickets(){
$this->controller->Ticket->deleteAll('Ticket.expires <= now() LIMIT 1');
}
/*
* actually for logical reason well be indiscrimnate and clean ALL tockets for this email
*/
function voidTicket($hash){
$this->controller->Ticket->deleteAll(array('hash' => $hash));
}
function checkTicket($hash){
$this->purgeTickets();
$ret=false;
$tick=$this->controller->Ticket->findByHash($hash);
if(empty($tick)){
//no more ticket
}else{
$ret=$tick;
}
return $ret;
}
} ?>
Its always nice to let users interact with actions, so; First this form allows users to create a ticket that will get them into the form above without their password.
Next the useticket action of the controller will allow users to link from the email they received with a unique ‘token’, like a temporary pass. They will be redirected here, and allowed to enter a new password.
Username: data[‘User’][‘username’] ;?> hidden(‘User/username’, array(‘size’ => ‘60’));?>
hidden(‘User/id’)?>