diff --git a/README.md b/README.md index 7ec993e..bec6e47 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,25 @@ server.listen(port, function () { // Attach WS to solid solidWs(server, app) ``` + +### With authorization + +You can pass an `authorize` callback to control which subscriptions are allowed: + +```javascript +var SolidWs = require('solid-ws') + +var solidWs = SolidWs(server, app, { + authorize: function (iri, req, callback) { + // iri: the resource URL being subscribed to + // req: the HTTP upgrade request (for auth headers/cookies) + // callback(err, allowed): call with allowed=true to permit, false to deny + + checkUserAccess(iri, req, function (err, hasAccess) { + callback(err, hasAccess) + }) + } +}) +``` + +If authorization fails, the client receives `err forbidden` instead of `ack`. diff --git a/lib/server.js b/lib/server.js index 623985f..8af5993 100644 --- a/lib/server.js +++ b/lib/server.js @@ -10,6 +10,11 @@ function defaultToChannel(iri) { return url.parse(iri).path } +// Default authorize function allows all subscriptions +function defaultAuthorize (iri, req, callback) { + callback(null, true) +} + function WsServer (server, opts) { var self = this @@ -17,6 +22,7 @@ function WsServer (server, opts) { this.suffix = opts.suffix || '.changes' this.store = opts.store || new InMemory(opts) var toChannel = opts.toChannel || defaultToChannel + var authorize = opts.authorize || defaultAuthorize // Starting WSS server var wss = new WebSocketServer({ @@ -26,9 +32,10 @@ function WsServer (server, opts) { }) // Handling a single connection - wss.on('connection', function (client) { + // The 'ws' library passes the upgrade request as the second argument + wss.on('connection', function (client, req) { debug('New connection') - // var location = url.parse(client.upgradeReq.url, true) + client.upgradeReq = req // Handling messages client.on('message', function (message) { @@ -47,14 +54,23 @@ function WsServer (server, opts) { return } - var channel = toChannel ? toChannel(iri) : iri - self.store.subscribe(channel, iri, client, function (err, uuid) { - if (err) { - // TODO Should return an error + // Check authorization before allowing subscription + authorize(iri, client.upgradeReq, function (err, allowed) { + if (err || !allowed) { + debug('Subscription denied for ' + iri) + client.send('err ' + iri + ' forbidden') return } - client.send('ack ' + tuple[1]) + var channel = toChannel ? toChannel(iri) : iri + self.store.subscribe(channel, iri, client, function (err, uuid) { + if (err) { + // TODO Should return an error + return + } + + client.send('ack ' + tuple[1]) + }) }) })