Implemented simple authentication page

This commit is contained in:
Leonetienne 2022-04-08 20:46:36 +02:00
parent d6d5f85d1a
commit 82faaee4e6
4 changed files with 334 additions and 5 deletions

14
authenticate.html Normal file
View File

@ -0,0 +1,14 @@
<html>
<head>
<title>Authentication required</title>
</head>
<body>
<h1>Authentication required</h1>
<form action="/api--authenticate" method="POST">
<label for="password">Password</label>
<input type="password" id="password" name="password"></input>
<input type="submit" value="Submit">
</form>
</body>
</html>

139
package-lock.json generated
View File

@ -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"
}
}
}
}

View File

@ -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"
}
}

181
server.js
View File

@ -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!<br><br>Need to reset your password? Replace the password hash in access.yaml with a new one.<br>This password hashes to: <em>' + passwordHash + '</em>.');
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;