<?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)
	{
		$key = Registry::get('db')->queryValue('
			SELECT reset_key
			FROM users
			WHERE id_user = {int:id}',
			[
				'id' => $id_user,
			]);

		return $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 = {int: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 = {int: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 = {string:key},
				reset_blocked_until = UNIX_TIMESTAMP() + ' . static::DEFAULT_RESET_TIMEOUT . '
			WHERE id_user = {int: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 = {int:new_time_out}
			WHERE id_user = {int:id_user}',
			[
				'id_user' => $id_user,
				'new_time_out' => time() + $newResetTimeOut,
			]);

		if (!$success)
			throw new UnexpectedValueException('Could not set password reset timeout!');

		return $newResetTimeOut;
	}
}