Initial Commit

This commit is contained in:
Alexander Harding 2025-03-07 12:35:48 -05:00
commit ac3d214b26
13 changed files with 2922 additions and 0 deletions

11
http-proxy.code-workspace Normal file
View File

@ -0,0 +1,11 @@
{
"folders": [
{
"path": "proxy"
},
{
"path": "ui"
}
],
"settings": {}
}

136
proxy/.gitignore vendored Normal file
View File

@ -0,0 +1,136 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

9
proxy/eslint.config.js Normal file
View File

@ -0,0 +1,9 @@
import globals from "globals";
import pluginJs from "@eslint/js";
/** @type {import('eslint').Linter.Config[]} */
export default [
{languageOptions: { globals: globals.node }},
pluginJs.configs.recommended,
];

View File

@ -0,0 +1,4 @@
{
"url": "backwards.dev",
"server": "localhost:3000"
}

2529
proxy/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
proxy/package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "proxy",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"dev": "nodemon .",
"start": "NODE_ENV=\"PRODUCTION\" nodemon ."
},
"keywords": [],
"author": "Backwards",
"license": "MIT",
"type": "module",
"description": "",
"dependencies": {
"chalk": "^5.4.1",
"dotenv": "^16.4.7",
"ejs": "^3.1.10",
"express": "^4.21.2",
"mongodb": "^6.14.2",
"nodemon": "^3.1.9"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"cross-env": "^7.0.3",
"eslint": "^9.21.0",
"globals": "^16.0.0"
}
}

13
proxy/src/common.js Normal file
View File

@ -0,0 +1,13 @@
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
export const __filename = fileURLToPath(import.meta.url);
export const __dirname = dirname(__filename);
export function getDateTime() {
return new Intl.DateTimeFormat('en-GB', {
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
}).format(new Date()).replace(',', '');
}

View File

@ -0,0 +1,6 @@
{
"codes": [
"500",
"503"
]
}

View File

@ -0,0 +1,19 @@
{
"200": {
"200": "Success!"
},
"400": {
"400": "It seems we weren't prepared for that request.\nPlease try that another way...",
"401": "It seems you aren't authenticated.\nTry authenticating and trying this request again.",
"403": "It seems you don't have permission to access this.",
"404": "It seems what you're looking for doesn't exist.\nCheck the URL or resource and try again.",
"405": "The request method isn't allowed for this resource.\nCheck the API documentation for supported methods.",
"408": "The request took too long to complete.\nPlease try again later."
},
"500": {
"500": "Something went wrong on our end.\nTry again later or contact support if the issue persists.",
"502": "Bad gateway.\nThe server received an invalid response from an upstream server.",
"503": "The server is temporarily unavailable.\nPlease try again later.",
"504": "Gateway timeout.\nThe server took too long to respond."
}
}

View File

@ -0,0 +1,48 @@
import { consoleFunctions } from "./console.js";
import { readFileSync } from "node:fs";
import { MongoClient } from "mongodb";
import dotenv from "dotenv";
import { join } from "node:path";
import { __dirname } from "../common.js";
dotenv.config();
export const appFunctions = {
dbClient: new MongoClient(process.env.NODE_ENV == "PRODUCTION" ? process.env.MONGO_CONNECTION_STRING : process.env.MONGO_CONNECTION_STRING_DEV),
async appInit(prod, port) {
console.clear();
if (!this.dbClient || !this.dbClient.db)
await this.dbClient.connect();
const collection = this.dbClient.db("HTTP-Proxy").collection("Routes");
const documents = await collection.find().toArray();
const routes = [];
documents.forEach((document, index) => {
routes.push(`${index + 1}. From: ${document.url}, to ${document.server}.`);
});
await this.dbClient.close();
console.log(consoleFunctions.init(prod, port, routes));
},
findErrorRoute: (routes) => {
console.log(routes);
},
messageFromCode: (code) => {
const data = JSON.parse(readFileSync(join(__dirname, "data", "messages.json")));
const codeClass = code.toString().split("").shift().padEnd("3", 0);
return data[codeClass][code].replace("\n", "<br>");
},
sendError: async (code, res) => {
console.log(code)
if (code.toString().startsWith("/")) code = code.slice(0, 1);
console.log(code)
if (!code.toString().startsWith(5)) return res.redirect(`https://err.backwardshosting.ca/${code}`);
const message = appFunctions.messageFromCode(code);
res.render("index", { code, message });
}
}

View File

@ -0,0 +1,32 @@
import chalk from "chalk"
import { getDateTime } from "../common.js"
const primary = chalk.cyan,
bgPrimary = chalk.whiteBright.bgCyan,
secondary = chalk.blue;
export const consoleFunctions = {
ruler: () => "-".repeat(process.stdout.columns),
init: function (prod, port, routes) {
let output = [];
if (!prod) {
output.push(bgPrimary("DEVELOPMENT"));
} else {
output.push(bgPrimary("PRODUCTION"));
}
output.push(primary(`Proxy listening on port`) + " " + secondary.underline(port));
output.push("");
output.push(primary("Web Proxies"));
output.push(routes.join("\n"));
output.push("");
output.push(primary("API Proxies"));
output.push("...");
output.push("");
output.push("Logs");
output.push(this.ruler());
return output.join("\r\n")
},
log: function (content) {
console.log(`(${getDateTime()}) ${content}`);
}
}

42
proxy/src/index.js Normal file
View File

@ -0,0 +1,42 @@
import { join } from "node:path";
import express from"express";
import { __dirname } from "./common.js";
import { appFunctions } from "./functions/app.js";
import { consoleFunctions } from "./functions/console.js";
const app = express();
const prod = process.env.NODE_ENV === "PRODUCTION";
const port = (prod) ? 80 : 2025; // Port 80 (HTTP) or 2025 in development.
app.listen(port, () => appFunctions.appInit(prod, port)); // Redirect console messages to dedicated consoleFunctions module.
app.set('view engine', 'ejs');
app.set('views', join(__dirname, "view"));
app.use(async function (req, res) {
consoleFunctions.log("RECEIVED HTTP REQUEST");
consoleFunctions.log("GETTING ROUTES FROM DATABASE");
await appFunctions.dbClient.connect();
const collection = appFunctions.dbClient.db("HTTP-Proxy").collection("Routes");
const routes = await collection.find().toArray();
await appFunctions.dbClient.close();
consoleFunctions.log(`REQUESTED: ${req.headers.host}`);
const errorRoutes = routes.filter(route => route.url === req.headers.host && route.server === "ERROR");
if (errorRoutes.length > 0) {
appFunctions.sendError(req.url, res);
}
if (!routes) {
appFunctions.sendError(500, res);
}
appFunctions.findErrorRoute(routes);
appFunctions.sendError(501, res);
});

45
proxy/src/view/index.ejs Normal file
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
text-align: center;
height: 100vh;
background-image: linear-gradient(45deg, #1f1f24, #200d30);
}
.content > * {
margin: 0;
padding: 0;
}
* {
font-family: "Poppins", sans-serif;
color: white;
}
h1 {
font-size: 6em;
font-style: italic;
}
h3 {
font-size: 2em;
}
</style>
</head>
<body>
<div class="content">
<h1><%= code %></h1>
<h3><%- message %></h3>
</div>
<div class="spacer"></div>
</body>
</html>