refactor logout. cleanup

This commit is contained in:
Greg Sarjeant 2025-06-02 21:36:59 -04:00
parent fea0e9eb53
commit f635dd8724
8 changed files with 158 additions and 123 deletions

View File

@ -1,11 +1,22 @@
<?php
session_start();
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// Start a session and create a csrf token if necessary
// TODO - move these to an AuthController?
function start_session(){
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
function generate_csrf_token(bool $regenerate = false){
if (!isset($_SESSION['csrf_token']) || $regenerate) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
}
start_session();
generate_csrf_token();
define('APP_ROOT', dirname(dirname(__FILE__)));
define('SRC_DIR', APP_ROOT . '/src');
@ -94,11 +105,14 @@ header('Content-Type: text/html; charset=utf-8');
// routes
$routes = [
['', 'HomeController'],
['', 'HomeController@tick', ['POST']],
['login', 'LoginController'],
['login', 'LoginController@login', ['POST']],
['', 'HomeController@handleTick', ['POST']],
['admin', 'AdminController'],
['admin', 'AdminController@save', ['POST']],
['login', 'AuthController@showLogin'],
['login', 'AuthController@handleLogin', ['POST']],
['logout', 'AuthController@handleLogout', ['GET', 'POST']],
['mood', 'MoodController'],
['mood', 'MoodController@set_mood', ['POST']],
['mood', 'MoodController@handleMood', ['POST']],
];

View File

@ -0,0 +1,114 @@
<?php
class AdminController {
// GET handler
// render the admin page
public function index(){
$config = Config::load();
$user = USER::load();
$vars = [
'user' => $user,
'config' => $config,
];
echo render_template(TEMPLATES_DIR . "/admin.php", $vars);
}
// POST handler
// save updated settings
public function save(){
$isLoggedIn = isset($_SESSION['user_id']);
if (!$isLoggedIn){
header('Location: ' . $config->basePath . 'login.php');
exit;
}
$config = Config::load();
$user = User::load();
// handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors = [];
// User profile
$username = trim($_POST['username'] ?? '');
$displayName = trim($_POST['display_name'] ?? '');
$about = trim($_POST['about'] ?? '');
$website = trim($_POST['website'] ?? '');
// Site settings
$siteTitle = trim($_POST['site_title']) ?? '';
$siteDescription = trim($_POST['site_description']) ?? '';
$basePath = trim($_POST['base_path'] ?? '/');
$itemsPerPage = (int) ($_POST['items_per_page'] ?? 25);
// Password
// TODO - Make sure I really shouldn't trim these
// (I'm assuming there may be people who end their password with a space character)
$password = $_POST['password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
// Validate user profile
if (!$username) {
$errors[] = "Username is required.";
}
if (!$displayName) {
$errors[] = "Display name is required.";
}
// Make sure the website looks like a URL and starts with a protocol
if ($website) {
if (!filter_var($website, FILTER_VALIDATE_URL)) {
$errors[] = "Please enter a valid URL (including http:// or https://).";
} elseif (!preg_match('/^https?:\/\//i', $website)) {
$errors[] = "URL must start with http:// or https://.";
}
}
// Validate site settings
if (!$siteTitle) {
$errors[] = "Site title is required.";
}
if (!preg_match('#^/[^?<>:"|\\*]*$#', $basePath)) {
$errors[] = "Base path must look like a valid URL path (e.g. / or /tkr/).";
}
if ($itemsPerPage < 1 || $itemsPerPage > 50) {
$errors[] = "Items per page must be a number between 1 and 50.";
}
// If a password was sent, make sure it matches the confirmation
if ($password && !($password = $confirmPassword)){
$errors[] = "Passwords do not match";
}
// TODO: Actually handle errors
if (empty($errors)) {
// Update site settings
$config->siteTitle = $siteTitle;
$config->siteDescription = $siteDescription;
$config->basePath = $basePath;
$config->itemsPerPage = $itemsPerPage;
// Save site settings and reload config from database
$config = $config->save();
// Update user profile
$user->username = $username;
$user->displayName = $displayName;
$user->about = $about;
$user->website = $website;
// Save user profile and reload user from database
$user = $user->save();
// Update the password if one was sent
if($password){
$user->set_password($password);
}
}
}
header('Location: ' . $config->basePath . '/admin');
exit;
}
}

View File

@ -1,6 +1,6 @@
<?php
class LoginController {
function index(?string $error = null){
class AuthController {
function showLogin(?string $error = null){
$config = Config::load();
$csrf_token = $_SESSION['csrf_token'];
@ -13,7 +13,7 @@ class LoginController {
echo render_template(TEMPLATES_DIR . '/login.php', $vars);
}
function login(){
function handleLogin(){
$config = Config::load();
$error = '';
@ -36,13 +36,21 @@ class LoginController {
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['csrf_token'] = generate_csrf_token(true);
header('Location: ' . $config->basePath);
exit;
} else {
$error = 'Invalid username or password';
}
}
}
$csrf_token = generateCsrfToken();
function handleLogout(){
$_SESSION = [];
session_destroy();
$config = Config::load();
header('Location: ' . $config->basePath);
exit;
}
}

View File

@ -27,7 +27,7 @@ class HomeController{
// POST handler
// Saves the tick and reloads the homepage
public function tick(){
public function handleTick(){
if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['tick'])) {
// ensure that the session is valid before proceeding
if (!validateCsrfToken($_POST['csrf_token'])) {

View File

@ -15,7 +15,7 @@
echo render_template(TEMPLATES_DIR . "/mood.php", $vars);
}
public function set_mood(){
public function handleMood(){
if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['mood'])) {
// ensure that the session is valid before proceeding
if (!validateCsrfToken($_POST['csrf_token'])) {
@ -33,7 +33,7 @@
// go back to the index and show the updated mood
header('Location: ' . $config->basePath);
//exit;
exit;
}
}

View File

@ -1,4 +1,5 @@
<?php
/*
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
@ -9,7 +10,7 @@ function generateCsrfToken() {
}
return $_SESSION['csrf_token'];
}
*/
function validateCsrfToken($token) {
return hash_equals($_SESSION['csrf_token'], $token);
}

View File

@ -1,102 +1,5 @@
<?php
#require_once __DIR__ . '/../bootstrap.php';
#confirm_setup();
#require_once CLASSES_DIR . '/Config.php';
#require LIB_DIR . '/session.php';
if (!$isLoggedIn){
header('Location: ' . $config->basePath . 'login.php');
}
require CLASSES_DIR . '/User.php';
$config = Config::load();
$user = User::load();
// handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors = [];
// User profile
$username = trim($_POST['username'] ?? '');
$displayName = trim($_POST['display_name'] ?? '');
$about = trim($_POST['about'] ?? '');
$website = trim($_POST['website'] ?? '');
// Site settings
$siteTitle = trim($_POST['site_title']) ?? '';
$siteDescription = trim($_POST['site_description']) ?? '';
$basePath = trim($_POST['base_path'] ?? '/');
$itemsPerPage = (int) ($_POST['items_per_page'] ?? 25);
// Password
// TODO - Make sure I really shouldn't trim these
// (I'm assuming there may be people who end their password with a space character)
$password = $_POST['password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
// Validate user profile
if (!$username) {
$errors[] = "Username is required.";
}
if (!$displayName) {
$errors[] = "Display name is required.";
}
// Make sure the website looks like a URL and starts with a protocol
if ($website) {
if (!filter_var($website, FILTER_VALIDATE_URL)) {
$errors[] = "Please enter a valid URL (including http:// or https://).";
} elseif (!preg_match('/^https?:\/\//i', $website)) {
$errors[] = "URL must start with http:// or https://.";
}
}
// Validate site settings
if (!$siteTitle) {
$errors[] = "Site title is required.";
}
if (!preg_match('#^/[^?<>:"|\\*]*$#', $basePath)) {
$errors[] = "Base path must look like a valid URL path (e.g. / or /tkr/).";
}
if ($itemsPerPage < 1 || $itemsPerPage > 50) {
$errors[] = "Items per page must be a number between 1 and 50.";
}
// If a password was sent, make sure it matches the confirmation
if ($password && !($password = $confirmPassword)){
$errors[] = "Passwords do not match";
}
// TODO: Actually handle errors
if (empty($errors)) {
// Update site settings
$config->siteTitle = $siteTitle;
$config->siteDescription = $siteDescription;
$config->basePath = $basePath;
$config->itemsPerPage = $itemsPerPage;
// Save site settings and reload config from database
$config = $config->save();
// Update user profile
$user->username = $username;
$user->displayName = $displayName;
$user->about = $about;
$user->website = $website;
// Save user profile and reload user from database
$user = $user->save();
// Update the password if one was sent
if($password){
$user->set_password($password);
}
}
}
?>
<?php /** @var Config $config */ ?>
<?php /** @var User $user */ ?>
<!DOCTYPE html>
<html lang="en">
@ -111,6 +14,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<div><a href="<?= $config->basePath ?>">Back to home</a></div>
<div>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<fieldset id="user_settings" class="admin-settings-group">
<legend>User settings</legend>
<label class="admin-option">Username: <input type="text" name="username" value="<?= $user->username ?>" required></label><br>

View File

@ -1,14 +1,8 @@
<?php
#require_once __DIR__ . '/../bootstrap.php';
#confirm_setup();
#require_once CLASSES_DIR . '/Config.php';
#require LIB_DIR . '/session.php';
$config = Config::load();
$_SESSION = [];
session_destroy();
$config = Config::load();
header('Location: ' . $config->basePath);
exit;