2 * @file Node entry and main driver
3 * @author Jordan Lavatai, Ken Grimes
6 * @copyright Strapp.io 2017
7 * @summary HTTP(S) Router that uses the first directory in the requested URL
11 const opts
= require('./opts.js')
13 const dlog
= (msg
) => console
.log(msg
)
16 constructor(...props
) {
17 Object
.assign(this, new.target
.defaults
, ...props
)
18 this.pendingResponses
= new Array(this.responseCacheSize
)
24 this.loadCache(this.cacheDir
, '')
25 require('fs').readFile('./www/strapp.min.js', (err
, data
) => {
28 this.cache
['strapp.min.js'] = ['application/javascript',data
]
32 loadCache(dirPath
, prefix
) {
33 require('fs').readdir(this.cacheDir
, (err
, files
) => {
37 files
.forEach((entName
) => {
38 let filePath
= `${dirPath}${require('path').sep}${entName}`
39 require('fs').stat(filePath
, (err
, stat
) => {
42 else if (stat
&& stat
.isDirectory())
43 this.loadCache(filePath
, `${entName}/`)
45 require('fs').readFile(filePath
, (err
, data
) => {
49 this.cache
[`${prefix}${entName}`] =
50 [require('mime').lookup(filePath
), data
]
58 this.httpd
= require('https')
59 .createServer(this.tls
, (rq
, rs
) => this.httpRequest(rq
, rs
))
61 this.httpd
= require('http')
62 .createServer((rq
, rs
) => this.httpRequest(rq
, rs
))
63 this.httpd
.listen(this, () => this.port
= this.httpd
.address().port
)
65 httpRequest(request
, response
) {
66 const htArgv
= request
.url
.slice(1).split('?')
67 dlog(`request for ${request.url} received`)
68 if (request
.method
=== 'GET' && htArgv
[0] in this.cache
) {
69 dlog(`found ${htArgv[0]} in cache`)
70 response
.writeHead(200, { 'Content-Type': this.cache
[htArgv
[0]][0] })
71 response
.write(this.cache
[htArgv
[0]][1])
76 const authHeader
= request
.headers
['Authorization']
78 const authInfo
= authHeader
.split(' ')
79 dlog(`authInfo: ${authInfo}`)
80 if (authInfo
[0] === 'STRAPP') {
82 switch (authInfo
.length
) {
83 case 3: answer
= authInfo
[2]
84 case 2: pubKey
= authInfo
[1]
87 response
.writeHead(400)
91 this.authenticate(pubKey
, answer
).then((question
, authenticated
) => {
92 response
.setHeader('WWW-Authenticate',
93 `STRAPP ${this.question(pubKey)} ${this.tls.key}`)
95 this.processRequestData(request
, response
, htArgv
, pubKey
)
98 response
.writeHead(401)
101 }).catch((err
) => console
.log(err
))
105 this.processRequestData(request
, response
, htArgv
, pubKey
)
107 processRequestData(request
, response
, args
, pubKey
) {
108 if (request
.method
=== 'PUT' || request
.method
=== 'POST') {
110 request
.on('data', (chunk
) => {
112 if (data
.length
> 5e5
)
113 request
.connection
.destroy()
115 request
.on('end', this.sendData(request
, response
, args
, pubKey
, data
))
118 this.sendData(request
, response
, args
, pubKey
)
120 authenticate(pubKey
, answer
) {
121 return new Promise((resolve
, reject
) => {
122 require('crypto').randomBytes(256, (err
, buf
) => {
126 let authenticated
= false
127 if (pubKey
in this.answers
) {
128 const answerBuf
= Buffer
.from(answer
)
129 require('crypto').privateDecrypt({ key
: this.tls
.cert
}, answerBuf
)
130 authenticated
= this.answers
[pubKey
].equals(answerBuf
)
132 this.answers
[pubKey
] = buf
133 let question
= Buffer
.from(buf
)
134 require('crypto').publicEncrypt({ key
: pubKey
}, question
)
135 resolve(question
.toString(), authenticated
)
140 sendData(request
, response
, args
, pubKey
, data
) {
142 let msgID
= this.msgID
++
143 if (this.msgID
>= this.pendingResponses
.length
)
145 this.pendingResponses
[msgID
] = response
148 this.app
.send(`${args[0]} ${request.method} ${pubKey} ${msgID} ${data}`)
151 require('fs').readFile('./bootstrapp.html', (err
, data
) => {
153 response
.writeHead(500)
157 response
.writeHead(200, { 'Content-Type': 'text/html' })
167 tls
: { key
: '', cert
: '' },
171 responseCacheSize
: 40,
174 remoteAdminKey
: undefined,
175 controller
: undefined,
181 //TODO: module.exports = Server
183 const server
= new Server({
185 tls
: { key
: require('fs').readFileSync('../certs/key.pem'),
186 cert
: require('fs').readFileSync('../certs/cert.pem')
191 // //TTL implementation
192 // setInterval(30000, () => {
193 // applications.forEach((app) => {
194 // app.sockets.forEach((socket, idx) => {
195 // if (!socket.connected) {
196 // console.log(`Socket ${socket} timed out`)
197 // socket.terminate()
198 // app.sockets.splice(idx, 1)
201 // socket.connected = false
202 // socket.ping('', false, true)
208 // const startApplication = (conf) => {
211 // httpd = require('https')
212 // .createServer(conf.tls, listen)
213 // .listen(conf, conf.callback)
215 // httpd = require('http')
216 // .createServer(listen)
217 // .listen(conf, conf.callback)
222 // openSocket: function(pubKey) {
223 // const wsd = require('ws').Server()({
225 // verifyClient: (info) => pubKey === parseAuth(info.req).pubKey
226 // && authenticate(info.req)
228 // let timeout = setTimeout(30000, wsd.close())
229 // wsd.on('connection', (socket) => {
230 // clearTimeout(timeout)
231 // socket.on('pong', socket.connected = true)
232 // socket.emit('pong')
233 // this.sockets.push(socket)
240 // startApplication(opts.port, opts.tls)
243 // httpd = require('https')
244 // .createServer(conf.tls)
245 // .listen(conf, resolve)
247 // httpd = require('http')
252 // const listener = function(port, conf) Promise((resolve, reject) => {
253 // resolve(Object.create(null, {
254 // httpd: { value: null },
255 // port: { value: 0 },
256 // transceiver: { value: null, writable: true, configurable: true }
268 // * @summary Authenticate a request
269 // * @arg {http.ClientRequest} request
270 // * @return {bool} true if the request is authentic
272 // const authenticate = (request) => {
276 // const startPortForward = function(port, httpOpts, transceiver) {
277 // const httpd, port, channel
281 // * Assumes Authorization header of form STRAPP {pubKey} {answer}
282 // * @summary parse the components of a STRAPP authorization header
283 // * @arg {http.ClientRequest} request
284 // * @return {string} the empty string '' on error
286 // const parseAuth = (request) => {
287 // if (request.headers['Authorization']) {
288 // request.auth = request.headers['Authorization'].split(' ')
289 // if (request.auth[0] === 'STRAPP')
290 // switch(request.auth.length) {
291 // case 3: request.answer = request.auth[2]
292 // case 2: request.pubKey = request.auth[1]
300 // * @summary Creates a promise that resolves a websocket to an application host
301 // * @arg {string} pubKey - the public key authorized to connect.
302 // * @arg {Object} [conf] - configuration for the websocket.
303 // * @arg {Object} [conf.tls] - TLS configuration for HTTPS layer security
304 // * @arg {string} [conf.tls.cert] - domain certificate for HTTPS
305 // * @arg {string} [conf.tls.key] - domain public key for HTTPS
306 // * @arg {string} [conf.host] - host to attach the port listener to (all IPs)
307 // * @arg {number} [conf.backlog] - how many connections to buffer (511)
308 // * @arg {boolean} [conf.exclusive] - don't share the port (false)
310 // const pWebSocket = function(pubKey, conf) Promise((resolve, reject) => {
314 // reject(new Error('no pubKey'))
315 // require('get-port')().then((port) => {
317 // new Promise((resolve, reject) => {
319 // httpd = require('https')
320 // .createServer(conf.tls)
321 // .listen(conf, resolve)
323 // httpd = require('http')
327 // const wsd = new Promise((resolve, reject) => {
328 // (require('ws').Server())({
330 // verifyClient: (info) => pubKey === parseAuth(info.req).pubKey
331 // && authenticate(info.req)
332 // }).on('connection', resolve)
333 // timeout = setTimeout(30000, reject('timeout'))
334 // }).then((socket) => {
335 // clearTimeout(timeout)
336 // const webSocket = Object.create(null, {
337 // socket: { value: socket },
338 // httpd: { value: httpd },
339 // port: { value: port },
340 // transmit: { value: socket.send },
341 // receive: { value: this.receive }
343 // socket.on('message', webSocket.receive)
344 // resolve(webSocket)
350 // Object.keys(opts['bindings']).forEach((key) => {
351 // router.createBind(key, opts['bindings'][key])
354 // router.startHttpServer({
355 // port: opts['port'],
356 // skelFile: './skel.html',
357 // clientJS: opts['client-js'],
358 // hostJS: opts['host-js'],
359 // httpdRoot: opts['file-dir'],
361 // certFile: opts['ca-cert'],
362 // keyFile: opts['ca-key']