From: ken Date: Tue, 1 Aug 2017 23:18:52 +0000 (+0000) Subject: initial X-Git-Url: https://git.kengrimes.com/?p=henge%2Fkiak.git;a=commitdiff_plain;h=81f466c54cf9a0a2428000d041651087a94aeb87 initial --- diff --git a/strappServer.js b/strappServer.js new file mode 100644 index 0000000..2f8d383 --- /dev/null +++ b/strappServer.js @@ -0,0 +1,365 @@ +/** +* @file Node entry and main driver +* @author Jordan Lavatai, Ken Grimes +* @version 0.0.3 +* @license AGPL-3.0 +* @copyright Strapp.io 2017 +* @summary HTTP(S) Router that uses the first directory in the requested URL +* as the route name +*/ +'use strict' +const opts = require('./opts.js') + +const dlog = (msg) => console.log(msg) + +class Server { + constructor(...props) { + Object.assign(this, new.target.defaults, ...props) + this.init() + } + init() { + if (this.cacheDir) + this.loadCache(this.cacheDir, '') + require('fs').readFile('./strapp.js', (err, data) => { + if (err) + throw new Error (err) + this.cache['strapp.js'] = ['application/javascript',data] + }) + this.startHttpd() + } + loadCache(dirPath, prefix) { + require('fs').readdir(this.cacheDir, (err, files) => { + if (err) + console.log(err) + else + files.forEach((entName) => { + let filePath = `${dirPath}${require('path').sep}${entName}` + require('fs').stat(filePath, (err, stat) => { + if (err) + console.log(err) + else if (stat && stat.isDirectory()) + this.loadCache(filePath, `${entName}/`) + else + require('fs').readFile(filePath, (err, data) => { + if (err) + console.log(err) + else + this.cache[`${prefix}${entName}`] = + [require('mime').lookup(filePath), data] + }) + }) + }) + }) + } + startHttpd() { + if (this.tls) + this.httpd = require('https') + .createServer(this.tls, (rq, rs) => this.httpRequest(rq, rs)) + else + this.httpd = require('http') + .createServer((rq, rs) => this.httpRequest(rq, rs)) + this.httpd.listen(this, () => this.port = this.httpd.address().port) + } + httpRequest(request, response) { + const htArgv = request.url.slice(1).split('?') + dlog(`request for ${request.url} received`) + if (request.method === 'GET' && htArgv[0] in this.cache) { + dlog(`found in cache`) + response.writeHead(200, { 'Content-Type': this.cache[htArgv[0]][0] }) + response.write(this.cache[htArgv[0]][1]) + response.end() + return + } + let pubKey = '' + const authHeader = request.headers['Authorization'] + if (authHeader) { + const authInfo = authHeader.split(' ') + dlog(`authInfo: ${authInfo}`) + if (authInfo[0] === 'STRAPP') { + let answer = '' + switch (authInfo.length) { + case 3: answer = authInfo[2] + case 2: pubKey = authInfo[1] + break + default: + response.writeHead(400) + response.end() + return + } + this.authenticate(pubKey, answer).then((question, authenticated) => { + response.setHeader('WWW-Authenticate', + `STRAPP ${this.question(pubKey)} ${this.tls.key}`) + if (authenticated) { + this.processRequestData(request, response, htArgv, pubKey) + } + else { + response.writeHead(401) + response.end() + } + }).catch((err) => console.log(err)) + } + } + else + this.processRequestData(request, response, htArgv, pubKey) + } + processRequestData(request, response, args, pubKey) { + if (request.method === 'PUT' || request.method === 'POST') { + let data = '' + request.on('data', (chunk) => { + data += chunk + if (data.length > 5e5) + request.connection.destroy() + }) + request.on('end', this.sendData(request, response, args, pubKey, data)) + } + else + this.sendData(request, response, args, pubKey) + } + authenticate(pubKey, answer) { + return new Promise((resolve, reject) => { + require('crypto').randomBytes(256, (err, buf) => { + if (err) + reject(err) + else { + let authenticated = false + if (pubKey in this.answers) { + const answerBuf = Buffer.from(answer) + require('crypto').privateDecrypt({ key: this.tls.cert }, answerBuf) + authenticated = this.answers[pubKey].equals(answerBuf) + } + this.answers[pubKey] = buf + let question = Buffer.from(buf) + require('crypto').publicEncrypt({ key: pubKey }, question) + resolve(question.toString(), authenticated) + } + }) + }) + } + sendData(request, response, args, pubKey, data) { + if (this.app) { + let msgID = this.msgID++ + if (this.msgID >= this.pendingResponses.length) + this.msgID = 0 + this.pendingResponses[msgID] = response + if (!data) + data = '' + this.app.send(`${args[0]} ${request.method} ${pubKey} ${msgID} ${data}`) + } + else { + response.writeHead(200, { 'Content-Type': 'text/html' }) + response.write(Server.bootstrapp) + response.end() + } + } +} +Server.defaults = { + port: 0, + tls: { key: '', cert: '' }, + host: undefined, + cache: {}, + cacheDir: './www', + pendingResponses: new Array(20), + msgID: 0, + answers: {}, + remoteAdminKey: undefined, + controller: undefined, + app: undefined, + httpd: undefined, + wsd: undefined +} +Server.bootstrapp = ` + + + bootstrapp + + + + + + +` + +//TODO: module.exports = Server + +const server = new Server({ + port: 2443, + tls: { key: require('fs').readFileSync('../certs/key.pem'), + cert: require('fs').readFileSync('../certs/cert.pem') + } +}) + + +// //TTL implementation +// setInterval(30000, () => { +// applications.forEach((app) => { +// app.sockets.forEach((socket, idx) => { +// if (!socket.connected) { +// console.log(`Socket ${socket} timed out`) +// socket.terminate() +// app.sockets.splice(idx, 1) +// } +// else { +// socket.connected = false +// socket.ping('', false, true) +// } +// }) +// }) +// }) + +// const startApplication = (conf) => { +// let httpd +// if (conf.tls) +// httpd = require('https') +// .createServer(conf.tls, listen) +// .listen(conf, conf.callback) +// else +// httpd = require('http') +// .createServer(listen) +// .listen(conf, conf.callback) + +// let app = { +// httpd: httpd, +// sockets: [], +// openSocket: function(pubKey) { +// const wsd = require('ws').Server()({ +// noServer: true, +// verifyClient: (info) => pubKey === parseAuth(info.req).pubKey +// && authenticate(info.req) +// }) +// let timeout = setTimeout(30000, wsd.close()) +// wsd.on('connection', (socket) => { +// clearTimeout(timeout) +// socket.on('pong', socket.connected = true) +// socket.emit('pong') +// this.sockets.push(socket) +// }) +// } +// } +// return app +// } + +// startApplication(opts.port, opts.tls) + +// if (conf.tls) +// httpd = require('https') +// .createServer(conf.tls) +// .listen(conf, resolve) +// else +// httpd = require('http') +// .createServer() +// .listen(resolve) + + +// const listener = function(port, conf) Promise((resolve, reject) => { +// resolve(Object.create(null, { +// httpd: { value: null }, +// port: { value: 0 }, +// transceiver: { value: null, writable: true, configurable: true } +// })) +// }) + +// const a = { +// httpd: null, +// port: 0, +// transceiver: null +// } + + +// /** @func +// * @summary Authenticate a request +// * @arg {http.ClientRequest} request +// * @return {bool} true if the request is authentic +// */ +// const authenticate = (request) => { +// return true +// } + +// const startPortForward = function(port, httpOpts, transceiver) { +// const httpd, port, channel +// } + +// /** @func +// * Assumes Authorization header of form STRAPP {pubKey} {answer} +// * @summary parse the components of a STRAPP authorization header +// * @arg {http.ClientRequest} request +// * @return {string} the empty string '' on error +// */ +// const parseAuth = (request) => { +// if (request.headers['Authorization']) { +// request.auth = request.headers['Authorization'].split(' ') +// if (request.auth[0] === 'STRAPP') +// switch(request.auth.length) { +// case 3: request.answer = request.auth[2] +// case 2: request.pubKey = request.auth[1] +// default: +// } +// } +// return request +// } + +// /** @func +// * @summary Creates a promise that resolves a websocket to an application host +// * @arg {string} pubKey - the public key authorized to connect. +// * @arg {Object} [conf] - configuration for the websocket. +// * @arg {Object} [conf.tls] - TLS configuration for HTTPS layer security +// * @arg {string} [conf.tls.cert] - domain certificate for HTTPS +// * @arg {string} [conf.tls.key] - domain public key for HTTPS +// * @arg {string} [conf.host] - host to attach the port listener to (all IPs) +// * @arg {number} [conf.backlog] - how many connections to buffer (511) +// * @arg {boolean} [conf.exclusive] - don't share the port (false) +// */ +// const pWebSocket = function(pubKey, conf) Promise((resolve, reject) => { +// let timeout +// conf = conf || {} +// if (!pubKey) +// reject(new Error('no pubKey')) +// require('get-port')().then((port) => { +// conf.port = port +// new Promise((resolve, reject) => { +// if (conf.tls) +// httpd = require('https') +// .createServer(conf.tls) +// .listen(conf, resolve) +// else +// httpd = require('http') +// .createServer() +// .listen(resolve) +// }).then(() => { +// const wsd = new Promise((resolve, reject) => { +// (require('ws').Server())({ +// server: httpd, +// verifyClient: (info) => pubKey === parseAuth(info.req).pubKey +// && authenticate(info.req) +// }).on('connection', resolve) +// timeout = setTimeout(30000, reject('timeout')) +// }).then((socket) => { +// clearTimeout(timeout) +// const webSocket = Object.create(null, { +// socket: { value: socket }, +// httpd: { value: httpd }, +// port: { value: port }, +// transmit: { value: socket.send }, +// receive: { value: this.receive } +// }) +// socket.on('message', webSocket.receive) +// resolve(webSocket) +// }).catch(reject) +// }).catch(reject) +// }).catch(reject) +// }) + +// Object.keys(opts['bindings']).forEach((key) => { +// router.createBind(key, opts['bindings'][key]) +// }) + +// router.startHttpServer({ +// port: opts['port'], +// skelFile: './skel.html', +// clientJS: opts['client-js'], +// hostJS: opts['host-js'], +// httpdRoot: opts['file-dir'], +// tls: { +// certFile: opts['ca-cert'], +// keyFile: opts['ca-key'] +// } +// })