Adds registration plugin

main
Yasen Pramatarov 2025-04-17 10:29:31 +03:00
parent 6443eb9b00
commit 26817c1bb6
5 changed files with 268 additions and 0 deletions

View File

@ -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>';
});

View File

@ -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($db, '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($db);
$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);
}

View File

@ -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 user_meta table
$sql2 = 'INSERT
INTO user_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();
}
}
}

View File

@ -0,0 +1,6 @@
{
"name": "Registration Plugin",
"version": "1.0.0",
"enabled": true,
"description": "Provides registration functionality as a plugin."
}

View File

@ -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 -->