Implemented simple authentication page
This commit is contained in:
parent
d6d5f85d1a
commit
82faaee4e6
14
authenticate.html
Normal file
14
authenticate.html
Normal 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
139
package-lock.json
generated
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
181
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!<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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user