{"_id":"@apihero/prisma-field-encryption","_rev":"1-34d44f4ca7ec85efa7218c2bca8a28da","name":"@apihero/prisma-field-encryption","dist-tags":{"latest":"1.3.5"},"versions":{"1.3.4":{"name":"@apihero/prisma-field-encryption","version":"1.3.4","description":"Transparent field-level encryption at rest for Prisma","main":"dist/index.js","types":"dist/index.d.ts","license":"MIT","bin":{"prisma-field-encryption":"dist/generator/main.js"},"author":{"name":"François Best","email":"contact@francoisbest.com","url":"https://francoisbest.com"},"repository":{"type":"git","url":"git+https://github.com/47ng/prisma-field-encryption.git"},"keywords":["prisma","middleware","encryption","aes-256-gcm"],"publishConfig":{"access":"public"},"scripts":{"clean":"rm -rf ./dist ./coverage","prebuild":"run-s generate:prisma","build":"tsc","postbuild":"chmod +x ./dist/generator/main.js && cd node_modules/.bin && ln -sf ../../dist/generator/main.js ./prisma-field-encryption","generate":"run-s generate:*","generate:prisma":"prisma generate","test":"run-s test:**","test:types":"tsc --noEmit","test:unit":"jest --config jest.config.unit.json","pretest:integration":"cp -f ./prisma/db.test.sqlite ./prisma/db.integration.sqlite","test:integration":"jest --config jest.config.integration.json --runInBand","test:coverage:merge":"nyc merge ./coverage ./coverage/coverage-final.json","test:coverage:report":"nyc report -t ./coverage --r html -r lcov -r clover","ci":"run-s build test","prepare":"husky install","premigrate":"run-s build generate","migrate":"ts-node ./src/tests/migrate.ts"},"dependencies":{"@47ng/cloak":"^1.1.0","@prisma/generator-helper":"^4.0.0","debug":"^4.3.4","immer":"^9.0.15","object-path":"^0.11.8","zod":"^3.17.3"},"peerDependencies":{"@prisma/client":"^3.8.0 || ^4"},"devDependencies":{"@commitlint/config-conventional":"^17.0.3","@prisma/client":"4.0.0","@prisma/sdk":"^3.15.2","@types/jest":"^28.1.4","@types/node":"^18.0.3","@types/object-path":"^0.11.1","commitlint":"^17.0.3","husky":"^8.0.1","jest":"^28","npm-run-all":"^4.1.5","nyc":"^15.1.0","prisma":"^4.0.0","sqlite":"^4.1.1","sqlite3":"^5.0.8","ts-jest":"^28.0.5","ts-node":"^10.8.2","typescript":"^4.7.4"},"jest":{"verbose":true,"preset":"ts-jest/presets/js-with-ts","testEnvironment":"node"},"prettier":{"arrowParens":"avoid","semi":false,"singleQuote":true,"tabWidth":2,"trailingComma":"none","useTabs":false},"commitlint":{"extends":["@commitlint/config-conventional"],"rules":{"type-enum":[2,"always",["build","chore","ci","clean","doc","feat","fix","perf","ref","revert","style","test"]],"subject-case":[0,"always","sentence-case"],"body-leading-blank":[2,"always",true]}},"gitHead":"8b6979bfe92d60d52f9030b1f9790a80947ac66d","bugs":{"url":"https://github.com/47ng/prisma-field-encryption/issues"},"homepage":"https://github.com/47ng/prisma-field-encryption#readme","_id":"@apihero/prisma-field-encryption@1.3.4","_nodeVersion":"18.8.0","_npmVersion":"8.18.0","dist":{"integrity":"sha512-oKgq/7fTRPRjf9LJwYnNhtqJQp5pabTKS4rRC/GQO2kBnU07OIHrBdJJUqOmyuGcl1vwG2tPWrKsHJb9+D0L6Q==","shasum":"87621c5b10b9ae915fe0cd279372750bf48c6b2b","tarball":"https://registry.npmjs.org/@apihero/prisma-field-encryption/-/prisma-field-encryption-1.3.4.tgz","fileCount":35,"unpackedSize":75005,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCID2S/F+luYklmZIVoMvxd/bpGJdY6rHHNMeoIZzJixFFAiAxxtVN1IrFt54BFd4p+SsZFjUm+4Nad6hBqIVF4dngHQ=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjLEpeACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq0mw/+MuD4wVAqOkdBrcIGyeJ8bA/Ig0xDke90sKwkA/qyOUscCmuZ\r\nqb/mLLhnuk3nd5p3wdVxfNC1sOVG7Zjq0eUn+cm6Lf47DxqROFUXmukJyqjq\r\n8STS08XDxymyvII6quNY4MgtRcm5eHrjBKpHruSIkkFCJevK0aBuALL6mzv0\r\ndt6OKeD01Yd3G01e9DxB6x/z+uhEJM6+NLFUnLNRb0urZMNSXVE5lxdOY3Oo\r\n/yh82wQvW8vdHtXKf+tRHcb2u8gR+cyizOPMYXJzUjKx62XgbhNBi35Qw/wK\r\nCUEi4K6W3pEglg5F80tfLPBFJZC6aPEaxdkiHehXVLDvwMewFlZ2NKni3+HV\r\n8Gb7bCp3NQ6JuXxkJg1iDafn6YzUTd9rr1+b4YCdmmUIL8U1yEXkyXmD/0d3\r\nYNGPUnQu1yhcgY0Bgat+peUqZK4fEGD4cz5q5J9PgdTQXJOZDhniyOR3jlWW\r\nVXmvTDH4olSCjoM7sEZTMAJIGyNshtg3dhxtEJIvdQ2jvJTe9PXdOMBqdIhj\r\n/eM5QvLgz7edFG/SEIocN6OAeEupDVhtRC7JPwUlyy4EnTTNY3iLqP+TAsT3\r\n7SKyshjGozX7cqEKxm6BQR+tc7SI1Ik1D4IGFjuNfxW5TMkDLBX5Goqs9uz7\r\nB4Zd3nDZDb0MsTVw5xIkBl9e2JhbyCylKs8=\r\n=lTwH\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"ericallam","email":"eallam@icloud.com"},"directories":{},"maintainers":[{"name":"mattaitken","email":"matt@mattaitken.com"},{"name":"ericallam","email":"eallam@icloud.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/prisma-field-encryption_1.3.4_1663847005956_0.11681680168868858"},"_hasShrinkwrap":false},"1.3.5":{"name":"@apihero/prisma-field-encryption","version":"1.3.5","description":"Transparent field-level encryption at rest for Prisma","main":"dist/index.js","types":"dist/index.d.ts","license":"MIT","author":{"name":"François Best","email":"contact@francoisbest.com","url":"https://francoisbest.com"},"repository":{"type":"git","url":"git+https://github.com/47ng/prisma-field-encryption.git"},"keywords":["prisma","middleware","encryption","aes-256-gcm"],"publishConfig":{"access":"public"},"scripts":{"clean":"rm -rf ./dist ./coverage","prebuild":"run-s generate:prisma","build":"tsc","postbuild":"chmod +x ./dist/generator/main.js && cd node_modules/.bin && ln -sf ../../dist/generator/main.js ./prisma-field-encryption","generate":"run-s generate:*","generate:prisma":"prisma generate","test":"run-s test:**","test:types":"tsc --noEmit","test:unit":"jest --config jest.config.unit.json","pretest:integration":"cp -f ./prisma/db.test.sqlite ./prisma/db.integration.sqlite","test:integration":"jest --config jest.config.integration.json --runInBand","test:coverage:merge":"nyc merge ./coverage ./coverage/coverage-final.json","test:coverage:report":"nyc report -t ./coverage --r html -r lcov -r clover","ci":"run-s build test","prepare":"husky install","premigrate":"run-s build generate","migrate":"ts-node ./src/tests/migrate.ts"},"dependencies":{"@47ng/cloak":"^1.1.0","@prisma/generator-helper":"^4.0.0","debug":"^4.3.4","immer":"^9.0.15","object-path":"^0.11.8","zod":"^3.17.3"},"peerDependencies":{"@prisma/client":"^3.8.0 || ^4"},"devDependencies":{"@commitlint/config-conventional":"^17.0.3","@prisma/client":"4.0.0","@prisma/sdk":"^3.15.2","@types/jest":"^28.1.4","@types/node":"^18.0.3","@types/object-path":"^0.11.1","commitlint":"^17.0.3","husky":"^8.0.1","jest":"^28","npm-run-all":"^4.1.5","nyc":"^15.1.0","prisma":"^4.0.0","sqlite":"^4.1.1","sqlite3":"^5.0.8","ts-jest":"^28.0.5","ts-node":"^10.8.2","typescript":"^4.7.4"},"jest":{"verbose":true,"preset":"ts-jest/presets/js-with-ts","testEnvironment":"node"},"prettier":{"arrowParens":"avoid","semi":false,"singleQuote":true,"tabWidth":2,"trailingComma":"none","useTabs":false},"commitlint":{"extends":["@commitlint/config-conventional"],"rules":{"type-enum":[2,"always",["build","chore","ci","clean","doc","feat","fix","perf","ref","revert","style","test"]],"subject-case":[0,"always","sentence-case"],"body-leading-blank":[2,"always",true]}},"gitHead":"8b6979bfe92d60d52f9030b1f9790a80947ac66d","bugs":{"url":"https://github.com/47ng/prisma-field-encryption/issues"},"homepage":"https://github.com/47ng/prisma-field-encryption#readme","_id":"@apihero/prisma-field-encryption@1.3.5","_nodeVersion":"18.8.0","_npmVersion":"8.18.0","dist":{"integrity":"sha512-Rj2QEQV3tgCQ/JmVX/oOsDn5SP71AcUsMGKvHwrTa4Mf6x4eTJivHoxld6/i3rd3Vq5Cq+MX9y9Kn5rcsoVORg==","shasum":"481aa878baabeb6f37a0e959c953579e571428e3","tarball":"https://registry.npmjs.org/@apihero/prisma-field-encryption/-/prisma-field-encryption-1.3.5.tgz","fileCount":35,"unpackedSize":74931,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIB2c2DhZd7ZkOMsM4Z2glMYZbVG+bQpw5xWCE+hietemAiBiQ1OI0QUZoExOUw82Uz9nhWVwrMpWiWOYmeQpqnOAwg=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjLEuJACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqXoA//YVNYF6vEqUbNXbQTNmmldrwDAi7Dw5FzxNUf71sPXccUrm/f\r\nVmNJVXWS5lA8RWXirV7YPMJA+1qJoVRvXt05i1EQzV2iiLQN34R3vKeQk3WQ\r\nxuMuZ7oWLa+VyW3/VGG8htqHSHCOCaqnrxVVak5nXJwOQkVJi5lQ6DvhUUId\r\nic/fl/s++63ylAh+YZCq5G/9EQyNKvNOBct3FQbuxVwMpcgPS7FMjCsa+loh\r\nsqD01i1uaJaJsHbWhSdLJbIlL1jy8ITwfNja3R4UcYliVFAPxGSUmpfhldL0\r\nz95ZzkBOqkBip6Yv+7UDqXpJXqzFE0F8HpXVPTLoE1FOhqbXiBUg1OCbREto\r\nLq71gVnM0dnQn4x4vD0IxslHNGwg1a8VU/FeQgfWx7f53QsRT7eEVXRhCGSd\r\nuMmZ9HMJB4Xo4Sxuy3o+9a0xo/TUgGzbo0n47+f9EGsVvbfvrfow1re5cuci\r\n9omPKgWQJnUglX6ql4WW3/uXRSiBhx7M/4hALkzvKQ0q/J/TzncZWOLFlkCh\r\nkXxoKunvXk3pMqlAy5zCEe9xNcmYgMJq7LnlW5mpiRqWZEhY3Ls+5csbV/Dh\r\n/s59AE64+6/QlmlUQZ0RPL7y1spBqcjJC9qjpObfz0HY85qCOEwpJdx/iMjk\r\njTQZYC3QpK1RsJvd2lUcm6kXsVD2MdhMaLg=\r\n=ueTZ\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"ericallam","email":"eallam@icloud.com"},"directories":{},"maintainers":[{"name":"mattaitken","email":"matt@mattaitken.com"},{"name":"ericallam","email":"eallam@icloud.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/prisma-field-encryption_1.3.5_1663847305476_0.4222672515921915"},"_hasShrinkwrap":false}},"time":{"created":"2022-09-22T11:43:25.885Z","1.3.4":"2022-09-22T11:43:26.120Z","modified":"2022-09-22T11:48:25.690Z","1.3.5":"2022-09-22T11:48:25.603Z"},"maintainers":[{"name":"mattaitken","email":"matt@mattaitken.com"},{"name":"ericallam","email":"eallam@icloud.com"}],"description":"Transparent field-level encryption at rest for Prisma","homepage":"https://github.com/47ng/prisma-field-encryption#readme","keywords":["prisma","middleware","encryption","aes-256-gcm"],"repository":{"type":"git","url":"git+https://github.com/47ng/prisma-field-encryption.git"},"author":{"name":"François Best","email":"contact@francoisbest.com","url":"https://francoisbest.com"},"bugs":{"url":"https://github.com/47ng/prisma-field-encryption/issues"},"license":"MIT","readme":"<h1 align=\"center\"><code>prisma-field-encryption</code></h1>\n\n<div align=\"center\">\n\n[![NPM](https://img.shields.io/npm/v/prisma-field-encryption?color=red)](https://www.npmjs.com/package/prisma-field-encryption)\n[![MIT License](https://img.shields.io/github/license/47ng/prisma-field-encryption.svg?color=blue)](https://github.com/47ng/prisma-field-encryption/blob/main/LICENSE)\n[![Continuous Integration](https://github.com/47ng/prisma-field-encryption/workflows/Continuous%20Integration/badge.svg?branch=next)](https://github.com/47ng/prisma-field-encryption/actions)\n[![Coverage Status](https://coveralls.io/repos/github/47ng/prisma-field-encryption/badge.svg?branch=next)](https://coveralls.io/github/47ng/prisma-field-encryption?branch=next)\n\n</div>\n\n<p align=\"center\">Transparent field-level encryption at rest for Prisma.</p>\n\n## Context\n\n[Demo repository](https://github.com/franky47/prisma-field-encryption-sandbox).\n\nSee this [Twitter thread](https://twitter.com/fortysevenfx/status/1463265166682898438) for more information.\n\n## Installation\n\n```shell\n$ yarn add prisma-field-encryption\n# or\n$ npm i prisma-field-encryption\n```\n\n> _Note: this requires Prisma 3.8.0 or higher._\n\n## Usage\n\n### 1. Add the middleware to your Prisma client\n\n```ts\nimport { PrismaClient } from '@prisma/client'\nimport { fieldEncryptionMiddleware } from 'prisma-field-encryption'\n\nexport const client = new PrismaClient()\n\n// This is a function, don't forget to call it:\nclient.$use(fieldEncryptionMiddleware())\n```\n\n_Tip: place the middleware as low as you need cleartext data._\n\n_Any middleware registered after field encryption will receive encrypted data for the selected fields._\n\n### 2. Setup your encryption key\n\nGenerate an encryption key:\n\n- Via a web UI: [cloak.47ng.com](https://cloak.47ng.com)\n- Via the command line:\n\n```shell\n$ cloak generate\n```\n\n> _Note: the `cloak` CLI comes pre-installed with `prisma-field-encryption` as part of the [`@47ng/cloak`](https://github.com/47ng/cloak) dependency._\n\nThe preferred method to provide your key is via the `PRISMA_FIELD_ENCRYPTION_KEY`\nenvironment variable:\n\n```shell\n# .env\nPRISMA_FIELD_ENCRYPTION_KEY=k1.aesgcm256.DbQoar8ZLuUsOHZNyrnjlskInHDYlzF3q6y1KGM7DUM=\n```\n\nYou can also pass it directly in the configuration:\n\n```ts\nclient.$use(\n  fieldEncryptionMiddleware({\n    // Don't version hardcoded keys though, this is an example:\n    encryptionKey: 'k1.aesgcm256.DbQoar8ZLuUsOHZNyrnjlskInHDYlzF3q6y1KGM7DUM='\n  })\n)\n```\n\n_Tip: a key provided in code will take precedence over a key from the environment._\n\n### 3. Annotate your schema\n\nIn your Prisma schema, add `/// @encrypted` to the fields you want to encrypt:\n\n```prisma\nmodel Post {\n  id        Int     @id @default(autoincrement())\n  title     String\n  content   String? /// @encrypted <- annotate fields to encrypt\n  published Boolean @default(false)\n  author    User?   @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n  authorId  Int?\n}\n\nmodel User {\n  id    Int     @id @default(autoincrement())\n  email String  @unique\n  name  String? /// @encrypted <- can be optional\n  posts Post[]\n}\n```\n\n_Tip: make sure you use a triple-slash. Double slash comments won't work._\n\n> #### Note on @db.VarChar & field max lengths\n>\n> Encryption adds quite a bit of overhead, so you'll need to raise your database\n> field maximum lengths (usually declared with `@db.VarChar(someNumber)` [or similar](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string)).\n>\n> You can calculate the corresponding ciphertext length for a given clear-text length here:\n> https://cloak.47ng.com/ciphertext-length-calculator\n\n### 4. Regenerate your client\n\nMake sure you have a generator for the Prisma client:\n\n```prisma\ngenerator client {\n  provider = \"prisma-client-js\"\n}\n```\n\nThen generate it using the `prisma` CLI:\n\n```shell\n$ prisma generate\n```\n\nYou're done!\n\n## Filtering using `where`\n\n> _Note: this functionality is in preview in `^1.4.0-beta.4`_\n\nYou cannot filter **directly** on encrypted fields:\n\n```prisma\nmodel User {\n  id    String @id\n  email String /// @encrypted\n}\n```\n\n```ts\n// This will return empty results:\nprisma.user.findUnique({\n  where: {\n    email: 'blofeld@spectre.corp'\n  }\n})\n```\n\nThis is because the encryption is not deterministic: encrypting the same input\nmultiple times will yield different outputs, due to the use of random initialisation\nvectors to keep ciphertext safe. Therefore Prisma cannot match the query to the data.\n\nFor the same reason, indexes should not be placed on encrypted fields.\n\nTo circumvent this issue, the middleware provides support for a separate field\ncontaining a hash of the clear-text input, which is stable and can be used for\n**exact** matching _(partial matching like `startsWith`, `contains` is not possible)_.\n\nTo use it, add a field next to your encrypted field with the following annotation:\n\n```prisma\nmodel User {\n  id        String  @id\n  email     String  @unique /// @encrypted\n  emailHash String? @unique /// @encryption:hash(email) <- the name of the source field\n\n  // Note that the @unique directive on `email` is here to enable\n  // the Prisma user.findUnique({ where: { email }}) API,\n  // and the @unique directive on `emailHash` is where you actually\n  // ensure that there will be no duplicates (short of hash collisions).\n  // The emailHash field is marked as nullable so you don't need to specify\n  // it when creating records (it will be computed for you).\n}\n```\n\nThe annotation will automatically keep the `emailHash` field up to date when\ncreating or updating `email` values, and will allow the following:\n\n```ts\n// Now this works\nprisma.user.findUnique({\n  where: {\n    email: 'james.bond@mi6.co.uk'\n  }\n})\n```\n\nInternally, the `where` clause will be rewritten to match the emailHash field\nwith the computed hash of the clear-text input (kind of like a password check).\n\n### Hashing options\n\nThe default hash is a SHA-256 of the input interpreted as UTF-8,\nwith a hexadecimal output encoding (lowercase).\n\nYou can change those settings in the annotation, as follows:\n\n```\n/// @encryption:hash(email)?algorithm=sha512 <- anything supported by Node crypto.createHash\n/// @encryption:hash(email)?inputEncoding=hex\n/// @encryption:hash(email)?outputEncoding=base64\n\n// Combine settings:\n/// @encryption:hash(email)?algorithm=sha512&inputEncoding=base64&outputEncoding=base64\n```\n\nYou can provide a salt to be appended after the input data, to protect from\nrainbow table attacks. There are multiple ways to do so, listed by order of precedence:\n\n1. Specify a salt directly in the Prisma schema:\n\n```\n/// @encryption:hash(email)?salt=0be97e77063ea3f7a0f128b06ef9b1ec\n```\n\n2. Specify the name of an environment variable where to read the salt:\n\n```\n/// @encryption:hash(email)?saltEnv=EMAIL_HASH_SALT\n```\n\n3. Use a global salt in the `PRISMA_FIELD_ENCRYPTION_HASH_SALT` environment variable that will apply to all hash fields.\n\nThe salt should be of the same encoding as the associated data to hash.\n\n## Migrations\n\nAdding encryption to an existing field is a transparent operation: Prisma will\nencrypt data on new writes, and decrypt on read when data is encrypted, but\nyour existing data will remain in clear text.\n\nEncrypting existing data should be done in a migration. The package comes with\na built-in automatic migration generator, in the form of a Prisma generator:\n\n```prisma\ngenerator client {\n  provider        = \"prisma-client-js\"\n  previewFeatures = [\"interactiveTransactions\"]\n}\n\ngenerator fieldEncryptionMigrations {\n  provider = \"prisma-field-encryption\"\n  output   = \"./where/you/want/your/migrations\"\n}\n```\n\n_Tip: the migrations generator makes use of the `interactiveTransactions` preview feature. Make sure it's enabled on your Prisma Client generator._\n\nYour migrations directory will contain:\n\n- One migration per model\n- An `index.ts` file that runs them all concurrently\n\nAll migrations files follow the same API:\n\n```ts\nexport async function migrate(\n  client: PrismaClient,\n  reportProgress?: ProgressReportCallback\n)\n```\n\nThe progress report callback is optional, and will log progress to the console\nif ommitted.\n\n### Following migrations progress\n\nA progress report is an object with the following fields:\n\n- `model`: The model name\n- `processed`: How many records have been processed\n- `totalCount`: How many records were present at the start of the migration\n- `performance`: How long it took to update the last record (in ms)\n\nNote: because the totalCount is only computed once, additions or deletions\nwhile a migration is running may cause the final processedCount to not equal\ntotalCount.\n\n### Custom cursors\n\nRecords will be iterated upon by increasing order of a **cursor** field.\n\nA cursor field has to respect the following constraints:\n\n- Be `@unique`\n- Not be encrypted itself\n\nBy default, records will try to use the `@id` field.\n\n> Note: Compound `@@id` primary keys are not supported.\n\nIf the `@id` field does not satisfy cursor constraints, the generator will\nfallback to the first field that satisfies those constraints.\n\nIf you wish to iterate over another field, you can do so by annotating the\ndesired field with `@encryption:cursor`:\n\n```prisma\nmodel User {\n  id     Int    @id       // Generator would use this by default\n  email  String @unique  /// @encryption:cursor <- iterate over this field instead\n}\n```\n\nMigrations will look for cursor fields in your models in this order:\n\n1. Fields explictly annotated with `@encryption:cursor`\n2. The `@id` field\n3. The first `@unique` field\n\nIf no cursor is found for a model with encrypted fields, the generator will\nthrow an error when running `prisma generate`.\n\n## Key management\n\nThis library is based on [@47ng/cloak](https://github.com/47ng/cloak), which comes\nwith key management built-in. Here are the basic principles:\n\n- You have one current encryption key\n- You can have many decryption keys for existing data\n\nThis allows seamless rotation of the encryption key:\n\n1. Generate a new encryption key\n2. Add the old one to the decryption keys\n\nThe `PRISMA_FIELD_DECRYPTION_KEYS` can contain a comma-separated list of keys\nto use for decryption:\n\n```shell\nPRISMA_FIELD_DECRYPTION_KEYS=key1,key2,key3\n```\n\nOr specify keys programmatically:\n\n```ts\nprismaClient.$use(\n  fieldEncryptionMiddleware({\n    decryptionKeys: [\n      'k1.aesgcm256.DbQoar8ZLuUsOHZNyrnjlskInHDYlzF3q6y1KGM7DUM='\n      // Add other keys here. Order does not matter.\n    ]\n  })\n)\n```\n\n_Tip: the current encryption key is already part of the decryption keys, no need to add it there._\n\nKey rotation on existing fields (decrypt with old key and re-encrypt with the\nnew one) is done by [data migrations](#migrations).\n\n## Custom Prisma client location\n\n> _Note: this functionality is in preview in `^1.4.0-beta.2`_\n\nIf you are generating your Prisma client to a custom location, you'll need to\ntell the middleware where to look for the DMMF _(the internal AST generated by Prisma that we use to read those triple-slash comments)_:\n\n```ts\nimport { Prisma } from '../my/prisma/client'\n\nprismaClient.$use(\n  fieldEncryptionMiddleware({\n    dmmf: Prisma.dmmf\n  })\n)\n```\n\n**Roadmap:**\n\n- [x] Provide multiple decryption keys\n- [x] Add facilities for migrations & key rotation\n- [ ] Add compatibility with [@47ng/cloak](https://github.com/47ng/cloak) keychain environments\n\n## Encryption / decryption modes\n\n> _Note: this functionality is in preview in `^1.4.0-beta.3`_\n\nFor each field with an `/// @encrypted` annotation, you can specify two\nextra modes of operation:\n\n```prisma\nmodel User {\n  // Default mode behaves as follows:\n  // -> data coming into the database is encrypted\n  // <- data coming from the database is only decrypted if necessary\n  //    (allow existing clear-text data to pass through)\n  name String /// @encrypted\n\n  // Strict mode:\n  // -> data coming into the database is encrypted\n  // <- data coming from the database is decrypted, and throws an error\n  //    if decryption fails.\n  // This mode can be useful once you've run your data migrations\n  // and know that all data should be encrypted, or when you add\n  // a new encrypted field to a model.\n  ssn String /// @encrypted?mode=strict\n\n  // Readonly mode:\n  // -> data coming into the database is NOT encrypted\n  // <- data coming from the database is only decrypted if necessary\n  // This mode can be use to phase out encryption on a field that no longer\n  // requires encryption. Before removing the @encrypted annotation,\n  // run a data migration with this mode to decrypt all values for this\n  // field in the database.\n  noLongerSecret String /// @encrypted?mode=readonly\n}\n```\n\n## Debugging\n\n> _Note: this functionality is in preview in `^1.4.0-beta.3`_\n\nThe middleware uses [`debug`](https://www.npmjs.com/package/debug) to\nprint internal operations.\n\n> _Note: it will log keys and clear-text data, so be mindful of your logs destination_.\n\nThe following namespaces are available:\n\n- `prisma-field-encryption:setup`: Setup (encryption/decryption keys & schema analysis)\n- `prisma-field-encryption:runtime`: Various generic runtime (per-query) info\n- `prisma-field-encryption:encryption`: Encryption-specific operations (clear-text input, per-field information and encrypted input)\n- `prisma-field-encryption:decryption`: Decryption-specific operations (raw data from the database, per-field information and decrypted result)\n- `prisma-field-encryption:*`: Logs everything\n\nSet the `DEBUG` environment variable to the namespaces you want to log:\n\n```shell\n# macOS/Unix:\n$ DEBUG=\"prisma-field-encryption:*\" npm run my-server-start-script\n\n# Windows:\n> set DEBUG=prisma-field-encryption:* & npm run my-server-start-script\n```\n\n> _Tip: you might want to set the `DEBUG_DEPTH` variable to control object printout depth._\n\n## Caveats & limitations\n\n### Field type\n\nYou can only encrypt `String` fields.\n\nPRs are welcome to support more field types, see the following issues for reference:\n\n- #11 for JSON fields\n- #26 for Bytes fields\n\n### Miscellaneous\n\n[Raw database access](https://www.prisma.io/docs/concepts/components/prisma-client/raw-database-access)\noperations are not supported.\n\nAdding encryption adds overhead, both in storage space and in time to run queries,\nthough its impact hasn't been measured yet.\n\n## How does this work ?\n\nThe middleware reads the Prisma AST (DMMF) to find annotations (only triple-slash\ncomments make it there) and build a list of encrypted Model.field pairs.\n\nWhen a query is received, if there's input data to encrypt (write operations),\nthe relevant fields are encrypted. Then the encrypted data is sent to the\ndatabase.\n\nData returned from the database is scanned for encrypted fields, and those are\nattempted to be decrypted. Errors will be logged and any unencrypted data will\nbe passed through, allowing seamless setup.\n\nThe generated data migrations files iterate over models that contain encrypted\nfields, record by record, using the `interactiveTransaction` preview feature to\nensure that a record is not overwritten by other concurrent updates.\n\nBecause of the transparent encryption provided by the middleware, iterating over\nrecords looks like a no-op (reading then updating with the same data), but this\nwill take care of:\n\n- Encrypting fields newly `/// @encrypted`\n- Rotating the encryption key when it changed\n- Decrypting fields where encryption is being disabled with `/// @encrypted?mode=readonly`. Once that migration has run, you can remove the annotation on those fields.\n\n## Do I need this ?\n\nSome data is sensitive, and it's easy to give read access to the database to\na contractor or have backups end up somewhere they shouldn't be.\n\nFor those cases, encrypting the data per-field can make sense.\n\nAn example use-case is Two Factor authentication TOTP secrets: your app needs\nthem to authenticate your users, but nobody else should have access to them.\n\n## Cryptography\n\nCipher used: AES-GCM with 256 bit keys.\n\n## Obligatory disclaimer about passwords\n\n🚨 **DO NOT USE THIS TO ENCRYPT PASSWORDS WITHOUT ADDITIONAL SECURITY MEASURES** 🚨\n\nPasswords should be hashed & salted using a slow, constant-time one-way function. However, this library could be used to encrypt the salted and hashed password as a [pepper](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#peppering) to provide an additional layer of security. It is recommended that the encryption key be stored in a [Hardware Security Module](https://en.wikipedia.org/wiki/Hardware_security_module) on the server.\n\nFor hashing passwords, don't reinvent the wheel: use Argon2id if you can, otherwise scrypt.\n\n## License\n\n[MIT](./LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com)\n\nUsing this package at work ? [Sponsor me](https://github.com/sponsors/franky47) to help with support and maintenance.\n","readmeFilename":"README.md"}