Skip to content

Commit

Permalink
Merge b44122a into 18d759d
Browse files Browse the repository at this point in the history
  • Loading branch information
gr2m committed Sep 10, 2016
2 parents 18d759d + b44122a commit f8a58f7
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 162 deletions.
26 changes: 23 additions & 3 deletions index.js
Expand Up @@ -3,9 +3,10 @@ module.exports.register.attributes = {
name: 'hoodie-server'
}

var format = require('util').format

var getConfig = require('./lib/config')
var registerPlugins = require('./lib/plugins')
var userDatabases = require('./lib/utils/user-databases')

function register (server, options, next) {
getConfig(server, options, function (error, config) {
Expand All @@ -19,8 +20,27 @@ function register (server, options, next) {
}

// add / remove user databases on signups / account deletions
server.plugins.account.api.accounts.on('add', userDatabases.add.bind(null, config, server))
server.plugins.account.api.accounts.on('remove', userDatabases.remove.bind(null, config, server))
server.plugins.account.api.accounts.on('add', function (account) {
server.log(['account', 'info'], format('created for %s (id: %s)', account.username, account.id))

server.plugins.store.api.create('user/' + account.id, {
access: ['read', 'write'],
role: ['id:' + account.id]
})

.then(function (dbName) {
server.log(['store', 'info'], format('database %s created', dbName))
})
})
server.plugins.account.api.accounts.on('remove', function (account) {
server.log(['account', 'info'], format('removed for %s (id: %s)', account.username, account.id))

server.plugins.store.api.destroy('user/' + account.id)

.then(function (dbName) {
server.log(['store', 'info'], format('database %s destroyed', dbName))
})
})

next(null, server, config)
})
Expand Down
9 changes: 1 addition & 8 deletions lib/config/store/index.js
@@ -1,20 +1,13 @@
module.exports = storeConfig

var storePreAuthHook = require('./pre-auth-hook')
var toCouchDbUrl = require('../../utils/pouchdb-options-to-couchdb-url')

function storeConfig (state, callback) {
state.config.store.hooks = {
onPreAuth: storePreAuthHook
}

var couchDbUrl = toCouchDbUrl(state.db.options)

if (couchDbUrl) {
state.config.store.couchdb = couchDbUrl
} else {
state.config.store.PouchDB = state.config.PouchDB
}
state.config.store.PouchDB = state.config.PouchDB

callback(null, state.config)
}
42 changes: 33 additions & 9 deletions lib/config/store/pre-auth-hook.js
Expand Up @@ -21,23 +21,47 @@ function onStorePreAuth (request, reply) {
server.plugins.account.api.sessions.find(sessionToken)

.then(function (session) {
var accountId = session.account.id
var isRequestToUserDb = request.path.indexOf(accountId) !== -1
// PouchDB’s replication sends an initial GET to CouchDB root initially
var isGetRootPath = request.path === '/hoodie/store/api/' && request.method === 'get'

if (!isGetRootPath && !isRequestToUserDb) {
throw new Error('unauthorized')
}
var requiredAccess = request.method === 'get' ? 'read' : 'write'

delete request.headers.authorization
request.headers.cookie = 'AuthSession=' + session.id

reply.continue()
if (isGetRootPath) {
return reply.continue()
}

var storePath = request.path.substr('/hoodie/store/api/'.length)
var dbPath = storePath.split('/')[0]
var dbName = decodeURIComponent(dbPath)
var roles = session.account.roles.concat('id:' + session.account.id)

return server.plugins.store.api.hasAccess(dbName, {
access: requiredAccess,
role: roles
})

.then(function (hasAccess) {
if (hasAccess) {
return reply.continue()
}

reply(Boom.unauthorized())
})

.catch(function (error) {
throw error
})
})

.catch(function () {
reply(Boom.unauthorized())
.catch(function (error) {
if (error.status === 404) { // session not found
return reply(Boom.unauthorized())
}

server.log(['store', 'error'], error.message)
reply(Boom.wrap(error, 500))
})
}

Expand Down
86 changes: 0 additions & 86 deletions lib/utils/user-databases.js

This file was deleted.

118 changes: 111 additions & 7 deletions test/unit/config/store-pre-auth-hook-test.js
Expand Up @@ -4,7 +4,11 @@ var test = require('tap').test

var preAuthHook = proxyquire('../../../lib/config/store/pre-auth-hook', {
'boom': {
unauthorized: simple.stub().returnWith(new Error('unauthorized'))
unauthorized: simple.stub().returnWith(new Error('unauthorized')),
wrap: simple.stub().callFn(function (error, status) {
error.status = status
return error
})
}
})

Expand All @@ -14,17 +18,27 @@ test('store pre auth hook', function (t) {
id: 'session123'
},
account: {
id: 'user123'
id: 'user123',
roles: []
}
}
var findSessionStub = simple.stub().returnWith({ // don’group use resolveWith to avoid async
var findSessionStub = simple.stub().returnWith({ // don’t use resolveWith to avoid async
then: function (callback) {
callback(session)
return {
catch: function () {}
}
}
})
var hasAccessStub = simple.stub().returnWith({ // don’t use resolveWith to avoid async
then: function (callback) {
callback(true)
return {
catch: function () {}
}
}
})

var serverStub = {
plugins: {
account: {
Expand All @@ -33,11 +47,16 @@ test('store pre auth hook', function (t) {
find: findSessionStub
}
}
},
store: {
api: {
hasAccess: hasAccessStub
}
}
}
}
var request = {
path: 'user123',
path: '/hoodie/store/api/user%2F123',
headers: {
authorization: 'Session session123'
},
Expand All @@ -62,7 +81,8 @@ test('store pre auth hook root path', function (t) {
id: 'session123'
},
account: {
id: 'user123'
id: 'user123',
roles: []
}
}
var findSessionStub = simple.stub().returnWith({ // don’group use resolveWith to avoid async
Expand Down Expand Up @@ -105,16 +125,48 @@ test('store pre auth hook root path', function (t) {
t.end()
})

test('store pre auth hook session not found error', function (t) {
var findSessionStub = simple.stub().rejectWith({status: 404})
var serverStub = {
plugins: {
account: {
api: {
sessions: {
find: findSessionStub
}
}
}
}
}
var request = {
path: '/hoodie/store/api/user%2F456',
headers: {
authorization: 'Session session123'
},
connection: {
server: serverStub
}
}

t.plan(2)
preAuthHook(request, function (error) {
t.ok(error)
t.is(error.message, 'unauthorized', 'throws unauthorized error')
})
})

test('store pre auth hook unauthorized error', function (t) {
var session = {
session: {
id: 'session123'
},
account: {
id: 'user123'
id: 'user123',
roles: []
}
}
var findSessionStub = simple.stub().resolveWith(session)
var hasAccessStub = simple.stub().resolveWith(false)
var serverStub = {
plugins: {
account: {
Expand All @@ -123,11 +175,16 @@ test('store pre auth hook unauthorized error', function (t) {
find: findSessionStub
}
}
},
store: {
api: {
hasAccess: hasAccessStub
}
}
}
}
var request = {
path: 'user456',
path: '/hoodie/store/api/user%2F456',
headers: {
authorization: 'Session session123'
},
Expand All @@ -142,3 +199,50 @@ test('store pre auth hook unauthorized error', function (t) {
t.is(error.message, 'unauthorized', 'throws unauthorized error')
})
})

test('store pre auth hook server error', function (t) {
var session = {
session: {
id: 'session123'
},
account: {
id: 'user123',
roles: []
}
}
var findSessionStub = simple.stub().resolveWith(session)
var hasAccessStub = simple.stub().rejectWith(new Error('ooops'))
var serverStub = {
log: simple.stub(),
plugins: {
account: {
api: {
sessions: {
find: findSessionStub
}
}
},
store: {
api: {
hasAccess: hasAccessStub
}
}
}
}
var request = {
path: '/hoodie/store/api/user%2F456',
headers: {
authorization: 'Session session123'
},
connection: {
server: serverStub
}
}

t.plan(3)
preAuthHook(request, function (error) {
t.ok(error)
t.is(error.status, 500, 'throws 500 error')
t.is(error.message, 'ooops', 'oooopsie')
})
})

0 comments on commit f8a58f7

Please sign in to comment.