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
)
22 this.loadCache(this.cacheDir
, '')
23 require('fs').readFile('./strapp.js', (err
, data
) => {
26 this.cache
['strapp.js'] = ['application/javascript',data
]
30 loadCache(dirPath
, prefix
) {
31 require('fs').readdir(this.cacheDir
, (err
, files
) => {
35 files
.forEach((entName
) => {
36 let filePath
= `${dirPath}${require('path').sep}${entName}`
37 require('fs').stat(filePath
, (err
, stat
) => {
40 else if (stat
&& stat
.isDirectory())
41 this.loadCache(filePath
, `${entName}/`)
43 require('fs').readFile(filePath
, (err
, data
) => {
47 this.cache
[`${prefix}${entName}`] =
48 [require('mime').lookup(filePath
), data
]
56 this.httpd
= require('https')
57 .createServer(this.tls
, (rq
, rs
) => this.httpRequest(rq
, rs
))
59 this.httpd
= require('http')
60 .createServer((rq
, rs
) => this.httpRequest(rq
, rs
))
61 this.httpd
.listen(this, () => this.port
= this.httpd
.address().port
)
63 httpRequest(request
, response
) {
64 const htArgv
= request
.url
.slice(1).split('?')
65 dlog(`request for ${request.url} received`)
66 if (request
.method
=== 'GET' && htArgv
[0] in this.cache
) {
67 dlog(`found in cache`)
68 response
.writeHead(200, { 'Content-Type': this.cache
[htArgv
[0]][0] })
69 response
.write(this.cache
[htArgv
[0]][1])
74 const authHeader
= request
.headers
['Authorization']
76 const authInfo
= authHeader
.split(' ')
77 dlog(`authInfo: ${authInfo}`)
78 if (authInfo
[0] === 'STRAPP') {
80 switch (authInfo
.length
) {
81 case 3: answer
= authInfo
[2]
82 case 2: pubKey
= authInfo
[1]
85 response
.writeHead(400)
89 this.authenticate(pubKey
, answer
).then((question
, authenticated
) => {
90 response
.setHeader('WWW-Authenticate',
91 `STRAPP ${this.question(pubKey)} ${this.tls.key}`)
93 this.processRequestData(request
, response
, htArgv
, pubKey
)
96 response
.writeHead(401)
99 }).catch((err
) => console
.log(err
))
103 this.processRequestData(request
, response
, htArgv
, pubKey
)
105 processRequestData(request
, response
, args
, pubKey
) {
106 if (request
.method
=== 'PUT' || request
.method
=== 'POST') {
108 request
.on('data', (chunk
) => {
110 if (data
.length
> 5e5
)
111 request
.connection
.destroy()
113 request
.on('end', this.sendData(request
, response
, args
, pubKey
, data
))
116 this.sendData(request
, response
, args
, pubKey
)
118 authenticate(pubKey
, answer
) {
119 return new Promise((resolve
, reject
) => {
120 require('crypto').randomBytes(256, (err
, buf
) => {
124 let authenticated
= false
125 if (pubKey
in this.answers
) {
126 const answerBuf
= Buffer
.from(answer
)
127 require('crypto').privateDecrypt({ key
: this.tls
.cert
}, answerBuf
)
128 authenticated
= this.answers
[pubKey
].equals(answerBuf
)
130 this.answers
[pubKey
] = buf
131 let question
= Buffer
.from(buf
)
132 require('crypto').publicEncrypt({ key
: pubKey
}, question
)
133 resolve(question
.toString(), authenticated
)
138 sendData(request
, response
, args
, pubKey
, data
) {
140 let msgID
= this.msgID
++
141 if (this.msgID
>= this.pendingResponses
.length
)
143 this.pendingResponses
[msgID
] = response
146 this.app
.send(`${args[0]} ${request.method} ${pubKey} ${msgID} ${data}`)
149 response
.writeHead(200, { 'Content-Type': 'text/html' })
150 response
.write(Server
.bootstrapp
)
157 tls
: { key
: '', cert
: '' },
161 pendingResponses
: new Array(20),
164 remoteAdminKey
: undefined,
165 controller
: undefined,
170 Server
.bootstrapp
= `
173 <title>bootstrapp</title>
175 <script src='/strapp.js'></script>
182 //TODO: module.exports = Server
184 const server
= new Server({
186 tls
: { key
: require('fs').readFileSync('../certs/key.pem'),
187 cert
: require('fs').readFileSync('../certs/cert.pem')
192 // //TTL implementation
193 // setInterval(30000, () => {
194 // applications.forEach((app) => {
195 // app.sockets.forEach((socket, idx) => {
196 // if (!socket.connected) {
197 // console.log(`Socket ${socket} timed out`)
198 // socket.terminate()
199 // app.sockets.splice(idx, 1)
202 // socket.connected = false
203 // socket.ping('', false, true)
209 // const startApplication = (conf) => {
212 // httpd = require('https')
213 // .createServer(conf.tls, listen)
214 // .listen(conf, conf.callback)
216 // httpd = require('http')
217 // .createServer(listen)
218 // .listen(conf, conf.callback)
223 // openSocket: function(pubKey) {
224 // const wsd = require('ws').Server()({
226 // verifyClient: (info) => pubKey === parseAuth(info.req).pubKey
227 // && authenticate(info.req)
229 // let timeout = setTimeout(30000, wsd.close())
230 // wsd.on('connection', (socket) => {
231 // clearTimeout(timeout)
232 // socket.on('pong', socket.connected = true)
233 // socket.emit('pong')
234 // this.sockets.push(socket)
241 // startApplication(opts.port, opts.tls)
244 // httpd = require('https')
245 // .createServer(conf.tls)
246 // .listen(conf, resolve)
248 // httpd = require('http')
253 // const listener = function(port, conf) Promise((resolve, reject) => {
254 // resolve(Object.create(null, {
255 // httpd: { value: null },
256 // port: { value: 0 },
257 // transceiver: { value: null, writable: true, configurable: true }
269 // * @summary Authenticate a request
270 // * @arg {http.ClientRequest} request
271 // * @return {bool} true if the request is authentic
273 // const authenticate = (request) => {
277 // const startPortForward = function(port, httpOpts, transceiver) {
278 // const httpd, port, channel
282 // * Assumes Authorization header of form STRAPP {pubKey} {answer}
283 // * @summary parse the components of a STRAPP authorization header
284 // * @arg {http.ClientRequest} request
285 // * @return {string} the empty string '' on error
287 // const parseAuth = (request) => {
288 // if (request.headers['Authorization']) {
289 // request.auth = request.headers['Authorization'].split(' ')
290 // if (request.auth[0] === 'STRAPP')
291 // switch(request.auth.length) {
292 // case 3: request.answer = request.auth[2]
293 // case 2: request.pubKey = request.auth[1]
301 // * @summary Creates a promise that resolves a websocket to an application host
302 // * @arg {string} pubKey - the public key authorized to connect.
303 // * @arg {Object} [conf] - configuration for the websocket.
304 // * @arg {Object} [conf.tls] - TLS configuration for HTTPS layer security
305 // * @arg {string} [conf.tls.cert] - domain certificate for HTTPS
306 // * @arg {string} [conf.tls.key] - domain public key for HTTPS
307 // * @arg {string} [conf.host] - host to attach the port listener to (all IPs)
308 // * @arg {number} [conf.backlog] - how many connections to buffer (511)
309 // * @arg {boolean} [conf.exclusive] - don't share the port (false)
311 // const pWebSocket = function(pubKey, conf) Promise((resolve, reject) => {
315 // reject(new Error('no pubKey'))
316 // require('get-port')().then((port) => {
318 // new Promise((resolve, reject) => {
320 // httpd = require('https')
321 // .createServer(conf.tls)
322 // .listen(conf, resolve)
324 // httpd = require('http')
328 // const wsd = new Promise((resolve, reject) => {
329 // (require('ws').Server())({
331 // verifyClient: (info) => pubKey === parseAuth(info.req).pubKey
332 // && authenticate(info.req)
333 // }).on('connection', resolve)
334 // timeout = setTimeout(30000, reject('timeout'))
335 // }).then((socket) => {
336 // clearTimeout(timeout)
337 // const webSocket = Object.create(null, {
338 // socket: { value: socket },
339 // httpd: { value: httpd },
340 // port: { value: port },
341 // transmit: { value: socket.send },
342 // receive: { value: this.receive }
344 // socket.on('message', webSocket.receive)
345 // resolve(webSocket)
351 // Object.keys(opts['bindings']).forEach((key) => {
352 // router.createBind(key, opts['bindings'][key])
355 // router.startHttpServer({
356 // port: opts['port'],
357 // skelFile: './skel.html',
358 // clientJS: opts['client-js'],
359 // hostJS: opts['host-js'],
360 // httpdRoot: opts['file-dir'],
362 // certFile: opts['ca-cert'],
363 // keyFile: opts['ca-key']