Edit index.php

This commit is contained in:
ザカリアス・ウィリアム・ポージー 2025-05-29 00:34:32 +09:00
parent 78ff3f3c58
commit 3924c93494

View File

@ -1,21 +1,50 @@
<?php <?php
// Improved Discord Bot Admin API
// Error reporting (disable on production)
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Configure secure session parameters:
session_set_cookie_params([
'httponly' => true,
'secure' => true, // Ensure HTTPS is used in production
'samesite' => 'Strict'
]);
session_start(); session_start();
// === Login & Authentication === // --- CSRF Utility Functions ---
// Only proceed if the current session is authenticated. function getCsrfToken() {
// If not, show a login form. if (empty($_SESSION['csrf_token'])) {
if (!isset($_SESSION['logged_in'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['username'], $_POST['password'])) { }
$user_env = getenv('user'); // environment variable "user" return $_SESSION['csrf_token'];
$pass_env = getenv('pass'); // environment variable "pass" }
if ($_POST['username'] === $user_env && $_POST['password'] === $pass_env) {
$_SESSION['logged_in'] = true; function validateCsrfToken($token) {
header("Location: index.php"); return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
exit; }
// --- Login and Authentication ---
if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['username'], $_POST['password'], $_POST['csrf_token'])) {
if (!validateCsrfToken($_POST['csrf_token'])) {
$error = "Invalid CSRF token.";
} else { } else {
$error = "Invalid credentials."; $envUser = getenv('user');
$envPass = getenv('pass');
// Using hash_equals for timing attack prevention
if (hash_equals($_POST['username'], $envUser) && hash_equals($_POST['password'], $envPass)) {
$_SESSION['logged_in'] = true;
session_regenerate_id(true);
header("Location: " . $_SERVER['PHP_SELF']);
exit;
} else {
$error = "Invalid credentials.";
}
} }
} }
$loginToken = getCsrfToken();
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@ -64,8 +93,9 @@ if (!isset($_SESSION['logged_in'])) {
<body> <body>
<div class="login-container"> <div class="login-container">
<h2>Login</h2> <h2>Login</h2>
<?php if(isset($error)) echo "<p class='error'>{$error}</p>"; ?> <?php if(isset($error)) { echo "<p class='error'>" . htmlspecialchars($error) . "</p>"; } ?>
<form method="POST"> <form method="POST" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($loginToken); ?>">
<label>Username</label> <label>Username</label>
<input type="text" name="username" required> <input type="text" name="username" required>
<label>Password</label> <label>Password</label>
@ -76,65 +106,76 @@ if (!isset($_SESSION['logged_in'])) {
</body> </body>
</html> </html>
<?php <?php
exit; // Do not execute the rest until logged in. exit;
} }
// === End Authentication === // --- Action Handlers ---
$output = "";
// Determine which action to do. function handleVersionAction() {
$action = ""; $botDir = '/home/server/wdiscordbotserver';
if (isset($_GET['action'])) { if (is_dir($botDir)) {
$action = $_GET['action']; $cmd = 'cd ' . escapeshellarg($botDir) . ' && git rev-parse HEAD 2>&1';
} elseif (isset($_POST['action'])) { return trim(shell_exec($cmd));
$action = $_POST['action']; }
return "Directory not found.";
} }
$output = ""; // To hold any output from actions function handleUpdateAction() {
// Only allow updates using a POST request with a valid CSRF token.
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['csrf_token']) || !validateCsrfToken($_POST['csrf_token'])) {
return "Unauthorized update request.";
}
$botDir = '/home/server/wdiscordbotserver';
$result = "";
if (is_dir($botDir)) {
$rmCmd = 'rm -rf ' . escapeshellarg($botDir) . ' 2>&1';
$result .= shell_exec($rmCmd);
}
$cloneCmd = 'git clone https://gitlab.com/pancakes1234/wdiscordbotserver.git ' . escapeshellarg($botDir) . ' 2>&1';
$result .= shell_exec($cloneCmd);
return $result;
}
// Handle the different actions. function handleDataAction() {
switch ($action) { $baseDir = realpath('/home/server');
case "version": $file = $_GET['file'] ?? null;
// Gets the current commit from /home/server/wdiscordbotserver. $response = "";
$botDir = '/home/server/wdiscordbotserver'; if ($file) {
if (is_dir($botDir)) { $realFile = realpath($file);
$cmd = 'cd ' . escapeshellarg($botDir) . ' && git rev-parse HEAD 2>&1'; if ($realFile === false || strpos($realFile, $baseDir) !== 0) {
$output = shell_exec($cmd); $response = "Invalid file.";
} else { } else {
$output = "Directory not found."; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content'], $_POST['csrf_token'])) {
} if (!validateCsrfToken($_POST['csrf_token'])) {
break; $response = "Invalid CSRF token.";
} else {
case "update": if (file_put_contents($realFile, $_POST['content']) !== false) {
// Removes the folder and clones the repository anew. $response = "File updated successfully.";
$botDir = '/home/server/wdiscordbotserver'; } else {
if (is_dir($botDir)) { $response = "Failed to update file.";
$rmCmd = 'rm -rf ' . escapeshellarg($botDir) . ' 2>&1'; }
$output .= shell_exec($rmCmd);
}
$cloneCmd = 'git clone https://gitlab.com/pancakes1234/wdiscordbotserver.git ' . escapeshellarg($botDir) . ' 2>&1';
$output .= shell_exec($cloneCmd);
break;
case "data":
// If editing a file, process its content.
if (isset($_GET['file'])) {
$file = $_GET['file'];
$baseDir = realpath('/home/server');
$realFile = realpath($file);
if ($realFile === false || strpos($realFile, $baseDir) !== 0) {
$output = "Invalid file.";
} else {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content'])) {
file_put_contents($realFile, $_POST['content']);
$output = "File updated successfully.";
} }
} }
} }
break; }
return $response;
}
// Other actions (such as terminal) will be handled in the UI below. // --- Process Request Actions ---
$action = $_GET['action'] ?? ($_POST['action'] ?? "");
switch ($action) {
case "version":
$output = handleVersionAction();
break;
case "update":
$output = handleUpdateAction();
break;
case "data":
$output = handleDataAction();
break;
// Additional action cases (e.g., "terminal") can be handled below.
default: default:
// No action or unrecognized action.
break; break;
} }
?> ?>
@ -143,7 +184,6 @@ switch ($action) {
<head> <head>
<title>Discord Bot Admin API</title> <title>Discord Bot Admin API</title>
<style> <style>
/* General styling */
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
background: #e9e9e9; background: #e9e9e9;
@ -160,7 +200,7 @@ switch ($action) {
header { header {
margin-bottom: 20px; margin-bottom: 20px;
} }
header button { header button, header form button {
padding: 10px 20px; padding: 10px 20px;
margin-right: 10px; margin-right: 10px;
border: none; border: none;
@ -169,7 +209,7 @@ switch ($action) {
cursor: pointer; cursor: pointer;
border-radius: 3px; border-radius: 3px;
} }
header button:hover { header button:hover, header form button:hover {
background: #0056b3; background: #0056b3;
} }
.output { .output {
@ -211,7 +251,7 @@ switch ($action) {
} }
</style> </style>
<script> <script>
// For Version and Update actions we can use fetch to load the result via AJAX. // For Version and Update actions we can use AJAX.
function doAction(action) { function doAction(action) {
if (action === "data" || action === "terminal") { if (action === "data" || action === "terminal") {
window.location.href = "?action=" + action; window.location.href = "?action=" + action;
@ -220,116 +260,114 @@ switch ($action) {
.then(response => response.text()) .then(response => response.text())
.then(data => { .then(data => {
document.getElementById("output").innerText = data; document.getElementById("output").innerText = data;
})
.catch(err => {
document.getElementById("output").innerText = "Error: " + err;
}); });
} }
} }
// SSH Connect functionality using the "ssh://" protocol. // SSH Connect functionality using the "ssh://" protocol.
function doSSH() { function doSSH() {
var sshUser = "<?php echo getenv('user'); ?>"; // SSH username from the env. var sshUser = "<?php echo htmlspecialchars(getenv('user')); ?>";
var sshHost = window.location.hostname; // use current hostname. var sshHost = window.location.hostname;
window.location.href = "ssh://" + sshUser + "@" + sshHost; window.location.href = "ssh://" + sshUser + "@" + sshHost;
} }
function showTab(tab) {
document.getElementById('wetty').style.display = (tab === 'wetty') ? 'block' : 'none';
document.getElementById('xterm').style.display = (tab === 'xterm') ? 'block' : 'none';
}
window.addEventListener('load', function() {
if(document.getElementById('xterm-container')) {
const terminal = new Terminal();
terminal.open(document.getElementById('xterm-container'));
const socket = new WebSocket('ws://' + window.location.hostname + ':3001');
socket.onopen = function() {
terminal.write("Connected to shell\r\n");
};
terminal.onData(function(data) {
socket.send(data);
});
socket.onmessage = function(event) {
terminal.write(event.data);
};
socket.onerror = function() {
terminal.write("\r\nError connecting to shell.\r\n");
};
socket.onclose = function() {
terminal.write("\r\nConnection closed.\r\n");
};
}
});
</script> </script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css" />
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<header> <header>
<h1>Discord Bot Admin API</h1> <h1>Discord Bot Admin API</h1>
<button onclick="doAction('version')">Version</button> <button onclick="doAction('version')">Version</button>
<button onclick="doAction('update')">Update</button> <!-- Update action now uses a form (POST with CSRF token) to improve safety -->
<form style="display:inline;" method="POST" action="?action=update" onsubmit="return confirm('Are you sure you want to update the bot?');">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(getCsrfToken()); ?>">
<button type="submit">Update</button>
</form>
<button onclick="window.location.href='?action=data'">Data</button> <button onclick="window.location.href='?action=data'">Data</button>
<button onclick="doSSH()">SSH Connect</button> <button onclick="doSSH()">SSH Connect</button>
<button onclick="window.location.href='?action=terminal'">Terminal</button> <button onclick="window.location.href='?action=terminal'">Terminal</button>
</header> </header>
<!-- Output area for AJAX-returned actions --> <!-- AJAX Output Area -->
<div id="output" class="output"><?php echo htmlspecialchars($output); ?></div> <div id="output" class="output"><?php echo htmlspecialchars($output); ?></div>
<?php <?php if ($action === "data"):
// === Data Section === $baseDir = realpath('/home/server');
if ($action === "data") { if (!isset($_GET['file'])): ?>
if (!isset($_GET['file'])) { <h2>Files in <?php echo htmlspecialchars($baseDir); ?></h2>
$baseDir = '/home/server'; <ul class="file-list">
echo "<h2>Files in {$baseDir}</h2>"; <?php
echo "<ul class='file-list'>"; try {
$iterator = new RecursiveIteratorIterator( $iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($baseDir, RecursiveDirectoryIterator::SKIP_DOTS) new RecursiveDirectoryIterator($baseDir, RecursiveDirectoryIterator::SKIP_DOTS)
); );
foreach ($iterator as $fileInfo) { foreach ($iterator as $fileInfo) {
$filePath = $fileInfo->getPathname(); $filePath = $fileInfo->getPathname();
echo "<li><a href='?action=data&file=" . urlencode($filePath) . "'>" . htmlspecialchars($filePath) . "</a></li>"; echo "<li><a href='?action=data&file=" . urlencode($filePath) . "'>" . htmlspecialchars($filePath) . "</a></li>";
}
} catch (Exception $e) {
echo "<li>Error reading files: " . htmlspecialchars($e->getMessage()) . "</li>";
} }
echo "</ul>"; ?>
} else { </ul>
<?php else:
$file = $_GET['file']; $file = $_GET['file'];
$baseDir = realpath('/home/server');
$realFile = realpath($file); $realFile = realpath($file);
if ($realFile === false || strpos($realFile, $baseDir) !== 0) { if ($realFile === false || strpos($realFile, $baseDir) !== 0): ?>
echo "<p>Invalid file.</p>"; <p>Invalid file.</p>
} else { <?php else: ?>
echo "<h2>Editing: " . htmlspecialchars($realFile) . "</h2>"; <h2>Editing: <?php echo htmlspecialchars($realFile); ?></h2>
echo "<form method='POST' action='?action=data&file=" . urlencode($realFile) . "'>"; <form method="POST" action="?action=data&file=<?php echo urlencode($realFile); ?>">
echo "<textarea name='content'>" . htmlspecialchars(file_get_contents($realFile)) . "</textarea><br>"; <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(getCsrfToken()); ?>">
echo "<input type='hidden' name='action' value='data'>"; <textarea name="content"><?php echo htmlspecialchars(file_get_contents($realFile)); ?></textarea><br>
echo "<input type='submit' value='Save' style='padding:10px 20px; margin-top:10px;'>"; <input type="hidden" name="action" value="data">
echo "</form>"; <input type="submit" value="Save" style="padding:10px 20px; margin-top:10px;">
} </form>
} <?php endif;
} endif;
// === Terminal Section === elseif ($action === "terminal"): ?>
elseif ($action === "terminal") :
// This section provides two terminal options:
// 1. Wetty Terminal via an iframe (assumes Wetty is running on port 3000)
// 2. A direct integration of xterm.js (which connects via WebSocket to a Node.js pty server on port 3001)
?>
<div id="terminal-tabs" style="margin-bottom:10px;"> <div id="terminal-tabs" style="margin-bottom:10px;">
<button onclick="showTab('wetty')">Wetty Terminal</button> <button onclick="showTab('wetty')">Wetty Terminal</button>
<button onclick="showTab('xterm')">Xterm Terminal</button> <button onclick="showTab('xterm')">Xterm Terminal</button>
</div> </div>
<div id="wetty" class="terminal-tab" style="display:block;"> <div id="wetty" class="terminal-tab" style="display:block;">
<h2>Wetty Terminal</h2> <h2>Wetty Terminal</h2>
<iframe src="http://<?php echo $_SERVER['HTTP_HOST']; ?>:3000" width="100%" height="500px" frameborder="0"></iframe> <iframe src="http://<?php echo htmlspecialchars($_SERVER['HTTP_HOST']); ?>:3000" width="100%" height="500px" frameborder="0"></iframe>
</div> </div>
<div id="xterm" class="terminal-tab" style="display:none;"> <div id="xterm" class="terminal-tab" style="display:none;">
<h2>Xterm Terminal</h2> <h2>Xterm Terminal</h2>
<div id="xterm-container" style="width: 100%; height: 500px;"></div> <div id="xterm-container" style="width: 100%; height: 500px;"></div>
</div> </div>
<!-- Load xterm.js resources --> <?php endif; ?>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css" />
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>
<script>
function showTab(tab) {
document.getElementById('wetty').style.display = (tab === 'wetty') ? 'block' : 'none';
document.getElementById('xterm').style.display = (tab === 'xterm') ? 'block' : 'none';
}
// Initialize xterm.js for the Xterm Terminal.
// NOTE: You MUST run a WebSocket Node.js server (for example, using node-pty and ws)
// listening on port 3001 to support this connection.
window.addEventListener('load', function() {
// Initialize the terminal only once.
const terminal = new Terminal();
terminal.open(document.getElementById('xterm-container'));
const socket = new WebSocket('ws://<?php echo $_SERVER['HTTP_HOST']; ?>:3001');
socket.onopen = function() {
terminal.write("Connected to shell\r\n");
};
terminal.onData(function(data) {
socket.send(data);
});
socket.onmessage = function(event) {
terminal.write(event.data);
};
socket.onerror = function() {
terminal.write("\r\nError connecting to shell.\r\n");
};
socket.onclose = function() {
terminal.write("\r\nConnection closed.\r\n");
};
});
</script>
<?php
endif;
?>
</div> </div>
</body> </body>
</html> </html>