UNPKG

own-couchdb

Version:
404 lines (325 loc) 9.85 kB
'use strict'; function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var finalhandler = _interopDefault(require('finalhandler')); var fs = require('fs'); var invariant = _interopDefault(require('invariant')); var http = _interopDefault(require('http')); var PouchDB = _interopDefault(require('pouchdb')); var fetchAuth = _interopDefault(require('fetch-auth-node')); var random = _interopDefault(require('random-gen')); var Router = _interopDefault(require('router')); var babelHelpers = {}; babelHelpers.asyncToGenerator = function (fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; }; babelHelpers.extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; babelHelpers; function getServerConfig() { var _JSON$parse = JSON.parse(fs.readFileSync(process.env.CONFIG || 'config.json', 'utf8')); const auth = _JSON$parse.auth; var _JSON$parse$db = _JSON$parse.db; const db = _JSON$parse$db === undefined ? 'own' : _JSON$parse$db; var _JSON$parse$endpoint = _JSON$parse.endpoint; const endpoint = _JSON$parse$endpoint === undefined ? 'http://127.0.0.1:5984' : _JSON$parse$endpoint; var _JSON$parse$port = _JSON$parse.port; const port = _JSON$parse$port === undefined ? 5986 : _JSON$parse$port; invariant(auth, 'missing couchdb auth header that can write to the own db'); invariant(endpoint, `missing couchdb endpoint's own db`); invariant(endpoint, 'missing couchdb endpoint'); invariant(port, 'missing port'); return { auth, db, endpoint, port }; } function getDb(endpoint, db, Authorization) { return new PouchDB(`${ endpoint }/${ db }`, { ajax: { headers: { Authorization } } }); } const NOT_FOUND = 404; var getAcl = (() => { var ref = babelHelpers.asyncToGenerator(function* (req, res, next) { const app = req.params.app; const own = req.own; const db = getDb(own.endpoint, own.db, own.auth); let doc; try { doc = yield db.get(app); } catch (err) { if (err.status !== NOT_FOUND) { res.statusCode = 401; return res.end('{}'); } } req.own.acl = { db, doc }; next(); }); function getAcl(_x, _x2, _x3) { return ref.apply(this, arguments); } return getAcl; })(); function hasRole(user, role) { return user.roles.indexOf(role) !== -1; } const ADMIN = '_admin'; function isAdmin$1(user) { return hasRole(user, ADMIN); } function isOwner$1(user, aclDoc) { return hasRole(user, aclDoc._id) && aclDoc.admins.indexOf(user.name) !== -1; } function getCode(req, res) { var _req$own = req.own; const acl = _req$own.acl; const userCtx = _req$own.userCtx; if (typeof acl.doc !== 'undefined') { if (isAdmin$1(userCtx) || isOwner$1(userCtx, acl.doc)) { res.statusCode = 200; res.end(JSON.stringify(acl.doc.code)); } else { res.statusCode = 401; res.end('{}'); } } else { res.statusCode = 404; res.end('{}'); } } var getUserContext = (() => { var ref = babelHelpers.asyncToGenerator(function* (req, res, next) { const endpoint = req.own.endpoint; try { var _ref = yield fetchAuth(`${ endpoint }/_session`, req.headers.authorization); const userCtx = _ref.userCtx; req.own.userCtx = userCtx; next(); } catch (err) { console.error(err); res.statusCode = 401; res.end('{}'); } }); function getUserContext(_x, _x2, _x3) { return ref.apply(this, arguments); } return getUserContext; })(); var join = (() => { var ref = babelHelpers.asyncToGenerator(function* (req, res) { var _req$params = req.params; const app = _req$params.app; const code = _req$params.code; var _req$own = req.own; const acl = _req$own.acl; const auth = _req$own.auth; const endpoint = _req$own.endpoint; const userCtx = _req$own.userCtx; const usersDb = getDb(endpoint, '_users', auth); try { if (acl.doc.code === code.toUpperCase()) { const user = yield usersDb.get(`org.couchdb.user:${ userCtx.name }`); if (!hasRole(user, app)) { yield usersDb.put(babelHelpers.extends({}, user, { roles: [...user.roles, app] })); } res.statusCode = 200; res.end('{}'); } else { res.statusCode = 401; res.end('{}'); } } catch (err) { res.statusCode = 500; res.end('{}'); } }); function join(_x, _x2) { return ref.apply(this, arguments); } return join; })(); function getRandomCode() { let blacklist = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; let code; do { code = random.upper(4); } while (blacklist.indexOf(code) > -1); return code; } var renewCode = (() => { var ref = babelHelpers.asyncToGenerator(function* (req, res) { var _req$own = req.own; const acl = _req$own.acl; const userCtx = _req$own.userCtx; if (typeof acl.doc !== 'undefined') { if (isAdmin$1(userCtx) || isOwner$1(userCtx, acl.doc)) { try { const blacklist = [acl.doc.code, ...acl.doc.blacklist]; const code = getRandomCode(blacklist); yield acl.db.put(babelHelpers.extends({}, acl.doc, { blacklist, code })); res.statusCode = 200; res.end(JSON.stringify(code)); } catch (err) { console.error(err); res.statusCode = 500; res.end(); } } else { res.statusCode = 401; res.end('{}'); } } else { res.statusCode = 404; res.end('{}'); } }); function renewCode(_x, _x2) { return ref.apply(this, arguments); } return renewCode; })(); var leave = (() => { var ref = babelHelpers.asyncToGenerator(function* (req, res) { var _req$own = req.own; const acl = _req$own.acl; const auth = _req$own.auth; const endpoint = _req$own.endpoint; const userCtx = _req$own.userCtx; var _req$params = req.params; const app = _req$params.app; const email = _req$params.email; const usersDb = getDb(endpoint, '_users', auth); try { if (isAdmin(userCtx) || isOwner(userCtx, acl.doc)) { const user = yield usersDb.get(`org.couchdb.user:${ email }`); if (hasRole(user, app)) { yield usersDb.put(babelHelpers.extends({}, user, { roles: user.roles.filter(function (r) { return r !== app; }) })); yield renewCode(req, res); } } else { res.statusCode = 401; res.end('{}'); } } catch (err) { res.statusCode(500); res.end('{}'); } }); function leave(_x, _x2) { return ref.apply(this, arguments); } return leave; })(); function appToDbName(app) { return app.replace(/\./g, ''); } function createDesignDocForApp(app) { return `function(newDoc, oldDoc, userCtx) { if (userCtx.roles.indexOf("_admin") === -1 && userCtx.roles.indexOf("${ app }") === -1) { throw({forbidden: true}); } }`; } var own = (() => { var ref = babelHelpers.asyncToGenerator(function* (req, res) { var _req$own = req.own; const acl = _req$own.acl; const auth = _req$own.auth; const endpoint = _req$own.endpoint; const userCtx = _req$own.userCtx; const app = req.params.app; if (typeof acl.doc === 'undefined') { try { // create a design doc that will only allow users on the group to edit stuff yield getDb(endpoint, appToDbName(app), auth).put({ _id: `_design/only-${ app }`, validate_doc_update: createDesignDocForApp(app) }); // set the admins on the ACL doc const code = getRandomCode(); yield acl.db.put({ _id: app, admins: [userCtx.name], blacklist: [], code }); req.own.acl.doc = yield acl.db.get(app); req.params.code = code; // allow the user to access the app yield join(req, res); } catch (err) { console.error('own', err, err.stack); res.statusCode = 500; res.end('{}'); } } else { res.statusCode = 401; res.end('{}'); } }); function own(_x, _x2) { return ref.apply(this, arguments); } return own; })(); const router = Router(); router.use(getUserContext); router.route('/:app').all(getAcl).get(getCode).post(own).purge(renewCode); router.route('/:app/:code').all(getAcl).put(join); router.route('/:app/:email').all(getAcl).delete(leave); var _getServerConfig = getServerConfig(); const auth = _getServerConfig.auth; const db = _getServerConfig.db; const endpoint = _getServerConfig.endpoint; const port = _getServerConfig.port; http.createServer((req, res) => { req.own = { auth, db, endpoint }; router(req, res, finalhandler(req, res)); }).listen(port); console.log(`CouchDB own running on port http://localhost:${ port } against ${ endpoint }`);