diff --git a/.gitignore b/.gitignore index 40b878d..cf7c602 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules/ \ No newline at end of file +node_modules/ +config.json \ No newline at end of file diff --git a/controllers/HomeController.ts b/controllers/HomeController.ts index e69de29..0233ad6 100644 --- a/controllers/HomeController.ts +++ b/controllers/HomeController.ts @@ -0,0 +1,7 @@ +import Controller from "./Controller"; + +export default class HomeController extends Controller { + public AllowAnonymous_Index() { + return this.view(); + } +} \ No newline at end of file diff --git a/index.ts b/index.ts index ce91035..f878a4b 100644 --- a/index.ts +++ b/index.ts @@ -12,7 +12,7 @@ import HomeController from "./controllers/HomeController"; import Database from "./objects/Database"; import { join } from "path"; -Console.customHeader(`EUS Hosting Panel server started at ${new Date()}`); +Console.customHeader(`EUS server started at ${new Date()}`); const fastify = Fastify({ logger: false @@ -38,7 +38,7 @@ fastify.register(FastifyCookie, { fastify.register(FastifyStatic, { root: join(__dirname, "wwwroot"), - prefix: `${Config.ports.webroot}/static/` + //prefix: `${Config.ports.web}/static/` }); fastify.setNotFoundHandler(async (_req, res) => { @@ -49,10 +49,8 @@ new Database(Config.database.address, Config.database.port, Config.database.user Controller.FastifyInstance = fastify; new HomeController(); -new AccountController(); -new FileController(); -fastify.listen({ port: Config.ports.http, host: "127.0.0.1" }, (err, address) => { +fastify.listen({ port: Config.ports.web, host: "127.0.0.1" }, (err, address) => { if (err) { Console.printError(`Error occured while spinning up fastify:\n${err}`); process.exit(1); diff --git a/objects/Config.ts b/objects/Config.ts index 89164f9..7750228 100644 --- a/objects/Config.ts +++ b/objects/Config.ts @@ -1,3 +1,26 @@ -export default class Config { - +import { readFileSync } from "fs"; +const config = JSON.parse(readFileSync("./config.json").toString()); + +export default abstract class Config { + public static ports:IPorts = config.ports; + public static database:IDatabase = config.database; + public static session:ISession = config.session; +} + +interface IPorts { + web: number +} + +interface IDatabase { + address: string, + port: number, + username: string, + password: string, + name: string +} + +interface ISession { + secret: string, + validity: number, + length: number } \ No newline at end of file diff --git a/objects/Session.ts b/objects/Session.ts index 6698064..b4a7b9c 100644 --- a/objects/Session.ts +++ b/objects/Session.ts @@ -1,67 +1,55 @@ -import { FastifyReply, FastifyRequest } from "fastify"; +import Config from "./Config"; +import FastifyCookie from "@fastify/cookie"; +import FunkyArray from "funky-array"; import SessionUser from "./SessionUser"; -import UserType from "../enums/UserType"; +import { FastifyReply, FastifyRequest } from "fastify"; +import User from "../entities/User"; +import { randomBytes } from "crypto"; -export default class RequestCtx { - public controllerName:string; - public actionName:string; - public session?:SessionUser; - public req: FastifyRequest; - public res: FastifyReply; +type Cookies = { [cookieName: string]: string | undefined } - public constructor(req: FastifyRequest, res: FastifyReply, controllerName:string, actionName:string, sessionUser?:SessionUser) { - this.session = sessionUser; - this.req = req; - this.res = res; - this.controllerName = controllerName; - this.actionName = actionName; - } - - view(view?:string | Object, model?: Object) { - let viewName: string = this.actionName; - let viewModel: Object = {}; - if (typeof(view) === "string") { - viewName = view; - } else if (typeof(view) === "object") { - viewModel = view; - } - if (typeof(model) === "object") { - viewModel = model; - } - // @ts-ignore inject session - viewModel["session"] = this.session; - // @ts-ignore inject enums - viewModel["UserLevel"] = UserLevel; - return this.res.view(`views/${this.controllerName}/${viewName}.ejs`, viewModel); - } - - // TODO: query params - redirectToAction(action:string, controller?:string) { - const controllerName = controller ?? this.controllerName; - if (action === "index") { - if (controllerName === "home") { - return this.res.redirect(`/`, 302); - } else { - return this.res.redirect(`/${controllerName}`, 302); +export default abstract class Session { + public static Sessions = new FunkyArray(); + public static SessionExpiryInterval = setInterval(() => { + const currentTime = Date.now(); + for (const key of Session.Sessions.keys) { + const session = Session.Sessions.get(key); + if (!session || (session && currentTime >= session.validityPeriod.getTime())) { + Session.Sessions.remove(key); } - } else { - return this.res.redirect(`/${controllerName}/${action}`, 302); + } + }, 3600000); + + public static AssignUserSession(res:FastifyReply, user:User) { + const validPeriod = new Date(); + validPeriod.setTime(validPeriod.getTime() + Config.session.validity); + const key = randomBytes(Config.session.length).toString("hex"); + + Session.Sessions.set(key, new SessionUser(user.Id, validPeriod)); + + res.setCookie("EHP_SESSION", key, { + path: "/", + signed: true + }); + } + + public static Clear(cookies:Cookies, res:FastifyReply) { + if ("EHP_SESSION" in cookies && typeof(cookies["EHP_SESSION"]) === "string") { + const key:unknown = FastifyCookie.unsign(cookies["EHP_SESSION"], Config.session.secret); + Session.Sessions.remove(key as string); + + res.clearCookie("EHP_SESSION"); } } - ok(message?:string) { - return this.res.status(200).send(message ?? ""); - } - - badRequest(message?:string) { - return this.res.status(400).send(message ?? ""); - } - - unauthorised(message?:string) { - return this.res.status(401).send(message ?? ""); - } - - forbidden(message?:string) { - return this.res.status(403).send(message ?? ""); + public static CheckValiditiy(cookies:Cookies) { + if ("EHP_SESSION" in cookies && typeof(cookies["EHP_SESSION"]) === "string") { + const key = FastifyCookie.unsign(cookies["EHP_SESSION"], Config.session.secret); + if (key.valid && Session.Sessions.has(key.value ?? "badkey")) { + return Session.Sessions.get(key.value ?? "badkey"); + } + } + + return undefined; } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 16e063f..07c1001 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,9 @@ "@fastify/view": "^10.0.1", "ejs": "^3.1.10", "fastify": "^5.2.0", - "hsconsole": "^1.0.2" + "funky-array": "^1.0.0", + "hsconsole": "^1.0.2", + "mysql2": "^3.12.0" }, "devDependencies": { "@types/ejs": "^3.1.5", @@ -421,6 +423,15 @@ "fastq": "^1.17.1" } }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -622,6 +633,15 @@ } } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -882,6 +902,21 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/funky-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/funky-array/-/funky-array-1.0.0.tgz", + "integrity": "sha512-1oMg2rdLkvQV+RzV6IwaPw0wngQkVb/9bIfr+Twj/MqWKaBVM6KPwkq2IBSjTf0PC3GLeiMZ6weeG3jdhOlIYg==", + "license": "MIT" + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/glob": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", @@ -977,6 +1012,18 @@ "node": ">= 0.8" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -1054,6 +1101,12 @@ "node": ">=0.12.0" } }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1119,6 +1172,12 @@ "set-cookie-parser": "^2.6.0" } }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", @@ -1128,6 +1187,21 @@ "node": "20 || >=22" } }, + "node_modules/lru.min": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz", + "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1175,6 +1249,47 @@ "dev": true, "license": "MIT" }, + "node_modules/mysql2": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.12.0.tgz", + "integrity": "sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/nodemon": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", @@ -1440,6 +1555,12 @@ "node": ">=10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/secure-json-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.1.tgz", @@ -1458,6 +1579,11 @@ "node": ">=10" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", @@ -1534,6 +1660,15 @@ "node": ">= 10.x" } }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/package.json b/package.json index 032c7f4..aa787b1 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,11 @@ "author": "tgpholly", "license": "MIT", "type": "commonjs", - "scripts": {}, + "scripts": { + "updateCheck": "check-outdated", + "dev": "nodemon --watch './**/*.ts' index.ts", + "build": "tsx --build --clean" + }, "devDependencies": { "@types/ejs": "^3.1.5", "@types/node": "^22.10.2", @@ -28,6 +32,8 @@ "@fastify/view": "^10.0.1", "ejs": "^3.1.10", "fastify": "^5.2.0", - "hsconsole": "^1.0.2" + "funky-array": "^1.0.0", + "hsconsole": "^1.0.2", + "mysql2": "^3.12.0" } }