47ng / prisma-field-encryption

Transparent field-level encryption at rest for Prisma
https://github.com/franky47/prisma-field-encryption-sandbox
MIT License
246 stars 29 forks source link

Prisma client extension #66

Closed franky47 closed 1 year ago

franky47 commented 1 year ago

Prisma client 4.16.0 deprecates middlewares and the $use method, and favours client extensions instead.

There's a little bit of plumbing needed to use the same logic across both interfaces, and the extension could probably be refined to only handle models with encrypted fields, but this first draft seems to work.

Tasks

Closes #63.

github-actions[bot] commented 1 year ago

Pull Request Test Coverage Report for Build 5637980703


Totals Coverage Status
Change from base Build 5637962771: -0.06%
Covered Lines: 244
Relevant Lines: 289

💛 - Coveralls
franky47 commented 1 year ago

Migration from 1.4.x

While the middleware interface is still exported and functional, it is recommended you upgrade your Prisma client to 4.7.0+ and switch to the client extension mechanism.

Prisma client version

For Prisma client versions 4.7.0 to 4.15.0 (included), you'll need to enable the clientExtensions feature flag:

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["clientExtensions"]
}

For Prisma client version 4.16.0+, the client extensions mechanism is generally available without the need for a preview feature flag.

Converting the middleware to an extension

This assumes you create and export your Prisma client and attach the middleware in the same file. In v1.4.0, it would have looked like this:

import { PrismaClient } from '@prisma/client'
import { fieldEncryptionMiddleware } from 'prisma-field-encryption'

export const client = new PrismaClient()

client.$use(fieldEncryptionMiddleware())

Client extensions require exporting the result of the extended client, so the equivalent code will look like this:

import { PrismaClient } from '@prisma/client'
import { fieldEncryptionExtension } from 'prisma-field-encryption'

// Create a Prisma client
const globalClient = new PrismaClient()

// Extend with encryption support and export the resulting client
export const client = globalClient.$extends(
  fieldEncryptionExtension()
)

Migrations

When calling the generated migrate function for data migrations (to rotate encryption keys), you'll have to do an explicit cast of the client type, due to an issue with Prisma types not allowing to precisely represent extended clients in function arguments (see https://github.com/prisma/prisma/issues/20326):

// Import from your generated client location, not @prisma/client
import { PrismaClient } from '.prisma/client' // or custom path
import { migrate } from './where/you/want/your/migrations'
import { fieldEncryptionExtension } from 'prisma-field-encryption'

const client = new PrismaClient().$extends(fieldEncryptionExtension())

// Explicit cast needed here ↴
await migrate(client as PrismaClient)
PrawiraGenestonlia commented 1 year ago

Hi @franky47.. This is an extremely useful feature.. Are there anything that I can help to get this PR merged and release?

franky47 commented 1 year ago

The only downside I see right now is the need for an explicit cast when calling the migration functions. Which is not an issue at all, and all other feedback is good, so it can be merged. I'll do that tonight.

github-actions[bot] commented 1 year ago

:tada: This PR is included in version 1.5.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket: