Skip to content

[Bug] Field-level rules don't select prisma extension computed fields #2117

Closed
@Migushthe2nd

Description

@Migushthe2nd

Description and expected behavior
When adding field-level acces rules, specifically @deny that accesses this, the prisma query executed selects all fields in the model, but this does not include Prisma extension computed fields.

// schema
model User {
    uuid              String           @id @default(uuid())
    email             String           @unique @deny('read', auth().uuid != this.uuid) // <-- using `this` here
    username          String           @unique

    @@allow('read', true)
}
// prisma-client
createZenstackPrisma = (prisma: PrismaService, cls: ClsService)=> {
    const extended = prisma.$extends(Prisma.defineExtension({
      name: "urls-extension",
      result: {
          user: {
              pageUrl: {
                  needs: { username: true },
                  compute: (e) => `example.com/${username}`,
              },
          },
      },
    }))
    // technically we cannot provide an extended instance here according to typescript because it is mising e.g. .$on. Only the base instance has that. 
    // We still do that here to show that it also doesn't work with extensions applied before enhancing
    const enhanced = enhance(extended, { user: cls.get("auth") }, {
        logPrismaQuery: true, // note that prisma is also set to log "info" level
    })

    return enhanced
}

Not specifying select fields does not include computed fields:

await zenstack.user.findUniqueOrThrow({
    where: {
        uuid: '00000000-0000-0000-0000-000000000000',
    },
})
// prisma:info [policy]
{
  "where": {
    "uuid": "00000000-0000-0000-0000-000000000000"
  },
  "select": { // selects all fields from the model by default, no pageUrl
    "email": true,
    "uuid": true,
    "username": true
  }
}
// EXPECTED output
{
  "where": {
    "uuid": "00000000-0000-0000-0000-000000000000"
  },
  "select": { // selects all fields from the model by default, no pageUrl
    "email": true,
    "uuid": true,
    "username": true,
    "pageUrl": true
  }
}

Manually specifying select fields works as expected:

await zenstack.user.findUniqueOrThrow({
    where: {
        uuid: '00000000-0000-0000-0000-000000000000',
    },
    select: {
        pageUrl: true,
        email: true,
    }
})
// prisma:info [policy]
{
  "where": {
    "uuid": "00000000-0000-0000-0000-000000000000"
  },
  "select": { // selects what I specified + what it needs to evaluate @deny expression
    "pageUrl": true,
    "email": true,
    "uuid": true
  } // note that later prisma also adds the `username` to the selection to compute the pageUrl
}

Environment (please complete the following information):

  • ZenStack version: 2.14.2
  • Prisma version: 6.7.0
  • Database type: Postgresql

Additional Context
In the docs it mentions that Currently there's a limitation that computed fields are not governed by field-level access policies. This means that if you have a computed field that depends on a field that the current user cannot read, the computed field will still be calculated and returned. But this does not appear to be true, as it does not even select the field.

I suppose the generated model-meta from the zmodel doesn't include computed fields, so I'm not sure if there is an easy solution. Something that could work is e.g. a @@computed(['pageUrl']) attribute so that the field is in the model metadata.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions