Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

findUnique isn't working #61

Closed
francistogram opened this issue Jun 10, 2023 · 7 comments
Closed

findUnique isn't working #61

francistogram opened this issue Jun 10, 2023 · 7 comments

Comments

@francistogram
Copy link

Here's my model

model ApiKey {
  id String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid

  name      String  @map("name")
  token     String  @unique @map("token") /// @encrypted
  tokenHash String? @unique @map("token_hash") @db.Text /// @encryption:hash(token)

  userID String @map("user_id") @db.Uuid
  user   User   @relation("UserApiKeys", fields: [userID], references: [id], onDelete: Cascade, onUpdate: NoAction)

  workspaceID String    @map("workspace_id") @db.Uuid
  workspace   Workspace @relation("WorkspaceApiKeys", fields: [workspaceID], references: [id], onDelete: Cascade, onUpdate: NoAction)

  createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
  updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamptz(6)

  @@map("api_key")
}

It seems to be correctly encrypting the keys e.g. if I look at the database directly I see

  • token = "v1.aesgcm256.b52f8efd.uujweP0t9Bi6kEur.lk9R-BR2_o..."
  • token_hash = "1307434863b6c28b94c625628664d5bade226b00325d684d810507c999bfeaea"

And then when I do prisma.apiKey.findMany() the values are correct

I turned on the debugger using DEBUG="prisma-field-encryption:*" yarn dev and when I search for the API key by token e.g.

2023-06-10T04:00:26.194Z prisma-field-encryption:encryption Swapping encrypted search of ApiKey.token with hash search under tokenHash (hash: 6dee1f26153dcfe0d645244f8bdd4ae0d3f43b23c0d877bee5053ecd4bd7a862)
2023-06-10T04:00:26.195Z prisma-field-encryption:encryption Encrypted input: {
  args: {
    where: {
      tokenHash: '6dee1f26153dcfe0d645244f8bdd4ae0d3f43b23c0d877bee5053ecd4bd7a862'
    }
  },
  dataPath: [],
  runInTransaction: false,
  action: 'findMany',
  model: 'ApiKey'
}
2023-06-10T04:00:26.331Z prisma-field-encryption:decryption Raw result from database: []
2023-06-10T04:00:26.331Z prisma-field-encryption:decryption Decrypted result: []

and you'll notice that the tokenHash is different which is why the return value is empty but I'm not sure why the hash is different

Within my .env I've got just PRISMA_FIELD_ENCRYPTION_KEY set and then I just do this within clients/prisma.ts

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

const globalForPrisma = global as unknown as { prisma: PrismaClient };

declare global {
  // allow global `var` declarations
  // eslint-disable-next-line no-var
  var prisma: PrismaClient | undefined;
}

const prisma =
  globalForPrisma.prisma ||
  new PrismaClient({
    log: ["error"],
  });

prisma.$use(fieldEncryptionMiddleware());

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = prisma;
}

export default prisma;

Any idea where I might be going wrong? I'm pretty close and imagine it's just one setting or variable I didn't set correctly

@francistogram
Copy link
Author

francistogram commented Jun 10, 2023

Oddly enough it'll work if I run the server using DEBUG="prisma-field-encryption:*" yarn dev sometimes it'll work but not if I just simply do yarn dev

@franky47
Copy link
Member

Looks like you're using Next.js, which has been known to cause some issues with hot-reloading in dev mode, could you look at this issue and see if yours behaves the same way?

@francistogram
Copy link
Author

Oooohhh that seemed to fix it. Appreciate the help and thanks for open sourcing this package!

For anyone else I ended up with this in clients/prisma.ts

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

const globalForPrisma = global as unknown as { prisma: PrismaClient };

declare global {
  // allow global `var` declarations
  // eslint-disable-next-line no-var
  var prisma: PrismaClient | undefined;
}

const prisma =
  globalForPrisma.prisma ||
  new PrismaClient({
    log: ["error"],
  });

// Only add the middleware to prisma once so if it's set within the global no need to add it again
if (!globalForPrisma.prisma) {
  prisma.$use(fieldEncryptionMiddleware());
}

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = prisma;
}

export default prisma;

@franky47
Copy link
Member

Sounds like adding a way to detect multiple uses of the middleware with the same configuration on the same Prisma client would be a good DX improvement.

@francistogram
Copy link
Author

Hmmm is there any way to check if middleware has already been configured on the client?

Happy to take a stab at it and put up a PR if you have any thoughts / could point me in the right direction

@franky47
Copy link
Member

franky47 commented Jun 10, 2023

I was thinking of something along the lines of a global variable in the middleware module, or a dedicated property on the Prisma client, that would keep a reference of instanciations and associated configurations (or maybe a hash of if to reduce access to sensitive data).

Not sure how the CJS vs ESM loading mechanisms and hot reloading would play around such an idea though.

@ikbenignace
Copy link

If you're using NestJS, I have a solution for this issue:

You can create an export for getting PrismaClient with the extension like this:

export function createPrismaClientWithEncryption() {
  return new PrismaClient().$extends(
    fieldEncryptionExtension()
  );
}

I've made a PrismaModule, in this module you can create a provider with a useValue and refer to the function above:

@Global()
@Module({
  providers: [ 
    {
      provide: PrismaService,
      useValue: createPrismaClientWithEncryption(),
    }
  ],
  exports: [PrismaService]
})
export class PrismaModule {}

I hope this helps a lot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants