Sellery/server.js

268 lines
7.8 KiB
JavaScript
Raw Permalink Normal View History

2022-04-08 21:41:43 +02:00
var https = require('https');
2022-04-08 18:09:52 +02:00
var fs = require('fs');
var path = require('path');
2022-04-08 20:46:36 +02:00
var querystring = require('querystring');
var crypto = require('crypto');
2022-04-08 21:00:17 +02:00
var toml = require('toml');
2022-04-08 20:46:36 +02:00
2022-04-08 21:00:17 +02:00
// Parse config file
const config = toml.parse(fs.readFileSync('config.toml', 'utf-8'));
2022-04-08 18:09:52 +02:00
// Compose webroot directory
const webrootDir = config.WEBROOT
.replace('$WORKING_DIR', __dirname);
2022-04-08 20:46:36 +02:00
// Just a few mime types
2022-04-08 18:09:52 +02:00
const contentTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.webp': 'image/webp',
'.bmp': 'image/bmp',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.mp4': 'video/mp4',
'.webm': 'video/webm',
};
2022-04-08 20:46:36 +02:00
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?
2022-04-08 21:00:17 +02:00
if (Date.now() - sessionById.timestamp > config.SESSION_DURATION * 1000) {
2022-04-08 20:46:36 +02:00
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;
}
2022-04-08 18:09:52 +02:00
2022-04-08 20:46:36 +02:00
// 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;
}
2022-04-08 21:01:43 +02:00
//! Duh?
2022-04-08 20:46:36 +02:00
function SHA512Digest(string) {
return crypto.createHash('sha512').update(string, 'utf-8').digest('hex');
}
//! Duh?
function hashPassword(password) {
// Salt it
password = 'PQoFvPytZyi7yW/uX4IQ5I' + password + 'ZNUwEfVyn55pI91Myp2+RrOXWFtx5';
// Shake it
for (let i = 0; i < password.length * 500; i++) {
password = SHA512Digest(password + 'z4J7qWugOOfjd8FBbpcFyANjfe4axc4fM2Dj65IMr')
}
// Serve it
return password;
}
2022-04-08 21:01:43 +02:00
//! This function simply serves the authentication page
2022-04-08 20:46:36 +02:00
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;
}
});
}
2022-04-08 21:01:43 +02:00
//! This function will handle the api--authenticate call, checks if the users password
//! is valid, and if yes, creates a new session and sets the session cookie.
2022-04-08 20:46:36 +02:00
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 = hashPassword(password);
2022-04-08 20:46:36 +02:00
// Is the password good?
2022-04-08 21:00:17 +02:00
if (passwordHash === config.PASSWD_HASH) {
2022-04-08 20:46:36 +02:00
// Yes, it is:
// Create session
const sessionId = createSession();
response.writeHead(303, {
2022-04-08 20:46:36 +02:00
'Content-Type': 'text/html',
'Set-Cookie': 'sesid=' + sessionId,
'Location': request.headers.referer
2022-04-08 20:46:36 +02:00
});
response.end('Access granted! You\'re in!');
return;
} else {
// Log failed login attempt
console.log('Failed login attempt by ' + request.connection.remoteAddress);
const now = new Date();
fs.appendFile(
'failed-login-attempts.txt',
'[' + (now.getDate()+1) + '.' + (now.getMonth()+1) + '.' + now.getFullYear() + ' ' + now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds() + '] Failed login attempt by ' + request.connection.remoteAddress + '\n',
() => {}
);
2022-04-08 20:46:36 +02:00
response.writeHead(401, {
'Content-Type': 'text/html'
});
2022-04-08 22:25:49 +02:00
response.end('WOOP! WOOP! Invalid password!<br>This attempt as been logged.<br><br>Need to reset your password? Replace the password hash in config.toml with a new one.<br>This password hashes to: <em>' + passwordHash + '</em>.');
2022-04-08 20:46:36 +02:00
return;
}
return;
});
}
2022-04-08 21:01:43 +02:00
//! This function just serves files as they are...
2022-04-08 20:46:36 +02:00
function serverStaticFiles(request, response) {
// Return index.html if no url is supplied
const urlToFetch = (request.url != '/') ? request.url : '/index.html';
2022-04-08 18:09:52 +02:00
// Fetch requested file
fs.readFile(webrootDir + urlToFetch, function (error, data) {
2022-04-08 18:09:52 +02:00
if(!error) {
const mimetype = path.extname(urlToFetch);
2022-04-08 18:09:52 +02:00
if (!(typeof mimetype === 'undefined')) {
response.writeHead(200, {
'Content-Type': mimetype
});
response.end(data);
return;
} else {
response.writeHead(500, {
'Content-Type': 'text/html'
});
console.error('Unknown file mime type for file: ' + __dirname + urlToFetch);
2022-04-08 18:09:52 +02:00
response.end('Unknown file mime type.');
return;
}
} else {
response.writeHead(404, {
'Content-Type': 'text/html'
});
console.error('File not found: ' + JSON.stringify(error));
response.end('File not found.');
return;
}
});
2022-04-08 20:46:36 +02:00
}
2022-04-08 21:41:43 +02:00
const serverOptions = {
key: fs.readFileSync(config.SSL_KEY_FILE),
cert: fs.readFileSync(config.SSL_CERT_FILE)
};
var server = https.createServer(serverOptions, function (request, response) {
2022-04-08 20:46:36 +02:00
// 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);
// 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;
}
}
2022-04-08 18:09:52 +02:00
});
2022-04-08 21:41:43 +02:00
server.listen(config.WEBSERVER_PORT);
console.log('Node.js sellery server running and listening to port ' + config.WEBSERVER_PORT);
2022-04-08 18:09:52 +02:00