Compare commits
6 Commits
14eefb99e9
...
4877354e8d
Author | SHA1 | Date |
---|---|---|
|
4877354e8d | |
|
61d23cd8c2 | |
|
8dfd54eb9f | |
|
af8d86321f | |
|
26817c1bb6 | |
|
6443eb9b00 |
|
@ -87,7 +87,7 @@
|
|||
</li>
|
||||
<?php } else { ?>
|
||||
<li><a href="<?= htmlspecialchars($app_root) ?>?page=login">login</a></li>
|
||||
<li><a href="<?= htmlspecialchars($app_root) ?>?page=register">register</a></li>
|
||||
<?php do_hook('main_public_menu', ['app_root' => $app_root, 'section' => 'main', 'position' => 100]); ?>
|
||||
<?php } ?>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
// Add to allowed URLs
|
||||
register_hook('filter_allowed_urls', function($urls) {
|
||||
$urls[] = 'register';
|
||||
return $urls;
|
||||
});
|
||||
|
||||
// Add to publicly accessible pages
|
||||
register_hook('filter_public_pages', function($pages) {
|
||||
$pages[] = 'register';
|
||||
return $pages;
|
||||
});
|
||||
|
||||
// Configuration for main menu injection
|
||||
define('REGISTRATIONS_MAIN_MENU_SECTION', 'main');
|
||||
define('REGISTRATIONS_MAIN_MENU_POSITION', 30);
|
||||
register_hook('main_public_menu', function($ctx) {
|
||||
$section = defined('REGISTRATIONS_MAIN_MENU_SECTION') ? REGISTRATIONS_MAIN_MENU_SECTION : 'main';
|
||||
$position = defined('REGISTRATIONS_MAIN_MENU_POSITION') ? REGISTRATIONS_MAIN_MENU_POSITION : 100;
|
||||
echo '<li><a href="?page=register">register</a></li>';
|
||||
});
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* User registration
|
||||
*
|
||||
* This page ("register") handles user registration if the feature is enabled in the configuration.
|
||||
* It accepts a POST request with a username and password, attempts to register the user,
|
||||
* and redirects to the login page on success or displays an error message on failure.
|
||||
*/
|
||||
|
||||
// Define plugin base path if not already defined
|
||||
if (!defined('PLUGIN_REGISTER_PATH')) {
|
||||
define('PLUGIN_REGISTER_PATH', dirname(__FILE__, 2) . '/');
|
||||
}
|
||||
require_once PLUGIN_REGISTER_PATH . 'models/register.php';
|
||||
require_once dirname(__FILE__, 4) . '/app/classes/user.php';
|
||||
require_once dirname(__FILE__, 4) . '/app/classes/validator.php';
|
||||
require_once dirname(__FILE__, 4) . '/app/helpers/security.php';
|
||||
|
||||
// registration is allowed, go on
|
||||
if ($config['registration_enabled'] == true) {
|
||||
|
||||
try {
|
||||
global $dbWeb, $logObject, $userObject;
|
||||
|
||||
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
|
||||
|
||||
// Apply rate limiting
|
||||
require_once dirname(__FILE__, 4) . '/app/includes/rate_limit_middleware.php';
|
||||
checkRateLimit($dbWeb, 'register');
|
||||
|
||||
$security = SecurityHelper::getInstance();
|
||||
|
||||
// Sanitize input
|
||||
$formData = $security->sanitizeArray($_POST, ['username', 'password', 'confirm_password', 'csrf_token', 'terms']);
|
||||
|
||||
// Validate CSRF token
|
||||
if (!$security->verifyCsrfToken($formData['csrf_token'] ?? '')) {
|
||||
throw new Exception(Feedback::get('ERROR', 'CSRF_INVALID')['message']);
|
||||
}
|
||||
|
||||
$validator = new Validator($formData);
|
||||
$rules = [
|
||||
'username' => [
|
||||
'required' => true,
|
||||
'min' => 3,
|
||||
'max' => 20
|
||||
],
|
||||
'password' => [
|
||||
'required' => true,
|
||||
'min' => 8,
|
||||
'max' => 100
|
||||
],
|
||||
'confirm_password' => [
|
||||
'required' => true,
|
||||
'matches' => 'password'
|
||||
],
|
||||
'terms' => [
|
||||
'required' => true,
|
||||
'equals' => 'on'
|
||||
]
|
||||
];
|
||||
|
||||
$username = $formData['username'] ?? 'unknown';
|
||||
|
||||
if ($validator->validate($rules)) {
|
||||
$password = $formData['password'];
|
||||
|
||||
// registering
|
||||
$register = new Register($dbWeb);
|
||||
$result = $register->register($username, $password);
|
||||
|
||||
// redirect to login
|
||||
if ($result === true) {
|
||||
// Get the new user's ID for logging
|
||||
$userId = $userObject->getUserId($username)[0]['id'];
|
||||
$logObject->insertLog($userId, "Registration: New user \"$username\" registered successfully. IP: $user_IP", 'user');
|
||||
Feedback::flash('NOTICE', 'DEFAULT', "Registration successful. You can log in now.");
|
||||
header('Location: ' . htmlspecialchars($app_root . '?page=login'));
|
||||
exit();
|
||||
// registration fail, redirect to login
|
||||
} else {
|
||||
$logObject->insertLog(null, "Registration: Failed registration attempt for user \"$username\". IP: $user_IP. Reason: $result", 'system');
|
||||
Feedback::flash('ERROR', 'DEFAULT', "Registration failed. $result");
|
||||
header('Location: ' . htmlspecialchars($app_root . '?page=register'));
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
$error = $validator->getFirstError();
|
||||
$logObject->insertLog(null, "Registration: Failed validation for user \"" . ($username ?? 'unknown') . "\". IP: $user_IP. Reason: $error", 'system');
|
||||
Feedback::flash('ERROR', 'DEFAULT', $error);
|
||||
header('Location: ' . htmlspecialchars($app_root . '?page=register'));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$logObject->insertLog(null, "Registration: System error. IP: $user_IP. Error: " . $e->getMessage(), 'system');
|
||||
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
||||
}
|
||||
|
||||
// Get any new feedback messages
|
||||
include dirname(__FILE__, 4) . '/app/helpers/feedback.php';
|
||||
|
||||
// Load the template
|
||||
include PLUGIN_REGISTER_PATH . 'views/form-register.php';
|
||||
|
||||
// registration disabled
|
||||
} else {
|
||||
echo Feedback::render('NOTICE', 'DEFAULT', 'Registration is disabled', false);
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* class Register
|
||||
*
|
||||
* Handles user registration.
|
||||
*/
|
||||
class Register {
|
||||
/**
|
||||
* @var PDO|null $db The database connection instance.
|
||||
*/
|
||||
private $db;
|
||||
private $rateLimiter;
|
||||
private $twoFactorAuth;
|
||||
|
||||
/**
|
||||
* Register constructor.
|
||||
* Initializes the database connection.
|
||||
*
|
||||
* @param object $database The database object to initialize the connection.
|
||||
*/
|
||||
public function __construct($database) {
|
||||
if ($database instanceof PDO) {
|
||||
$this->db = $database;
|
||||
} else {
|
||||
$this->db = $database->getConnection();
|
||||
}
|
||||
require_once dirname(__FILE__, 4) . '/app/classes/ratelimiter.php';
|
||||
require_once dirname(__FILE__, 4) . '/app/classes/twoFactorAuth.php';
|
||||
|
||||
$this->rateLimiter = new RateLimiter($database);
|
||||
$this->twoFactorAuth = new TwoFactorAuthentication($database);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers a new user.
|
||||
*
|
||||
* @param string $username The username of the new user.
|
||||
* @param string $password The password for the new user.
|
||||
*
|
||||
* @return bool|string True if registration is successful, error message otherwise.
|
||||
*/
|
||||
public function register($username, $password) {
|
||||
try {
|
||||
// we have two inserts, start a transaction
|
||||
$this->db->beginTransaction();
|
||||
|
||||
// hash the password, don't store it plain
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
// insert into users table
|
||||
$sql = 'INSERT
|
||||
INTO users (username, password)
|
||||
VALUES (:username, :password)';
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->bindValue(':username', $username);
|
||||
$query->bindValue(':password', $hashedPassword);
|
||||
|
||||
// execute the first query
|
||||
if (!$query->execute()) {
|
||||
// rollback on error
|
||||
$this->db->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
// insert the last user id into users_meta table
|
||||
$sql2 = 'INSERT
|
||||
INTO users_meta (user_id)
|
||||
VALUES (:user_id)';
|
||||
$query2 = $this->db->prepare($sql2);
|
||||
$query2->bindValue(':user_id', $this->db->lastInsertId());
|
||||
|
||||
// execute the second query
|
||||
if (!$query2->execute()) {
|
||||
// rollback on error
|
||||
$this->db->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
// if all is OK, commit the transaction
|
||||
$this->db->commit();
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
// rollback on any error
|
||||
$this->db->rollBack();
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Registration Plugin",
|
||||
"version": "1.0.0",
|
||||
"enabled": true,
|
||||
"description": "Provides registration functionality as a plugin."
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<!-- registration form -->
|
||||
<div class="card text-center w-50 mx-auto">
|
||||
<h2 class="card-header">Register</h2>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Enter credentials for registration:</p>
|
||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register">
|
||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
||||
<div class="form-group mb-3">
|
||||
<input type="text" class="form-control w-50 mx-auto" name="username" placeholder="Username"
|
||||
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
||||
required autofocus />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<input type="password" class="form-control w-50 mx-auto" name="password" placeholder="Password"
|
||||
pattern=".{8,}" title="Eight or more characters"
|
||||
required />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<input type="password" class="form-control w-50 mx-auto" name="confirm_password" placeholder="Confirm password"
|
||||
pattern=".{8,}" title="Eight or more characters"
|
||||
required />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label" for="terms">
|
||||
<input type="checkbox" class="form-check-input" id="terms" name="terms" required>
|
||||
I agree to the <a href="<?= htmlspecialchars($app_root) ?>?page=terms" target="_blank">terms & conditions</a> and <a href="<?= htmlspecialchars($app_root) ?>?page=privacy" target="_blank">privacy policy</a>
|
||||
</label>
|
||||
</div>
|
||||
<small class="text-muted mt-2">
|
||||
We use cookies to improve your experience. See our <a href="<?= htmlspecialchars($app_root) ?>?page=cookies" target="_blank">cookies policy</a>
|
||||
</small>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary" value="Register" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /registration form -->
|
|
@ -113,7 +113,6 @@ $allowed_urls = [
|
|||
|
||||
'login',
|
||||
'logout',
|
||||
'register',
|
||||
|
||||
'about',
|
||||
];
|
||||
|
@ -156,7 +155,18 @@ if ($config_file) {
|
|||
$app_root = $config['folder'];
|
||||
|
||||
// List of pages that don't require authentication
|
||||
$public_pages = ['login', 'register', 'help', 'about'];
|
||||
$public_pages = ['login', 'help', 'about'];
|
||||
|
||||
// Let plugins filter/extend public_pages
|
||||
function filter_public_pages($pages) {
|
||||
if (!empty($GLOBALS['plugin_hooks']['filter_public_pages'])) {
|
||||
foreach ($GLOBALS['plugin_hooks']['filter_public_pages'] as $callback) {
|
||||
$pages = call_user_func($callback, $pages);
|
||||
}
|
||||
}
|
||||
return $pages;
|
||||
}
|
||||
$public_pages = filter_public_pages($public_pages);
|
||||
|
||||
// Check if the requested page requires authentication
|
||||
if (!isset($_COOKIE['username']) && !$validSession && !in_array($page, $public_pages)) {
|
||||
|
@ -280,6 +290,15 @@ if ($page == 'logout') {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Plugin loading logic for all enabled plugins ---
|
||||
$plugin_controllers = [];
|
||||
foreach ($GLOBALS['enabled_plugins'] as $plugin_name => $plugin_info) {
|
||||
$controller_path = $plugin_info['path'] . '/controllers/' . $plugin_name . '.php';
|
||||
if (file_exists($controller_path)) {
|
||||
$plugin_controllers[$plugin_name] = $controller_path;
|
||||
}
|
||||
}
|
||||
|
||||
// page building
|
||||
include '../app/templates/page-header.php';
|
||||
include '../app/templates/page-menu.php';
|
||||
|
@ -288,7 +307,11 @@ if ($page == 'logout') {
|
|||
}
|
||||
if (in_array($page, $allowed_urls)) {
|
||||
// all normal pages
|
||||
include "../app/pages/{$page}.php";
|
||||
if (isset($plugin_controllers[$page])) {
|
||||
include $plugin_controllers[$page];
|
||||
} else {
|
||||
include "../app/pages/{$page}.php";
|
||||
}
|
||||
} else {
|
||||
// the page is not in allowed urls, loading "not found" page
|
||||
include '../app/templates/error-notfound.php';
|
||||
|
|
Loading…
Reference in New Issue