Compare commits
4 Commits
035681ab28
...
0f6dda44b8
Author | SHA1 | Date |
---|---|---|
|
0f6dda44b8 | |
|
b4b5a7ac8f | |
|
a45e064c18 | |
|
ecb4e0fab4 |
|
@ -90,33 +90,38 @@ class User {
|
||||||
* @return bool True if login is successful, false otherwise.
|
* @return bool True if login is successful, false otherwise.
|
||||||
*/
|
*/
|
||||||
public function login($username, $password) {
|
public function login($username, $password) {
|
||||||
// get client IP address
|
try {
|
||||||
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
// Get user's IP address
|
||||||
|
require_once __DIR__ . '/../helpers/logs.php';
|
||||||
|
$ipAddress = getUserIP();
|
||||||
|
|
||||||
// Record attempt
|
// Record attempt
|
||||||
$this->rateLimiter->attempt($username, $ipAddress);
|
$this->rateLimiter->attempt($username, $ipAddress);
|
||||||
|
|
||||||
// Check rate limiting first
|
// Check rate limiting first
|
||||||
if (!$this->rateLimiter->isAllowed($username, $ipAddress)) {
|
if (!$this->rateLimiter->isAllowed($username, $ipAddress)) {
|
||||||
$remainingTime = $this->rateLimiter->getDecayMinutes();
|
$remainingTime = $this->rateLimiter->getDecayMinutes();
|
||||||
throw new Exception("Too many login attempts. Please try again in {$remainingTime} minutes.");
|
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.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once __DIR__ . '/../helpers/security.php';
|
require_once __DIR__ . '/../helpers/security.php';
|
||||||
|
require_once __DIR__ . '/../helpers/logs.php';
|
||||||
|
|
||||||
function applyCsrfMiddleware() {
|
function applyCsrfMiddleware() {
|
||||||
global $dbWeb, $logObject;
|
|
||||||
$security = SecurityHelper::getInstance();
|
$security = SecurityHelper::getInstance();
|
||||||
|
|
||||||
// Skip CSRF check for GET requests
|
// Skip CSRF check for GET requests
|
||||||
|
@ -11,9 +11,10 @@ function applyCsrfMiddleware() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip CSRF check for initial login attempt
|
// Skip CSRF check for initial login and registration attempts
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' &&
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' &&
|
||||||
isset($_GET['page']) && $_GET['page'] === 'login' &&
|
isset($_GET['page']) &&
|
||||||
|
in_array($_GET['page'], ['login', 'register']) &&
|
||||||
!isset($_SESSION['username'])) {
|
!isset($_SESSION['username'])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -23,13 +24,14 @@ function applyCsrfMiddleware() {
|
||||||
$token = $_POST['csrf_token'] ?? '';
|
$token = $_POST['csrf_token'] ?? '';
|
||||||
if (!$security->verifyCsrfToken($token)) {
|
if (!$security->verifyCsrfToken($token)) {
|
||||||
// Log CSRF attempt
|
// Log CSRF attempt
|
||||||
|
$ipAddress = getUserIP();
|
||||||
$logMessage = sprintf(
|
$logMessage = sprintf(
|
||||||
"CSRF attempt detected - IP: %s, Page: %s, User: %s",
|
"CSRF attempt detected - IP: %s, Page: %s, User: %s",
|
||||||
$_SERVER['REMOTE_ADDR'],
|
$ipAddress,
|
||||||
$_GET['page'] ?? 'unknown',
|
$_GET['page'] ?? 'unknown',
|
||||||
$_SESSION['username'] ?? 'anonymous'
|
$_SESSION['username'] ?? 'anonymous'
|
||||||
);
|
);
|
||||||
$logObject->insertLog(0, $logMessage);
|
$logObject->insertLog(0, $logMessage, 'system');
|
||||||
|
|
||||||
// Return error message
|
// Return error message
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once __DIR__ . '/../classes/ratelimiter.php';
|
require_once __DIR__ . '/../classes/ratelimiter.php';
|
||||||
|
require_once __DIR__ . '/../helpers/logs.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rate limit middleware for page requests
|
* Rate limit middleware for page requests
|
||||||
|
@ -14,7 +15,7 @@ function checkRateLimit($database, $endpoint, $userId = null) {
|
||||||
global $app_root;
|
global $app_root;
|
||||||
$isTest = defined('PHPUNIT_RUNNING');
|
$isTest = defined('PHPUNIT_RUNNING');
|
||||||
$rateLimiter = new RateLimiter($database);
|
$rateLimiter = new RateLimiter($database);
|
||||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
$ipAddress = getUserIP();
|
||||||
|
|
||||||
// Check if request is allowed
|
// Check if request is allowed
|
||||||
if (!$rateLimiter->isPageRequestAllowed($ipAddress, $endpoint, $userId)) {
|
if (!$rateLimiter->isPageRequestAllowed($ipAddress, $endpoint, $userId)) {
|
||||||
|
|
|
@ -12,9 +12,7 @@
|
||||||
if ($config['registration_enabled'] == true) {
|
if ($config['registration_enabled'] == true) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
global $dbWeb, $logObject, $userObject;
|
||||||
// connect to database
|
|
||||||
$dbWeb = connectDB($config)['db'];
|
|
||||||
|
|
||||||
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
|
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
|
||||||
|
|
||||||
|
@ -42,8 +40,9 @@ if ($config['registration_enabled'] == true) {
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$username = $_POST['username'] ?? 'unknown';
|
||||||
|
|
||||||
if ($validator->validate($rules)) {
|
if ($validator->validate($rules)) {
|
||||||
$username = $_POST['username'];
|
|
||||||
$password = $_POST['password'];
|
$password = $_POST['password'];
|
||||||
|
|
||||||
// registering
|
// registering
|
||||||
|
@ -51,22 +50,29 @@ if ($config['registration_enabled'] == true) {
|
||||||
|
|
||||||
// redirect to login
|
// redirect to login
|
||||||
if ($result === true) {
|
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.");
|
Feedback::flash('NOTICE', 'DEFAULT', "Registration successful. You can log in now.");
|
||||||
header('Location: ' . htmlspecialchars($app_root));
|
header('Location: ' . htmlspecialchars($app_root));
|
||||||
exit();
|
exit();
|
||||||
// registration fail, redirect to login
|
// registration fail, redirect to login
|
||||||
} else {
|
} else {
|
||||||
|
$logObject->insertLog(0, "Registration: Failed registration attempt for user \"$username\". IP: $user_IP. Reason: $result", 'system');
|
||||||
Feedback::flash('ERROR', 'DEFAULT', "Registration failed. $result");
|
Feedback::flash('ERROR', 'DEFAULT', "Registration failed. $result");
|
||||||
header('Location: ' . htmlspecialchars($app_root));
|
header('Location: ' . htmlspecialchars($app_root));
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
} else {
|
} 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'));
|
header('Location: ' . htmlspecialchars($app_root . '?page=register'));
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
$logObject->insertLog(0, "Registration: System error. IP: $user_IP. Error: " . $e->getMessage(), 'system');
|
||||||
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=login">
|
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=login">
|
||||||
<?php include 'csrf_token.php'; ?>
|
<?php include 'csrf_token.php'; ?>
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<input type="text" class="form-control w-50 mx-auto" name="username" placeholder="Username".
|
<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 _"
|
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
||||||
required autofocus />
|
required autofocus />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-3">
|
<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"
|
pattern=".{2,}" title="Eight or more characters"
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,12 +4,22 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">Enter credentials for registration:</p>
|
<p class="card-text">Enter credentials for registration:</p>
|
||||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register">
|
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register">
|
||||||
<input type="text" name="username" placeholder="Username" required autofocus />
|
<?php include 'csrf_token.php'; ?>
|
||||||
<br />
|
<div class="form-group mb-3">
|
||||||
<input type="password" name="password" placeholder="Password" required />
|
<input type="text" class="form-control w-50 mx-auto" name="username" placeholder="Username"
|
||||||
<br />
|
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
||||||
<input type="password" name="confirm_password" placeholder="Confirm password" required />
|
required autofocus />
|
||||||
<br /> <br />
|
</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" />
|
<input type="submit" class="btn btn-primary" value="Register" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,14 +24,6 @@ require '../app/includes/sanitize.php';
|
||||||
session_name('jilo');
|
session_name('jilo');
|
||||||
session_start();
|
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
|
// Initialize feedback message system
|
||||||
require_once '../app/classes/feedback.php';
|
require_once '../app/classes/feedback.php';
|
||||||
$system_messages = [];
|
$system_messages = [];
|
||||||
|
@ -137,6 +129,14 @@ include '../app/helpers/logs.php';
|
||||||
$logObject = new Log($dbWeb);
|
$logObject = new Log($dbWeb);
|
||||||
$user_IP = getUserIP();
|
$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
|
// init rate limiter
|
||||||
require '../app/classes/ratelimiter.php';
|
require '../app/classes/ratelimiter.php';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue