3

I am using PouchDB and CouchDB in an ionic application. While I can successfully sync local and remote databases on Chrome and Android, I get unauthorized error on Safari / iOS when I run the sync command. Below is a simplified version of my database service provider.

import PouchDB from 'pouchdb';
import PouchDBAuthentication from 'pouchdb-authentication';

@Injectable()
export class CouchDbServiceProvider {
    private db: any;
    private remote: any;
    constructor() {
      PouchDB.plugin(PouchDBAuthentication);
      this.db = new PouchDB('localdb', {skip_setup: true});
    }
    ...
    login(credentials) {
      let couchDBurl = 'URL of my couchDB database';
      this.remote = new PouchDB(couchDBurl); 
      this.remote.logIn(credentials.username, credentials.password, function (err, response) {
            if (err) { concole.log('login error') }
            else {
                let options = { live: true, retry: true, continuous: true };
                this.db.sync(this.remote, options).on('error', (err_) => { console.log('sync error')});
            }
      })
    }
    ...
}

In the code above, this.remote.logIn(...) is successful but this.db.sync(...) fails. I have checked the requests via the network tab of developer tools and I believe the issue is that the cookie that's retruned in the response header of this.remote.logIn(...) is not used by the subsequent calls (thus the unauthorized error). The issue is fixed once third-party cookies are enabled on Safari, which is not an option on iOS.

How can I fix this problem?

One potential solution I'm considering is overriding fetch to use native http client (i.e., an instance of HTTP from @ionic-native/http). It seems modifying http clients is a possibility (e.g., according to this conversation) but I'm not sure how to achieve that.

curious_ys
  • 57
  • 1
  • 6

3 Answers3

2

Changing the HTTP plumbing sounds like a really bad idea - time cost, mainly - unless you just absolutely have to use sessions/cookies...If you don't, read on.

as noted here regarding pouchDB Security, I tried using pouchdb-authentication when it was actively maintained and went another route due to multiple issues (I don't recall specifics, it was 6 years ago).

Do note the last commit to pouchdb-authentication seems to be 3 years ago. Although inactivity is not an negative indicator on the surface - a project may have simply reached a solid conclusion - installing pouchdb-authentication yields this

found 6 vulnerabilities (2 moderate, 3 high, 1 critical)

That plus the lack of love given to plugin over the last few years makes for a dangerous technical debt to add for a new project.

If possible simply send credentials using the auth option when creating (or opening) a remote database, e.g.

const credentials = { username: 'foo', passwd: 'bar' };
this.remote = new PouchDB(couchDBurl, { auth: credentials }); 

I don't recall why but I wrote code that is in essence what follows below, and have reused it ad nauseum because it just works with the fetch option

const user = { name: 'foo', pass: 'bar' };
const options = { fetch: function (url, opts) {
    opts.headers.set('Authorization', 'Basic ' + window.btoa(user.name+':'+user.pass));
    return PouchDB.fetch(url, opts);
  }
};

this.remote = new PouchDB(couchDBurl, options); 

I believe I chose this approach due to the nature of my authentication workflow discussed in the first link of this answer.

RamblinRose
  • 4,883
  • 2
  • 21
  • 33
  • 1
    Thanks! This worked with the following tweak as typescript complained about `set` not being recognized: `const headers: any = opts.headers; opts.headers.set('Authorization', 'Basic ' + window.btoa(user.name+':'+user.pass));` – curious_ys Jan 12 '22 at 23:47
1

I agree with @RamblinRose that you might have to include the headers manually when you define the PouchDB object.

I myself have found a solution when working with JWTs that need to be included in the header for sync purposes.

See this answer. Note: RxDB uses PouchDB under the hood so it's applicable to this situation. It helped me sync, hope it does you too!

https://stackoverflow.com/a/64503760/5012227

zinglax
  • 118
  • 4
1

One potential solution I'm considering is overriding fetch to use native http client (i.e., an instance of HTTP from @ionic-native/http). It seems modifying http clients is a possibility (e.g., according to this conversation) but I'm not sure how to achieve that.

Yes, this is a possible option - especially if you want to use SSL pinning which will only work with native requests. And you don't need to worry about CORS (apart from ionic serve).

You can achieve this e.g. by taking an existing fetch - polyfill and modifying it s.t. it uses the http plugin instead of xhr. And since you'll only deal with JSON when interacting with the CouchDB, you can throw away most of the polyfill.

LyteFM
  • 895
  • 8
  • 12
  • Is there an example showing how this is done that I can look at? – curious_ys Jan 18 '22 at 23:20
  • I can't paste the code, sorry. But I basically took that `fetch` - polyfill, made the requests with `@ionic-native/http` instead of `xhr` and tossed out the code that wasn't needed when only using JSON (which is enough for PouchDB/CouchDB) – LyteFM Jan 22 '22 at 16:20