beta4 changes (#26)
These changes are mostly in underlying libraries; however, this now uses the [inspired by F#](https://git.bitbadger.solutions/bit-badger/inspired-by-fsharp) library to handle the feed parsing pipeline and optional return values Reviewed-on: #26
This commit was merged in pull request #26.
This commit is contained in:
@@ -1,49 +1,77 @@
|
||||
<?php declare(strict_types=1);
|
||||
<?php
|
||||
/**
|
||||
* @author Daniel J. Summers <daniel@bitbadger.solutions>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FeedReaderCentral;
|
||||
|
||||
use BitBadger\InspiredByFSharp\Option;
|
||||
use BitBadger\PDODocument\{DocumentException, Field, Patch};
|
||||
|
||||
/**
|
||||
* Security functions
|
||||
*/
|
||||
class Security
|
||||
readonly class Security
|
||||
{
|
||||
/** @var int Run as a single user requiring no password */
|
||||
public const SINGLE_USER = 0;
|
||||
public const SingleUserMode = 0;
|
||||
|
||||
/** @var int Run as a single user requiring a password */
|
||||
public const SINGLE_USER_WITH_PASSWORD = 1;
|
||||
public const SingleUserPasswordMode = 1;
|
||||
|
||||
/** @var int Require users to provide e-mail address and password */
|
||||
public const MultiUserMode = 2;
|
||||
|
||||
/**
|
||||
* @var int Run as a single user requiring no password
|
||||
* @deprecated Use Security::SingleUserMode instead
|
||||
*/
|
||||
public const SINGLE_USER = 0;
|
||||
|
||||
/**
|
||||
* @var int Run as a single user requiring a password
|
||||
* @deprecated Use Security::SingleUserPasswordMode instead
|
||||
*/
|
||||
public const SINGLE_USER_WITH_PASSWORD = 1;
|
||||
|
||||
/**
|
||||
* @var int Require users to provide e-mail address and password
|
||||
* @deprecated Use Security::MultiUserMode instead
|
||||
*/
|
||||
public const MULTI_USER = 2;
|
||||
|
||||
/** @var string The e-mail address for the single user */
|
||||
public const SINGLE_USER_EMAIL = 'solouser@example.com';
|
||||
public const SingleUserEmail = 'solouser@example.com';
|
||||
|
||||
/** @var string The password for the single user with no password */
|
||||
public const SINGLE_USER_PASSWORD = 'no-password-required';
|
||||
public const SingleUserPassword = 'no-password-required';
|
||||
|
||||
/** @var string The password algorithm to use for our passwords */
|
||||
public const PW_ALGORITHM = PASSWORD_DEFAULT;
|
||||
public const PasswordAlgorithm = PASSWORD_DEFAULT;
|
||||
|
||||
/** Prevent instances of this class */
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* Verify a user's password
|
||||
*
|
||||
* @param User $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 Option<string> $returnTo The URL to which the user should be redirected
|
||||
* @throws DocumentException if any is encountered
|
||||
*/
|
||||
private static function verifyPassword(User $user, string $password, ?string $returnTo): void
|
||||
private static function verifyPassword(User $user, string $password, Option $returnTo): void
|
||||
{
|
||||
if (password_verify($password, $user->password)) {
|
||||
if (password_needs_rehash($user->password, self::PW_ALGORITHM)) {
|
||||
Patch::byId(Table::USER, $user->id, ['password' => password_hash($password, self::PW_ALGORITHM)]);
|
||||
if (password_needs_rehash($user->password, self::PasswordAlgorithm)) {
|
||||
Patch::byId(Table::User, $user->id, ['password' => password_hash($password, self::PasswordAlgorithm)]);
|
||||
}
|
||||
$_SESSION[Key::USER_ID] = $user->id;
|
||||
$_SESSION[Key::USER_EMAIL] = $user->email;
|
||||
frc_redirect($returnTo ?? '/');
|
||||
$_SESSION[Key::UserId] = $user->id;
|
||||
$_SESSION[Key::UserEmail] = $user->email;
|
||||
frc_redirect($returnTo->getOrDefault('/'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,21 +80,21 @@ class Security
|
||||
*
|
||||
* @param string $email The e-mail address for the user (cannot be the single-user mode user)
|
||||
* @param string $password The password provided by the user
|
||||
* @param string|null $returnTo The URL to which the user should be redirected
|
||||
* @param Option<string> $returnTo The URL to which the user should be redirected
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
public static function logOnUser(string $email, string $password, ?string $returnTo): void {
|
||||
if (SECURITY_MODEL == self::SINGLE_USER_WITH_PASSWORD) {
|
||||
$dbEmail = self::SINGLE_USER_EMAIL;
|
||||
public static function logOnUser(string $email, string $password, Option $returnTo): void
|
||||
{
|
||||
if (SECURITY_MODEL === self::SingleUserPasswordMode) {
|
||||
$dbEmail = self::SingleUserEmail;
|
||||
} else {
|
||||
if ($email == self::SINGLE_USER_EMAIL) {
|
||||
if ($email === self::SingleUserEmail) {
|
||||
add_error('Invalid credentials; log on unsuccessful');
|
||||
return;
|
||||
}
|
||||
$dbEmail = $email;
|
||||
}
|
||||
$user = User::findByEmail($dbEmail);
|
||||
if ($user) self::verifyPassword($user, $password, $returnTo);
|
||||
User::findByEmail($dbEmail)->iter(fn(User $it) => self::verifyPassword($it, $password, $returnTo));
|
||||
add_error('Invalid credentials; log on unsuccessful');
|
||||
}
|
||||
|
||||
@@ -77,9 +105,10 @@ class Security
|
||||
* @param string $password The new password for this user
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
public static function updatePassword(string $email, string $password): void {
|
||||
Patch::byFields(Table::USER, [Field::EQ('email', $email)],
|
||||
['password' => password_hash($password, self::PW_ALGORITHM)]);
|
||||
public static function updatePassword(string $email, string $password): void
|
||||
{
|
||||
Patch::byFields(Table::User, [Field::EQ('email', $email)],
|
||||
['password' => password_hash($password, self::PasswordAlgorithm)]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,13 +116,13 @@ class Security
|
||||
*
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
private static function logOnSingleUser(): void {
|
||||
$user = User::findByEmail(self::SINGLE_USER_EMAIL);
|
||||
if (!$user) {
|
||||
User::add(self::SINGLE_USER_EMAIL, self::SINGLE_USER_PASSWORD);
|
||||
$user = User::findByEmail(self::SINGLE_USER_EMAIL);
|
||||
}
|
||||
self::verifyPassword($user, self::SINGLE_USER_PASSWORD, $_GET['returnTo']);
|
||||
private static function logOnSingleUser(): void
|
||||
{
|
||||
$user = User::findByEmail(self::SingleUserEmail)->getOrCall(function () {
|
||||
User::add(self::SingleUserEmail, self::SingleUserPassword);
|
||||
return User::findByEmail(self::SingleUserEmail)->get();
|
||||
});
|
||||
self::verifyPassword($user, self::SingleUserPassword, $_GET['returnTo']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,12 +131,13 @@ class Security
|
||||
* @param bool $redirectIfAnonymous Whether to redirect the request if there is no user logged on
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
public static function verifyUser(bool $redirectIfAnonymous = true): void {
|
||||
if (key_exists(Key::USER_ID, $_SESSION)) return;
|
||||
public static function verifyUser(bool $redirectIfAnonymous = true): void
|
||||
{
|
||||
if (key_exists(Key::UserId, $_SESSION)) return;
|
||||
|
||||
if (SECURITY_MODEL == self::SINGLE_USER) self::logOnSingleUser();
|
||||
if (SECURITY_MODEL === self::SingleUserMode) self::logOnSingleUser();
|
||||
|
||||
if (SECURITY_MODEL != self::SINGLE_USER_WITH_PASSWORD && SECURITY_MODEL != self::MULTI_USER) {
|
||||
if (SECURITY_MODEL !== self::SingleUserPasswordMode && SECURITY_MODEL != self::MultiUserMode) {
|
||||
die('Unrecognized security model (' . SECURITY_MODEL . ')');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user