{"action":"created","issue":{"url":"https://api.github.com/repos/pouchdb/pouchdb/issues/4364","labels_url":"https://api.github.com/repos/pouchdb/pouchdb/issues/4364/labels{/name}","comments_url":"https://api.github.com/repos/pouchdb/pouchdb/issues/4364/comments","events_url":"https://api.github.com/repos/pouchdb/pouchdb/issues/4364/events","html_url":"https://github.com/pouchdb/pouchdb/issues/4364","id":108431501,"number":4364,"title":"Authentication hook required inside live replication retry loop to support offline ->online -> offline -> online state changes","user":{"login":"tohagan","id":301525,"avatar_url":"https://avatars.githubusercontent.com/u/301525?v=3","gravatar_id":"","url":"https://api.github.com/users/tohagan","html_url":"https://github.com/tohagan","followers_url":"https://api.github.com/users/tohagan/followers","following_url":"https://api.github.com/users/tohagan/following{/other_user}","gists_url":"https://api.github.com/users/tohagan/gists{/gist_id}","starred_url":"https://api.github.com/users/tohagan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tohagan/subscriptions","organizations_url":"https://api.github.com/users/tohagan/orgs","repos_url":"https://api.github.com/users/tohagan/repos","events_url":"https://api.github.com/users/tohagan/events{/privacy}","received_events_url":"https://api.github.com/users/tohagan/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/pouchdb/pouchdb/labels/enhancement","name":"enhancement","color":"02d7e1"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":9,"created_at":"2015-09-26T01:15:51Z","updated_at":"2015-11-14T03:41:17Z","closed_at":null,"body":"Currently to implement cookie authentication (and potentially other token based authentications) we have a couple of implementation options:\r\n\r\n**Option 1**. login() then sync()\r\n\r\nI think this is what current [pouchdb-authentication](https://github.com/nolanlawson/pouchdb-authentication) db.login() example shows.\r\n\r\n- Perform cookie auth when **online**  (db.login())\r\n- Now start up live sync() that watches for when you go offline.\r\n\r\n- Assumes user is initially online when app starts up \r\n- Fails if they are initially offline (no hook to initiate db.login() in retry loop)\r\n\r\n**Option 2**.  Wait until the remote db is online then sync\r\n\r\n- Fails if the server or network subsequently goes offline.\r\n- sync() will retry and attempt to reconnect but cookie session may have timed out.\r\n\r\n**Option 3**. Perform login as a callback hook or authentication adapter \r\n\r\n- Register an authentication provider plugin (like an adapter) with remote db instance (note that different db instances may require different authentication so just registering it with PouchDb is not the best solution).\r\n- Start up live db.sync() or db.replicate() and supply authentication options as part of sync() or replication() options. \r\n- PouchDb replication retry loop now always initiates authentication (even the 1st time).\r\n\r\nNow works if app state changes from **offline** -> **online** -> **offline** -> **online** as the authentication is reapplied each time the replication goes online. We're no longer juggling multiple retry loops. \r\n\r\nIdea: If we're using [Cordova's Network Information plugin](https://github.com/apache/cordova-plugin-network-information), the replication retry loop might be able to leverage it's \"online\" event - or there might be a way for the app to inform the sync handle that this event has occurred. "},"comment":{"url":"https://api.github.com/repos/pouchdb/pouchdb/issues/comments/156620261","html_url":"https://github.com/pouchdb/pouchdb/issues/4364#issuecomment-156620261","issue_url":"https://api.github.com/repos/pouchdb/pouchdb/issues/4364","id":156620261,"user":{"login":"tohagan","id":301525,"avatar_url":"https://avatars.githubusercontent.com/u/301525?v=3","gravatar_id":"","url":"https://api.github.com/users/tohagan","html_url":"https://github.com/tohagan","followers_url":"https://api.github.com/users/tohagan/followers","following_url":"https://api.github.com/users/tohagan/following{/other_user}","gists_url":"https://api.github.com/users/tohagan/gists{/gist_id}","starred_url":"https://api.github.com/users/tohagan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tohagan/subscriptions","organizations_url":"https://api.github.com/users/tohagan/orgs","repos_url":"https://api.github.com/users/tohagan/repos","events_url":"https://api.github.com/users/tohagan/events{/privacy}","received_events_url":"https://api.github.com/users/tohagan/received_events","type":"User","site_admin":false},"created_at":"2015-11-14T03:41:17Z","updated_at":"2015-11-14T03:41:17Z","body":"\r\nHere's the code from [replicate.js line 245](https://github.com/pouchdb/pouchdb/blob/master/lib/replicate/replicate.js#L245) that calls backOff()` that fires an emit('pause', error)` event. \r\n\r\n      var non403s = allErrors.filter(function (error) {\r\n        return error.name !== 'unauthorized' && error.name !== 'forbidden';\r\n      });\r\n      if (non403s.length > 0) {\r\n        var error = allErrors.pop();\r\n        if (allErrors.length > 0) {\r\n          error.other_errors = allErrors;\r\n        }\r\n        error.result = result;\r\n        backOff(opts, returnValue, error, function () {\r\n          replicate(src, target, opts, returnValue);\r\n        });\r\n      } else {\r\n        result.errors = allErrors;\r\n        returnValue.emit('complete', result);\r\n        returnValue.removeAllListeners();\r\n      }\r\n\r\nThis is the only place where 'pause' returns an error. It sends this event when there is at least one non 403 error which I'm assuming means that we had one of [these http error codes](https://wiki.apache.org/couchdb/HTTP_status_list).  In contrast, if we get a 403 unauthorized/forbidden from a validation doc and *no other errors* then this will cause the replication/sync to complete. \r\n\r\n**Observation 1**: I'm not convinced that the `if (non403s.length > 0)` condition is correct. I would have thought that *any* 403 error should cause the replication to stop (e.g. complete) because if you get any 403's the replication can then **never succeed**.   I guess what happens at the moment is that it will backOff and retry and then just get a single 403 and then complete with that error. We'd also get the original errors reported via 'error' event inside `backOff()`.\r\n\r\nI think that this design assumes that validation docs in the server should have been replicated into the client to acts as a client side prechecks to validate changes prior to them being pushed to the server as otherwise they would **cause all future replications to fail**. Validations that depend on ctxUser may not work very well client side unless you replicate _user or at least current userCtx somehow.  Is that what local validation docs do? What am I missing?  \r\n\r\n      var all403s = allErrors.filter(function (error) {\r\n        return error.name === 'unauthorized' || error.name === 'forbidden';\r\n      });\r\n\r\n      if (all403s.length > 0) {\r\n        result.errors = allErrors;\r\n        returnValue.emit('complete', result);\r\n        returnValue.removeAllListeners();\r\n      } else { \r\n        var error = allErrors.pop();\r\n        if (allErrors.length > 0) {\r\n          error.other_errors = allErrors;\r\n        }\r\n        error.result = result;\r\n        backOff(opts, returnValue, error, function () {\r\n          replicate(src, target, opts, returnValue);\r\n        });\r\n      }\r\n\r\n**Observation 2**  failedAuth() will very rarely if ever be true except in the extremely rare case where we get at least one non-403 and the 1st error is a 403.  Right?\r\n\r\nSo I think I should be just testing for a failedAuth() only in the 'complete' event by checking `error.name === 'unauthorized'` in `result.errors` and then attempting a db.login() then db.sync().  If the login fails with a `403 unauthorized` we should abort the replication. If we get another error we should retry the login .. ideally with backoff (Perhaps the backoff is not really needed as 403 followed by non403 would probably be very rare).\r\n"}}