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
2022-04-08 22:24:41 +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' ) ;
}
2022-04-10 16:19:25 +02:00
//! 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' ] ;
2022-04-10 16:19:25 +02:00
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 ( ) ;
2022-04-08 21:31:18 +02:00
response . writeHead ( 303 , {
2022-04-08 20:46:36 +02:00
'Content-Type' : 'text/html' ,
2022-04-08 21:31:18 +02:00
'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 {
2022-04-08 22:06:03 +02:00
// 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 ) {
2022-04-10 16:22:19 +02:00
// 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
2022-04-10 16:22:19 +02:00
fs . readFile ( webrootDir + urlToFetch , function ( error , data ) {
2022-04-08 18:09:52 +02:00
if ( ! error ) {
2022-04-10 16:22:19 +02:00
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'
} ) ;
2022-04-10 16:22:19 +02:00
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