Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed express-pouchdb's replication not handling all forms of basic authentication #463

Open
cjshearer opened this issue Apr 15, 2022 · 0 comments

Comments

@cjshearer
Copy link

cjshearer commented Apr 15, 2022

Currently, express-pouchdb does not handle sources and targets in any form other than a url. However, CouchDB specifies three ways to provide authentication for the replication route, two of which provide source and target as objects that are unhandled, so attempting to use these alternate forms of authentication cause the PouchDB server to throw a 500 error. I fixed this by replacing the source and target with PouchDB objects generated by req.PouchDB.new, which handles these forms of authentication. Is this the right way to go about this? If so, I'll make a PR and link it here.

Reproducible setup and test:

const axios = require("axios");
const importFresh = require("import-fresh");
const btoa = require("btoa");

/**
 * Creates an in-memory PouchDB server, a PouchDB object to access it, and
 * expose the server on localhost:port.
 *
 * @param {number} port - the port at which the in-memory server should be
 * made accessible.
 *
 * @returns {PouchDB} a PouchDB object to access the in-memory PouchDB server
 */
function pouchDBMemoryServer(username, password, port) {
  // we need to disable caching of required modules to create separate pouchdb
  // server instances
  var InMemPouchDB = importFresh("pouchdb").defaults({
    db: importFresh("memdown"),
  });

  const app = importFresh("express-pouchdb")(InMemPouchDB, {
    inMemoryConfig: true,
  });

  // add admin
  app.couchConfig.set("admins", username, password, () => {});

  app.listen(port);
  return InMemPouchDB;
}

async function testIt() {
  // create two in-memory PouchDB servers
  let sourcePort = 5984;
  let targetPort = 5985;
  let username = "username";
  let password = "password";
  let sourceInstance = pouchDBMemoryServer(username, password, sourcePort);
  let targetInstance = pouchDBMemoryServer(username, password, targetPort);

  // create a database in the source instance
  let dbName = "u-test";
  let db = new sourceInstance(dbName);

  await db.put({ _id: "0", exampleDoc: "test" });

  // create basic authorization as base64 string. Note that the _replicate route
  // could also take this as { auth: { username, password } }
  const authOpts = {
    headers: {
      Authorization: "Basic " + btoa(`${username}:${password}`),
    },
  };

  // ensure database is present
  await axios
    .get(`http://localhost:${sourcePort}/${dbName}`, authOpts)
    .then((r) => console.log(r.status, r.statusText, r.data));

  // attempt to replicate
  await axios
    .post(
      `http://localhost:${sourcePort}/_replicate`,
      {
        source: {
          url: `http://localhost:${sourcePort}/_replicate/${dbName}`,
          ...authOpts,
        },
        target: {
          url: `http://localhost:${targetPort}/_replicate/${dbName}`,
          ...authOpts,
        },
        create_target: true,
        continuous: true,
      },
      authOpts
    )
    .then((r) => console.log(r.data))
    .catch((err) => console.log(err.status));
}

await testIt();

Which throws:

> await testIt();
200 OK {
  doc_count: 1,
  update_seq: 1,
  backend_adapter: 'MemDOWN',
  db_name: 'u-test',
  auto_compaction: false,
  adapter: 'leveldb',
  instance_start_time: '1650047291155'
}
undefined
undefined
> Error: Missing/invalid DB name
    at PouchAlt.PouchDB (/home/.../node_modules/pouchdb/lib/index.js:2649:11)
    at new PouchAlt (/home/.../node_modules/pouchdb/lib/index.js:2786:13)
    at staticSecurityWrappers.replicate (/home/.../node_modules/pouchdb-security/lib/index.js:211:62)
    at callHandlers (/home/.../node_modules/pouchdb-wrappers/lib/index.js:467:17)
    at Function.replicate (/home/.../node_modules/pouchdb-wrappers/lib/index.js:428:12)
    at /home/.../node_modules/express-pouchdb/lib/routes/replicate.js:38:17
    at Layer.handle [as handle_request] (/home/.../node_modules/express/lib/router/layer.js:95:5)
    at next (/home/.../node_modules/express/lib/router/route.js:137:13)
    at /home/.../node_modules/express-pouchdb/lib/utils.js:41:7
    at /home/.../node_modules/body-parser/lib/read.js:130:5
    at invokeCallback (/home/.../node_modules/raw-body/index.js:224:16)
    at done (/home/.../node_modules/raw-body/index.js:213:7)
    at IncomingMessage.onEnd (/home/.../node_modules/raw-body/index.js:273:7)
    at IncomingMessage.emit (node:events:402:35)
    at IncomingMessage.emit (node:domain:537:15)
    at endReadableNT (node:internal/streams/readable:1343:12)

But after applying the below diff (generated by patch-package), everything works as expected:

> await testIt();
200 OK {
  doc_count: 1,
  update_seq: 1,
  backend_adapter: 'MemDOWN',
  db_name: 'u-test',
  auto_compaction: false,
  adapter: 'leveldb',
  instance_start_time: '1650046811592'
}
{ ok: true }
diff --git a/node_modules/express-pouchdb/lib/routes/replicate.js b/node_modules/express-pouchdb/lib/routes/replicate.js
index aa1a790..30d5f50 100644
--- a/node_modules/express-pouchdb/lib/routes/replicate.js
+++ b/node_modules/express-pouchdb/lib/routes/replicate.js
@@ -1,17 +1,30 @@
 "use strict";
 
 var utils  = require('../utils'),
-    extend = require('extend');
+    extend = require('extend'),
+    cleanFilename = require('../clean-filename');
 
 module.exports = function (app) {
   var histories = {};
 
   // Replicate a database
   app.post('/_replicate', utils.jsonParser, function (req, res) {
+    var dbOpts = utils.makeOpts(req);
 
-    var source = req.body.source,
-        target = req.body.target,
-        opts = utils.makeOpts(req, {continuous: !!req.body.continuous});
+    // handle various forms of authorization using PouchDB.new
+    var source = req.PouchDB.new(
+          cleanFilename(
+            req.body.source.url ? req.body.source.url : req.body.source
+          ),
+          dbOpts
+        ),
+        target = req.PouchDB.new(
+          cleanFilename(
+            req.body.source.url ? req.body.source.url : req.body.source
+          ),
+          dbOpts
+        ),
+        opts = utils.makeOpts(req, { continuous: !!req.body.continuous })
 
     if (req.body.filter) {
       opts.filter = req.body.filter;

This issue body was partially generated by patch-package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant