Add 30day persistent token remember me for login
This commit is contained in:
@@ -6,9 +6,9 @@ $usersFile = USERS_DIR . USERS_FILE;
|
|||||||
|
|
||||||
// Determine if we are in setup mode:
|
// Determine if we are in setup mode:
|
||||||
// - Query parameter setup=1 is passed
|
// - Query parameter setup=1 is passed
|
||||||
// - And users.txt is either missing or empty
|
// - And users.txt is either missing or empty (zero bytes or trimmed content is empty)
|
||||||
$isSetup = (isset($_GET['setup']) && $_GET['setup'] === '1');
|
$isSetup = (isset($_GET['setup']) && $_GET['setup'] === '1');
|
||||||
if ($isSetup && (!file_exists($usersFile) || trim(file_get_contents($usersFile)) === '')) {
|
if ($isSetup && (!file_exists($usersFile) || filesize($usersFile) == 0 || trim(file_get_contents($usersFile)) === '')) {
|
||||||
// Allow initial admin creation without session checks.
|
// Allow initial admin creation without session checks.
|
||||||
$setupMode = true;
|
$setupMode = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
7
auth.js
7
auth.js
@@ -78,9 +78,14 @@ function initAuth() {
|
|||||||
if (authForm) {
|
if (authForm) {
|
||||||
authForm.addEventListener("submit", function (event) {
|
authForm.addEventListener("submit", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
// Get the "Remember me" checkbox value.
|
||||||
|
const rememberMe = document.getElementById("rememberMeCheckbox")
|
||||||
|
? document.getElementById("rememberMeCheckbox").checked
|
||||||
|
: false;
|
||||||
const formData = {
|
const formData = {
|
||||||
username: document.getElementById("loginUsername").value.trim(),
|
username: document.getElementById("loginUsername").value.trim(),
|
||||||
password: document.getElementById("loginPassword").value.trim()
|
password: document.getElementById("loginPassword").value.trim(),
|
||||||
|
remember_me: rememberMe
|
||||||
};
|
};
|
||||||
sendRequest("auth.php", "POST", formData, { "X-CSRF-Token": window.csrfToken })
|
sendRequest("auth.php", "POST", formData, { "X-CSRF-Token": window.csrfToken })
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
|||||||
31
auth.php
31
auth.php
@@ -10,6 +10,9 @@ $lockoutTime = 30 * 60; // 30 minutes in seconds
|
|||||||
$attemptsFile = USERS_DIR . 'failed_logins.json'; // JSON file for tracking failed login attempts
|
$attemptsFile = USERS_DIR . 'failed_logins.json'; // JSON file for tracking failed login attempts
|
||||||
$failedLogFile = USERS_DIR . 'failed_login.log'; // Plain text log for fail2ban
|
$failedLogFile = USERS_DIR . 'failed_login.log'; // Plain text log for fail2ban
|
||||||
|
|
||||||
|
// Persistent tokens file for "Remember me"
|
||||||
|
$persistentTokensFile = USERS_DIR . 'persistent_tokens.json';
|
||||||
|
|
||||||
// Load failed attempts data from file.
|
// Load failed attempts data from file.
|
||||||
function loadFailedAttempts($file) {
|
function loadFailedAttempts($file) {
|
||||||
if (file_exists($file)) {
|
if (file_exists($file)) {
|
||||||
@@ -46,11 +49,9 @@ if (isset($failedAttempts[$ip])) {
|
|||||||
function authenticate($username, $password)
|
function authenticate($username, $password)
|
||||||
{
|
{
|
||||||
global $usersFile;
|
global $usersFile;
|
||||||
|
|
||||||
if (!file_exists($usersFile)) {
|
if (!file_exists($usersFile)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
$lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
foreach ($lines as $line) {
|
foreach ($lines as $line) {
|
||||||
list($storedUser, $storedPass, $storedRole) = explode(':', trim($line), 3);
|
list($storedUser, $storedPass, $storedRole) = explode(':', trim($line), 3);
|
||||||
@@ -65,6 +66,7 @@ function authenticate($username, $password)
|
|||||||
$data = json_decode(file_get_contents("php://input"), true);
|
$data = json_decode(file_get_contents("php://input"), true);
|
||||||
$username = trim($data["username"] ?? "");
|
$username = trim($data["username"] ?? "");
|
||||||
$password = trim($data["password"] ?? "");
|
$password = trim($data["password"] ?? "");
|
||||||
|
$rememberMe = isset($data["remember_me"]) && $data["remember_me"] === true;
|
||||||
|
|
||||||
// Validate input: ensure both fields are provided.
|
// Validate input: ensure both fields are provided.
|
||||||
if (!$username || !$password) {
|
if (!$username || !$password) {
|
||||||
@@ -72,7 +74,7 @@ if (!$username || !$password) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate username format: allow only letters, numbers, underscores, dashes, and spaces.
|
// Validate username format.
|
||||||
if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $username)) {
|
if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $username)) {
|
||||||
echo json_encode(["error" => "Invalid username format. Only letters, numbers, underscores, dashes, and spaces are allowed."]);
|
echo json_encode(["error" => "Invalid username format. Only letters, numbers, underscores, dashes, and spaces are allowed."]);
|
||||||
exit;
|
exit;
|
||||||
@@ -92,6 +94,29 @@ if ($userRole !== false) {
|
|||||||
$_SESSION["username"] = $username;
|
$_SESSION["username"] = $username;
|
||||||
$_SESSION["isAdmin"] = ($userRole === "1"); // "1" indicates admin
|
$_SESSION["isAdmin"] = ($userRole === "1"); // "1" indicates admin
|
||||||
|
|
||||||
|
// If "Remember me" is checked, generate a persistent login token.
|
||||||
|
if ($rememberMe) {
|
||||||
|
// Generate a secure random token.
|
||||||
|
$token = bin2hex(random_bytes(32));
|
||||||
|
$expiry = time() + (30 * 24 * 60 * 60); // 30 days
|
||||||
|
// Load existing persistent tokens.
|
||||||
|
$persistentTokens = [];
|
||||||
|
if (file_exists($persistentTokensFile)) {
|
||||||
|
$persistentTokens = json_decode(file_get_contents($persistentTokensFile), true);
|
||||||
|
if (!is_array($persistentTokens)) {
|
||||||
|
$persistentTokens = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save token along with username and expiry.
|
||||||
|
$persistentTokens[$token] = [
|
||||||
|
"username" => $username,
|
||||||
|
"expiry" => $expiry
|
||||||
|
];
|
||||||
|
file_put_contents($persistentTokensFile, json_encode($persistentTokens, JSON_PRETTY_PRINT));
|
||||||
|
// Set the cookie. (Assuming $secure is defined in config.php.)
|
||||||
|
setcookie('remember_me_token', $token, $expiry, '/', '', $secure, true);
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode(["success" => "Login successful", "isAdmin" => $_SESSION["isAdmin"]]);
|
echo json_encode(["success" => "Login successful", "isAdmin" => $_SESSION["isAdmin"]]);
|
||||||
} else {
|
} else {
|
||||||
// On failed login, update failed attempts.
|
// On failed login, update failed attempts.
|
||||||
|
|||||||
23
config.php
23
config.php
@@ -27,6 +27,29 @@ if (empty($_SESSION['csrf_token'])) {
|
|||||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-login via persistent token if session is not active.
|
||||||
|
if (!isset($_SESSION["authenticated"]) && isset($_COOKIE['remember_me_token'])) {
|
||||||
|
$persistentTokensFile = USERS_DIR . 'persistent_tokens.json';
|
||||||
|
if (file_exists($persistentTokensFile)) {
|
||||||
|
$persistentTokens = json_decode(file_get_contents($persistentTokensFile), true);
|
||||||
|
if (is_array($persistentTokens) && isset($persistentTokens[$_COOKIE['remember_me_token']])) {
|
||||||
|
$tokenData = $persistentTokens[$_COOKIE['remember_me_token']];
|
||||||
|
if ($tokenData['expiry'] >= time()) {
|
||||||
|
// Token is valid; auto-authenticate the user.
|
||||||
|
$_SESSION["authenticated"] = true;
|
||||||
|
$_SESSION["username"] = $tokenData["username"];
|
||||||
|
// Optionally, set admin status if stored in token data:
|
||||||
|
// $_SESSION["isAdmin"] = $tokenData["isAdmin"];
|
||||||
|
} else {
|
||||||
|
// Token expired; remove it and clear the cookie.
|
||||||
|
unset($persistentTokens[$_COOKIE['remember_me_token']]);
|
||||||
|
file_put_contents($persistentTokensFile, json_encode($persistentTokens, JSON_PRETTY_PRINT));
|
||||||
|
setcookie('remember_me_token', '', time() - 3600, '/', '', $secure, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Define BASE_URL (this should point to where index.html is, e.g. your uploads directory)
|
// Define BASE_URL (this should point to where index.html is, e.g. your uploads directory)
|
||||||
define('BASE_URL', 'http://yourwebsite/uploads/');
|
define('BASE_URL', 'http://yourwebsite/uploads/');
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,10 @@
|
|||||||
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-login">Login</button>
|
<button type="submit" class="btn btn-primary btn-block btn-login">Login</button>
|
||||||
|
<div class="form-group remember-me-container">
|
||||||
|
<input type="checkbox" id="rememberMeCheckbox" name="remember_me" />
|
||||||
|
<label for="rememberMeCheckbox">Remember me</label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ body {
|
|||||||
body {
|
body {
|
||||||
letter-spacing: 0.2px;
|
letter-spacing: 0.2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-dash {
|
.custom-dash {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
transform: scaleX(1.5);
|
transform: scaleX(1.5);
|
||||||
@@ -53,7 +54,8 @@ body {
|
|||||||
/************************************************************/
|
/************************************************************/
|
||||||
/* FLEXBOX HEADER: LOGO, TITLE, BUTTONS FIXED */
|
/* FLEXBOX HEADER: LOGO, TITLE, BUTTONS FIXED */
|
||||||
/************************************************************/
|
/************************************************************/
|
||||||
#uploadCard, #folderManagementCard {
|
#uploadCard,
|
||||||
|
#folderManagementCard {
|
||||||
min-height: 342px;
|
min-height: 342px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +335,10 @@ body.dark-mode .material-icons.gallery-icon {
|
|||||||
/* ===========================================================
|
/* ===========================================================
|
||||||
FORMS & LOGIN
|
FORMS & LOGIN
|
||||||
=========================================================== */
|
=========================================================== */
|
||||||
|
.remember-me-container {
|
||||||
|
margin-top: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
#loginForm {
|
#loginForm {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
|
|||||||
Reference in New Issue
Block a user