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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
/server/data.json
node_modules
13 changes: 13 additions & 0 deletions server/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.nyc_output/
.DS_Store
.git
.gitignore
.env
Dockerfile
docker-compose.yml
README.md
LICENSE
36 changes: 36 additions & 0 deletions server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#---------- Build Stage ----------
FROM node:22-alpine AS build
WORKDIR /app

#----------- Copy Manifest files ------------
COPY ./package.json ./
COPY ./package-lock.json ./
COPY ./tsconfig.json ./

#------------ Install Dependencies --------------
RUN npm install

#------------ Copy The main Source code ---------
COPY www ./www
COPY src ./src

#------------- Run the TS Transpiler -------------
RUN npm run build

#------------ Prod Stage ---------------
FROM node:22-alpine AS prod
WORKDIR /app

COPY package*.json ./
RUN npm install --only=production --legacy-peer-deps

COPY --from=build /app/www ./www
COPY --from=build /app/dist ./dist

EXPOSE 10080 10081

RUN addgroup -S cookie && adduser -S cookie-guy -G cookie
RUN chown -R cookie-guy:cookie /app
USER cookie-guy

CMD [ "npm","run","start" ]
21 changes: 21 additions & 0 deletions server/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#--------- Development Environment -------
FROM node:22-alpine AS build
WORKDIR /app

COPY ./package*.json ./
COPY ./tsconfig.json ./

RUN npm install
RUN npm install -g nodemon

COPY . .

RUN npm run build

EXPOSE 10080 10081

RUN addgroup -S cookie && adduser -S cookie-guy -G cookie
RUN chown -R cookie-guy:cookie /app
USER cookie-guy

CMD [ "npm","run","dev" ]
2 changes: 1 addition & 1 deletion server/data.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"index": 2,
"index": 3,
"cacheID": "eb60b0a3"
}
238 changes: 118 additions & 120 deletions server/main.js → server/dist/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cookieParser from "cookie-parser";
import crypto from "crypto";
import cors from "cors";
import dotenv from "dotenv";
dotenv.config();
const generateUUID = (pattern = "xxxx-xxxx-xxxx-xxxx-xxxx", charset = "abcdefghijklmnopqrstuvwxyz0123456789") => pattern.replace(/[x]/g, () => charset[Math.floor(Math.random() * charset.length)]);
const hashNumber = (value) => crypto.createHash("MD5")
.update(value.toString())
Expand All @@ -19,9 +20,10 @@ const createRoutes = (base, count) => {
return array;
};
class Storage {
_path = path.join(path.resolve(), "data.json");
_content = {};
_contentProxy;
constructor() {
this._path = path.join(path.resolve(), "data.json");
this._content = {};
if (!this.existsPersistent())
this.createPersistent();
this.read();
Expand Down Expand Up @@ -66,8 +68,8 @@ const STORAGE = new Storage().content;
dotenv.config();
const WEBSERVER_DOMAIN_1 = process.env["HOST_MAIN"] ?? "localhost:10080";
const WEBSERVER_DOMAIN_2 = process.env["HOST_DEMO"] ?? "localhost:10081";
const WEBSERVER_PORT_1 = +process.env["PORT_MAIN"] ?? 10080;
const WEBSERVER_PORT_2 = +process.env["PORT_DEMO"] ?? 10081;
const WEBSERVER_PORT_1 = parseInt(process.env["PORT_MAIN"] ?? "10080", 10);
const WEBSERVER_PORT_2 = parseInt(process.env["PORT_DEMO"] ?? "10081", 10);
const CACHE_IDENTIFIER = STORAGE.cacheID ?? generateUUID("xxxxxxxx", "0123456789abcdef");
const N = 32;
const FILE = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=";
Expand All @@ -78,121 +80,117 @@ webserver_1.options('*', cors());
webserver_2.options('*', cors());
console.info(`supercookie | Starting up using N=${N}, C-ID='${CACHE_IDENTIFIER}' ...`);
console.info(`supercookie | There are ${Math.max(maxN - 1 - (STORAGE.index ?? 1), 0)}/${maxN - 1} unique identifiers left.`);
let Webserver = (() => {
class Webserver {
static getVector(identifier) {
const booleanVector = (identifier >>> 0).toString(2)
.padStart(this.routes.length, '0').split('')
.map((element) => element === '1')
.reverse();
const vector = new Array();
booleanVector.forEach((value, index) => value ? vector.push(this.getRouteByIndex(index)) : void 0);
return vector;
}
static getIdentifier(vector, size = vector.size) {
return parseInt(this.routes.map((route) => vector.has(route) ? 0 : 1)
.join('').slice(0, size).split('').reverse().join(''), 2);
}
static hasRoute(route) {
return this.routes.includes(route);
}
static getRouteByIndex(index) {
return this.routes[index] ?? null;
}
static getIndexByRoute(route) {
return this.routes.indexOf(route) ?? null;
}
static getNextRoute(route) {
const index = this.routes.indexOf(route);
if (index === -1)
throw "Route is not valid.";
return this.getRouteByIndex(index + 1);
}
static setCookie(res, name, value, options = { httpOnly: false, expires: new Date(Date.now() + 60 * 1000) }) {
return res.cookie(name, value, options), res;
}
static sendFile(res, route, options = {}, type = "html") {
let content = fs.readFileSync(route).toString();
Object.keys(options).sort((a, b) => b.length - a.length).forEach((key) => {
content = content.replace(new RegExp(`\{\{${key}\}\}`, 'g'), (options[key]?.toString() || '')
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;"));
});
res.header({
"Cache-Control": "private, no-cache, no-store, must-revalidate",
"Expires": -1,
"Pragma": "no-cache"
});
res.type(type);
return res.send(content), res;
}
}
Webserver.routes = createRoutes(CACHE_IDENTIFIER, N).map((value) => `${CACHE_IDENTIFIER}:${value}`);
return Webserver;
})();
let Profile = (() => {
class Profile {
constructor(uid, identifier = null) {
this._identifier = null;
this._visitedRoutes = new Set();
this._storageSize = -1;
this._uid = uid;
if (identifier !== null)
this._identifier = identifier,
this._vector = Webserver.getVector(identifier);
Profile.list.add(this);
}
static get(uid) {
return this.has(uid) ?
Array.from(this.list).filter((profile) => profile.uid === uid)?.pop() :
null;
}
static has(uid) {
return Array.from(this.list).some((profile) => profile.uid === uid);
}
static from(uid, identifier) {
return !this.has(uid) ? new Profile(uid, identifier) : null;
}
destructor() {
Profile.list.delete(this);
}
get uid() {
return this._uid;
}
get vector() {
return this._vector;
}
get visited() {
return this._visitedRoutes;
}
get identifier() {
return this._identifier;
}
getRouteByIndex(index) {
return this.vector[index] ?? null;
}
_isReading() {
return this._identifier === null;
}
_visitRoute(route) {
this._visitedRoutes.add(route);
}
_calcIdentifier() {
return this._identifier = Webserver.getIdentifier(this._visitedRoutes, this._storageSize), this.identifier;
}
_setStorageSize(size) {
this._storageSize = size;
}
get storageSize() {
return this._storageSize;
}
}
Profile.list = new Set();
return Profile;
})();
class Webserver {
static routes = createRoutes(CACHE_IDENTIFIER, N).map((value) => `${CACHE_IDENTIFIER}:${value}`);
static getVector(identifier) {
const booleanVector = (identifier >>> 0).toString(2)
.padStart(this.routes.length, '0').split('')
.map((element) => element === '1')
.reverse();
const vector = new Array();
booleanVector.forEach((value, index) => value ? vector.push(this.getRouteByIndex(index)) : void 0);
return vector;
}
static getIdentifier(vector, size = vector.size) {
return parseInt(this.routes.map((route) => vector.has(route) ? 0 : 1)
.join('').slice(0, size).split('').reverse().join(''), 2);
}
static hasRoute(route) {
return this.routes.includes(route);
}
static getRouteByIndex(index) {
return this.routes[index] ?? null;
}
static getIndexByRoute(route) {
return this.routes.indexOf(route) ?? null;
}
static getNextRoute(route) {
const index = this.routes.indexOf(route);
if (index === -1)
throw "Route is not valid.";
return this.getRouteByIndex(index + 1);
}
static setCookie(res, name, value, options = { httpOnly: false, expires: new Date(Date.now() + 60 * 1000) }) {
return res.cookie(name, value, options), res;
}
static sendFile(res, route, options = {}, type = "html") {
let content = fs.readFileSync(route).toString();
Object.keys(options).sort((a, b) => b.length - a.length).forEach((key) => {
content = content.replace(new RegExp(`\{\{${key}\}\}`, 'g'), (options[key]?.toString() || '')
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;"));
});
res.header({
"Cache-Control": "private, no-cache, no-store, must-revalidate",
"Expires": -1,
"Pragma": "no-cache"
});
res.type(type);
return res.send(content), res;
}
}
class Profile {
static list = new Set();
static get(uid) {
return this.has(uid) ?
Array.from(this.list).filter((profile) => profile.uid === uid)?.pop() :
null;
}
static has(uid) {
return Array.from(this.list).some((profile) => profile.uid === uid);
}
static from(uid, identifier) {
return !this.has(uid) ? new Profile(uid, identifier) : null;
}
_uid;
_vector;
_identifier = null;
_visitedRoutes = new Set();
_storageSize = -1;
constructor(uid, identifier = null) {
this._uid = uid;
if (identifier !== null)
this._identifier = identifier,
this._vector = Webserver.getVector(identifier);
Profile.list.add(this);
}
destructor() {
Profile.list.delete(this);
}
get uid() {
return this._uid;
}
get vector() {
return this._vector;
}
get visited() {
return this._visitedRoutes;
}
get identifier() {
return this._identifier;
}
getRouteByIndex(index) {
return this.vector[index] ?? null;
}
_isReading() {
return this._identifier === null;
}
_visitRoute(route) {
this._visitedRoutes.add(route);
}
_calcIdentifier() {
return this._identifier = Webserver.getIdentifier(this._visitedRoutes, this._storageSize), this.identifier;
}
_setStorageSize(size) {
this._storageSize = size;
}
get storageSize() {
return this._storageSize;
}
}
;
webserver_2.set("trust proxy", 1);
webserver_2.use(cookieParser());
Expand All @@ -206,7 +204,7 @@ webserver_2.use((req, res, next) => {
const midSet = new Set();
const generateWriteToken = () => {
const uuid = generateUUID();
setTimeout(() => midSet.delete(uuid), 1000 * 60);
setTimeout(() => midSet.delete(uuid), 1_000 * 60);
return midSet.add(uuid), uuid;
};
const deleteWriteToken = (token) => midSet.delete(token);
Expand Down
16 changes: 16 additions & 0 deletions server/docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
supercookie-dev:
build:
context: .
dockerfile: Dockerfile.dev
restart: unless-stopped
env_file:
- .env
- .env.localhost
ports:
- "${PORT_MAIN:-10080}:${PORT_MAIN:-10080}"
- "${PORT_DEMO:-10081}:${PORT_DEMO:-10081}"
volumes:
- .:/app
- /app/node_modules
command: ["npm", "run", "dev"]
Loading