Skip to content
This repository has been archived by the owner on Jun 12, 2018. It is now read-only.

Commit

Permalink
add a axon <-> websocket proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
Romain committed May 24, 2016
1 parent 21da656 commit cb48733
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 63 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"americano": "0.4.5",
"async": "1.5.2",
"axon": "0.6.1",
"cookie-parser": "1.4.1",
"cookie-session": "1.2.0",
"cozy-url-sdk": "1.0.2",
Expand All @@ -33,7 +34,8 @@
"pug-runtime": "2.0.0",
"randomstring": "1.1.4",
"request-json": "0.5.5",
"send": "0.13.2"
"send": "0.13.2",
"socket.io": "1.4.5"
},
"devDependencies": {
"chai": "3.5.0",
Expand Down
3 changes: 1 addition & 2 deletions server/controllers/routes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ experiment = require './experimental'
sharing = require './sharing'
utils = require '../middlewares/authentication'

passport = require 'passport'

module.exports =

'routes': get: index.showRoutes
Expand Down Expand Up @@ -47,6 +45,7 @@ module.exports =
'apps/:name*': all: [utils.isAuthenticated, apps.appWithSlash]

'replication/*': all: devices.replication
# 'ds-api/socket.io': websocket -> DS axon (see lib/websocket)
'ds-api/*': all: devices.dsApi
'versions': get: devices.getVersions
# Temporary - 01/05/14
Expand Down
62 changes: 2 additions & 60 deletions server/lib/proxy.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,12 @@ http = require 'http'
# the fact that too many file descriptors are opened. It stops creating them
# and create a waiting queue when the number of fd reaches the limit set by the
# kernel.
fs = require 'graceful-fs'
httpProxy = require 'http-proxy'
async = require 'async'
passport = require 'passport'
config = require '../config'
urlHelper = require 'cozy-url-sdk'
logger = require('printit')
date: false
prefix: 'lib:proxy'

router = require './router'
initializeWebsocketProxy = require './websocket'
errorHandler = require '../middlewares/errors'

# singleton variable
Expand All @@ -37,57 +32,4 @@ module.exports.initializeProxy = (app, server) ->
name: 'error'
errorHandler err, req, res

# Manage socket.io's websocket
server.on 'upgrade', (req, socket, head) ->
# Dirty trick to authenticate websockets
req.originalUrl = req.url
fakeRes = on: ->
[cookieParser, sessionParser, initialize, session] = config.authSteps
async.series [
(callback) -> cookieParser req, fakeRes, callback
(callback) -> sessionParser req, fakeRes, callback
(callback) -> initialize req, fakeRes, callback
(callback) -> session req, fakeRes, callback
], (err) ->

proxyWS = (host, port) ->
proxy.ws req, socket, head,
target: "ws://#{host}:#{port}"
ws: true

fail = (err) ->
logger.error err if err?
logger.error "Socket unauthorized"
socket.end "HTTP/1.1 400 Connection Refused \r\n" +
"Connection: close\r\n\r\n", 'ascii'

return fail err if err

# express doesn't expose its router, we do it manually
[_, publicOrPrivate, slug] = req.url.split '/'
routes = router.getRoutes()

urlHelperSlug = slug.replace 'data-system', 'dataSystem'
host = 'localhost'
port = routes[slug]?.port

if urlHelper[urlHelperSlug]
host = urlHelper[urlHelperSlug].host()
port = urlHelper[urlHelperSlug].port()

# /public/XXXXXX
if publicOrPrivate is 'public'
req.url = req.url.replace "/public/#{slug}", '/public'
proxyWS host, port

# (AUTH) /apps/XXXXX
else if publicOrPrivate is 'apps' and req.isAuthenticated()
req.url = req.url.replace "/apps/#{slug}", ''
proxyWS host, port

# (AUTH) /XXXXX -> HOME
else if req.isAuthenticated()
proxyWS urlHelper.home.host(), urlHelper.home.port()

else
fail new Error('socket not authorized')
initializeWebsocketProxy server, proxy
90 changes: 90 additions & 0 deletions server/lib/websocket.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
async = require 'async'
logger = require('printit')
date: false
prefix: 'lib:websockets'

config = require '../config'
urlHelper = require 'cozy-url-sdk'
router = require './router'
http = require 'http'
{checkDeviceAuth} = require '../controllers/devices'
socketio = require 'socket.io'
axon = require 'axon'

doProxyWS = (proxy, req, socket, head, host, port) ->
proxy.ws req, socket, head,
target: "ws://#{host}:#{port}"
ws: true

closeWS = (socket, err) ->
logger.error err if err?
logger.error "Socket unauthorized"
socket.end "HTTP/1.1 400 Connection Refused \r\n" +
"Connection: close\r\n\r\n", 'ascii'

# This is equivalent to having this request pass through
# express cookie authentication flow.
# It seems hacky, maybe there is a better way.
applyCookieAuthMiddlewares = (req, callback) ->
req.originalUrl = req.url
fakeRes = on: ->
applyMiddleware = (middleware, next) ->
middleware req, fakeRes, next

async.mapSeries config.authSteps, applyMiddleware, callback

module.exports = (server, proxy) ->

# Bind socket.io on a useless HTTP server
sioserver = socketio(new http.Server(->))

# start axon's socket
axonsocket = axon.socket 'sub-emitter'
axonsocket.connect 9105

# Forward all events to socket.io
# to @TODO may be we should filter by permissions
axonsocket.on '*', (event, id) -> sioserver.emit event, id

server.on 'upgrade', (req, socket, head) ->
applyCookieAuthMiddlewares req, (err) ->
return closeWS socket, err if err

# express doesn't expose its router, we do it manually
[_, publicOrPrivate, slug] = req.url.split '/'
routes = router.getRoutes()

urlHelperSlug = slug.replace 'data-system', 'dataSystem'
host = 'localhost'
port = routes[slug]?.port

if urlHelper[urlHelperSlug]
host = urlHelper[urlHelperSlug].host()
port = urlHelper[urlHelperSlug].port()

# DS API socket
if req.url is '/ds-api/socket.io'
checkDeviceAuth req, (auth) ->
if auth
sioserver.eio.handleUpgrade req, socket, head
else
closeWS socket, new Error('socket not authenticated')

# /public/XXXXXX
else if publicOrPrivate is 'public'
req.url = req.url.replace "/public/#{slug}", '/public'
doProxyWS proxy, req, socket, head, host, port

# (AUTH) /apps/XXXXX
else if publicOrPrivate is 'apps' and req.isAuthenticated()
req.url = req.url.replace "/apps/#{slug}", ''
doProxyWS proxy, req, socket, head, host, port

# (AUTH) /XXXXX -> HOME
else if req.isAuthenticated()
host = urlHelper.home.host()
port = urlHelper.home.port()
doProxyWS proxy, req, socket, head, host, port

else
closeWS socket, new Error('socket not authorized')

0 comments on commit cb48733

Please sign in to comment.