From 16929cb6f3e469b4bbc64edb17941fd51f684d87 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:35:51 -0700 Subject: [PATCH 1/3] fix(policy): extra fields included for policy evaluation should be dropped upon return fixes #2283 --- .../enhancements/node/policy/policy-utils.ts | 14 +- packages/testtools/src/schema.ts | 6 +- tests/regression/tests/issue-2283/dev.db | Bin 0 -> 208896 bytes .../tests/issue-2283/regression.test.ts | 685 ++++++++++++++++++ 4 files changed, 703 insertions(+), 2 deletions(-) create mode 100644 tests/regression/tests/issue-2283/dev.db create mode 100644 tests/regression/tests/issue-2283/regression.test.ts diff --git a/packages/runtime/src/enhancements/node/policy/policy-utils.ts b/packages/runtime/src/enhancements/node/policy/policy-utils.ts index d5a63e24f..ed9e435c6 100644 --- a/packages/runtime/src/enhancements/node/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/node/policy/policy-utils.ts @@ -1528,12 +1528,24 @@ export class PolicyUtil extends QueryUtils { continue; } - if (queryArgs?.omit?.[field] === true) { + if (queryArgs?.omit && typeof queryArgs.omit === 'object' && queryArgs.omit[field] === true) { // respect `{ omit: { [field]: true } }` delete entityData[field]; continue; } + if (queryArgs?.select && typeof queryArgs.select === 'object' && !queryArgs.select[field]) { + // respect select + delete entityData[field]; + continue; + } + + if (fieldInfo.isDataModel && queryArgs?.include && typeof queryArgs.include === 'object' && !queryArgs.include[field]) { + // respect include + delete entityData[field]; + continue; + } + if (hasFieldLevelPolicy) { // 1. remove fields selected for checking field-level policies but not selected by the original query args // 2. evaluate field-level policies and remove fields that are not readable diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index 746b257cc..1cf79dc82 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -150,6 +150,7 @@ export type SchemaLoadOptions = { prismaLoadPath?: string; prismaClientOptions?: object; generateNoCompile?: boolean; + dbFile?: string; }; const defaultOptions: SchemaLoadOptions = { @@ -337,7 +338,10 @@ export function createProjectAndCompile(schema: string, options: SchemaLoadOptio }); } - if (opt.pushDb) { + if (opt.dbFile) { + fs.cpSync(opt.dbFile, path.join(projectDir, 'prisma/dev.db')); + } + else if (opt.pushDb) { run('npx prisma db push --skip-generate --accept-data-loss'); } diff --git a/tests/regression/tests/issue-2283/dev.db b/tests/regression/tests/issue-2283/dev.db new file mode 100644 index 0000000000000000000000000000000000000000..8eab9f73677d208cf335d442f88624bb4d05da19 GIT binary patch literal 208896 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 V5P$##AOHafKmY;|fWXNU_ { + it('regression', async () => { + const { enhance } = await loadSchema( + ` +// Base models +abstract model Base { + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt() +} + +abstract model BaseWithCuid extends Base { + id String @id @default(cuid()) +} + +abstract model Publishable { + published Boolean @default(false) +} + +// Media models +model Image extends BaseWithCuid { + storageRef String + displayName String? + width Int + height Int + size BigInt + + // Relations + userProfiles UserProfile[] + labProfiles LabProfile[] + contents Content[] + modules Module[] + classes Class[] + + @@allow('all', true) +} + +model Video extends BaseWithCuid { + storageRef String + displayName String? + durationMillis Int + width Int? + height Int? + size BigInt + + // Relations + previewForContent Content[] + previewForModule Module[] + classes Class[] + + @@allow('all', true) +} + +// User models +model User extends Base { + id String @id @default(uuid()) + email String @unique + displayName String? + + profile UserProfile? + labs UserLabJoin[] + ownedLabs Lab[] + + @@allow('all', true) +} + +model UserProfile extends BaseWithCuid { + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String @unique + bio String? + instagram String? + profilePhoto Image? @relation(fields: [profilePhotoId], references: [id], onDelete: SetNull) + profilePhotoId String? + + @@allow('all', true) +} + +// Lab models +model Lab extends BaseWithCuid, Publishable { + name String + profile LabProfile? + owners User[] + community UserLabJoin[] + roles Role[] + privileges Privilege[] + content Content[] + permissions LabPermission[] + + @@allow('create', auth() != null) + @@allow('read', owners?[id == auth().id] || published) + @@allow('update', + owners?[id == auth().id] + || + community?[ + userLabRoles?[ + userId == auth().id + && + role.privileges?[ + privilege.labPermissions?[ + type == "ALLOW_ADMINISTRATION" + ] + ] + ] + ] + ) + @@allow('delete', owners?[id == auth().id]) +} + +model LabProfile extends BaseWithCuid { + lab Lab @relation(fields: [labId], references: [id], onDelete: Cascade) + labId String @unique + bio String? + instagram String? + profilePhoto Image? @relation(fields: [profilePhotoId], references: [id], onDelete: SetNull) + profilePhotoId String? + slug String? @unique + + @@allow('read', check(lab, "read")) + @@allow('create', lab.owners?[id == auth().id]) + @@allow('update', check(lab, "update")) + @@allow('delete', check(lab, "delete")) +} + +// User-Lab relationship +model UserLabJoin extends Base { + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String + lab Lab @relation(fields: [labId], references: [id], onDelete: Restrict) + labId String + userLabRoles UserLabRole[] + + @@id(name: "userLabJoinId", [userId, labId]) + + @@allow('create', auth().id == userId) + @@allow('update', auth().id == userId) + @@allow('read', true) + @@allow('delete', auth().id == userId) +} + +// Role and Permission models +model Role extends BaseWithCuid { + name String + shortDescription String? + longDescription String? + lab Lab @relation(fields: [labId], references: [id], onDelete: Cascade) + labId String + userLabRoles UserLabRole[] + privileges RolePrivilegeJoin[] + public Boolean @default(false) + priority Int @default(0) + isTeamRole Boolean @default(false) + + @@unique([labId, id]) + @@unique([name, labId]) + + @@allow('read', + auth().id != null + && + ( + userLabRoles?[userId == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + && + labId == this.labId + ] + ] + || + lab.owners?[id == auth().id] + ) + ) + @@allow('create', + auth().id != null + && + ( + lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + && + privilege.labId == this.labId + ] + ] + ] + ) + ) + @@allow('update', + auth().id != null + && + ( + lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + && + privilege.labId == this.labId + ] + ] + ] + ) + ) + @@allow('delete', + auth().id != null + && + ( + lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + && + privilege.labId == this.labId + ] + ] + ] + ) + ) +} + +model UserLabRole extends Base { + userLabJoin UserLabJoin @relation(fields: [userId, labId], references: [userId, labId], onDelete: Cascade) + userId String + labId String + role Role @relation(fields: [labId, roleId], references: [labId, id], onDelete: Cascade) + roleId String + expiresAt DateTime? + + @@id(name: "userLabRoleId", [userId, labId, roleId]) + + @@allow('read', auth().id != null) + @@allow('create', + auth().id != null + && + ( + userLabJoin.lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.labId == labId + && + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) + @@allow('update', + auth().id != null + && + ( + userLabJoin.lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.labId == labId + && + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) + @@allow('delete', + auth().id != null + && + ( + userLabJoin.lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.labId == labId + && + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) +} + +model Privilege extends BaseWithCuid { + name String + longDescription String? + shortDescription String + lab Lab @relation(fields: [labId], references: [id], onDelete: Cascade) + labId String + roles RolePrivilegeJoin[] + labPermissions LabPermission[] + public Boolean @default(false) + + @@unique([name, labId]) + + @@allow('read', auth().id != null) + @@allow('create', + auth().id != null + && + ( + lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.labId == labId + && + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) + @@allow('update', + auth().id != null + && + ( + lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.labId == labId + && + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) + @@allow('delete', + auth().id != null + && + ( + lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.labId == labId + && + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) +} + +model LabPermission extends BaseWithCuid { + name String + lab Lab @relation(fields: [labId], references: [id], onDelete: Cascade) + labId String + privileges Privilege[] + type String + + @@unique([name, labId]) + + @@allow('read', auth().id != null) + @@allow('create', + auth().id != null + && + ( + lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.labId == this.labId + && + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) + @@allow('update', + auth().id != null + && + ( + lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.labId == this.labId + && + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) + @@allow('delete', + auth().id != null + && + ( + lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.labId == this.labId + && + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) +} + +model RolePrivilegeJoin extends Base { + role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) + roleId String + privilege Privilege @relation(fields: [privilegeId], references: [id], onDelete: Cascade) + privilegeId String + order Int? + + @@id(name: "rolePrivilegeJoinId", [roleId, privilegeId]) + + @@allow('read', auth().id != null) + @@allow('create', + auth().id != null + && + ( + role.lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) + @@allow('update', + auth().id != null + && + ( + role.lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) + @@allow('delete', + auth().id != null + && + ( + role.lab.owners?[id == auth().id] + || + auth().labs?[ + userLabRoles?[ + role.privileges?[ + privilege.labPermissions?[type == "ALLOW_ADMINISTRATION"] + ] + ] + ] + ) + ) +} + +// Content models +model Content extends BaseWithCuid { + lab Lab @relation(fields: [labId], references: [id], onDelete: Cascade) + labId String + name String + shortDescription String? + longDescription String? + thumbnail Image? @relation(fields: [thumbnailId], references: [id]) + thumbnailId String? + modules Module[] + published Boolean + previewVideo Video? @relation(fields: [previewVideoId], references: [id]) + previewVideoId String? + order Int + + @@unique([labId, order]) + + @@allow('read', + lab.owners?[id == auth().id] + || + lab.community?[ + userId == auth().id + && + userLabRoles?[ + labId == this.labId + && + role.privileges?[ + privilege.labPermissions?[ + type in ["ALLOW_ADMINISTRATION"] + ] + ] + ] + ] + || + published == true + ) + @@allow('create', + lab.owners?[id == auth().id] + || + lab.community?[ + userId == auth().id + && + userLabRoles?[ + labId == this.labId + && + role.privileges?[ + privilege.labPermissions?[ + type in ["ALLOW_ADMINISTRATION"] + ] + ] + ] + ] + ) + @@allow('update', + lab.owners?[id == auth().id] + || + lab.community?[ + userId == auth().id + && + userLabRoles?[ + labId == this.labId + && + role.privileges?[ + privilege.labPermissions?[ + type in ["ALLOW_ADMINISTRATION"] + ] + ] + ] + ] + ) + @@allow('delete', + lab.owners?[id == auth().id] + || + lab.community?[ + userId == auth().id + && + userLabRoles?[ + labId == this.labId + && + role.privileges?[ + privilege.labPermissions?[ + type in ["ALLOW_ADMINISTRATION"] + ] + ] + ] + ] + ) +} + +model Module extends BaseWithCuid { + name String + shortDescription String? + longDescription String? + thumbnail Image? @relation(fields: [thumbnailId], references: [id]) + thumbnailId String? + content Content @relation(fields: [contentId], references: [id], onDelete: Restrict) + contentId String + classes Class[] + order Int + published Boolean + category String? + previewVideo Video? @relation(fields: [previewVideoId], references: [id]) + previewVideoId String? + + @@unique([order, category, contentId]) + + @@allow('read', + content.lab.owners?[id == auth().id] + || + content.lab.permissions?[ + privileges?[ + roles?[ + role.userLabRoles?[ + userId == auth().id + ] + ] + && + labPermissions?[ + type in ["ALLOW_ADMINISTRATION"] + ] + ] + ] + || + ( + check(content, 'read') + && + published == true + ) + ) + @@allow('create', check(content, 'create')) + @@allow('update', check(content, 'update')) + @@allow('delete', check(content, 'delete')) +} + +model Class extends BaseWithCuid { + name String + shortDescription String? + longDescription String? + thumbnail Image? @relation(fields: [thumbnailId], references: [id]) + thumbnailId String? + module Module @relation(fields: [moduleId], references: [id], onDelete: Restrict) + moduleId String + order Int + published Boolean + video Video? @relation(fields: [videoId], references: [id]) + videoId String? + category String? + + @@unique([order, category, moduleId]) + + @@allow('read', check(module, 'read')) + @@allow('create', check(module, 'create')) + @@allow('update', check(module, 'update')) + @@allow('delete', check(module, 'delete')) +} +`, + { + dbFile: path.join(__dirname, 'dev.db'), + logPrismaQuery: true + } + ); + + const db = enhance(); + + const r = await db.labProfile.findUnique({ + where: { + slug: 'test-lab-slug', + lab: { + published: true, + }, + }, + select: { + lab: { + select: { + id: true, + name: true, + content: { + where: { + published: true, + }, + select: { + id: true, + name: true, + modules: { + select: { + id: true, + name: true, + classes: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }); + + console.log(JSON.stringify(r, null, 2)); + expect(r.lab.content[0].modules[0].classes[0].module).toBeUndefined(); + }); +}); From 908a21df19f55241bbcc2a57c17993134abf63e5 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:36:43 -0700 Subject: [PATCH 2/3] update --- tests/regression/tests/issue-2283/regression.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/regression/tests/issue-2283/regression.test.ts b/tests/regression/tests/issue-2283/regression.test.ts index fc0ce9839..3259c4b4a 100644 --- a/tests/regression/tests/issue-2283/regression.test.ts +++ b/tests/regression/tests/issue-2283/regression.test.ts @@ -679,7 +679,6 @@ model Class extends BaseWithCuid { }, }); - console.log(JSON.stringify(r, null, 2)); expect(r.lab.content[0].modules[0].classes[0].module).toBeUndefined(); }); }); From 119ec8b587c43dcd92f31e93eb2d72e5c166fb2f Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:36:58 -0700 Subject: [PATCH 3/3] update --- tests/regression/tests/issue-2283/regression.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/regression/tests/issue-2283/regression.test.ts b/tests/regression/tests/issue-2283/regression.test.ts index 3259c4b4a..86ee7ec84 100644 --- a/tests/regression/tests/issue-2283/regression.test.ts +++ b/tests/regression/tests/issue-2283/regression.test.ts @@ -635,7 +635,6 @@ model Class extends BaseWithCuid { `, { dbFile: path.join(__dirname, 'dev.db'), - logPrismaQuery: true } );