added official hacktheboo2024 writeups

This commit is contained in:
eplots 2024-10-23 11:10:43 +02:00
parent 1f7a9b0566
commit e3c46450f7
327 changed files with 14303 additions and 0 deletions

View file

@ -0,0 +1,14 @@
FROM node:18
RUN apt-get update && apt-get install -y netcat-openbsd
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

View file

@ -0,0 +1,19 @@
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_PATH=/usr/src/app/data/shadowbrook.db
volumes:
- .:/usr/src/app
- ./data:/usr/src/app/data # Ensure data is persistent
depends_on:
- db
db:
image: nouchka/sqlite3
volumes:
- ./data:/data

View file

@ -0,0 +1,17 @@
{
"name": "shadowbrook-library",
"version": "1.0.0",
"description": "A spooky library of forbidden knowledge",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js"
},
"dependencies": {
"@blakeembrey/template": "1.1.0",
"body-parser": "^1.19.0",
"ejs": "^3.1.10",
"express": "^4.17.1",
"sqlite3": "^5.0.0",
"squirrelly": "^9.0.0"
}
}

View file

@ -0,0 +1,23 @@
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const manuscriptRoutes = require('./routes/manuscripts');
const indexRoutes = require('./routes/index');
const app = express();
// Middleware
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
// View engine setup
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Routes
app.use('/', indexRoutes);
app.use('/manuscripts', manuscriptRoutes);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Shadowbrook Library is running on port ${PORT}`);
});

View file

@ -0,0 +1,81 @@
const sqlite3 = require('sqlite3').verbose();
const fs = require('fs');
const path = require('path');
// Define the database path
const dbDirectory = path.join(__dirname, '../data');
const dbPath = process.env.DB_PATH || path.join(dbDirectory, 'shadowbrook.db');
// Ensure the database directory exists
if (!fs.existsSync(dbDirectory)) {
console.log(`Directory '${dbDirectory}' does not exist. Creating...`);
fs.mkdirSync(dbDirectory, { recursive: true });
}
// Connect to SQLite database
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('Error connecting to SQLite database:', err.message);
} else {
console.log('Connected to SQLite database.');
// Create manuscripts table if it doesn't exist
db.run(`CREATE TABLE IF NOT EXISTS manuscripts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
author TEXT NOT NULL,
content TEXT NOT NULL
)`, (err) => {
if (err) {
console.error('Error creating manuscripts table:', err.message);
} else {
console.log('Manuscripts table ready.');
// Initialize database with creepy Halloween-themed manuscripts
const manuscripts = [
{
title: "The Whispering Shadows",
author: "Unknown",
content: "In the dead of night, the shadows in the old library seem to move, whispering secrets of those long forgotten. Some say they hear faint voices calling their name..."
},
{
title: "The Cursed Pumpkin",
author: "Edgar Fallow",
content: "Every year, the pumpkin with a face twisted in agony reappears on the doorstep of the abandoned house. Its origins remain unknown, but those who dare carve it disappear without a trace."
},
{
title: "The Haunting of Willow Lane",
author: "Mary Blackthorn",
content: "A house once full of life now stands as a decrepit shell. At midnight, the laughter of children echoes through the halls, though none have lived there for decades..."
},
{
title: "The Witch's Grimoire",
author: "Seraphina Nightshade",
content: "Bound in human skin, the Witch's Grimoire contains forbidden spells. It is said those who read its pages under a blood moon shall be granted immense power — but at a terrible cost."
},
{
title: "The Mask of Eternal Night",
author: "Mortimer Graves",
content: "A porcelain mask with hollow eyes sits locked away in a museum. Legends tell that wearing it opens a portal to a realm of endless darkness, where souls are consumed for eternity."
}
];
const insertManuscript = db.prepare(`INSERT INTO manuscripts (title, author, content) VALUES (?, ?, ?)`);
manuscripts.forEach((manuscript) => {
insertManuscript.run(manuscript.title, manuscript.author, manuscript.content, (err) => {
if (err) {
console.error('Error inserting manuscript:', err.message);
} else {
console.log(`Manuscript "${manuscript.title}" added.`);
}
});
});
insertManuscript.finalize();
}
});
}
});
module.exports = db;

View file

@ -0,0 +1,360 @@
/* General Styles */
body {
background-color: #0d0d0d; /* Darker background for deeper contrast */
color: #e6e6e6;
font-family: 'Merriweather', serif;
margin: 0;
padding: 0;
text-align: center;
overflow-x: hidden;
}
a {
color: #ff6347;
text-decoration: none;
transition: color 0.3s ease;
}
a:hover {
color: #ffa07a;
}
/* Cool Form Styles */
.add-manuscript {
margin-top: 50px;
padding: 40px;
background-color: #1c1c1c;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8), 0 0 15px rgba(255, 99, 71, 0.5);
position: relative;
max-width: 600px;
margin: 50px auto;
background-image: linear-gradient(135deg, rgba(255, 69, 0, 0.4), rgba(255, 99, 71, 0.2));
}
.add-manuscript h2 {
font-size: 2.5rem;
color: #ff6347;
margin-bottom: 20px;
text-transform: uppercase;
letter-spacing: 2px;
background: linear-gradient(45deg, #ff6347, #ff4500);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 10px rgba(255, 69, 0, 0.5);
}
/* Form Label Styling */
.add-manuscript form label {
font-size: 1.2rem;
text-transform: uppercase;
color: #ff6347;
display: block;
text-align: left;
margin-bottom: 8px;
letter-spacing: 1px;
}
.add-manuscript form input[type="text"],
.add-manuscript form textarea {
width: 100%;
padding: 12px 15px;
background-color: #1a1a1a;
border: 1px solid #444;
border-radius: 5px;
color: #e6e6e6;
font-size: 1rem;
outline: none;
margin-bottom: 20px;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.add-manuscript form input[type="text"]:focus,
.add-manuscript form textarea:focus {
border-color: #ff4500;
box-shadow: 0 0 15px rgba(255, 69, 0, 0.6);
background-color: #222;
}
/* Ghostly Glow Effects */
.add-manuscript form input[type="text"]:focus::after,
.add-manuscript form textarea:focus::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 5px;
box-shadow: 0 0 15px rgba(255, 69, 0, 0.7);
animation: pulseGlow 1.5s infinite ease-in-out;
}
/* Animated Glowing Borders */
@keyframes pulseGlow {
0% {
box-shadow: 0 0 5px rgba(255, 69, 0, 0.6);
}
50% {
box-shadow: 0 0 20px rgba(255, 69, 0, 0.8);
}
100% {
box-shadow: 0 0 5px rgba(255, 69, 0, 0.6);
}
}
/* Container */
.container {
width: 90%;
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
position: relative;
z-index: 1;
}
/* Floating Ghostly Effects */
.container::before {
content: '';
position: absolute;
top: -50px;
right: -50px;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(255,255,255,0.1), transparent);
border-radius: 50%;
animation: float 6s ease-in-out infinite;
z-index: -1;
}
@keyframes float {
0% {
transform: translateY(0) rotate(0deg);
}
50% {
transform: translateY(-30px) rotate(180deg);
}
100% {
transform: translateY(0) rotate(360deg);
}
}
/* Header */
header {
background-color: #222;
padding: 30px;
text-align: center;
border-bottom: 2px solid #444;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.6);
position: relative;
}
header .logo h1 {
color: #ff4500;
font-size: 4rem;
letter-spacing: 4px;
text-transform: uppercase;
background: linear-gradient(45deg, #ff6347, #ff4500);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
nav ul {
list-style-type: none;
padding: 0;
margin: 30px 0;
}
nav ul li {
display: inline-block;
margin: 0 15px;
font-size: 1.4rem;
}
nav ul li a {
color: #e6e6e6;
position: relative;
}
nav ul li a::before {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: -2px;
left: 0;
background-color: #ff4500;
visibility: hidden;
transition: all 0.3s ease-in-out;
}
nav ul li a:hover::before {
visibility: visible;
width: 100%;
}
/* Footer */
footer {
margin-top: 40px;
padding: 20px;
background-color: #111;
color: #777;
font-size: 0.9rem;
box-shadow: 0 -4px 15px rgba(0, 0, 0, 0.6);
position: relative;
}
footer::before {
content: '';
position: absolute;
top: 0;
left: 50%;
width: 100%;
height: 1px;
background: linear-gradient(to right, transparent, #777, transparent);
transform: translateX(-50%);
}
/* Home Section */
.home-section {
margin-top: 50px;
text-align: center;
}
.home-section h2 {
font-size: 3rem;
color: #ff6347;
letter-spacing: 2px;
text-shadow: 0 0 10px rgba(255, 99, 71, 0.5), 0 0 20px rgba(255, 69, 0, 0.3);
}
.home-section p {
font-size: 1.4rem;
color: #b3b3b3;
max-width: 800px;
margin: 20px auto;
line-height: 1.8;
}
.button {
padding: 12px 25px;
background-color: #ff4500;
color: #fff;
border: none;
border-radius: 5px;
font-size: 1.2rem;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
z-index: 1;
}
.button::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(135deg, rgba(255, 69, 0, 0.8), transparent);
z-index: -1;
opacity: 0;
transition: opacity 0.3s ease;
}
.button:hover::before {
opacity: 1;
}
.button:hover {
box-shadow: 0 0 20px rgba(255, 69, 0, 0.7);
}
/* Spooky Animations */
.spooky-button {
background-color: #111;
color: #ff6347;
border: 1px solid #ff6347;
position: relative;
overflow: hidden;
z-index: 1;
transition: color 0.3s ease;
}
.spooky-button:hover {
color: #fff;
box-shadow: 0 0 20px rgba(255, 69, 0, 0.5);
}
.spooky-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 300%;
height: 300%;
background: radial-gradient(circle, rgba(255, 69, 0, 0.1), transparent);
transform: translate(-50%, -50%) scale(0);
transition: transform 0.4s ease;
z-index: -1;
}
.spooky-button:hover::before {
transform: translate(-50%, -50%) scale(1);
}
/* Manuscript View */
.manuscript-view h2 {
font-size: 2.5rem;
color: #ff6347;
text-shadow: 0 0 10px rgba(255, 99, 71, 0.5);
margin-bottom: 20px;
}
.manuscript-view article {
margin: 20px 0;
padding: 30px;
background-color: #1a1a1a;
border: 1px solid #444;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.6);
position: relative;
}
.manuscript-view article p {
font-size: 1.2rem;
color: #e6e6e6;
line-height: 1.6;
}
/* Glowing Borders on Focus */
input[type="text"], textarea {
padding: 10px;
font-size: 1rem;
border: 1px solid #444;
border-radius: 5px;
background-color: #1a1a1a;
color: #e6e6e6;
outline: none;
transition: border 0.3s ease, box-shadow 0.3s ease;
}
input[type="text"]:focus, textarea:focus {
border-color: #ff4500;
box-shadow: 0 0 10px rgba(255, 69, 0, 0.5);
}
/* Ghostly Shadows on Scroll */
@media (min-width: 768px) {
.container::after {
content: '';
position: absolute;
bottom: -100px;
left: 50%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1), transparent);
border-radius: 50%;
animation: float 7s ease-in-out infinite;
z-index: -1;
transform: translateX(-50%);
}
}

View file

@ -0,0 +1,54 @@
const express = require('express');
const router = express.Router();
const { template } = require("@blakeembrey/template"); // Use require instead of import
// 1. GET: Home page
router.get('/', (req, res) => {
const { user } = req.query; // Optional query parameter to pass user's name
let additionalContent = '';
if (user) {
try {
const decodedUser = decodeURIComponent(user);
console.log('User:', decodedUser);
const fn = template("Welcome back, {{user}}!", decodedUser);
const userRender = fn({ user: decodedUser });
additionalContent = `
<p><strong>${userRender}</strong> Your knowledge quest continues. Browse the hidden manuscripts or leave your own wisdom behind.</p>
<a class="button spooky-button" href="/manuscripts/add">Contribute a Manuscript</a>
`;
} catch (error) {
console.error('Error decoding or rendering user:', error);
additionalContent = ''; // Render nothing if error occurs
}
} else {
additionalContent = `
<form action="/" method="GET" class="username-form">
<label for="username">Enter your name to personalize your journey:</label>
<input type="text" id="username" name="user" placeholder="Your Name" required>
<button type="submit" class="button spooky-button">Enter the Vault</button>
</form>
`;
}
const content = `
<section class="home-section">
<h2>Welcome to the Forbidden Vault of Knowledge</h2>
<p>Hello, ${user || 'Adventurer'}! Dare to enter? Search the library, or contribute your own arcane manuscript.</p>
<a class="button spooky-button" href="/manuscripts/search">Search the Vault</a>
${additionalContent}
</section>
`;
// Safely render the layout and pass the content to be injected
try {
res.render('layouts/main', { title: "Welcome to Shadowbrook Library", content });
} catch (renderError) {
console.error('Error rendering the page:', renderError);
res.status(200).send(''); // Return empty response in case of error
}
});
module.exports = router;

View file

@ -0,0 +1,122 @@
const express = require('express');
const router = express.Router();
const db = require('../config/db');
// 1. GET: Search Manuscripts
router.get('/search', (req, res) => {
const { query = '', user } = req.query; // Default query to an empty string if not provided
const searchQuery = `%${query}%`;
db.all("SELECT * FROM manuscripts WHERE title LIKE ? OR content LIKE ?", [searchQuery, searchQuery], (err, rows) => {
if (err) {
res.status(500).send("Error querying the database.");
return;
}
// Generate dynamic HTML for search input and results
let content = `
<section class="search-results">
<h2>Search Manuscripts</h2>
<form action="/manuscripts/search" method="get" class="search-form">
<input type="text" name="query" value="${query}" placeholder="Search for manuscripts..." required />
<button type="submit">Search</button>
</form>
<ul>
`;
if (rows.length > 0) {
rows.forEach(manuscript => {
content += `
<li>
<a href="/manuscripts/view/${manuscript.id}">${manuscript.title}</a>
</li>
`;
});
} else {
content += `
<li>No results found for "${query}".</li>
`;
}
content += `
</ul>
</section>
`;
// Render the layout and inject the dynamic content as a string
res.render('layouts/main', { title: `Search Results for "${query}"`, content, user });
});
});
// 2. GET: View a single manuscript by ID
router.get('/view/:id', (req, res) => {
const { id } = req.params;
const { user } = req.query;
db.get("SELECT * FROM manuscripts WHERE id = ?", [id], (err, manuscript) => {
if (err || !manuscript) {
res.status(404).send("Manuscript not found.");
return;
}
// Generate dynamic content for the manuscript view
const content = `
<section class="manuscript-view">
<h2>${manuscript.title}</h2>
<p><strong>Author:</strong> ${manuscript.author}</p>
<article>
<p>${manuscript.content}</p>
</article>
</section>
`;
// Render the layout and inject the dynamic content
res.render('layouts/main', { title: manuscript.title, content, user });
});
});
// 3. GET: Add Manuscript form
router.get('/add', (req, res) => {
const { user } = req.query;
// Generate content for the add manuscript form
const content = `
<section class="add-manuscript">
<h2>Add a New Arcane Manuscript</h2>
<form action="/manuscripts/add" method="POST">
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
<label for="author">Author:</label>
<input type="text" id="author" name="author" required>
<label for="content">Content:</label>
<textarea id="content" name="content" rows="6" required></textarea>
<button type="submit" class="button spooky-button">Submit Manuscript</button>
</form>
</section>
`;
// Render the layout and inject the form
res.render('layouts/main', { title: "Add a New Manuscript", content, user });
});
// 4. POST: Add a New Manuscript
router.post('/add', (req, res) => {
const { title, author, content } = req.body;
const { user } = req.query;
db.run("INSERT INTO manuscripts (title, author, content) VALUES (?, ?, ?)", [title, author, content], function(err) {
if (err) {
res.status(500).send("Error inserting into the database.");
return;
}
// After adding, redirect to the newly added manuscript's page
res.redirect(`/manuscripts/view/${this.lastID}?user=${user}`);
});
});
module.exports = router;

View file

@ -0,0 +1,7 @@
<%- include('layouts/main', { title: "404 - Page Not Found" }) %>
<section class="error-page">
<h2>404 - You have ventured too far...</h2>
<p>It seems you've stumbled upon forbidden knowledge not meant to be found, <%= user %>. Return to safety.</p>
<a class="button spooky-button" href="/">Go Back Home</a>
</section>

View file

@ -0,0 +1,7 @@
<%- include('layouts/main', { title: "Welcome to Shadowbrook Library" }) %>
<section class="home-section">
<h2>Welcome to the Forbidden Vault of Knowledge</h2>
<p>Hello, <%= user %>! Dare to enter? Search the library, or contribute your own arcane manuscript.</p>
<a class="button spooky-button" href="/manuscripts/search">Search the Vault</a>
</section>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title || "Shadowbrook Library" %></title>
<link rel="stylesheet" href="/dark-theme.css">
</head>
<body>
<header>
<%- include('../partials/header') %> <!-- Includes the header partial -->
</header>
<main>
<%- content %> <!-- This will be passed explicitly from the routes -->
</main>
<footer>
<%- include('../partials/footer') %> <!-- Includes the footer partial -->
</footer>
</body>
</html>

View file

@ -0,0 +1,17 @@
<%- include('../layouts/main', { title: "Add a New Manuscript" }) %>
<section class="add-manuscript">
<h2>Add a New Arcane Manuscript</h2>
<form action="/manuscripts/add" method="POST">
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
<label for="author">Author:</label>
<input type="text" id="author" name="author" required>
<label for="content">Content:</label>
<textarea id="content" name="content" rows="6" required></textarea>
<button type="submit" class="button spooky-button">Submit Manuscript</button>
</form>
</section>

View file

@ -0,0 +1,13 @@
<%- include('../layouts/main', { title: manuscript.title }) %>
<section class="manuscript-view">
<h2><%= manuscript.title %></h2>
<p><strong>Author:</strong> <%= manuscript.author %></p>
<article>
<p><%= manuscript.content %></p>
</article>
<footer>
<p>Submitted by: <%= user || "Anonymous" %></p>
</footer>
</section>

View file

@ -0,0 +1,4 @@
<footer>
<p>&copy; <%= new Date().getFullYear() %> Shadowbrook Library - Beware the forbidden knowledge...</p>
</footer>

View file

@ -0,0 +1,15 @@
<header>
<div class="logo">
<a href="/">
<h1>Shadowbrook Library</h1>
</a>
</div>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/manuscripts/search">Search Manuscripts</a></li>
<li><a href="/manuscripts/add">Add Manuscript</a></li>
</ul>
</nav>
</header>