{"id":13630382,"url":"https://github.com/garbados/comdb","last_synced_at":"2025-04-16T01:27:33.496Z","repository":{"id":32801049,"uuid":"143048226","full_name":"garbados/comdb","owner":"garbados","description":"A PouchDB plugin that transparently encrypts and decrypts its data.","archived":false,"fork":false,"pushed_at":"2023-01-07T23:34:02.000Z","size":1498,"stargazers_count":62,"open_issues_count":7,"forks_count":4,"subscribers_count":8,"default_branch":"main","last_synced_at":"2024-10-19T21:47:26.437Z","etag":null,"topics":["couchdb","cryptography","pouchdb"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/garbados.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-31T17:50:59.000Z","updated_at":"2024-10-07T22:11:12.000Z","dependencies_parsed_at":"2023-01-14T22:16:47.725Z","dependency_job_id":null,"html_url":"https://github.com/garbados/comdb","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbados%2Fcomdb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbados%2Fcomdb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbados%2Fcomdb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbados%2Fcomdb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/garbados","download_url":"https://codeload.github.com/garbados/comdb/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249181353,"owners_count":21225889,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["couchdb","cryptography","pouchdb"],"created_at":"2024-08-01T22:01:40.737Z","updated_at":"2025-04-16T01:27:33.479Z","avatar_url":"https://github.com/garbados.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# ComDB\n\n[![CI](https://github.com/garbados/comdb/actions/workflows/ci.yaml/badge.svg)](https://github.com/garbados/comdb/actions/workflows/ci.yaml)\n[![Coverage Status](https://img.shields.io/coveralls/github/garbados/comdb/master.svg?style=flat-square)](https://coveralls.io/github/garbados/comdb?branch=master)\n[![Stability](https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square)](https://nodejs.org/api/documentation.html#documentation_stability_index)\n[![NPM Version](https://img.shields.io/npm/v/comdb.svg?style=flat-square)](https://www.npmjs.com/package/comdb)\n[![JS Standard Style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)\n\nA [PouchDB](https://pouchdb.com/) plugin that transparently encrypts and decrypts its data so that only encrypted data is sent during replication, while encrypted data that you receive is automatically decrypted. Uses [TweetNaCl](https://www.npmjs.com/package/tweetnacl) for cryptography.\n\nAs an example, here's what happens when you replicate data to a [CouchDB](https://couchdb.apache.org/) cluster:\n\n```javascript\nconst PouchDB = require('pouchdb')\nPouchDB.plugin(require('comdb'))\n\nconst password = 'extremely secure value'\n\nconst db = new PouchDB(POUCH_PATH)\ndb.setPassword(password).then(async () =\u003e {\n  await db.post({\n    _id: 'gay-agenda',\n    type: 'queerspiracy',\n    agenda: ['be gay', 'do crimes']\n  })\n  // now replicate to a couchdb instance\n  await db.replicate.to(`${COUCH_URL}/FALGSC`)\n})\n```\n\nNow you can check the CouchDB for the encrypted information:\n\n```\n$ curl \"$COUCH_URL/FALGSC/_all_docs?include_docs=true\" | jq .\n{\n  \"total_rows\": 1,\n  \"offset\": 0,\n  \"rows\": [\n    {\n      \"id\": \"...\",\n      \"key\": \"...\",\n      \"value\": {\n        \"rev\": \"1-[...]\"\n      },\n      \"doc\": {\n        \"_id\": \"...\",\n        \"_rev\": \"1-[...]\",\n        \"payload\": \"...\",\n      }\n    }\n}\n```\n\nComDB can also restore encrypted data that it doesn't already have\nusing your password.\n\n```javascript\n// using a different and empty database\nconst db = new PouchDB(`${POUCH_PATH}-2`)\n// but using the same password and encrypted copy\ndb.setPassword(password, { name: `${COUCH_URL}/FALGSC` })\n// you can restore data from a remote source\nreturn db.loadEncrypted().then(async () =\u003e {\n  return db.allDocs({ include_docs: true })\n}).then(({ rows }) =\u003e {\n  const { doc } = rows[0].doc\n  console.log(doc)\n})\n----\n{ _id: 'gay-agenda',\n  _rev: '1-[...]',\n  type: 'queerspiracy',\n  agenda: [ 'be gay', 'do crimes' ] }\n```\n\nThis way, the server can't (easily) know anything about your data, but you can still maintain query indexes.\n\nIn the above example, we replicated data from a local encrypted copy of our data, but you can use a CouchDB instance as your encrypted copy. That way, your documents will be automatically backed up to the remote instance.\n\n```javascript\nconst db = new PouchDB(POUCH_PATH)\nawait db.setPassword(password, { name: COUCH_URL })\n```\n\nYou can also set up encryption on another device by using `db.exportComDB()` and `db.importComDB()`.\nThis is useful when you want to maintain a separate encrypted copy of your data, for example\nbecause you want that separate copy to live on another device, while retaining the ability to\nreplicate with the original encrypted copy.\n\n```javascript\n// on one machine, get the encryption key. it'll be a long string.\nconst key = await db.exportComDB()\n// then on another machine, use the key with your password to set up encryption\nconst db = new PouchDB(POUCH_PATH)\nawait db.importComDB(password, key)\n// now you can replicate over from the original encrypted backup\nawait db.replicate.from(COUCH_URL)\n```\n\nNow you can give your data to strangers *with confidence!*\n\nFor more examples, check out the [`/examples`](./examples) folder.\n\n## Install\n\nYou can get ComDB with [npm](https://www.npmjs.com/):\n\n```bash\n$ npm i comdb\n```\n\nNow you can `require()` it in your [node.js](https://nodejs.org/en/) projects:\n\n```javascript\nconst PouchDB = require('pouchdb')\nPouchDB.plugin(require('comdb'))\n\nconst db = new PouchDB(...)\nawait db.setPassword(...)\n```\n\nYou can also use PouchDB and ComDB in the browser using [browserify](http://browserify.org/).\n\n## Usage\n\nComDB adds and extends several methods to PouchDB and any instances of it:\n\n### `PouchDB.replicate(source, target, [opts], [callback])`\n\nComDB wraps PouchDB's replicator to check if either the source or the target have an `_encrypted` attribute, reflecting that they are ComDB instances. If it finds the attribute, it changes the parameter to use the encrypted database rather than its decrypted one. If neither the source or target is a ComDB instance, the replicator behaves as normal.\n\nYou can also disable this functionality by passing `comdb: false` in the `opts`\nparameter:\n\n```javascript\nPouchDB.replicate(db1, db2, { comdb: false })\n```\n\nThe instance methods `db.replicate.to` and `db.replicate.from` automatically use `PouchDB.replicate` so that wrapping the static method causes the instance methods to exhibit the same behavior.\n\nOriginal: [`PouchDB.replicate`](https://pouchdb.com/api.html#replication)\n\n### `async db.setPassword(password, [opts])`\n\nMutates the instance with crypto tooling so that it can encrypt and decrypt documents.\n\n```javascript\nawait db.setPassword('hello world')\n// db will now maintain an encrypted copy\n```\n\n- `password`: A string used to encrypt and decrypt documents.\n- `opts.name`: A name or connection string for the encrypted database.\n- `opts.opts`: An options object passed to the encrypted database's constructor. Use this to pass any options accepted by [PouchDB's constructor](https://pouchdb.com/api.html#create_database).\n\n### `async db.destroy([opts], callback)`\n\nComDB wraps PouchDB's database destruction method so that both the encrypted and decrypted databases are destroyed. ComDB adds two options to the method:\n\n- `encrypted_only`: Destroy only the encrypted database. This is useful when a backup has become compromised and you need to burn it.\n- `unencrypted_only`: Destroy only the unencrypted database. This is useful if you are using a remote encrypted backup and want to burn the local device so you can restore from backup on a fresh one.\n\nOriginal: [db.destroy()](https://pouchdb.com/api.html#delete_database)\n\n### `async db.exportComDB()`\n\nExport the encryption key specific to your database's encrypted copy. This is necessary to creating new encrypted copies that can still replicate with the original, for example if you're creating an encrypted copy on a phone by replicating down from a server.\n\n```javascript\n// on one machine\nconst db1 = new PouchDB('device-1')\nawait db1.setPassword(password)\nconst key = await db1.exportComDB()\n// then, on another\nconst db2 = new PouchDB('device-2')\nawait db2.importComDB(password, key)\n// now db2 can replicate with db1\nawait PouchDB.sync(db1, db2)\n```\n\n### `async db.importComDB(password, encryptionKey)`\n\nSet up ComDB, like `db.setPassword()`, but rather than generating a new encryption key for your encrypted copy, ComDB will use the given one. This allows you to replicate with other encrypted databases using the same password and encryption key.\n\n```javascript\n// on one machine\nconst db1 = new PouchDB('device-1')\nawait db1.setPassword(password)\nconst key = await db1.exportComDB()\n// then, on another\nconst db2 = new PouchDB('device-2')\nawait db2.importComDB(password, key)\n// now db2 can replicate with db1\nawait PouchDB.sync(db1, db2)\n```\n\n### `async db.loadEncrypted(opts = {})`\n\nLoad changes from the encrypted database into the decrypted one. Useful if you are restoring from backup:\n\n```javascript\n// in-memory database is wiped on restart and so needs to be repopulated\nconst db = new PouchDB('local', { adapter: 'memory' })\n// the encrypted DB lives on remote disk, so we can load docs from it\ndb.setPassword(PASSWORD, { name: REMOTE_URL }).then(async () =\u003e {\n  await db.loadEncrypted()\n  // all encrypted docs have been loaded into the decrypted database\n})\n```\n\nAccepts the same options as [PouchDB.replicate()](https://pouchdb.com/api.html#replication).\n\n### `async db.loadDecrypted(opts = {})`\n\nLoad changes from the decrypted database into the encrypted one. Useful if you are instrumenting encryption onto a database that already exists.\n\n```javascript\n// db already exists, we are just adding encryption\nconst db = new PouchDB('local')\ndb.setPassword(PASSWORD).then(async () =\u003e {\n  await db.loadDecrypted()\n  // all decrypted docs have been loaded into the encrypted database\n})\n```\n\nAccepts the same options as [db.changes()](https://pouchdb.com/api.html#changes).\n\n## Recipe: End-to-End Encryption\n\nComDB can instrument end-to-end encryption of application data using [pouchdb-adapter-memory](https://www.npmjs.com/package/pouchdb-adapter-memory), so that documents are only decrypted in memory while everything on disk remains encrypted.\n\nConsider this setup:\n\n```javascript\n// in-memory database is wiped on restart and so needs to be repopulated\nconst db = new PouchDB('local', { adapter: 'memory' })\n// the encrypted copy lives on local disk, so we can load docs from it\ndb.setPassword(PASSWORD).then(async () =\u003e {\n  // repopulate database from encrypted local copy\n  await db.loadEncrypted()\n  // decrypted database is up to date, app is ready to go\n})\n```\n\nYou can then replicate your encrypted database with a remote CouchDB installation to ensure you can restore your data even if your device is compromised:\n\n```javascript\n// sync local encrypted with remote\nconst remoteDb = 'https://...' // CouchDB url\nconst sync = PouchDB.sync(db, remoteDb, { live: true, retry: true })\n```\n\nNow you'll have three copies of your data:\n\n- One in local memory, decrypted.\n- One on local disk, encrypted.\n- One on remote disk, encrypted.\n\nThe user syncs local disk with remote disk to have a remote encrypted backup, so the user can restore their info when switching devices. The local disk populates the in-memory database on startup, so that the only data that remains on disk remains encrypted. The user retains all their information locally, so they do not require network connectivity to use the app normally.\n\n## Development\n\nTo hack on ComDB, check out the [issues page](https://github.com/garbados/comdb/issues). To submit a patch, [submit a pull request](https://github.com/garbados/comdb/pulls).\n\nTo run the test suite, use `npm test` in the source directory:\n\n```bash\n$ git clone garbados/comdb\n$ cd comdb\n$ npm i\n$ npm test\n```\n\nA formal code of conduct is forthcoming. Pending it, contributions will be moderated at the maintainers' discretion.\n\n## License\n\n[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.html)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgarbados%2Fcomdb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgarbados%2Fcomdb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgarbados%2Fcomdb/lists"}