314 lines
10 KiB
JavaScript
314 lines
10 KiB
JavaScript
const express = require('express');
|
|
const helmet = require('helmet');
|
|
const rateLimit = require('express-rate-limit');
|
|
const cors = require('cors');
|
|
const xssClean = require('xss-clean');
|
|
const mongoSanitize = require('express-mongo-sanitize');
|
|
const cookieParser = require('cookie-parser');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const app = express();
|
|
const port = 8080; // Port 80 for HTTP
|
|
|
|
// Set security HTTP headers using Helmet
|
|
app.use(helmet());
|
|
|
|
// Rate limiting to prevent DoS attacks
|
|
const limiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 100, // limit each IP to 100 requests per windowMs
|
|
message: 'Too many requests from this IP, please try again later',
|
|
});
|
|
app.use(limiter);
|
|
|
|
// Enable CORS for external API calls
|
|
app.use(cors());
|
|
|
|
// Sanitize data to prevent NoSQL injection and XSS attacks
|
|
app.use(mongoSanitize());
|
|
app.use(xssClean());
|
|
|
|
// Body parser to read data into req.body
|
|
app.use(express.json({ limit: '10kb' }));
|
|
|
|
// Cookie-parser middleware
|
|
app.use(cookieParser());
|
|
|
|
// Set EJS as the view engine
|
|
app.set('view engine', 'ejs');
|
|
app.set('views', path.join(__dirname, 'views')); // Set the views directory
|
|
|
|
// Function to detect w3m requests
|
|
function isW3M(req) {
|
|
const userAgent = req.headers['user-agent'];
|
|
return userAgent && userAgent.toLowerCase().includes('w3m');
|
|
}
|
|
|
|
// Function to detect Lynx
|
|
function isLynx(req) {
|
|
const userAgent = req.headers['user-agent'];
|
|
return userAgent && userAgent.toLowerCase().includes('lynx');
|
|
}
|
|
|
|
// Serve static files from the 'public' directory
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
|
|
// Function to detect curl requests
|
|
function isCurl(req) {
|
|
const userAgent = req.headers['user-agent'];
|
|
return userAgent && userAgent.includes('curl');
|
|
}
|
|
|
|
// Serve the install.sh script dynamically based on query parameter repo_id
|
|
app.get('/install.sh', (req, res) => {
|
|
const repoId = req.query.repo_id;
|
|
|
|
if (!repoId) {
|
|
return res.status(400).send('Error: No repo_id provided.');
|
|
}
|
|
|
|
// Path to the install.sh script
|
|
const scriptPath = path.join(__dirname, 'public/scripts/install.sh');
|
|
|
|
// Make sure the file exists
|
|
if (!fs.existsSync(scriptPath)) {
|
|
return res.status(404).send('Error: install.sh not found.');
|
|
}
|
|
|
|
// Read the install.sh script and send it as a response with the correct content type
|
|
res.setHeader('Content-Type', 'application/x-sh');
|
|
fs.createReadStream(scriptPath).pipe(res);
|
|
});
|
|
|
|
// In-house IP address retrieval API
|
|
app.get('/ip', (req, res) => {
|
|
//const userIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
|
const userIp = (req.headers['x-forwarded-for'] || req.connection.remoteAddress).split(',')[0].trim();
|
|
|
|
// If request is made using curl, send a plain text response
|
|
if (isCurl(req)) {
|
|
res.setHeader('Content-Type', 'text/plain');
|
|
res.send(`Public IP: ${userIp}\n`);
|
|
} else {
|
|
// request is made on browser, send simple HTML response
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="stylesheet" href="/style.css">
|
|
<title>Public IP</title>
|
|
<style>
|
|
body {
|
|
font-family: 'Courier New', Courier, monospace;
|
|
background-color: #000;
|
|
color: #fff;
|
|
margin: 0;
|
|
padding: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.ip-container {
|
|
background-color: rgba(255, 255, 255, 0.05);
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
width: fit-content;
|
|
box-shadow: 0 6px 12px rgba(255, 255, 255, 0.2);
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
cursor: pointer;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.ip-container:hover {
|
|
transform: scale(1.05);
|
|
box-shadow: 0 8px 16px rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.ip-container p {
|
|
font-size: 24px;
|
|
color: #ff4081;
|
|
}
|
|
|
|
a {
|
|
text-decoration: none;
|
|
color: inherit;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Clickable container that redirects to the homepage -->
|
|
<a href="/">
|
|
<div class="ip-container">
|
|
<p>Public IP: ${userIp}</p>
|
|
</div>
|
|
</a>
|
|
</body>
|
|
</html>
|
|
`);
|
|
}
|
|
});
|
|
|
|
// Function to get the gradient title from logo.txt
|
|
function getGradientTitle() {
|
|
const logoPath = path.join(__dirname, 'public', 'logo.txt'); // Ensure this points to where your file is located
|
|
|
|
try {
|
|
// Read the logo file containing pre-colored ANSI text
|
|
const logo = fs.readFileSync(logoPath, 'utf8');
|
|
return logo;
|
|
} catch (error) {
|
|
console.error('Error reading logo.txt:', error);
|
|
return ''; // Return empty string if the file cannot be read
|
|
}
|
|
}
|
|
|
|
function getDescription() {
|
|
//return `\nx1b[90mKleinPanic\nLinux Projects and Utilities.\nhttps://kleinpanic.com\n\nGitHub: https://github.com/kleinpanic\x1b[0m`;
|
|
return `
|
|
\x1b[90mKleinPanic\x1b[0m
|
|
\x1b[90mLinux Projs and Utils.\x1b[0m
|
|
\x1b[34mhttps://kleinpanic.com\x1b[0m
|
|
|
|
\x1b[90mGitHub:\x1b[0m \x1b[34mhttps://github.com/kleinpanic\x1b[0m
|
|
`;
|
|
}
|
|
|
|
// Function to color each table row
|
|
function getColoredTableRow(name, description, installCommand) {
|
|
const nameColor = '\x1b[38;5;36m'; // Green for project name
|
|
const descriptionColor = '\x1b[38;5;33m'; // Blue for description
|
|
const installCommandColor = '\x1b[38;5;135m'; // Cyan for install command
|
|
const resetColor = '\x1b[0m'; // Reset color
|
|
|
|
return `| ${nameColor}${name}${resetColor} | ${descriptionColor}${description}${resetColor} | ${installCommandColor}${installCommand}${resetColor} |`;
|
|
}
|
|
|
|
const axios = require('axios');
|
|
|
|
// Serve the curl-specific route
|
|
app.get('/', async (req, res) => {
|
|
if (isCurl(req)) {
|
|
try {
|
|
//const response = await axios.get('https://api.github.com/users/kleinpanic/repos');
|
|
const response = await axios.get('https://api.github.com/users/kleinpanic/repos', {
|
|
params: {
|
|
per_page: 100, // Request up to 100 repositories
|
|
}
|
|
});
|
|
const repos = response.data;
|
|
|
|
// Function to truncate text with "..." if needed
|
|
function truncateText(text, maxLength) {
|
|
return text.length > maxLength ? text.slice(0, maxLength - 3) + '...' : text;
|
|
}
|
|
|
|
// Function to pad text for alignment
|
|
function padText(text, length) {
|
|
return text.padEnd(length, ' ');
|
|
}
|
|
|
|
// Combine title and description lines
|
|
const titleLines = getGradientTitle().split('\n');
|
|
const descriptionLines = getDescription().split('\n');
|
|
|
|
// Combine title and description side by side line by line
|
|
let combinedTitleDescription = '';
|
|
for (let i = 0; i < titleLines.length || i < descriptionLines.length; i++) {
|
|
const titleLine = titleLines[i] || ''; // Handle empty title lines
|
|
const descriptionLine = descriptionLines[i] || ''; // Handle empty description lines
|
|
combinedTitleDescription += `${titleLine} ${descriptionLine}\n`; // Combine them
|
|
}
|
|
|
|
// Create the table headers with grey borders
|
|
let tableHeader = `\x1b[90m+ ------------------------- + -------------------------------------- + ---------------------- +
|
|
| PROJECT NAME | DESCRIPTION | INSTALL COMMAND |
|
|
+ ------------------------- + -------------------------------------- + ---------------------- +\x1b[0m`;
|
|
|
|
// Format the project list into the table rows with color
|
|
const tableRows = repos.map((repo, index) => {
|
|
const name = padText(truncateText(repo.name || 'Unknown', 25), 25);
|
|
const description = padText(truncateText(repo.description || 'No description', 38), 38);
|
|
|
|
// Add extra whitespace for the first 9 install commands
|
|
const installCommand = index < 9
|
|
? padText(`/install.sh?repo_id=${index + 1} `, 20) // Adding a space at the end for alignment
|
|
: padText(`/install.sh?repo_id=${index + 1}`, 20);
|
|
|
|
return getColoredTableRow(name, description, installCommand);
|
|
}).join('\n');
|
|
|
|
// Create the table footer with last updated
|
|
const tableFooter = `\x1b[90m+ ------------------------- + -------------------------------------- + ---------------------- +
|
|
Last updated: ${new Date().toUTCString()}\n\x1b[0m`; // Newline added at the end
|
|
|
|
// Combine everything together
|
|
const output = `
|
|
${combinedTitleDescription}
|
|
${tableHeader}
|
|
${tableRows}
|
|
${tableFooter}`;
|
|
|
|
res.setHeader('Content-Type', 'text/plain');
|
|
res.send(output);
|
|
} catch (error) {
|
|
res.status(500).send('Error fetching projects from GitHub.\n');
|
|
}
|
|
} else if (isW3M(req)) {
|
|
res.sendFile(path.join(__dirname, 'views', 'w3m-index.html')); // Serve w3m-specific index
|
|
} else if (isLynx(req)) {
|
|
res.sendFile(path.join(__dirname, 'views', 'lynx-index.html')); // Serve Lynx-specific index
|
|
} else {
|
|
// Serve index.html for browser requests
|
|
res.sendFile(path.join(__dirname, 'views', 'index.html'));
|
|
}
|
|
});
|
|
|
|
|
|
// Serve projects page with different views for w3m
|
|
app.get('/projects', (req, res) => {
|
|
if (isW3M(req)) {
|
|
res.sendFile(path.join(__dirname, 'views', 'w3m-projects.html')); // Serve w3m-friendly HTML
|
|
} else {
|
|
res.sendFile(path.join(__dirname, 'views', 'projects.html')); // Serve normal HTML for other browsers
|
|
}
|
|
});
|
|
|
|
|
|
// Routes
|
|
const indexRoute = require('./routes/index');
|
|
const aboutRoute = require('./routes/about');
|
|
const projectsRoute = require('./routes/projects');
|
|
const blogRoute = require('./routes/blog');
|
|
const contactRoute = require('./routes/contact');
|
|
const monitorRoute = require('./routes/monitor');
|
|
const artRoute = require('./routes/art');
|
|
|
|
app.use('/', indexRoute);
|
|
app.use('/about', aboutRoute);
|
|
app.use('/projects', projectsRoute);
|
|
app.use('/blog', blogRoute);
|
|
app.use('/contact', contactRoute);
|
|
app.use('/monitor', monitorRoute);
|
|
app.use('/art', artRoute);
|
|
|
|
// Error handling middleware for CSRF errors - ADD LATER AFTER FIXING PUBLIC JS ISSUES
|
|
app.use((err, req, res, next) => {
|
|
if (err.code === 'EBADCSRFTOKEN') {
|
|
res.status(403).send('Form tampered with.');
|
|
} else {
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
// Start the server on port 80
|
|
app.listen(port, () => {
|
|
console.log(`Server running on http://kleinpanic.com`);
|
|
});
|