commit b50468eb5ab7208c786caed52edbf072fac2a912 Author: metacryst Date: Sun Jan 4 01:15:22 2026 -0600 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25c8fdb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..77eb21e --- /dev/null +++ b/index.js @@ -0,0 +1,167 @@ +import express from 'express' +import cors from 'cors' +import cookieParser from 'cookie-parser' +import http from 'http' +import fs from 'fs' +import chalk from 'chalk' +import moment from 'moment' +import path from 'path' +import * as useragent from "express-useragent"; + +import "./util.js" +import Socket from './ws/ws.js' +import Database from "./db/db.js" +import AuthHandler from './auth.js'; + +export default class Server { + db; + auth; + UIPath = path.join(global.__dirname, '../ui') + DBPath = path.join(global.__dirname, './db') + + registerRoutes(router) { + /* Auth */ + router.post('/login', this.auth.login) + router.get('/profile', this.auth.getProfile) + router.get('/signout', this.auth.logout) + + /* Site */ + router.get('/*', this.get) + return router + } + + verifyToken = (req, res, next) => { + const { token } = req.query; + if (!token) { + return res.status(400).json({ error: 'Token is required' }); + } + let fromDB = this.db.tokens.get(token) + if (!fromDB) { + return res.status(403).json({ error: 'Invalid or expired token' }); + } else if(fromDB.used) { + return res.status(403).json({ error: 'Invalid or expired token' }); + } + next() + } + + authMiddleware = (req, res, next) => { + const authHeader = req.headers.authorization; + if (!authHeader) { + return res.status(401).json({ error: 'Authorization token required.' }); + } + + const [scheme, token] = authHeader.split(' '); + if (scheme !== 'Bearer' || !token) { + return res.status(401).json({ error: 'Malformed authorization header.' }) + } + + try { + const payload = this.auth.verify(token); + req.user = payload; + return next(); + } catch (err) { + return res.status(403).json({ error: 'Invalid or expired token.' }); + } + } + + get = async (req, res) => { + + let url = req.url + + let publicSite = () => { + let filePath; + if(url.startsWith("/_")) { + filePath = path.join(this.UIPath, url); + } else if(url.includes("75820185")) { + filePath = path.join(this.UIPath, "public", url.split("75820185")[1]); + } else { + filePath = path.join(this.UIPath, "public", "index.html"); + } + + res.sendFile(filePath); + } + + let privateSite = () => { + let filePath; + let platformFolder = req.useragent.isMobile ? "mobile" : "desktop" + if(url.startsWith("/_")) { + filePath = path.join(this.UIPath, url); + } else if(url.includes("75820185")) { + filePath = path.join(this.UIPath, platformFolder, url.split("75820185")[1]); + } else { + filePath = path.join(this.UIPath, platformFolder, "index.html"); + } + + res.sendFile(filePath); + } + + if(!this.auth.isLoggedInUser(req, res)) { + publicSite() + } else { + privateSite() + } + } + + logRequest(req, res, next) { + const formattedDate = moment().format('M.D'); + const formattedTime = moment().format('h:mma'); + if(req.url.includes("/api/")) { + console.log(chalk.blue(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`)); + } else { + if(req.url === "/") + console.log(chalk.gray(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`)); + } + next(); + } + + logResponse(req, res, next) { + const originalSend = res.send; + res.send = function (body) { + if(res.statusCode >= 400) { + console.log(chalk.blue( `<-${chalk.red(res.statusCode)}- ${req.method} ${req.url} | ${chalk.red(body)}`)); + } else { + console.log(chalk.blue(`<-${res.statusCode}- ${req.method} ${req.url}`)); + } + originalSend.call(this, body); + }; + next(); + } + + constructor() { + this.db = new Database() + global.db = this.db + this.auth = new AuthHandler() + const app = express(); + app.use(cors({ origin: '*' })); + app.use(express.json()); + app.use(express.urlencoded({ extended: true })); + app.use(cookieParser()); + app.use(useragent.express()); + + app.use(this.logRequest); + app.use(this.logResponse); + + let router = express.Router(); + this.registerRoutes(router) + app.use('/', router); + + const server = http.createServer(app); + global.Socket = new Socket(server); + const PORT = 3005; + server.listen(PORT, () => { + console.log("\n") + console.log(chalk.yellow("*************** Hyperia ***************")) + console.log(chalk.yellowBright(`Server is running on port ${PORT}: http://localhost`)); + console.log(chalk.yellow("***************************************")) + console.log("\n") + }); + + process.on('SIGINT', async () => { + console.log(chalk.red('Closing server...')); + console.log(chalk.green('Database connection closed.')); + process.exit(0); + }); + + Object.preventExtensions(this); + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d7109b2 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "Parchment", + "version": "1.0.0", + "scripts": { + "start": "node server/index.js" + }, + "dependencies": { + "argon2": "^0.44.0", + "chalk": "^4.1.2", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "express": "^4.18.2", + "express-useragent": "^2.0.2", + "jsonwebtoken": "^9.0.2", + "moment": "^2.30.1", + "stripe": "^20.0.0", + "ws": "^8.18.3", + "zod": "^4.1.12" + } +} \ No newline at end of file