diff --git a/authenticate.html b/authenticate.html
new file mode 100644
index 0000000..2831c2f
--- /dev/null
+++ b/authenticate.html
@@ -0,0 +1,14 @@
+
+
+ Authentication required
+
+
+ Authentication required
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 5d49f4f..301ea09 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,10 +4,149 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "asn1.js": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+ "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "bagpipe": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/bagpipe/-/bagpipe-0.3.5.tgz",
+ "integrity": "sha1-40HRZPyyTN8E6n4Ft2XsEMiupqE="
+ },
+ "bn.js": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
+ },
+ "crypto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+ "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="
+ },
+ "fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "requires": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.10",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
+ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
+ },
"http": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
"integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "kruptein": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-2.2.3.tgz",
+ "integrity": "sha512-BTwprBPTzkFT9oTugxKd3WnWrX630MqUDsnmBuoa98eQs12oD4n4TeI0GbpdGcYn/73Xueg2rfnw+oK4dovnJg==",
+ "requires": {
+ "asn1.js": "^5.4.1"
+ }
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "querystring": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz",
+ "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg=="
+ },
+ "retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "session-file-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/session-file-store/-/session-file-store-1.5.0.tgz",
+ "integrity": "sha512-60IZaJNzyu2tIeHutkYE8RiXVx3KRvacOxfLr2Mj92SIsRIroDsH0IlUUR6fJAjoTW4RQISbaOApa2IZpIwFdQ==",
+ "requires": {
+ "bagpipe": "^0.3.5",
+ "fs-extra": "^8.0.1",
+ "kruptein": "^2.0.4",
+ "object-assign": "^4.1.1",
+ "retry": "^0.12.0",
+ "write-file-atomic": "3.0.3"
+ }
+ },
+ "signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ },
+ "typedarray-to-buffer": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+ "requires": {
+ "is-typedarray": "^1.0.0"
+ }
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ },
+ "write-file-atomic": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
+ "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
+ "requires": {
+ "imurmurhash": "^0.1.4",
+ "is-typedarray": "^1.0.0",
+ "signal-exit": "^3.0.2",
+ "typedarray-to-buffer": "^3.1.5"
+ }
}
}
}
diff --git a/package.json b/package.json
index a8ec756..bcb26b7 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,9 @@
"author": "Leon Etienne",
"license": "BSD-2-Clause",
"dependencies": {
- "http": "0.0.1-security"
+ "crypto": "^1.0.1",
+ "http": "0.0.1-security",
+ "querystring": "^0.2.1",
+ "session-file-store": "^1.5.0"
}
}
diff --git a/server.js b/server.js
index 57f1fab..b51c402 100755
--- a/server.js
+++ b/server.js
@@ -1,9 +1,14 @@
-#!/home/menethil/.nvm/versions/node/v14.16.1/bin/node
-
var http = require('http');
var fs = require('fs');
var path = require('path');
+var querystring = require('querystring');
+var crypto = require('crypto');
+var execSync = require('child_process').execSync;
+//! How many seconds (from the last interaction) a session stays valid
+const SESSION_DURATION = 10*60;
+
+// Just a few mime types
const contentTypes = {
'.html': 'text/html',
'.css': 'text/css',
@@ -19,9 +24,150 @@ const contentTypes = {
'.webm': 'video/webm',
};
-var server = http.createServer(function (request, response) {
- // Handle requests here...
+sessions = [];
+//! Will create a session and return it's id.
+function createSession() {
+ const timestamp = Date.now();
+ const sessionId = SHA512Digest(timestamp.toString() + Math.floor(Math.random() * 100000).toString());
+
+ sessions.push({
+ 'sessionId': sessionId,
+ 'timestamp': timestamp
+ });
+
+ return sessionId;
+}
+
+//! Will check if a session of a given id exsists, and if it has been expired.
+//! Will removed if expired.
+//! Will also renew a sessions timestamp
+function isSessisionValid(id) {
+ // Get an array of all sessions matching this id (should be 1 or 0)
+ var filteredSessions = sessions.filter((value, index, array) => {
+ return value.sessionId === id;
+ });
+
+ // Quick-reject: No session of this id
+ if (filteredSessions.length === 0) {
+ console.log('No session of this id...');
+ return false;
+ }
+
+ // Else: fetch the session
+ var sessionById = filteredSessions[0];
+
+ // Is the session still valid?
+ if (Date.now() - sessionById.timestamp > SESSION_DURATION * 1000) {
+ console.log('Session is no longer valid, because it expired... Removing it...');
+
+ // Remove the session from the list of sessions
+ const indexOfSession = sessions.indexOf(sessionById);
+ sessions.splice(indexOfSession, 1);
+
+ return false;
+ }
+
+ // Else: It must be valid. We should update its timestamp.
+ console.log('Session is active. Bumping timestamp...');
+ sessionById.timestamp = Date.now();
+ return true;
+}
+
+//! Will decode cookies to an array
+//! Source: https://stackoverflow.com/a/3409200
+//! I know this fails if a cookie contains '='. Mine don't!
+function parseCookies(request) {
+ const list = {};
+ const cookieHeader = request.headers?.cookie;
+ if (!cookieHeader) return list;
+
+ cookieHeader.split(`;`).forEach(function(cookie) {
+ let [ name, ...rest] = cookie.split(`=`);
+ name = name?.trim();
+ if (!name) return;
+ const value = rest.join(`=`).trim();
+ if (!value) return;
+ list[name] = decodeURIComponent(value);
+ });
+
+ return list;
+}
+
+function SHA512Digest(string) {
+ return crypto.createHash('sha512').update(string, 'utf-8').digest('hex');
+}
+
+function serveAuthenticatePage(request, response) {
+ fs.readFile(__dirname + '/authenticate.html', function (error, data) {
+ if (!error) {
+ response.writeHead(200, {
+ 'Content-Type': 'text/html'
+ });
+ response.end(data);
+ return;
+ } else {
+ response.writeHead(500, {
+ 'Content-Type': 'text/html'
+ });
+
+ console.error('Unable to read authentication html file: ' + JSON.stringify(error));
+ response.end('Internal server error.');
+ return;
+ }
+ });
+}
+
+// FIX THIS BS!
+const PASSWD_HASH = 'a3c1443b087cf5338d3696f6029fdf791ee4829a27e19c9f257a06ca0d88b5b518ac9868bb13199e807553bda62d3dc15b6354862f34fcab0a7c4c45530349ea';
+
+function testAuthentication(request, response) {
+ // Wait for the request to have been received completely (including request body)
+ console.log('Request is trying to authenticate... Waiting for request body...');
+ response.writeHead(200, {
+ 'Content-Type': 'text/html'
+ });
+
+ // Collect post data (request body)
+ var requestBody = '';
+ request.on('data', function(data) {
+ requestBody += data.toString();
+ });
+
+ // Process post data
+ request.on('end', function() {
+ console.log('Received complete request body.');
+
+ // Extract password from the request and hash it
+ const postData = querystring.parse(requestBody);
+ const password = postData['password'];
+ const passwordHash = SHA512Digest(password);
+
+ // Is the password good?
+ if (passwordHash === PASSWD_HASH) {
+ // Yes, it is:
+ // Create session
+ const sessionId = createSession();
+
+ response.writeHead(200, {
+ 'Content-Type': 'text/html',
+ 'Set-Cookie': 'sesid=' + sessionId
+ });
+ response.end('Access granted! You\'re in!');
+ return;
+ } else {
+ response.writeHead(401, {
+ 'Content-Type': 'text/html'
+ });
+ response.end('WOOP! WOOP! Invalid password!
Need to reset your password? Replace the password hash in access.yaml with a new one.
This password hashes to: ' + passwordHash + '.');
+ return;
+ }
+
+ return;
+ });
+}
+
+function serverStaticFiles(request, response) {
// Fetch requested file
fs.readFile(__dirname + request.url, function (error, data) {
if(!error) {
@@ -52,6 +198,33 @@ var server = http.createServer(function (request, response) {
return;
}
});
+}
+
+var server = http.createServer(function (request, response) {
+ // Handle requests here...
+
+ // If request is trying to authenticate
+ if (request.url == '/api--authenticate') {
+ testAuthentication(request, response);
+ return;
+ }
+ else /* Request is not trying to authenticate */ {
+
+ // Parse request cookies
+ const cookies = parseCookies(request);
+ console.log(cookies);
+
+ // Check if the user is authenticated
+ if ((cookies.hasOwnProperty('sesid')) && (isSessisionValid(cookies['sesid']))) {
+ // Session is authenticated. File access is granted.
+ serverStaticFiles(request, response);
+ return;
+
+ } else /* Session is not authenticated */ {
+ serveAuthenticatePage(request, response);
+ return;
+ }
+ }
});
const port = 80;