Currently, it's possible to reset passwords for users that do not have any password reset by providing an empty key, which would match '' or NULL.
174 lines
4.0 KiB
PHP
174 lines
4.0 KiB
PHP
<?php
|
|
/*****************************************************************************
|
|
* Authentication.php
|
|
* Contains key class Authentication.
|
|
*
|
|
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
* Authentication class, containing various static functions used for account verification
|
|
* and session management.
|
|
*/
|
|
class Authentication
|
|
{
|
|
const DEFAULT_RESET_TIMEOUT = 30;
|
|
|
|
/**
|
|
* Checks a password for a given username against the database.
|
|
*/
|
|
public static function checkPassword($emailaddress, $password)
|
|
{
|
|
// Retrieve password hash for user matching the provided emailaddress.
|
|
$password_hash = Registry::get('db')->queryValue('
|
|
SELECT password_hash
|
|
FROM users
|
|
WHERE emailaddress = :emailaddress',
|
|
[
|
|
'emailaddress' => $emailaddress,
|
|
]);
|
|
|
|
// If there's no hash, the user likely does not exist.
|
|
if (!$password_hash)
|
|
return false;
|
|
|
|
return password_verify($password, $password_hash);
|
|
}
|
|
|
|
public static function checkResetKey($id_user, $reset_key)
|
|
{
|
|
// Users without a pending reset have a blank key, which must never match.
|
|
if (!is_string($reset_key) || $reset_key === '')
|
|
return false;
|
|
|
|
$key = Registry::get('db')->queryValue('
|
|
SELECT reset_key
|
|
FROM users
|
|
WHERE id_user = :id',
|
|
[
|
|
'id' => $id_user,
|
|
]);
|
|
|
|
if (!is_string($key) || $key === '')
|
|
return false;
|
|
|
|
return hash_equals($key, $reset_key);
|
|
}
|
|
|
|
/**
|
|
* Computes a password hash.
|
|
*/
|
|
public static function computeHash($password)
|
|
{
|
|
$hash = password_hash($password, PASSWORD_DEFAULT);
|
|
if (!$hash)
|
|
throw new Exception('Hash creation failed!');
|
|
return $hash;
|
|
}
|
|
|
|
public static function consumeResetKey($id_user)
|
|
{
|
|
return Registry::get('db')->query('
|
|
UPDATE users
|
|
SET reset_key = NULL,
|
|
reset_blocked_until = NULL
|
|
WHERE id_user = :id_user',
|
|
['id_user' => $id_user]);
|
|
}
|
|
|
|
public static function getResetTimeOut($id_user)
|
|
{
|
|
$resetTime = Registry::get('db')->queryValue('
|
|
SELECT reset_blocked_until
|
|
FROM users
|
|
WHERE id_user = :id_user',
|
|
['id_user' => $id_user]);
|
|
|
|
return max(0, $resetTime - time());
|
|
}
|
|
|
|
/**
|
|
* Verifies whether the user is currently logged in.
|
|
*/
|
|
public static function isLoggedIn()
|
|
{
|
|
if (!isset($_SESSION['user_id']))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
$exists = Member::fromId($_SESSION['user_id']);
|
|
return true;
|
|
}
|
|
catch (NotFoundException $e)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a new activation key.
|
|
*/
|
|
public static function newActivationKey()
|
|
{
|
|
$alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
$string = '';
|
|
for ($i = 0; $i < 16; $i++)
|
|
$string .= $alpha[mt_rand(0, strlen($alpha) - 1)];
|
|
return $string;
|
|
}
|
|
|
|
public static function setResetKey($id_user)
|
|
{
|
|
return Registry::get('db')->query('
|
|
UPDATE users
|
|
SET reset_key = :key,
|
|
reset_blocked_until = UNIX_TIMESTAMP() + ' . static::DEFAULT_RESET_TIMEOUT . '
|
|
WHERE id_user = :id',
|
|
[
|
|
'id' => $id_user,
|
|
'key' => self::newActivationKey(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resets a password for a certain user.
|
|
*/
|
|
public static function updatePassword($id_user, $hash)
|
|
{
|
|
return Registry::get('db')->query('
|
|
UPDATE users
|
|
SET
|
|
password_hash = :hash,
|
|
reset_key = :blank
|
|
WHERE id_user = :id_user',
|
|
[
|
|
'id_user' => $id_user,
|
|
'hash' => $hash,
|
|
'blank' => '',
|
|
]);
|
|
}
|
|
|
|
public static function updateResetTimeOut($id_user)
|
|
{
|
|
$currentResetTimeOut = static::getResetTimeOut($id_user);
|
|
|
|
// New timeout: between 30 seconds, double the current timeout, and a full day
|
|
$newResetTimeOut = min(max(static::DEFAULT_RESET_TIMEOUT, $currentResetTimeOut * 2), 60 * 60 * 24);
|
|
|
|
$success = Registry::get('db')->query('
|
|
UPDATE users
|
|
SET reset_blocked_until = :new_time_out
|
|
WHERE id_user = :id_user',
|
|
[
|
|
'id_user' => $id_user,
|
|
'new_time_out' => time() + $newResetTimeOut,
|
|
]);
|
|
|
|
if (!$success)
|
|
throw new UnexpectedValueException('Could not set password reset timeout!');
|
|
|
|
return $newResetTimeOut;
|
|
}
|
|
}
|