Compare commits

...

4 Commits

7 changed files with 76 additions and 52 deletions

View File

@ -90,33 +90,38 @@ class User {
* @return bool True if login is successful, false otherwise.
*/
public function login($username, $password) {
// get client IP address
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
try {
// Get user's IP address
require_once __DIR__ . '/../helpers/logs.php';
$ipAddress = getUserIP();
// Record attempt
$this->rateLimiter->attempt($username, $ipAddress);
// Record attempt
$this->rateLimiter->attempt($username, $ipAddress);
// Check rate limiting first
if (!$this->rateLimiter->isAllowed($username, $ipAddress)) {
$remainingTime = $this->rateLimiter->getDecayMinutes();
throw new Exception("Too many login attempts. Please try again in {$remainingTime} minutes.");
// Check rate limiting first
if (!$this->rateLimiter->isAllowed($username, $ipAddress)) {
$remainingTime = $this->rateLimiter->getDecayMinutes();
throw new Exception("Too many login attempts. Please try again in {$remainingTime} minutes.");
}
// Then check credentials
$query = $this->db->prepare("SELECT * FROM users WHERE username = :username");
$query->bindParam(':username', $username);
$query->execute();
$user = $query->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
return true;
}
// Get remaining attempts AFTER this failed attempt
$remainingAttempts = $this->rateLimiter->getRemainingAttempts($username, $ipAddress);
throw new Exception("Invalid credentials. {$remainingAttempts} attempts remaining.");
} catch (Exception $e) {
return $e->getMessage();
}
// Then check credentials
$query = $this->db->prepare("SELECT * FROM users WHERE username = :username");
$query->bindParam(':username', $username);
$query->execute();
$user = $query->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
return true;
}
// Get remaining attempts AFTER this failed attempt
$remainingAttempts = $this->rateLimiter->getRemainingAttempts($username, $ipAddress);
throw new Exception("Invalid credentials. {$remainingAttempts} attempts remaining.");
}

View File

@ -1,9 +1,9 @@
<?php
require_once __DIR__ . '/../helpers/security.php';
require_once __DIR__ . '/../helpers/logs.php';
function applyCsrfMiddleware() {
global $dbWeb, $logObject;
$security = SecurityHelper::getInstance();
// Skip CSRF check for GET requests
@ -11,9 +11,10 @@ function applyCsrfMiddleware() {
return true;
}
// Skip CSRF check for initial login attempt
// Skip CSRF check for initial login and registration attempts
if ($_SERVER['REQUEST_METHOD'] === 'POST' &&
isset($_GET['page']) && $_GET['page'] === 'login' &&
isset($_GET['page']) &&
in_array($_GET['page'], ['login', 'register']) &&
!isset($_SESSION['username'])) {
return true;
}
@ -23,13 +24,14 @@ function applyCsrfMiddleware() {
$token = $_POST['csrf_token'] ?? '';
if (!$security->verifyCsrfToken($token)) {
// Log CSRF attempt
$ipAddress = getUserIP();
$logMessage = sprintf(
"CSRF attempt detected - IP: %s, Page: %s, User: %s",
$_SERVER['REMOTE_ADDR'],
$ipAddress,
$_GET['page'] ?? 'unknown',
$_SESSION['username'] ?? 'anonymous'
);
$logObject->insertLog(0, $logMessage);
$logObject->insertLog(0, $logMessage, 'system');
// Return error message
http_response_code(403);

View File

@ -1,6 +1,7 @@
<?php
require_once __DIR__ . '/../classes/ratelimiter.php';
require_once __DIR__ . '/../helpers/logs.php';
/**
* Rate limit middleware for page requests
@ -14,7 +15,7 @@ function checkRateLimit($database, $endpoint, $userId = null) {
global $app_root;
$isTest = defined('PHPUNIT_RUNNING');
$rateLimiter = new RateLimiter($database);
$ipAddress = $_SERVER['REMOTE_ADDR'];
$ipAddress = getUserIP();
// Check if request is allowed
if (!$rateLimiter->isPageRequestAllowed($ipAddress, $endpoint, $userId)) {

View File

@ -12,9 +12,7 @@
if ($config['registration_enabled'] == true) {
try {
// connect to database
$dbWeb = connectDB($config)['db'];
global $dbWeb, $logObject, $userObject;
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
@ -42,8 +40,9 @@ if ($config['registration_enabled'] == true) {
]
];
$username = $_POST['username'] ?? 'unknown';
if ($validator->validate($rules)) {
$username = $_POST['username'];
$password = $_POST['password'];
// registering
@ -51,22 +50,29 @@ if ($config['registration_enabled'] == true) {
// redirect to login
if ($result === true) {
// Get the new user's ID for logging
$user_id = $userObject->getUserId($username)[0]['id'];
$logObject->insertLog($user_id, "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));
exit();
// registration fail, redirect to login
} else {
$logObject->insertLog(0, "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));
exit();
}
} else {
Feedback::flash('ERROR', 'DEFAULT', $validator->getFirstError());
$error = $validator->getFirstError();
$logObject->insertLog(0, "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(0, "Registration: System error. IP: $user_IP. Error: " . $e->getMessage(), 'system');
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
}

View File

@ -6,12 +6,12 @@
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=login">
<?php include 'csrf_token.php'; ?>
<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 _"
<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".
<input type="password" class="form-control w-50 mx-auto" name="password" placeholder="Password"
pattern=".{2,}" title="Eight or more characters"
required />
</div>

View File

@ -4,12 +4,22 @@
<div class="card-body">
<p class="card-text">Enter credentials for registration:</p>
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register">
<input type="text" name="username" placeholder="Username" required autofocus />
<br />
<input type="password" name="password" placeholder="Password" required />
<br />
<input type="password" name="confirm_password" placeholder="Confirm password" required />
<br />&nbsp;<br />
<?php include 'csrf_token.php'; ?>
<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>
<input type="submit" class="btn btn-primary" value="Register" />
</form>
</div>

View File

@ -24,14 +24,6 @@ require '../app/includes/sanitize.php';
session_name('jilo');
session_start();
// Initialize security middleware
require_once '../app/includes/csrf_middleware.php';
require_once '../app/helpers/security.php';
$security = SecurityHelper::getInstance();
// Verify CSRF token for POST requests
applyCsrfMiddleware();
// Initialize feedback message system
require_once '../app/classes/feedback.php';
$system_messages = [];
@ -137,6 +129,14 @@ include '../app/helpers/logs.php';
$logObject = new Log($dbWeb);
$user_IP = getUserIP();
// Initialize security middleware
require_once '../app/includes/csrf_middleware.php';
require_once '../app/helpers/security.php';
$security = SecurityHelper::getInstance();
// Verify CSRF token for POST requests
applyCsrfMiddleware();
// init rate limiter
require '../app/classes/ratelimiter.php';