- Add session support - Refactor security handling to use db connection - Fix db path issue
122 lines
4.9 KiB
PHP
122 lines
4.9 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Security functions
|
|
*/
|
|
class Security {
|
|
|
|
/** @var int Run as a single user requiring no password */
|
|
public const int SINGLE_USER = 0;
|
|
|
|
/** @var int Run as a single user requiring a password */
|
|
public const int SINGLE_USER_WITH_PASSWORD = 1;
|
|
|
|
/** @var int Require users to provide e-mail address and password */
|
|
public const int MULTI_USER = 2;
|
|
|
|
/** @var string The e-mail address for the single user */
|
|
public const string SINGLE_USER_EMAIL = 'solouser@example.com';
|
|
|
|
/** @var string The password for the single user with no password */
|
|
private const string SINGLE_USER_PASSWORD = 'no-password-required';
|
|
|
|
/**
|
|
* Find a user by their ID
|
|
*
|
|
* @param string $email The e-mail address of the user to retrieve
|
|
* @param SQLite3 $db The data connection to use to retrieve the user
|
|
* @return array|false The user information, or null if the user is not found
|
|
*/
|
|
private static function findUserByEmail(string $email, SQLite3 $db): array|false {
|
|
$query = $db->prepare('SELECT * FROM frc_user WHERE email = :email');
|
|
$query->bindValue(':email', $email);
|
|
$result = $query->execute();
|
|
return $result ? $result->fetchArray(SQLITE3_ASSOC) : false;
|
|
}
|
|
|
|
/**
|
|
* Add a user
|
|
*
|
|
* @param string $email The e-mail address for the user
|
|
* @param string $password The user's password
|
|
* @param SQLite3 $db The data connection to use to add the user
|
|
*/
|
|
public static function addUser(string $email, string $password, SQLite3 $db): void {
|
|
$query = $db->prepare('INSERT INTO frc_user (email, password) VALUES (:email, :password)');
|
|
$query->bindValue(':email', $email);
|
|
$query->bindValue(':password', password_hash($password, PASSWORD_DEFAULT));
|
|
$query->execute();
|
|
}
|
|
|
|
/**
|
|
* Verify a user's password
|
|
*
|
|
* @param array $user The user information retrieved from the database
|
|
* @param string $password The password provided by the user
|
|
* @param string|null $returnTo The URL to which the user should be redirected
|
|
* @param SQLite3 $db The database connection to use to verify the user's credentials
|
|
*/
|
|
private static function verifyPassword(array $user, string $password, ?string $returnTo, SQLite3 $db): void {
|
|
if (password_verify($password, $user['password'])) {
|
|
if (password_needs_rehash($user['password'], PASSWORD_DEFAULT)) {
|
|
$rehash = $db->prepare('UPDATE frc_user SET password = :hash WHERE id = :id');
|
|
$rehash->bindValue(':hash', password_hash($password, PASSWORD_DEFAULT));
|
|
$rehash->bindValue(':id', $user['id']);
|
|
$rehash->execute();
|
|
}
|
|
$_SESSION[Key::USER_ID] = $user['id'];
|
|
$_SESSION[Key::USER_EMAIL] = $user['email'];
|
|
frc_redirect($returnTo ?? '/');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log on a user with e-mail address and password
|
|
*
|
|
* @param string $email The e-mail address for the user
|
|
* @param string $password The password provided by the user
|
|
* @param string|null $returnTo The URL to which the user should be redirected
|
|
* @param SQLite3 $db The database connection to use to verify the user's credentials
|
|
*/
|
|
public static function logOnUser(string $email, string $password, ?string $returnTo, SQLite3 $db): void {
|
|
$user = self::findUserByEmail($email, $db);
|
|
if ($user) self::verifyPassword($user, $password, $returnTo, $db);
|
|
add_error('Invalid credentials; log on unsuccessful');
|
|
}
|
|
|
|
/**
|
|
* Log on the single user
|
|
*
|
|
* @param SQLite3 $db The data connection to use to retrieve the user
|
|
*/
|
|
private static function logOnSingleUser(SQLite3 $db): void {
|
|
$user = self::findUserByEmail(self::SINGLE_USER_EMAIL, $db);
|
|
if (!$user) {
|
|
self::addUser(self::SINGLE_USER_EMAIL, self::SINGLE_USER_PASSWORD, $db);
|
|
$user = self::findUserByEmail(self::SINGLE_USER_EMAIL, $db);
|
|
}
|
|
self::verifyPassword($user, self::SINGLE_USER_PASSWORD, $_GET['returnTo'], $db);
|
|
}
|
|
|
|
/**
|
|
* Verify that user is logged on
|
|
*
|
|
* @param SQLite3 $db The data connection to use if required
|
|
* @param bool $redirectIfAnonymous Whether to redirect the request if there is no user logged on
|
|
*/
|
|
public static function verifyUser(SQLite3 $db, bool $redirectIfAnonymous = true): void {
|
|
if (array_key_exists(Key::USER_ID, $_SESSION)) return;
|
|
|
|
if (SECURITY_MODEL == self::SINGLE_USER) self::logOnSingleUser($db);
|
|
|
|
if (SECURITY_MODEL != self::SINGLE_USER_WITH_PASSWORD && SECURITY_MODEL != self::MULTI_USER) {
|
|
die('Unrecognized security model (' . SECURITY_MODEL . ')');
|
|
}
|
|
|
|
if ($redirectIfAnonymous) {
|
|
header("Location: /user/log-on?returnTo={$_SERVER['REQUEST_URI']}", true, 307);
|
|
die();
|
|
}
|
|
}
|
|
}
|