Skip to content

Commit

Permalink
initial implementation of user password reset functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
daetal-us committed Sep 14, 2011
1 parent e30bcbe commit 24b8dc7
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 62 deletions.
98 changes: 91 additions & 7 deletions libraries/li3_users/controllers/UsersController.php
@@ -1,16 +1,18 @@
<?php
/**
* Lithium: the most rad php framework
* Lithium Sphere: communized sphere of influence
*
* @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
* @license http://opensource.org/licenses/bsd-license.php The BSD License
* @copyright Copyright 2011, Union of RAD (http://union-of-rad.org)
* @license http://www.opensource.org/licenses/MIT The MIT License
*/

namespace li3_users\controllers;

use li3_users\models\User;
use lithium\security\Auth;
use lithium\storage\Session;
use li3_swiftmailer\mailer\Transports;
use li3_swiftmailer\mailer\Message;

class UsersController extends \lithium\action\Controller {

Expand All @@ -30,7 +32,7 @@ public function login() {
$return = $this->request->params['return'];
}
$errors = $disabled = false;
$attempts = (integer) Session::read('attempts');
$attempts = (integer) Session::read('attempts', array('name' => 'cooldown'));
if (!empty($this->request->data)) {
$attempts++;
}
Expand All @@ -48,7 +50,7 @@ public function login() {
if (!empty($user)) {
$attempts = 0;
if (!empty($return)) {
Session::write('attempts', $attempts);
Session::write('attempts', $attempts, array('name' => 'cooldown'));
return $this->redirect(base64_decode($return));
}
} else {
Expand All @@ -58,7 +60,7 @@ public function login() {
}
break;
}
Session::write('attempts', $attempts);
Session::write('attempts', $attempts, array('name' => 'cooldown'));
return compact('user', 'return', 'errors', 'disabled');
}

Expand All @@ -78,7 +80,7 @@ public function register() {
$user = User::create($this->request->data);
if ($user->save()) {
Auth::set('user', $user->to('array'));
Session::write('attempts', 0);
Session::write('attempts', 0, array('name' => 'cooldown'));
$this->redirect(array(
'controller' => 'users', 'action' => 'view',
'args' => array($user->_id)
Expand Down Expand Up @@ -108,6 +110,88 @@ public function edit($_id = null) {
}
return compact('user');
}

public function reset($token = null) {
$success = $resetting = $errors = $emailed = false;
Session::write('reset_attempts', 0, array('name' => 'cooldown'));
$user = Session::read('user', array('name' => 'li3_user'));

$attempts = (integer) Session::read('reset_attempts', array('name' => 'cooldown'));
$cooldown = $this->_cooldown < $attempts;

$title = 'forgot your password?';

if (!empty($user)) {
$token = $user->token();
}

if (!$cooldown && !empty($token)) {
$resetting = true;
$title = "update your password";
if (!empty($this->request->data['_id']) && !empty($this->request->data['password'])) {
$_id = $this->request->data['_id'];
if (User::reset(compact('token','_id'))) {
$user = User::first($this->request->data['_id']);
$user->set(array('password' => $this->request->data['password']));
$success = $user->save();
$title = 'password updated!';
if (!$success) {
$title = 'almost there...';
$errors = $user->errors();
}
Auth::check('user', $this->request);
Session::write('reset_attempts', 0, array('name' => 'cooldown'));
} else {
$errors = array(
'token' => array('Your token has expired.')
);
}
$attempts++;
Session::write('reset_attempts', $attempts, array('name' => 'cooldown'));
}
}

if (empty($token) && !empty($this->request->data['_id'])) {
if ($user = User::first($this->request->data['_id'])) {
$user->token();
if ($this->_emailToken($user->data())) {
$emailed = true;
} else {
$errors = array(
'email' => array("Hm. It appears we couldn't email you.")
);
}
} else {
$errors = array(
'_id' => array('I think you typed that username incorrectly.')
);
}
}

return compact('success','resetting','errors','title','user','cooldown', 'emailed');
}

protected function _emailToken($data = array()) {
if (!empty($data)) {
$body = implode(array(
"you have requested to reset your password for lithium sphere.",
"the following url is just what you need:",
"\t" . \lithium\net\http\Router::match(array(
'controller' => 'users', 'action' => 'reset'
)) . "/{$data['token']}",
'this email will self destruct in 10 minutes and counting...'
), "\n\n");

$mailer = Transports::adapter('default');
$message = Message::newInstance()
->setSubject(' : reset your password : ')
->setFrom(array('lithified@lithify.me' => 'lithium sphere'))
->setTo($data['email'])
->setBody($body);
return $mailer->send($message);
}
return false;
}
}

?>
91 changes: 68 additions & 23 deletions libraries/li3_users/models/User.php
@@ -1,9 +1,9 @@
<?php
/**
* Lithium: the most rad php framework
* Lithium Sphere: communized sphere of influence
*
* @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
* @license http://opensource.org/licenses/bsd-license.php The BSD License
* @copyright Copyright 2011, Union of RAD (http://union-of-rad.org)
* @license http://www.opensource.org/licenses/MIT The MIT License
*/

namespace li3_users\models;
Expand All @@ -15,6 +15,12 @@
$conditions = array();
if (!empty($value)) {
$conditions[$options['field']] = $value;
if ($options['events']['update'] && !empty($options['values']['_id'])) {
if ($options['field'] == '_id' && $value == $options['values']['_id']) {
return true;
}
$conditions['_id'] = array('$ne' => $options['values']['_id']);
}
return !(boolean) User::find('count', compact('conditions'));
}
return false;
Expand All @@ -28,49 +34,43 @@ class User extends \lithium\data\Model {
'title' => null,
'class' => null,
'locked' => true,
'source' => null,
'source' => 'users',
'connection' => 'li3_users',
'initialized' => false
);

public $validates = array(
'_id' => array(
array('notEmpty', 'message' => 'a user id is required.'),
array(
'alphaNumeric',
'message' => 'Only numbers and letters are allowed for your username'
'message' => 'only numbers and letters are allowed for your username.'
),
array('notEmpty', 'message' => 'Please provide a user id.'),
array(
'lengthBetween',
'options' => array(
'min' => 4,
'min' => 1,
'max' => 250
),
'message' => 'Password must be be at least.'
'message' => 'please provide a user id.'
),
array('uniqueUserValue', 'message' => 'That username is already taken.')
array('uniqueUserValue', 'message' => 'that username is already taken.')
),
'password' => array(
array('notEmpty', 'message' => 'You must provide a password.'),
array(
'lengthBetween',
'options' => array(
'min' => 6,
'max' => 250
),
'message' => 'Password must be be at least.'
)
array('notEmpty', 'message' => 'a password is required.'),
),
'email' => array(
array('email', 'message' => 'Please provide a valid email address.'),
array('uniqueUserValue', 'message' => 'That email already has an account.')
array('email', 'message' => 'please provide a valid email address.'),
array('uniqueUserValue', 'message' => 'that email already has an account.')
)
);

protected $_schema = array(
'_id' => array('type' => 'string', 'length' => 250, 'primary' => true),
'password' => array('type' => 'string', 'length' => 250),
'email' => array('type' => 'string', 'length' => 250),
'token' => array('type' => 'string', 'length' => 16),
'expires' => array('type' => 'date'),
'created' => array('type' => 'date'),
'settings' => array('type' => 'array'),
'type' => array('type' => 'string', 'default' => 'user')
Expand All @@ -91,10 +91,9 @@ public static function __init(array $options = array()) {
static::applyFilter('save', function ($self, $params, $chain) {
if (empty($params['entity']->created)) {
$params['entity']->created = date('Y-m-d H:i:s');
$params['entity']->password = String::hash($params['entity']->password);
}
if (!empty($params['entity']->new_password)) {
$params['entity']->password = String::hash($params['entity']->new_password);
if (!empty($params['entity']->password)) {
$params['entity']->password = String::hash($params['entity']->password);
}
return $chain->next($self, $params, $chain);
});
Expand All @@ -117,6 +116,52 @@ public static function login($data) {
}
return false;
}

/**
* Validate a token against a user id
*
* @param array $data token and _id of user record
* @return boolean
*/
public static function reset($data = array()) {
$defaults = array(
'token' => null,
'_id' => null,
);
extract($data + $defaults);
if (!empty($token) && !empty($_id)) {
$user = static::find('all', array(
'conditions' => compact('_id','token'), 'limit' => 1
));
if ($user->count()) {
return $user->first()->expires->sec > time();
}
}
return false;
}

/**
* Initialize a token and expiry time for a user record
*
* @param array $record user record
* @param integer optional timestamp for expiration time to count from
* @return boolean|string token if valid else `false`
*/
public function token($record, $time = null) {
$token = false;
if (!empty($record->_id) && !empty($record->password)) {
if (empty($time)) {
$time = time();
}
$token = md5(
md5($record->_id) . md5($record->password) . md5($time)
);
$expires = date('Y-m-d H:i:s', strtotime('+10 minutes', $time));
$record->set(compact('token','expires'));
$record->save();
}
return $token;
}
}

?>

0 comments on commit 24b8dc7

Please sign in to comment.