New Admin Panel, OIDC Integration & Card dragDrop adjustments

This commit is contained in:
Ryan
2025-03-29 04:33:10 -04:00
committed by GitHub
parent 89777584cf
commit 051544dc5a
11 changed files with 847 additions and 388 deletions

119
auth.php
View File

@@ -1,19 +1,83 @@
<?php
require_once 'vendor/autoload.php';
require 'config.php';
header('Content-Type: application/json');
// --- OIDC Authentication Flow ---
if (isset($_GET['oidc'])) {
// Read and decrypt OIDC configuration from JSON file.
$adminConfigFile = USERS_DIR . 'adminConfig.json';
if (file_exists($adminConfigFile)) {
$encryptedContent = file_get_contents($adminConfigFile);
$decryptedContent = decryptData($encryptedContent, $encryptionKey);
if ($decryptedContent === false) {
echo json_encode(['error' => 'Failed to decrypt admin configuration.']);
exit;
}
$adminConfig = json_decode($decryptedContent, true);
if (isset($adminConfig['oidc'])) {
$oidcConfig = $adminConfig['oidc'];
$oidc_provider_url = !empty($oidcConfig['providerUrl']) ? $oidcConfig['providerUrl'] : 'https://your-oidc-provider.com';
$oidc_client_id = !empty($oidcConfig['clientId']) ? $oidcConfig['clientId'] : 'YOUR_CLIENT_ID';
$oidc_client_secret = !empty($oidcConfig['clientSecret']) ? $oidcConfig['clientSecret'] : 'YOUR_CLIENT_SECRET';
$oidc_redirect_uri = !empty($oidcConfig['redirectUri']) ? $oidcConfig['redirectUri'] : 'https://yourdomain.com/auth.php?oidc=callback';
} else {
$oidc_provider_url = 'https://your-oidc-provider.com';
$oidc_client_id = 'YOUR_CLIENT_ID';
$oidc_client_secret = 'YOUR_CLIENT_SECRET';
$oidc_redirect_uri = 'https://yourdomain.com/auth.php?oidc=callback';
}
} else {
$oidc_provider_url = 'https://your-oidc-provider.com';
$oidc_client_id = 'YOUR_CLIENT_ID';
$oidc_client_secret = 'YOUR_CLIENT_SECRET';
$oidc_redirect_uri = 'https://yourdomain.com/auth.php?oidc=callback';
}
$oidc = new Jumbojett\OpenIDConnectClient(
$oidc_provider_url,
$oidc_client_id,
$oidc_client_secret
);
$oidc->setRedirectURL($oidc_redirect_uri);
// Since PKCE is disabled in Keycloak, we do not set any PKCE parameters.
if ($_GET['oidc'] === 'callback') {
try {
$oidc->authenticate();
$username = $oidc->requestUserInfo('preferred_username');
session_regenerate_id(true);
$_SESSION["authenticated"] = true;
$_SESSION["username"] = $username;
$_SESSION["isAdmin"] = false;
header("Location: index.html");
exit();
} catch (Exception $e) {
echo json_encode(["error" => "Authentication failed: " . $e->getMessage()]);
exit();
}
} else {
try {
$oidc->authenticate();
exit();
} catch (Exception $e) {
echo json_encode(["error" => "Authentication initiation failed: " . $e->getMessage()]);
exit();
}
}
}
// --- Fallback: Form-based Authentication ---
$usersFile = USERS_DIR . USERS_FILE;
// --- Brute Force Protection Settings ---
$maxAttempts = 5;
$lockoutTime = 30 * 60; // 30 minutes in seconds
$attemptsFile = USERS_DIR . 'failed_logins.json'; // JSON file for tracking failed login attempts
$failedLogFile = USERS_DIR . 'failed_login.log'; // Plain text log for fail2ban
// Persistent tokens file for "Remember me"
$lockoutTime = 30 * 60;
$attemptsFile = USERS_DIR . 'failed_logins.json';
$failedLogFile = USERS_DIR . 'failed_login.log';
$persistentTokensFile = USERS_DIR . 'persistent_tokens.json';
// Load failed attempts data from file.
function loadFailedAttempts($file) {
if (file_exists($file)) {
$data = json_decode(file_get_contents($file), true);
@@ -24,30 +88,23 @@ function loadFailedAttempts($file) {
return [];
}
// Save failed attempts data to file.
function saveFailedAttempts($file, $data) {
file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT));
}
// Get current IP address.
$ip = $_SERVER['REMOTE_ADDR'];
$currentTime = time();
// Load failed attempts.
$failedAttempts = loadFailedAttempts($attemptsFile);
// Check if this IP is currently locked out.
if (isset($failedAttempts[$ip])) {
$attemptData = $failedAttempts[$ip];
if ($attemptData['count'] >= $maxAttempts && ($currentTime - $attemptData['last_attempt']) < $lockoutTime) {
echo json_encode(["error" => "Too many failed login attempts. Please try again later."]);
exit;
exit();
}
}
// --- Authentication Function ---
function authenticate($username, $password)
{
function authenticate($username, $password) {
global $usersFile;
if (!file_exists($usersFile)) {
return false;
@@ -56,51 +113,41 @@ function authenticate($username, $password)
foreach ($lines as $line) {
list($storedUser, $storedPass, $storedRole) = explode(':', trim($line), 3);
if ($username === $storedUser && password_verify($password, $storedPass)) {
return $storedRole; // Return the user's role
return $storedRole;
}
}
return false;
}
// Get JSON input.
$data = json_decode(file_get_contents("php://input"), true);
$username = trim($data["username"] ?? "");
$password = trim($data["password"] ?? "");
$rememberMe = isset($data["remember_me"]) && $data["remember_me"] === true;
// Validate input: ensure both fields are provided.
if (!$username || !$password) {
echo json_encode(["error" => "Username and password are required"]);
exit;
exit();
}
// Validate username format.
if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $username)) {
echo json_encode(["error" => "Invalid username format. Only letters, numbers, underscores, dashes, and spaces are allowed."]);
exit;
exit();
}
// Attempt to authenticate the user.
$userRole = authenticate($username, $password);
if ($userRole !== false) {
// On successful login, reset failed attempts for this IP.
if (isset($failedAttempts[$ip])) {
unset($failedAttempts[$ip]);
saveFailedAttempts($attemptsFile, $failedAttempts);
}
// Regenerate session ID to mitigate session fixation attacks.
session_regenerate_id(true);
$_SESSION["authenticated"] = true;
$_SESSION["username"] = $username;
$_SESSION["isAdmin"] = ($userRole === "1"); // "1" indicates admin
// If "Remember me" is checked, generate a persistent login token.
$_SESSION["isAdmin"] = ($userRole === "1");
if ($rememberMe) {
// Generate a secure random token.
$token = bin2hex(random_bytes(32));
$expiry = time() + (30 * 24 * 60 * 60); // 30 days
// Load existing persistent tokens.
$expiry = time() + (30 * 24 * 60 * 60);
$persistentTokens = [];
if (file_exists($persistentTokensFile)) {
$encryptedContent = file_get_contents($persistentTokensFile);
@@ -110,7 +157,6 @@ if ($userRole !== false) {
$persistentTokens = [];
}
}
// Save token along with username, expiry, and admin status.
$persistentTokens[$token] = [
"username" => $username,
"expiry" => $expiry,
@@ -118,13 +164,11 @@ if ($userRole !== false) {
];
$encryptedContent = encryptData(json_encode($persistentTokens, JSON_PRETTY_PRINT), $encryptionKey);
file_put_contents($persistentTokensFile, $encryptedContent, LOCK_EX);
// 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"]]);
} else {
// On failed login, update failed attempts.
if (isset($failedAttempts[$ip])) {
$failedAttempts[$ip]['count']++;
$failedAttempts[$ip]['last_attempt'] = $currentTime;
@@ -132,11 +176,8 @@ if ($userRole !== false) {
$failedAttempts[$ip] = ['count' => 1, 'last_attempt' => $currentTime];
}
saveFailedAttempts($attemptsFile, $failedAttempts);
// Log the failed attempt to the plain text log for fail2ban.
$logLine = date('Y-m-d H:i:s') . " - Failed login attempt for username: " . $username . " from IP: " . $ip . PHP_EOL;
file_put_contents($failedLogFile, $logLine, FILE_APPEND);
echo json_encode(["error" => "Invalid credentials"]);
}
?>