WiP for one time tokens

* Added token type class
* Storing Token Type as ID not varchar
* Added new system to user class and fixed issues with it
* Started on mail verification process in user class
* Updated autoloader
* Updated change password template

Addresses #330
This commit is contained in:
Sebastian Grewe 2013-07-14 21:06:26 +02:00
parent d5c14b9b44
commit 29d5d36a7e
8 changed files with 125 additions and 33 deletions

View File

@ -22,6 +22,8 @@ require_once(INCLUDE_DIR . '/database.inc.php');
require_once(INCLUDE_DIR . '/smarty.inc.php');
// Load classes that need the above as dependencies
require_once(CLASS_DIR . '/base.class.php');
require_once(CLASS_DIR . '/tokentype.class.php');
require_once(CLASS_DIR . '/token.class.php');
require_once(CLASS_DIR . '/block.class.php');
require_once(CLASS_DIR . '/setting.class.php');
require_once(CLASS_DIR . '/monitoring.class.php');

View File

@ -24,6 +24,9 @@ class Base {
public function setConfig($config) {
$this->config = $config;
}
public function setTokenType($tokentype) {
$this->tokentype = $tokentype;
}
public function setErrorMessage($msg) {
$this->sError = $msg;
}

View File

@ -0,0 +1,60 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY')) die('Hacking attempt');
class Token Extends Base {
var $table = 'tokens';
/**
* Fetch a token from our table
* @param name string Setting name
* @return value string Value
**/
public function getToken($strToken) {
$stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE token = ? LIMIT 1");
if ($stmt && $stmt->bind_param('s', $strToken) && $stmt->execute() && $result = $stmt->get_result())
return $result->fetch_assoc();
return false;
}
/**
* Insert a new token
* @param name string Name of the variable
* @param value string Variable value
* @return mixed Insert ID on success, false on failure
**/
public function createToken($strType, $account_id=NULL) {
$strToken = hash('sha256', $account_id.$strType.microtime());
if (!$iToken_id = $this->tokentype->getTypeId($strType)) {
$this->setErrorMessage('Invalid token type: ' . $strType);
return false;
}
$stmt = $this->mysqli->prepare("
INSERT INTO $this->table (token, type, account_id)
VALUES (?, ?, ?)
");
if ($stmt && $stmt->bind_param('sii', $strToken, $iToken_id, $account_id) && $stmt->execute())
return $stmt->insert_id;
$this->setErrorMessage('Unable to create new token');
$this->debug->append('Failed to create new token in database: ' . $this->mysqli->error);
return false;
}
/**
* Delete a used token
* @param token string Token name
* @return bool
**/
public function deleteToken($token) {
$stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE token = ? LIMIT 1");
if ($stmt && $stmt->bind_param('s', $token) && $stmt->execute())
return true;
return false;
}
}
$token = new Token();
$token->setDebug($debug);
$token->setMysql($mysqli);
$token->setTokenType($tokentype);

View File

@ -0,0 +1,21 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
class Token_Type Extends Base {
var $table = 'token_types';
/**
* Return ID for specific token
* @param strName string Token Name
* @return mixed ID on success, false on failure
**/
public function getTypeId($strName) {
return $this->getSingle($strName, 'id', 'name', 's');
}
}
$tokentype = new Token_Type();
$tokentype->setDebug($debug);
$tokentype->setMysql($mysqli);

View File

@ -9,11 +9,11 @@ class User {
private $userID = false;
private $table = 'accounts';
private $user = array();
private $tableAccountBalance = 'accountBalance';
public function __construct($debug, $mysqli, $salt, $config) {
public function __construct($debug, $mysqli, $token, $salt, $config) {
$this->debug = $debug;
$this->mysqli = $mysqli;
$this->token = $token;
$this->salt = $salt;
$this->config = $config;
$this->debug->append("Instantiated User class", 2);
@ -485,13 +485,13 @@ class User {
}
if ($this->mysqli->query("SELECT id FROM $this->table LIMIT 1")->num_rows > 0) {
$stmt = $this->mysqli->prepare("
INSERT INTO $this->table (username, pass, email, pin, api_key)
VALUES (?, ?, ?, ?, ?)
INSERT INTO $this->table (username, pass, email, pin, api_key, is_locked)
VALUES (?, ?, ?, ?, ?, ?)
");
} else {
$stmt = $this->mysqli->prepare("
INSERT INTO $this->table (username, pass, email, pin, api_key, is_admin)
VALUES (?, ?, ?, ?, ?, 1)
INSERT INTO $this->table (username, pass, email, pin, api_key, is_admin, is_locked)
VALUES (?, ?, ?, ?, ?, 1, 0)
");
}
@ -501,14 +501,19 @@ class User {
$apikey_hash = $this->getHash($username);
$username_clean = strip_tags($username);
if ($this->checkStmt($stmt) && $stmt->bind_param('sssss', $username_clean, $password_hash, $email1, $pin_hash, $apikey_hash)) {
if (!$stmt->execute()) {
$this->setErrorMessage( 'Unable to register' );
if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username or email already registered' );
return false;
//
$this->config['confirm_email'] ? $is_locked = 1 : $is_locked = 0;
if ($this->checkStmt($stmt) && $stmt->bind_param('sssssi', $username_clean, $password_hash, $email1, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) {
if ($this->config['confirm_email']) {
$this->token->createToken('confirm_email', $stmt->insert_id);
} else {
return true;
}
$stmt->close();
return true;
} else {
$this->setErrorMessage( 'Unable to register' );
if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username or email already registered' );
return false;
}
return false;
}
@ -520,9 +525,9 @@ class User {
* @param new2 string New password verification
* @return bool
**/
public function useToken($token, $new1, $new2) {
public function resetPassword($token, $new1, $new2) {
$this->debug->append("STA " . __METHOD__, 4);
if ($id = $this->getIdFromToken($token)) {
if ($token = $this->token->getToken($token)) {
if ($new1 !== $new2) {
$this->setErrorMessage( 'New passwords do not match' );
return false;
@ -532,14 +537,20 @@ class User {
return false;
}
$new_hash = $this->getHash($new1);
$stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ?, token = NULL WHERE id = ? AND token = ?");
if ($this->checkStmt($stmt) && $stmt->bind_param('sis', $new_hash, $id, $token) && $stmt->execute() && $stmt->affected_rows === 1) {
return true;
$stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE id = ?");
if ($this->checkStmt($stmt) && $stmt->bind_param('si', $new_hash, $token['account_id']) && $stmt->execute() && $stmt->affected_rows === 1) {
if ($this->token->deleteToken($token)) {
return true;
} else {
$this->setErrorMessage('Unable to invalidate used token');
}
} else {
$this->setErrorMessage('Unable to set new password');
}
} else {
$this->setErrorMessage("Unable find user for your token");
return false;
$this->setErrorMessage('Unable find user for your token');
}
$this->debug->append('Failed to update password:' . $this->mysqli->error);
return false;
}
@ -549,7 +560,7 @@ class User {
* @param smarty object Smarty object for mail templating
* @return bool
**/
public function resetPassword($username, $smarty) {
public function initResetPassword($username, $smarty) {
$this->debug->append("STA " . __METHOD__, 4);
// Fetch the users mail address
if (empty($username)) {
@ -560,16 +571,11 @@ class User {
$this->setErrorMessage("Unable to find a mail address for user $username");
return false;
}
if (!$this->setUserToken($this->getUserId($username))) {
if (!$token = $this->token->getToken($this->token->createToken('password_reset', $this->getUserId($username)))) {
$this->setErrorMessage("Unable to setup token for password reset");
return false;
}
// Send password reset link
if (!$token = $this->getUserToken($this->getUserId($username))) {
$this->setErrorMessage("Unable fetch token for password reset");
return false;
}
$smarty->assign('TOKEN', $token);
$smarty->assign('TOKEN', $token['token']);
$smarty->assign('USERNAME', $username);
$smarty->assign('SUBJECT', 'Password Reset Request');
$smarty->assign('WEBSITENAME', $this->config['website']['name']);
@ -608,4 +614,4 @@ class User {
}
// Make our class available automatically
$user = new User($debug, $mysqli, SALT, $config);
$user = new User($debug, $mysqli, $token, SALT, $config);

View File

@ -4,8 +4,8 @@
if (!defined('SECURITY'))
die('Hacking attempt');
if ($_POST['do'] == 'useToken') {
if ($user->useToken($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) {
if ($_POST['do'] == 'resetPassword') {
if ($user->resetPassword($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) {
$_SESSION['POPUP'][] = array('CONTENT' => 'Password reset complete! Please login.');
} else {
$_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg');

View File

@ -5,7 +5,7 @@ if (!defined('SECURITY'))
die('Hacking attempt');
// Process password reset request
if ($user->resetPassword($_POST['username'], $smarty)) {
if ($user->initResetPassword($_POST['username'], $smarty)) {
$_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mail account to finish your password reset');
} else {
$_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg');

View File

@ -3,7 +3,7 @@
<input type="hidden" name="token" value="{$smarty.request.token|escape}">
<input type="hidden" name="page" value="{$smarty.request.page|escape}">
<input type="hidden" name="action" value="{$smarty.request.action|escape}">
<input type="hidden" name="do" value="useToken">
<input type="hidden" name="do" value="resetPassword">
<table>
<tr><td>New Password: </td><td><input type="password" name="newPassword"></td></tr>
<tr><td>New Password Repeat: </td><td><input type="password" name="newPassword2"></td></tr>