prisma / prisma

Next-generation ORM for Node.js & TypeScript | PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, MongoDB and CockroachDB
https://www.prisma.io
Apache License 2.0
39.67k stars 1.55k forks source link

The whole database schema is visible in client-side when using NextJS #22045

Open juhawilppu opened 11 months ago

juhawilppu commented 11 months ago

Problem

When using NextJS, all the autogenerated prisma model TypeScript types are visible to the client-side. Effectively this exposes the whole database schema to client-side. There are many tables and fields that should not visible to the clients, like log tables. In general, the backend implementation and how the data is stored, should not be visible to clients.

No data gets leaked, only the schema.

This relates to fields such as

Suggested solution

Add a @@visibility setting to models and fields. Don't generate client-side types when visibility is set to backend.

This solution is similar to https://github.com/prisma/prisma/issues/5042 except it applies to the type definitions.

model PatientInformation {
  id                    Int         @id @default(autoincrement())
  encryptedSocialSecurityNumber String
  encryptedPhoneNumber  String
  encryptedEmail String
  @@map("patient_information")
  @@visibility("backend")
}

Additional context

Screenshot of the problem:

Screenshot 2023-11-21 at 9 17 11
SevInf commented 11 months ago

Hi @juhawilppu. Typescript types should be stripped before anything gets to the browser - not just for Prisma + Next.js but pretty much for any TS project. Could you provide a small reproduction where we could see it happening?

juhawilppu commented 11 months ago

Yes, I made a small code example here https://github.com/juhawilppu/prisma-nextjs-db-schema-problem.

Just run

npm i
npm run dev

and then open browser to http://localhost:3000/dynamic.

You will see this

Screenshot 2023-11-21 at 14 30 19

The problem happens when forcing rendering to client-side

# app/dynamic/page.tsx

const DynamicActions = dynamic(() => import('./DynamicActions'), {
  ssr: false,
})

and then importing a prisma enum in the client-side

# app/dynamic/DynamicActions.tsx

import { UserStatus } from '@prisma/client'

const test = () => {
    if ('ACTIVE' == UserStatus.ACTIVE.toString()) {
        return <div>The above code line causes the database schema to show in the chunk</div>
    }
    return <div>Test</div>
}
export default test

I now see that this can easily be avoided by not importing the UserStatus type in client-side. However, I didn't expect doing this would show the whole database schema, including PrivateTable, in client-side.

ferka123 commented 11 months ago

you should probably use import type instead of import

Yohannfra commented 11 months ago

It might also be a good idea to enable this eslint rule

https://typescript-eslint.io/rules/consistent-type-imports/

dvins commented 11 months ago

Just curious, your backend for fronted is directly manipulating the data store via Prisma? You don't have an intermediary API be REST/GraphQL, etc. that abstracts (and protects) these operations?

juhawilppu commented 11 months ago

you should probably use import type instead of import

Yeah, true. But using import type makes it not possible to use the type like a regular enum. I ended up making a TS duplicate of the enum and only use that in the code.

Just curious, your backend for fronted is directly manipulating the data store via Prisma? You don't have an intermediary API be REST/GraphQL, etc. that abstracts (and protects) these operations?

The frontend does not have a connection to the database. In the original code I'm rendering content based on the user's status which is obtained from the session - database is not even used in that view.

NextJS is, however, used for server-side rendering. So there are views which do make queries to the database because they're ran in the backend. Those views can then use the so-called hydration process to switch the rendering to the client-side and render dynamic content and make REST calls to endpoints using prisma. The line between backend and frontend is quite blurred and you need to be careful with it.

demedos commented 11 months ago

Just curious, your backend for fronted is directly manipulating the data store via Prisma? You don't have an intermediary API be REST/GraphQL, etc. that abstracts (and protects) these operations?

Just curious, what do you mean by intermediary? isn't nextJS's api routes enough to connect to the db via prisma to abstract and protect those routes?

dvins commented 11 months ago

Just curious, your backend for fronted is directly manipulating the data store via Prisma? You don't have an intermediary API be REST/GraphQL, etc. that abstracts (and protects) these operations?

Just curious, what do you mean by intermediary? isn't nextJS's api routes enough to connect to the db via prisma to abstract and protect those routes?

The path to software maintenance hell is paved by logic misplaced in the frontend tier, to say nothing of the audit and compliance related risks in heavily regulated areas like healthcare and finserv.

Granted, depending on what you are building there may be a time and place for it. But it seems every 10-15 years, and particularly these days within the Javascript/Typescript ecosystem, we insist on refighting battles over conceptual ground already littered with bodies from other stacks.

maiconcarraro commented 10 months ago

and then open browser to http://localhost:3000/dynamic.

@juhawilppu there is no dynamic route in the repo above, I tried to reproduce adding your snippets, but no success, even the schema is different w/o enum, did you face the same issue for production build? you can try running start & build locally to emulate, curious about this

juan-carlos-correa commented 3 months ago

Problem

When using NextJS, all the autogenerated prisma model TypeScript types are visible to the client-side. Effectively this exposes the whole database schema to client-side. There are many tables and fields that should not visible to the clients, like log tables. In general, the backend implementation and how the data is stored, should not be visible to clients.

No data gets leaked, only the schema.

This relates to fields such as

  • encryptedSocialSecurityNumber
  • encryptedPhoneNumber
  • encryptedEmail

Suggested solution

Add a @@visibility setting to models and fields. Don't generate client-side types when visibility is set to backend.

This solution is similar to #5042 except it applies to the type definitions.

model PatientInformation {
  id                    Int         @id @default(autoincrement())
  encryptedSocialSecurityNumber String
  encryptedPhoneNumber  String
  encryptedEmail String
  @@map("patient_information")
  @@visibility("backend")
}

Additional context

Screenshot of the problem: Screenshot 2023-11-21 at 9 17 11

I can confirm that this is happening to me too.

My current WIP approach is to remove all the Prisma client imports from client components, and validate that the Prisma schemas are not exposed as part of the client bundled code.

I'm using Prisma in server actions, on my mind, that code should not be part of the client/frontend bundled.

yunsii commented 3 months ago

you should probably use import type instead of import

How about when we need to import Enum?

ferka123 commented 3 months ago

you should probably use import type instead of import

How about when we need to import Enum?

you could import a union instead import type {$Enums} from @prisma/client $Enums.YourEnum as string | string | string

juan-carlos-correa commented 3 months ago

you should probably use import type instead of import

How about when we need to import Enum?

I ended up creating a new Enum 😔

You could try the ferka123's approach, I think if you do an import of prisma client (even for making an union), the schema is going to be exposed anyway 🤔

piyanggoon commented 3 months ago

Same issue use next.js server action, prisma schema got leak on bundle.

juan-carlos-correa commented 3 months ago

Same issue use next.js server action, prisma schema got leak on bundle.

Using a server action? do you have a code example?

I'm using server actions too and don't have the prisma schema leaked on the bundle.

In theory, a server action is like a lambda or a serverless function that the frontend communicates via HTTP requests, so it should not be part of the bundled code in the browser 🤔

piyanggoon commented 3 months ago

Same issue use next.js server action, prisma schema got leak on bundle.

Using a server action? do you have a code example?

I'm using server actions too and don't have the prisma schema leaked on the bundle.

In theory, a server action is like a lambda or a serverless function that the frontend communicates via HTTP requests, so it should not be part of the bundled code in the browser 🤔

use on lib next-safe-action (https://next-safe-action.dev/).

yunsii commented 3 months ago

you should probably use import type instead of import

How about when we need to import Enum?

you could import a union instead import type {$Enums} from @prisma/client $Enums.YourEnum as string | string | string

Yes, I got it, we can use $Enums directly in server side. But when used in client side, import type of $Enums instead. It seems we can import type always without caring about the import difference.