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

NestJS PrismaService and Extending it With Encryption #78

Open
jymchng opened this issue Aug 24, 2023 · 8 comments
Open

NestJS PrismaService and Extending it With Encryption #78

jymchng opened this issue Aug 24, 2023 · 8 comments

Comments

@jymchng
Copy link

jymchng commented Aug 24, 2023

I am trying to encrypt a particular field in my Prisma model.

Here is the full codes of my PrismaService residing my in NestJS's PrismaModule:

import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client'
import { OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { fieldEncryptionExtension } from 'prisma-field-encryption';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleDestroy, OnModuleInit {

    constructor() {
        super({
            log: ['query'],
            errorFormat: 'pretty',
        });
        // Extend PrismaClient with fieldEncryptionExtension
        this.$extends(fieldEncryptionExtension()); // extending `PrismaClient`
    }

    async onModuleInit() {
        await this.$connect();
    }

    async onModuleDestroy() {
        await this.$disconnect();
    }
}

On my prisma.schema:

model User {
  telegramId Int    @id @map("telegram_id")
  mnemonics  String @unique @db.VarChar(1024) /// @encrypted 

  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
}

Within my database, after using prismaService.user.create, the resulting entry in the PostgreSQL database is NOT encrypted.

import { Injectable } from '@nestjs/common';
import { User as UserModel } from '@prisma/client';
import { PrismaService } from 'src/prisma/services/prisma/prisma.service';
import { Mnemonic } from 'src/common/entities/mnemonic/mnemonic';

@Injectable()
export class AuthService {

    constructor(private readonly prismaSvc: PrismaService) {}

    async signUp(telegramId: number): Promise<Pick<UserModel, "telegramId">> {
        const mnemonic = Mnemonic.createNew()
        const user = await this.prismaSvc.user.create({data: {telegramId: telegramId, mnemonics: mnemonic.toString()}, select: {telegramId: true}});
        return user
    }
}

Any idea how does it work? Thank you.

@franky47
Copy link
Member

Your issue is with the extension mechanism.

NestJS uses a class that inherits from the PrismaClient, but applying extensions require to use the return value of $extends(), so applying extensions in the derived class constructor won't work.

There are issues on the Prisma repo about how to deal with Nest and extensions, you should be able to find a solution there.

@jymchng
Copy link
Author

jymchng commented Aug 24, 2023

Your issue is with the extension mechanism.

NestJS uses a class that inherits from the PrismaClient, but applying extensions require to use the return value of $extends(), so applying extensions in the derived class constructor won't work.

There are issues on the Prisma repo about how to deal with Nest and extensions, you should be able to find a solution there.

Thanks for your reply @franky47, prisma/prisma#20833

@jymchng
Copy link
Author

jymchng commented Aug 24, 2023

@franky47 Btw, do you have a quick workaround? Or can you point me more directly to issues that have some quick solutions?

@jymchng jymchng changed the title NestJS PrismaService and Extending it NestJS PrismaService and Extending it With Encryption Aug 24, 2023
@franky47
Copy link
Member

@jymchng
Copy link
Author

jymchng commented Aug 24, 2023

@jymchng
Copy link
Author

jymchng commented Aug 24, 2023

@franky47 Sir, would it be better practice to have two PrismaService, one specifically for creating records for tables with encrypted column(s) and another for general database operations? I see the link that you shared requires me to purposefully inject the CustomPrismaService whenever I need it.

constructor(
    // ✅ use `extendedPrismaClient` type for correct type-safety of your extended PrismaClient
    @Inject('PrismaService')
    private prismaService: CustomPrismaService<extendedPrismaClient>
  ) {}

@franky47
Copy link
Member

That sounds like a recipe for disaster, as a lot can go wrong: overwriting encrypted records with clear-text values (leaking data), or sending ciphertext to the front-end.

The extension makes operations transparent, so inject it to configure your client and you can forget about it being here (albeit losing some features like partial search and sorting on encrypted fields).

@jymchng
Copy link
Author

jymchng commented Aug 24, 2023

@franky47 Thank you for your reply, oh, I am just trying to encrypt one or two columns of some of my tables, I am not sure how I can actually 'inject it to configure your client', is the example in the documentation sufficient? I read it.

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

2 participants