diff --git a/server/config/account/index.js b/server/config/account/index.js deleted file mode 100644 index 72d3280b9..000000000 --- a/server/config/account/index.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = accountConfig - -var defaultsDeep = require('lodash').defaultsDeep - -function accountConfig (state, callback) { - var usersDb = state.getDatabase(state.config.db.authenticationDb) - usersDb.constructor.plugin(require('pouchdb-users')) - - usersDb.installUsersBehavior() - - .then(function () { - defaultsDeep(state.config.account, { - admins: state.config.db.admins, - secret: state.config.db.secret, - usersDb: usersDb - }) - - callback(null, state.config) - }) - - .catch(callback, state.config) -} diff --git a/server/config/assure-folders.js b/server/config/assure-folders.js deleted file mode 100644 index 100785252..000000000 --- a/server/config/assure-folders.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = assureFolders - -var parallel = require('async').parallel -var mkdirp = require('mkdirp') - -function assureFolders (state, callback) { - if (state.config.inMemory) { - return callback() - } - - var tasks = [ - mkdirp.bind(null, state.config.paths.data) - ] - if (state.config.db.prefix) { - tasks.push(mkdirp.bind(null, state.config.db.prefix)) - } - - parallel(tasks, callback) -} diff --git a/server/config/db/couchdb-check-vendor.js b/server/config/db/couchdb-check-vendor.js deleted file mode 100644 index f98488485..000000000 --- a/server/config/db/couchdb-check-vendor.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = checkVendor - -var findKey = require('lodash').findKey -var log = require('npmlog') - -function checkVendor (config, couch, callback) { - couch({url: '/'}, function (error, response, data) { - if (error || (response && response.statusCode !== 200)) { - return callback(new Error('Could not find CouchDB at ' + config.db.url)) - } - - var vendor = findKey(data, function (property) { - return /^welcome/i.test(property) - }) - - if (vendor !== 'couchdb') { - log.warn( - 'database', - 'You are not running an official CouchDB distribution, ' + - 'but "' + vendor + '". ' + - 'This might not be fully supported. Proceed at your own risk.' - ) - } - - callback(null) - }) -} diff --git a/server/config/db/couchdb-get-admins.js b/server/config/db/couchdb-get-admins.js deleted file mode 100644 index 60a10b83b..000000000 --- a/server/config/db/couchdb-get-admins.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = getAdmins - -function getAdmins (couch, callback) { - couch({ - url: '/_config/admins' - }, function (error, response, data) { - if (error || (response && response.statusCode !== 200)) { - return callback(new Error('Could not retrieve CouchDB admins')) - } - - callback(null, data) - }) -} diff --git a/server/config/db/couchdb-get-config.js b/server/config/db/couchdb-get-config.js deleted file mode 100644 index 0672296c0..000000000 --- a/server/config/db/couchdb-get-config.js +++ /dev/null @@ -1,23 +0,0 @@ -module.exports = getConfig - -var pick = require('lodash').pick - -function getConfig (couch, callback) { - couch({ - url: '/_config/couch_httpd_auth' - }, function (error, response, data) { - if (error || (response && response.statusCode !== 200)) { - return callback(new Error('Could not retrieve necessary CouchDB config values')) - } - - if (!data.secret) { - return callback(new Error('Could not retrieve CouchDB secret')) - } - - if (!data.authentication_db) { - return callback(new Error('Could not retrieve CouchDB authentication database')) - } - - callback(null, pick(data, ['secret', 'authentication_db'])) - }) -} diff --git a/server/config/db/couchdb-set-config.js b/server/config/db/couchdb-set-config.js deleted file mode 100644 index 82cf2381e..000000000 --- a/server/config/db/couchdb-set-config.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = setConfig - -function setConfig (couch, callback) { - couch({ - url: '/_config/httpd/authentication_handlers', - method: 'PUT', - body: '{couch_httpd_oauth, oauth_authentication_handler},{couch_httpd_auth, default_authentication_handler},{couch_httpd_auth, cookie_authentication_handler}' - }, function (error, response, data) { - if (error || (response && response.statusCode !== 200)) { - return callback(new Error('Could not set necessary CouchDB config')) - } - - callback(null) - }) -} diff --git a/server/config/db/couchdb.js b/server/config/db/couchdb.js deleted file mode 100644 index 2feac1dfa..000000000 --- a/server/config/db/couchdb.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = couchDbConfig - -var checkVendor = require('./couchdb-check-vendor') -var getAdmins = require('./couchdb-get-admins') -var getConfig = require('./couchdb-get-config') -var setConfig = require('./couchdb-set-config') - -var async = require('async') -var request = require('request') - -function couchDbConfig (state, callback) { - var couch = request.defaults({ - baseUrl: state.config.db.url, - json: true - }) - - async.series([ - async.apply(checkVendor, state.config, couch), - async.apply(setConfig, couch), - async.apply(getConfig, couch), - async.apply(getAdmins, couch) - ], function (err, results) { - if (err) return callback(err) - - state.config.db.admins = results[3] - state.config.db.secret = results[2].secret - state.config.db.authenticationDb = results[2].authentication_db - - callback(null, state.config) - }) -} diff --git a/server/config/db/factory.js b/server/config/db/factory.js deleted file mode 100644 index f1a801021..000000000 --- a/server/config/db/factory.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = getDatabaseFactory - -var url = require('url') - -function getDatabaseFactory (config) { - var PouchDB = require('pouchdb').defaults(config.db) - var db = function getDatabase (name) { - if (config.db.url) { - return new PouchDB(url.resolve(config.db.url, encodeURIComponent(name))) - } - return new PouchDB(name) - } - db.PouchDB = PouchDB - return db -} diff --git a/server/config/db/pouchdb.js b/server/config/db/pouchdb.js deleted file mode 100644 index 6802c4479..000000000 --- a/server/config/db/pouchdb.js +++ /dev/null @@ -1,71 +0,0 @@ -module.exports = pouchDbConfig - -var existsSync = require('fs').existsSync -var join = require('path').join - -// pouchdb-admins is made to be a PouchDB plugin but does not require a db, -// so can be used standalone -var Admins = require('pouchdb-admins').admins -var get = require('lodash/get') -var jsonfile = require('jsonfile') -var randomstring = require('randomstring') - -function pouchDbConfig (state, callback) { - var storePath = join(state.config.paths.data, 'config.json') - var storeExists = existsSync(storePath) - - var store = storeExists && jsonfile.readFileSync(storePath, { - throws: false - }) || {} - var secret = store.couch_httpd_auth_secret - var adminPassword = get(store, 'admins.admin') - - if (!secret) { - secret = randomstring.generate({ - charset: 'hex' - }) - - if (!state.config.inMemory && adminPassword) { - jsonfile.writeFileSync(storePath, Object.assign(store, { - couch_httpd_auth_secret: secret - }), {spaces: 2}) - } - } - - state.config.db.secret = secret - state.config.db.admins = store.admins - state.config.db.authenticationDb = '_users' - - if (adminPassword) { - return callback(null, state.config) - } - - var admins = new Admins({ - secret: secret - }) - - admins.set('admin', 'secret') - - .then(function () { - return admins.get('admin') - }) - - .then(function (doc) { - state.config.db.admins = { - admin: '-pbkdf2-' + doc.derived_key + ',' + doc.salt + ',10' - } - - if (!state.config.inMemory) { - jsonfile.writeFileSync(storePath, Object.assign(store, { - couch_httpd_auth_secret: secret, - admins: state.config.db.admins - }), {spaces: 2}) - } - - callback(null, state.config) - }) - - .catch(function (error) { - callback(error) - }) -} diff --git a/server/config/defaults.js b/server/config/defaults.js deleted file mode 100644 index fa6194295..000000000 --- a/server/config/defaults.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = getDefaults - -function getDefaults () { - return { - loglevel: 'warn', - paths: { - data: '.hoodie', - public: 'public' - }, - db: {}, - - // core modules - account: {}, - admin: {}, - client: {}, - store: {}, - - // plugins - plugins: {} - } -} diff --git a/server/config/index.js b/server/config/index.js deleted file mode 100644 index cdcd1f498..000000000 --- a/server/config/index.js +++ /dev/null @@ -1,59 +0,0 @@ -module.exports = getConfig - -var parseUrl = require('url').parse -var statSync = require('fs').statSync -var path = require('path') - -var defaultsDeep = require('lodash').defaultsDeep -var log = require('npmlog') -var series = require('async').series - -var accountConfig = require('./account') -var assureFolders = require('./assure-folders') -var couchDbConfig = require('./db/couchdb') -var getDatabaseFactory = require('./db/factory') -var getDefaults = require('./defaults') -var pouchDbConfig = require('./db/pouchdb') -var storeConfig = require('./store') - -function getConfig (config, callback) { - defaultsDeep(config, getDefaults()) - - if (!config.db.url) { - if (config.inMemory) { - log.info('config', 'Storing all data in memory only') - config.db.db = require('memdown') - } else { - config.db.prefix = path.join(config.paths.data, 'data' + path.sep) - log.info('config', 'No CouchDB URL provided, falling back to PouchDB') - log.info('config', 'Writing PouchDB database files to ' + config.db.prefix) - } - } - - var dbConfig = config.db.url ? couchDbConfig : pouchDbConfig - var state = { - config: config, - getDatabase: getDatabaseFactory(config) - } - - if (config.db.url && !parseUrl(config.db.url).auth) { - return callback('Authentication details missing from database URL: ' + config.db.url) - } - - // check if app has public folder. Fallback to Hoodie’s public folder if not - try { - statSync(config.paths.public).isDirectory() - } catch (err) { - config.paths.public = path.resolve(__dirname, '../../public') - log.info('config', 'The "public" app path does not exist. Serving ' + config.paths.public) - } - - series([ - assureFolders.bind(null, state), - dbConfig.bind(null, state), - accountConfig.bind(null, state), - storeConfig.bind(null, state) - ], function (error) { - callback(error, state.config) - }) -} diff --git a/server/config/store/index.js b/server/config/store/index.js deleted file mode 100644 index 159648b03..000000000 --- a/server/config/store/index.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = storeConfig - -var stripUrlAuth = require('strip-url-auth') - -var storePreAuthHook = require('./pre-auth-hook') - -function storeConfig (state, callback) { - state.config.store.hooks = { - onPreAuth: storePreAuthHook - } - - if (state.config.db.url) { - state.config.store.couchdb = stripUrlAuth(state.config.db.url) - } else { - state.config.store.PouchDB = state.getDatabase.PouchDB - } - - callback(null, state.config) -} diff --git a/server/config/store/pre-auth-hook.js b/server/config/store/pre-auth-hook.js deleted file mode 100644 index 5fd8b60e8..000000000 --- a/server/config/store/pre-auth-hook.js +++ /dev/null @@ -1,50 +0,0 @@ -module.exports = onStorePreAuth - -var Boom = require('boom') - -/** - * All requests to /hoodie/store are prefixed by user database names, which - * are "user/{accountId}". `onStorePreAuth` checks if the sessionToken is - * a valid session Id, and if it belongs to the right user. If either fails, - * it responds with an `unauthorized` error - * - * See https://github.com/hoodiehq/hoodie-store-server#optionshooks - */ -function onStorePreAuth (request, reply) { - var server = request.connection.server - var sessionToken = toSessionToken(request) - - if (!sessionToken) { - return reply(Boom.unauthorized()) - } - - 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') - } - - delete request.headers.authorization - request.headers.cookie = 'AuthSession=' + session.id - - reply.continue() - }) - - .catch(function () { - reply(Boom.unauthorized()) - }) -} - -function toSessionToken (request) { - var token - if (request.headers.authorization) { - token = request.headers.authorization.substring('Session '.length) - } - return token -} diff --git a/server/index.js b/server/index.js index 6d8d9e88f..66a80aebb 100644 --- a/server/index.js +++ b/server/index.js @@ -3,14 +3,50 @@ module.exports.register.attributes = { name: 'hoodie' } +var path = require('path') +var urlParse = require('url').parse + var corsHeaders = require('hapi-cors-headers') +var hoodieServer = require('@hoodie/server').register +var log = require('npmlog') +var PouchDB = require('pouchdb-core') -var getConfig = require('./config') var registerPlugins = require('./plugins') -var userDatabases = require('./utils/user-databases') function register (server, options, next) { - getConfig(options, function (error, config) { + if (!options.db) { + options.db = {} + } + + if (!options.db.url) { + if (options.inMemory) { + PouchDB.plugin(require('pouchdb-adapter-memory')) + log.info('config', 'Storing all data in memory only') + } else { + PouchDB.plugin(require('pouchdb-adapter-leveldb')) + options.db.prefix = path.join(options.paths.data, 'data' + path.sep) + log.info('config', 'No CouchDB URL provided, falling back to PouchDB') + log.info('config', 'Writing PouchDB database files to ' + options.db.prefix) + } + } + + if (options.db.url) { + if (!urlParse(options.db.url).auth) { + return next(new Error('Authentication details missing from database URL: ' + options.db.url)) + } + + PouchDB.plugin(require('pouchdb-adapter-http')) + options.db.prefix = options.db.url + delete options.db.url + } + + options.PouchDB = PouchDB.defaults(options.db) + delete options.db + + server.register({ + register: hoodieServer, + options: options + }, function (error) { if (error) { return next(error) } @@ -19,16 +55,12 @@ function register (server, options, next) { sandbox: 'plugin' }) - registerPlugins(server, config, function (error) { + registerPlugins(server, options, function (error) { if (error) { return next(error) } - // 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)) - - next(null, server, config) + next(null, server, options) }) }) } diff --git a/server/plugins/index.js b/server/plugins/index.js index 4c949c5b1..f3fd7494d 100644 --- a/server/plugins/index.js +++ b/server/plugins/index.js @@ -7,10 +7,7 @@ function registerPlugins (server, config, callback) { config: config } var hapiPlugins = [ - require('h2o2'), - require('inert'), - require('vision'), - require('lout') + require('inert') ] var localPlugins = [ require('./client'), @@ -23,19 +20,9 @@ function registerPlugins (server, config, callback) { register: register } }) - var hoodieCorePlugins = ['account', 'store'].map(function (name) { - return { - register: require('@hoodie/' + name), - options: config[name], - routes: { - prefix: '/hoodie/' + name + '/api' - } - } - }) - var plugins = hapiPlugins.concat(localPlugins, hoodieCorePlugins) log.silly('hapi', 'Registering internal plugins') - server.register(plugins, function (error) { + server.register(hapiPlugins.concat(localPlugins), function (error) { if (error) { return callback(error) } diff --git a/server/utils/remove-auth-from-url.js b/server/utils/remove-auth-from-url.js deleted file mode 100644 index 3e675bf3f..000000000 --- a/server/utils/remove-auth-from-url.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = removeAuth - -var parseUrl = require('url').parse - -function removeAuth (url) { - var parts = parseUrl(url) - return url.replace(parts.auth + '@', '') -} diff --git a/server/utils/user-databases.js b/server/utils/user-databases.js deleted file mode 100644 index ffa8eba41..000000000 --- a/server/utils/user-databases.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * TODO: this functionality should be moved into hoodie-store-server. It should - * expose an API at server.plugins.store.api the same way hoodie-account-server - * does it: https://github.com/hoodiehq/hoodie-account-server/tree/master/api - * - * Right now it only works correctly if CouchDB is used for data. PouchDB - * creates Databases when used for the first time, but databases get currently - * not removed when a user account gets removed - */ -module.exports = { - add: addUserDatabase, - remove: removeUserDatabase -} - -var async = require('async') -var log = require('npmlog') -var request = require('request') - -function addUserDatabase (config, server, account) { - log.info('account', 'created for %s (id: %s)', account.username, account.id) - - // databases & security only created if CouchDB used - if (!config.db.url) { - return - } - - async.series([ - createDatabase.bind(null, config, account), - createSecurity.bind(null, config, account) - ], function (error) { - if (error) { - log.error('user/%s not created: %s', account.id, error) - return - } - - log.info('account', 'database "user/%s" created for %s', account.id, account.username) - }) -} -function removeUserDatabase (config, server, account) { - log.info('account', 'removed for %s (id: %s)', account.username, account.id) - - // databases & security only created if CouchDB used - if (!config.db.url) { - return - } - - deleteDatabase(config, account, function (error) { - if (error) { - log.error('account', 'user/%s not deleted: %s', account.id, error) - return - } - - log.info('account', 'database user/%s deleted for %s', account.id, account.username) - }) -} - -function createDatabase (config, account, callback) { - var url = config.db.url + '/user%2f' + account.id - request.put(url, callback) -} - -function createSecurity (config, account, callback) { - var url = config.db.url + '/user%2f' + account.id + '/_security' - var security = { - admins: { - names: [], - roles: [] - }, - members: { - names: [], - roles: ['id:' + account.id] - } - } - request({ - method: 'PUT', - url: url, - json: true, - body: security - }, callback) -} - -function deleteDatabase (config, account, callback) { - var url = config.db.url + '/user%2f' + account.id - request.del(url, callback) -}