Add single user security mode (#3)

- Tweaks to SQL column definitions
- Implement class autoloading
- Split user config into its own file
This commit is contained in:
Daniel J. Summers 2024-04-05 22:14:24 -04:00
parent 8db4216ea2
commit 74bc83f266
5 changed files with 127 additions and 25 deletions

View File

@ -14,6 +14,9 @@ class Data {
return $db; return $db;
} }
/**
* Make sure the expected tables exist
*/
public static function ensureDb(): void { public static function ensureDb(): void {
$db = self::getConnection(); $db = self::getConnection();
$tables = array(); $tables = array();
@ -24,8 +27,7 @@ class Data {
CREATE TABLE frc_user ( CREATE TABLE frc_user (
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
email TEXT NOT NULL, email TEXT NOT NULL,
password TEXT NOT NULL, password TEXT NOT NULL)
salt TEXT NOT NULL)
SQL; SQL;
$db->exec($query); $db->exec($query);
$db->exec('CREATE INDEX idx_user_email ON frc_user (email)'); $db->exec('CREATE INDEX idx_user_email ON frc_user (email)');
@ -50,15 +52,46 @@ class Data {
published_on TEXT NOT NULL, published_on TEXT NOT NULL,
updated_on TEXT, updated_on TEXT,
content TEXT NOT NULL, content TEXT NOT NULL,
is_encoded INTEGER NOT NULL, is_encoded BOOLEAN NOT NULL,
is_read INTEGER NOT NULL, is_read BOOLEAN NOT NULL,
is_bookmarked INTEGER NOT NULL, is_bookmarked BOOLEAN NOT NULL,
FOREIGN KEY (feed_id) REFERENCES feed (id)) FOREIGN KEY (feed_id) REFERENCES feed (id))
SQL; SQL;
$db->exec($query); $db->exec($query);
} }
$db->close(); $db->close();
} }
/**
* Find a user by their ID
*
* @param string $email The e-mail address of the user to retrieve
* @return array|null The user information, or null if the user is not found
*/
public static function findUserByEmail(string $email): ?array {
$db = self::getConnection();
$query = $db->prepare('SELECT * FROM frc_user WHERE email = :email');
$query->bindValue(':email', $email);
$result = $query->execute();
if ($result) {
$user = $result->fetchArray(SQLITE3_ASSOC);
if ($user) return $user;
return null;
}
return null;
} }
Data::ensureDb(); /**
* Add a user
*
* @param string $email The e-mail address for the user
* @param string $password The user's password
*/
public static function addUser(string $email, string $password): void {
$db = self::getConnection();
$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();
}
}

51
src/lib/Security.php Normal file
View File

@ -0,0 +1,51 @@
<?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;
/**
* Verify that user is logged on
* @param bool $redirectIfAnonymous Whether to redirect the request if there is no user logged on
*/
public static function verifyUser(bool $redirectIfAnonymous = true): void {
switch (SECURITY_MODEL) {
case self::SINGLE_USER:
$user = self::retrieveSingleUser();
break;
case self::SINGLE_USER_WITH_PASSWORD:
die('Single User w/ Password has not yet been implemented');
case self::MULTI_USER:
die('Multi-User Mode has not yet been implemented');
default:
die('Unrecognized security model (' . SECURITY_MODEL . ')');
}
if (!$user && $redirectIfAnonymous) {
header('/logon?returnTo=' . $_SERVER["REQUEST_URI"], true, HTTP_REDIRECT_TEMP);
die();
}
$_REQUEST['FRC_USER_ID'] = $user['id'];
$_REQUEST['FRC_USER_EMAIL'] = $user['email'];
}
/**
* Retrieve the single user
* @return array The user information for the single user
*/
private static function retrieveSingleUser(): array {
$user = Data::findUserByEmail('solouser@example.com');
if ($user) return $user;
Data::addUser('solouser@example.com', 'no-password-required');
return Data::findUserByEmail('solouser@example.com');
}
}

View File

@ -1,8 +1,10 @@
<?php <?php
include '../start.php'; include '../start.php';
Security::verifyUser();
page_head('Welcome'); page_head('Welcome');
?> ?>
<p>Startup worked</p> <p>User ID <?=$_REQUEST['FRC_USER_ID']?> - e-mail <?=$_REQUEST['FRC_USER_EMAIL']?></p>
<?php <?php
page_foot(); page_foot();

View File

@ -1,23 +1,16 @@
<?php <?php
// USER CONFIGURATION ITEMS spl_autoload_register(function ($class) {
$file = implode(DIRECTORY_SEPARATOR, [ __DIR__, 'lib', "$class.php" ]);
if (file_exists($file)) {
require $file;
return true;
}
return false;
});
/** require 'user-config.php';
* Which security model should the application use?
* - 0 = single-user, no password
* - 1 = single-user with password
* - 2 = multi-user (all users require passwords)
*
* (NOTE THAT 1 AND 2 HAVE NOT YET BEEN IMPLEMENTED)
*/
const SECURITY_MODEL = 0;
/** The name of the database file where users and feeds should be kept */ Data::ensureDb();
const DATABASE_NAME = 'frc.db';
// END USER CONFIGURATION ITEMS
// (editing below this line is not advised)
include 'lib/Data.php';
/** /**
* Render the page title * Render the page title

23
src/user-config.php Normal file
View File

@ -0,0 +1,23 @@
<?php
/**
* USER CONFIGURATION ITEMS
*
* Editing the values here customizes the behavior of Feed Reader Central
*/
/**
* Which security model should the application use? Options are:
* - Security::SINGLE_USER (no e-mail required, does not require a password)
* - Security::SINGLE_USER_WITH_PASSWORD (no e-mail required, does require a password)
* - Security::MULTI_USER (e-mail and password required for all users)
*
* (NOTE THAT ONLY SINGLE_USER IS CURRENTLY IMPLEMENTED)
*/
const SECURITY_MODEL = Security::SINGLE_USER;
/** The name of the database file where users and feeds should be kept */
const DATABASE_NAME = 'frc.db';
// END USER CONFIGURATION ITEMS
// (editing below this line is not advised)