Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
.env
dist
116 changes: 55 additions & 61 deletions LicensingServer/index.js
Original file line number Diff line number Diff line change
@@ -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<string, { host: string, expires: number }>}
*/
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);
137 changes: 136 additions & 1 deletion LicensingServer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion LicensingServer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
46 changes: 0 additions & 46 deletions MasqrBackend/Checkfailed.html

This file was deleted.

Loading