diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f00ef2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +.env +dist \ No newline at end of file diff --git a/LicensingServer/index.js b/LicensingServer/index.js index d49fb06..841253d 100644 --- a/LicensingServer/index.js +++ b/LicensingServer/index.js @@ -1,104 +1,98 @@ -import http from 'http'; -import crypto from 'node:crypto'; -import { configDotenv } from 'dotenv'; -configDotenv(); +import http from "node:http"; +import { randomUUID } from "node:crypto"; +import connect from "connect"; +import cors from "cors"; +import "dotenv/config"; /** * @type {string[]} */ const keys = JSON.parse(process.env.PSK); -const activeLicences = new Map();; - /** - * @description Route for /newLicense which handles ID generation - * @param {http.IncomingMessage} req - * @param {http.ServerResponse} res + * @type {Map} */ -async function genID(req, res) { - const params = new URL(req.url, "http://localhost/").searchParams; - if (!keys.includes(req.headers.psk)) - return throwError(res, "Invalid PSK; Cannot assign licenses"); +const activeLicences = new Map(); + +const app = connect(); +app.use(cors({ + origin: process.env.ORIGIN +})); + +app.use("/newLicense", (req, res) => { + const params = new URL(req.url, "http://localhost/").searchParams; + if (!keys.includes(req.headers.psk)) + return throwError(res, "Invalid PSK; Cannot assign licenses") // Define default variables and throw errors for no hostname - const assignedKey = params.get("assignedLicense") || crypto.randomUUID().substring(0,6); + const assignedKey = params.get("assignedLicense") || randomUUID().substring(0,6); const proxyHost = params.get("host") const expireTime = params.get("expires") || Date.now() + (3 * 24 * 60 * 60 * 1000); if (!proxyHost) - return throwError(res, "No host defined in URL") + return throwError(res, "No host defined in URL"); - // License assignment - activeLicences.set(assignedKey, {host: proxyHost, expires: expireTime}); + activeLicences.set(assignedKey, { host: proxyHost, expires: expireTime }); // Success res.statusCode = 200; - res.end(JSON.stringify({assignedLicense: assignedKey, expires: expireTime})) -} + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ assignedLicense: assignedKey, expires: expireTime })); +}) + +app.use("/validate", (req, res) => { + const params = new URL(req.url, "http://localhost/").searchParams; + if (!activeLicences.get(params.get("license"))) + return throwError(res, "Invalid License", 403); + + if (activeLicences.get(params.get("license")).expires < Date.now()) { + activeLicences.delete(params.get("license")); + return throwError(res, "Expired License", 403); + } + if (activeLicences.get(params.get("license")).host != params.get("host")) + return throwError(res, "License for incorrect product", 403); + + res.statusCode = 200; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ status: "License valid" })); + activeLicences.delete(params.get("license")); +}) /** * @description House keeping task to clean up licenses that have been invalid for 7 or more days; this only really runs once per day */ async function cleanupLicenses() { for (const license in activeLicences.keys()) { - if (activeLicences.get(license).expires < (Date.now() + (7 * 24 * 60 * 60 * 1000))) { + if (activeLicences.get(license).expires < (Date.now() - (7 * 24 * 60 * 60 * 1000))) { activeLicences.delete(license); } } } -/** - * @description Route for /validate which handles License validation - * @param {http.IncomingMessage} req - * @param {http.ServerResponse} res - */ -async function validateID(req, res) { - const params = new URL(req.url, "http://localhost/").searchParams; - if (!activeLicences.get(params.get("license"))) { - res.statusCode = 403; - res.end(JSON.stringify({error: "Invalid License"})); - return; - } - if (activeLicences.get(params.get("license")).expires < Date.now()) { - delete activeLicences[params.get("license")]; - res.statusCode = 403; - res.end(JSON.stringify({error: "Expired License"})); - return; - } - if (activeLicences.get(params.get("license")).host != params.get("host")) { - res.statusCode = 403; - res.end(JSON.stringify({error: "License for incorrect product"})); - return; - } - res.statusCode = 200; - res.end(JSON.stringify({status: "License valid"})); - activeLicences.delete(params.get("license")); -} - /** * @description Error handler * @param {http.ServerResponse} res * @param {string} error + * @param {number | undefined} statusCode */ -function throwError(res, error) { - res.statusCode = 500; - res.end(JSON.stringify({error: error})); +function throwError(res, error, statusCode = 500) { + res.statusCode = statusCode; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ error: error })); } /** * @description Entrypoint and URL router */ -http.createServer(function (req, res) { - - if (req.url?.startsWith("/newLicense")) - return genID(req, res).catch(); - if (req.url?.startsWith("/validate")) - return validateID(req, res).catch(); +const server = http.createServer(); - res.statusCode = 404 - res.end(JSON.stringify({error: "Invalid route"})) +server.on("request", app); +server.on("listening", () => { + const addr = server.address(); + console.log(`Server running on port ${addr.port}`) +}) -}).listen(8004); +server.listen({ port: 8004 }); // Run cleanupLicenses once per day to prevent a memory leak -setInterval(cleanupLicenses, 24 * 60 * 60 * 1000); +setInterval(cleanupLicenses, 24 * 60 * 60 * 1000); \ No newline at end of file diff --git a/LicensingServer/package-lock.json b/LicensingServer/package-lock.json index 040d78e..ced85db 100644 --- a/LicensingServer/package-lock.json +++ b/LicensingServer/package-lock.json @@ -7,11 +7,47 @@ "": { "name": "licensingserver", "version": "1.0.0", - "license": "AGPL-3.0-or-later", + "license": "SEE LICENSE IN LICENSE", "dependencies": { + "connect": "^3.7.0", + "cors": "^2.8.5", "dotenv": "^16.3.1" } }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -22,6 +58,105 @@ "funding": { "url": "https://github.com/motdotla/dotenv?sponsor=1" } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } } } } diff --git a/LicensingServer/package.json b/LicensingServer/package.json index e1b517f..c4aea0e 100644 --- a/LicensingServer/package.json +++ b/LicensingServer/package.json @@ -4,12 +4,15 @@ "description": "", "main": "index.js", "type": "module", + "files": ["index.js"], "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Mercury Workshop", - "license": "UNLICENSED", + "license": "SEE LICENSE IN LICENSE", "dependencies": { + "connect": "^3.7.0", + "cors": "^2.8.5", "dotenv": "^16.3.1" } } diff --git a/MasqrBackend/Checkfailed.html b/MasqrBackend/Checkfailed.html deleted file mode 100644 index 0298304..0000000 --- a/MasqrBackend/Checkfailed.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - Example Domain - - - - - - - -
-

Example Domain

-

This domain is for use in illustrative examples in documents. You may use this - domain in literature without prior coordination or asking for permission. If you are expecting another page, please check your network or Refresh this page

-

More information...

-
- - diff --git a/MasqrBackend/index.js b/MasqrBackend/index.js deleted file mode 100644 index 7f8deeb..0000000 --- a/MasqrBackend/index.js +++ /dev/null @@ -1,104 +0,0 @@ -/* -* Masqr Project - Backend validator -* to set this up you'll want to create a few files, in addition to adding this to your next app -* those files include Checkfailed.html - this will be your fallback failure page -* placeholder.svg, a default image for v0.dev sites -* -* OPTIONAL -* a folder named Masqrd in your server directory -* inside Masqrd, an individual masqd page for when domain fails for EACH domain -* for example, if anura.christmas fails to validate, it will serve ./Masqrd/anura.christma.html -*/ - -import express from "express"; -import path from "path"; -import fs from "fs"; -import cookieParser from "cookie-parser"; - -// Masqr Constants -const LICENSE_SERVER_URL = "https://license.mercurywork.shop/validate?license="; -const whiteListedDomains = ["anura.pro", "anura.mercurywork.shop", "anura.christmas"]; // Add any public domains you have here -const failureFile = fs.readFileSync("Checkfailed.html", "utf8"); -const placeholder = fs.readFileSync("placeholder.svg", "utf8"); // For v0.dev websites - -const app = express(); - -app.use(cookieParser()) - -// Congratulations! Masqr failed to validate, this is either your first visit or you're a FRAUD -async function MasqFail(req, res) { - if (!req.headers.host) { - // no bitch still using HTTP/1.0 go away - return; - } - const unsafeSuffix = req.headers.host + ".html" - let safeSuffix = path.normalize(unsafeSuffix).replace(/^(\.\.(\/|\\|$))+/, ''); - let safeJoin = path.join(process.cwd()+"/Masqrd", safeSuffix); - try { - await fs.promises.access(safeJoin) // man do I wish this was an if-then instead of a "exception on fail" - const failureFileLocal = await fs.promises.readFile(safeJoin, "utf8"); - res.setHeader("Content-Type", "text/html"); - res.send(failureFileLocal); - return; - } catch(e) { - res.setHeader("Content-Type", "text/html"); - res.send(failureFile); - return; - } -} - -// Woooooo masqr yayyyy (said no one) -app.use(async (req, res, next) => { - if (req.headers.host && whiteListedDomains.includes(req.headers.host)) { - next(); - return; - } - if (req.url.includes("placeholder.svg")) { - res.setHeader("Content-Type", "image/svg+xml"); - res.send(placeholder); - return; - } - if (req.url.includes("/bare/")) { // replace this with your bare endpoint - next(); - return; - // Bypass for UV and other bares - } - - const authheader = req.headers.authorization; - - if (req.cookies["authcheck"]) { - next(); - return; - } - - - if (req.cookies['refreshcheck'] != "true") { - res.cookie("refreshcheck", "true", {maxAge: 10000}) // 10s refresh check - MasqFail(req, res) - return; - } - - if (!authheader) { - - res.setHeader('WWW-Authenticate', 'Basic'); // Yeah so we need to do this to get the auth params, kinda annoying and just showing a login prompt gives it away so its behind a 10s refresh check - res.status(401); - MasqFail(req, res) - return; - } - - const auth = Buffer.from(authheader.split(' ')[1], - 'base64').toString().split(':'); - const user = auth[0]; - const pass = auth[1]; - - const licenseCheck = ((await (await fetch(LICENSE_SERVER_URL + pass + "&host=" + req.headers.host)).json()))["status"] - console.log(LICENSE_SERVER_URL + pass + "&host=" + req.headers.host +" returned " +licenseCheck) - if (licenseCheck == "License valid") { - res.cookie("authcheck", "true", {expires: new Date((Date.now()) + (365*24*60*60 * 1000))}) // authorize session, for like a year, by then the link will be expired lol - res.send(``) // fun hack to make the browser refresh and remove the auth params from the URL - return; - } - - MasqFail(req, res) - return; -}) diff --git a/MasqrBackend/package-lock.json b/MasqrBackend/package-lock.json index 8290913..80757a6 100644 --- a/MasqrBackend/package-lock.json +++ b/MasqrBackend/package-lock.json @@ -7,10 +7,31 @@ "": { "name": "masqerbackend", "version": "1.0.0", - "license": "AntiSkip", + "license": "SEE LICENSE IN LICENSE", "dependencies": { + "cookie": "^0.6.0", "cookie-parser": "^1.4.6", "express": "^4.18.2" + }, + "devDependencies": { + "@types/cookie": "^0.6.0", + "@types/node": "^20.11.17", + "typescript": "^5.3.3" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", + "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" } }, "node_modules/accepts": { @@ -94,9 +115,9 @@ } }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -113,6 +134,14 @@ "node": ">= 0.8.0" } }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -651,6 +680,25 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/MasqrBackend/package.json b/MasqrBackend/package.json index 64f5329..8703211 100644 --- a/MasqrBackend/package.json +++ b/MasqrBackend/package.json @@ -1,16 +1,24 @@ { - "name": "masqerbackend", + "name": "@mercuryworkshop/masqrbackend", "version": "1.0.0", "type": "module", - "description": "", - "main": "index.js", + "description": "Masqr Project - Backend validator", + "main": "dist/index.js", + "files": ["dist", "../LICENSE"], "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "tsc -d", + "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Mercury Workshop", - "license": "UNLICENSED", + "license": "SEE LICENSE IN LICENSE", "dependencies": { + "cookie": "^0.6.0", "cookie-parser": "^1.4.6", "express": "^4.18.2" + }, + "devDependencies": { + "@types/cookie": "^0.6.0", + "@types/node": "^20.11.17", + "typescript": "^5.3.3" } -} +} \ No newline at end of file diff --git a/MasqrBackend/public/index.html b/MasqrBackend/public/index.html deleted file mode 100644 index 2e33b8f..0000000 --- a/MasqrBackend/public/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - proxy - - -

proxy

- - \ No newline at end of file diff --git a/MasqrBackend/src/index.ts b/MasqrBackend/src/index.ts new file mode 100644 index 0000000..dd95ef5 --- /dev/null +++ b/MasqrBackend/src/index.ts @@ -0,0 +1,97 @@ +/* +* Masqr Project - Backend validator +*/ + +import type { + IncomingMessage, + ServerResponse, + RequestListener +} from "node:http" +import cookie from "cookie"; +import { Buffer } from "node:buffer"; + +/** + * Merge object b with object a. + * + * var a = { foo: 'bar' } + * , b = { bar: 'baz' }; + * + * merge(a, b); + * // => { foo: 'bar', bar: 'baz' } + */ +function merge(a: T, b: T) : T{ + if (a && b) { + for (var key in b) { + a[key] = b[key]; + } + } + return a; +}; + +function MasqFail(req: IncomingMessage, res: ServerResponse) { + res.end("fail!!!"); +} + +async function checkLicense(serverUrl: string, host: string, license: string) : Promise { + const url = `${new URL("/validate?", serverUrl)}${new URLSearchParams({ license, host })}`; + const res = await fetch(url); + const json = await res.json(); + return json.status === "License valid"; +} + +interface MasqrConfig { + /** + * Add any public domains you have here + */ + whiteListedDomains?: string[] + /** + * Congratulations! Masqr failed to validate, this is either your first visit or you're a FRAUD + */ + fail: RequestListener, + licenseServerUrl?: string +} + +export default function masqr(config: MasqrConfig) { + const { whiteListedDomains, fail, licenseServerUrl } = merge({ fail: MasqFail, licenseServerUrl: "https://license.mercurywork.shop" }, config); + return async (req: IncomingMessage, res: ServerResponse, next: () => void) => { + if( + !req.headers.host || !req.url + ) { + fail(req, res); + return; + } + const cookies = cookie.parse(req.headers.cookie || ""); + if (whiteListedDomains?.includes(req.headers.host)) return next(); + + if (cookies["authcheck"]) return next(); + + if (cookies["refreshcheck"] != "true") { + res.setHeader("Set-Cookie", cookie.serialize("refreshcheck", "true", { maxAge: 10 * 1000 })); // 10s refresh check + fail(req, res); + return; + } + + const authheader = req.headers.authorization; + + if (!authheader) { + res.writeHead(401, { + "WWW-Authenticate": "Basic" // Yeah so we need to do this to get the auth params, kinda annoying and just showing a login prompt gives it away so its behind a 10s refresh check + }); + fail(req, res); + return; + } + + const [user, pass] = Buffer.from(authheader.split(" ")[1], "base64").toString().split(":"); + + const licenseValid = await checkLicense(licenseServerUrl!, req.headers.host, pass); + console.log(licenseValid); + if (licenseValid) { + res.writeHead(200, { + "Set-Cookie": cookie.serialize("authcheck", "true", { expires: new Date((Date.now()) + (365 * 24 * 60 * 60 * 1000)) }), + "Content-Type": "text/html" + }); // authorize session, for like a year, by then the link will be expired lol + res.end(``) // fun hack to make the browser refresh and remove the auth params from the URL + return; + } else fail(req, res); + } +} \ No newline at end of file diff --git a/MasqrBackend/tsconfig.json b/MasqrBackend/tsconfig.json new file mode 100644 index 0000000..31089c1 --- /dev/null +++ b/MasqrBackend/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + "outDir": "dist", + "target": "ESNext", + "module": "Node16", + "moduleResolution": "Node16", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + }, + "include": ["src"] + } + \ No newline at end of file diff --git a/MasqrFront/masqr.js b/MasqrFront/masqr.js deleted file mode 100644 index c580428..0000000 --- a/MasqrFront/masqr.js +++ /dev/null @@ -1,12 +0,0 @@ -LICENSE_SERVER_URL = "https://license.mercurywork.shop/validate?license="; -async function checkLicense(pass) { - if (localStorage["LICENSE_CHECK"]) { - return true; - } - licenseCheck = (await (await fetch(LICENSE_SERVER_URL + pass + "&host=" + location.origin)).json())["status"]; - if (licenseCheck == "License valid") { - localStorage["LICENSE_CHECK"] = true; - return true; - } - return false; -} \ No newline at end of file diff --git a/README.md b/README.md index 319368c..2937702 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # The MASQR project -This repository contains the backend for Masqr, and two example clients, using Express middleware and just browser side JS. +This repository contains the backend for Masqr and the express middleware. ## What is MASQR? @@ -8,7 +8,27 @@ Think of Masqr as a "anti link leaking" authentication system that allows you to ## How can deploy this? -This is fairly easy to deploy to your backend using `express` as seen in the `MasqrBackend` example, and you can implement this into different backends fairly similarly. +This is fairly easy to deploy to your backend using `express` as seen below, and you can implement this into different backends fairly similarly. + +```js +import express from "express"; +import masqr from "@mercuryworkshop/masqrbackend"; + +const app = express(); + +app.use(masqr({ + fail(req, res) { + res.end("fail!!!"); + }, + licenseServerUrl: "https://license.mercurywork.shop" +})); + +app.use((req, res) => { + res.end("proxy"); +}); + +app.listen(8080); +``` ## How it works @@ -18,4 +38,4 @@ How masqr works is really simple. When you get a link from a proxy bot of some s © Mercury Workshop 2024
-Licensed under the AntiSkip License +Licensed under the AntiSkip License \ No newline at end of file