2.2

The Apache CouchDB development community is proud to announce the immediate availability of version 2.2.

Version 2.2 incorporates 8 months of improvements to the already successful 2.1 release series.

For CouchDB < 2.0 users, the main improvements in 2.0 still apply for 2.2:

  • 99% API compatibility- native clustering for increased performance, data redundancy, and ability to scale
  • Easy querying with Mango
  • New Admin interface- Major performance improvements around compaction and replication.

Most importantly, CouchDB 2.0 finally fulfils CouchDB’s original vision of a distributed and clustered document database.

The 2018 Annual CouchDB User Survey has revealed that as of July 2019 98% of our user-base have adopted CouchDB 2.x with about 50% running CouchDB 1.x at the same time.

Note, as previously announced on the developer mailing list, there will be no further releases of CouchDB 1.x. CouchDB 1.x is a fine piece of software and folks with existing installations should be fine continuing to use it, just beware that there won’t be any further bug fixes or security fixes coming. This reflects project reality insofar as that the 1.x line has only received minimal maintenance in the past few years with the majority of the effort going to 2.x.

2.2 Highlights

See the official release notes document for an exhaustive list of all changes.

  • New pluggable storage engine framework. This internal refactor makes it possible for CouchDB to use different backends for storing the base database file itself. The refactor included a full migration of the existing “legacy” storage engine into the new framework
  • The minimum supported version of Erlang is now R17, not 16B03. Support for Erlang 21 is still ongoing and will be provided in a future release.
  • The CouchDB replicator can now make use of the /_session endpoint rather than relying entirely on HTTP basic authentication headers. This can greatly improve replication performance.
  • CouchDB no longer fails to complete replicating databases with large attachments. The fix for this issue included several related changes (GitHub issue 745 et.al.)
  • Multiple queries can now be made at the POST /{db}/_all_docs/queries, POST /{db}/_design_docs/queries and POST /{db}/_local_docs/queries endpoints. Also, a new endpoint POST /{db}/_design/{ddoc}/_view/{view}/queries
  • The least recently used (LRU) cache of databases is now only updated on database write, not read. This has lead to significant performance enhancements on very busy clusters.
  • The revision stemming algorithm was optimized down from O(N^2) to O(N) via a depth-first search approach, and then further improved by calling the stemming operation only when necessary.
  • CouchDB now checks for request authorization only once per each database request, improving the performance of any request that requires authorization.
  • If a user specifies a value for use_index that is not valid for the selector (does not meet coverage requirements or proper sort fields), attempt to fall back to a valid index or full DB scan rather than returning a 400.
  • CouchDB now includes a new builtin reduce function_approx_count_distinct, that uses a HyperLogLog algorithm to estimate the number of distinct keys in the view index. The precision is currently fixed to 2^11 observables, and therefore uses approximately 1.5KB of memory.
  • Much improved documentation. Highlights include:

    • A complete rewrite of the sharding documentation.
    • Developer installation notes (INSTALL.*.rst)
    • Much of the content of the original CouchDB Wiki has been imported into the official docs. (The old CouchDB Wiki is in the process of being deprecated.)

     

  • Much improved Fauxton functionality. Highlights include:

    • Search support in the code editor
    • Support for relative Fauxton URLs (i.e., not always at /_utils)
    • Replication setup enhancements for various authentication mechanisms
    • Fixes for IE10, IE11, and Edge (we hope…)
    • Resolving conflicts of design documents is now allowed

     

  • Many more smaller bug fixes and performance improvements, as well as more features and refinements. See the release notes for a full list.

 

CouchDB Authentication Without Server-Side Code

This is a guest post by community member Eirik Brandtzæg.

The powerful REST API in CouchDB makes building client-side only web applications a breeze.
It’s also possible to add security by requiring users to authenticate against the API. Here we will discuss how to authenticate against the API client-side, or via nginx, without any server-side code.

Enable authentication

Enable authentication by setting require_valid_user to true, located under Configuration > Main config > chttpd. With the setting enabled every request to CouchDB must either contain a valid Authentication or Cookie header, a simple yet effective security mechanism.

Client-side authentication

The best way for a client to login is to retrieve a cookie, which is done by making a POST against /_session with name and password in body, like this:
(note; won’t work)

POST /_session
Content-Type: application/json

{"name":"admin","password":"admin"}

However, it’s a bit more challenging, because the request making the POST must also include a valid Authroization or Cookie header for the user we want to authenticate.
The only way to properly receive a Cookie is to include the Authorization header, so the request using Basic authentication must look like this:

POST /_session
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=

{"name":"admin","password":"admin"}

With curl this could be achieved by the following command curl http://admin:admin@localhost:5984/_session -H 'Content-Type: application/json' -d '{"name":"admin","password":"admin"}'.

In a client-side web application we can achieve this by appending the header to the request, like this:

fetch('/_session', {
  method: 'POST',
  credentials: 'include',
  headers: {
    'content-type': 'application/json',
    authorization: `Basic ${btoa('admin:admin')}`
  },
  body: JSON.stringify({name: 'admin', password: 'admin'})
})

Now every subsequent request only need to include credentials: 'include'.
This solution also works cross-domain, by enabling CORS under Configuration > CORS, even with All domains (*) as origin.
It’s also possible to do all requests using the Authorization header, but the Cookie-approach is much cleaner and safer.

Project Fauxton

One of the immediate drawbacks of requiring valid users is that /_utils (Project Fauxton) is essentially unreachable, as the login-form is also locked down.

Logging in via the client-side approach above is possible, if the web application is hosted on the same origin (domain) as CouchDB, or by running the above fetch-code directly in the browser DevTools console.

nginx and proxy_pass

Another more sleek solution is to add nginx as a proxy, and render a custom login-form when CouchDB requires authentication. Any other proxy/gateway can also be used.

We can set nginx to proxy all requests to CouchDB, and tell it to intercept all errors. When intercepting errors nginx will show its own error pages, and then we can create our own 401 page, which is a login form.
Creating a custom path, e.g., /login, is also possible.
The config could look like this:

server {
  listen 80;
  
  location ~ 401.html$ {
    alias /usr/share/nginx/html/couchdb/login.html;
  }

  location / {
    error_page 401 /401.html;
    proxy_intercept_errors on;
    proxy_pass http://172.17.0.1:5984;
  }
}

Then we can make a custom login form as login.html.
Note, normal form with action won’t work, again because of the required Authorization header, but we can use the JavaScript snippet from above to bypass this:

<!doctype html>
<title>LOGIN!</title>
<form id="form">
  <input id="username" placeholder="name">
  <input id="password" placeholder="password" type="password">
  <button type="submit">LOGIN!</button>
  <p id="output"></p>

  <a href="/_utils">Go to Project Fauxton</a>
</form>
<script>
  form.addEventListener('submit', e => {
    e.preventDefault();
    const data = {name: username.value, password: password.value};
    output.innerText = '';
    fetch('/_session', {
      method: 'POST',
      credentials: 'include',
      headers: {
        'content-type': 'application/json', 
        authorization: `Basic ${btoa(data.name + ':' + data.password)}`},
      body: JSON.stringify(data)
    }).then(res => res.text()).then(res => output.innerText = res);
  });
</script>