diff --git a/package.json b/package.json index 1d830a1af..304cb5320 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "2.2.1", + "version": "2.2.2", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts index e754ae7aa..cece444ae 100644 --- a/packages/ide/jetbrains/build.gradle.kts +++ b/packages/ide/jetbrains/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.zenstack" -version = "2.2.1" +version = "2.2.2" repositories { mavenCentral() diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json index d97e0fd04..401e8bcc2 100644 --- a/packages/ide/jetbrains/package.json +++ b/packages/ide/jetbrains/package.json @@ -1,6 +1,6 @@ { "name": "jetbrains", - "version": "2.2.1", + "version": "2.2.2", "displayName": "ZenStack JetBrains IDE Plugin", "description": "ZenStack JetBrains IDE plugin", "homepage": "https://zenstack.dev", diff --git a/packages/language/package.json b/packages/language/package.json index 4fd5bd2bc..df4952d9f 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "2.2.1", + "version": "2.2.2", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/misc/redwood/package.json b/packages/misc/redwood/package.json index c695f0115..b8988e4d1 100644 --- a/packages/misc/redwood/package.json +++ b/packages/misc/redwood/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/redwood", "displayName": "ZenStack RedwoodJS Integration", - "version": "2.2.1", + "version": "2.2.2", "description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.", "repository": { "type": "git", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index 96ebd46a6..bc85639cd 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/openapi", "displayName": "ZenStack Plugin and Runtime for OpenAPI", - "version": "2.2.1", + "version": "2.2.2", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index 662e109a6..9d20e5bdc 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/swr", "displayName": "ZenStack plugin for generating SWR hooks", - "version": "2.2.1", + "version": "2.2.2", "description": "ZenStack plugin for generating SWR hooks", "main": "index.js", "repository": { diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index 44782eb36..da0d9b393 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/tanstack-query", "displayName": "ZenStack plugin for generating tanstack-query hooks", - "version": "2.2.1", + "version": "2.2.2", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", "exports": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index 4e40c56cf..16f3f4d39 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/trpc", "displayName": "ZenStack plugin for tRPC", - "version": "2.2.1", + "version": "2.2.2", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 053da9a58..544582bf9 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "2.2.1", + "version": "2.2.2", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/runtime/src/enhancements/policy/policy-utils.ts b/packages/runtime/src/enhancements/policy/policy-utils.ts index f5551b309..316389594 100644 --- a/packages/runtime/src/enhancements/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/policy/policy-utils.ts @@ -1074,10 +1074,17 @@ export class PolicyUtil extends QueryUtils { // can then cause infinite recursion when we visit relation later // recurse into relation fields - for (const [k, v] of Object.entries(args.select ?? args.include ?? {})) { - const field = resolveField(this.modelMeta, model, k); - if (field?.isDataModel && v && typeof v === 'object') { - this.injectReadCheckSelect(field.type, v); + const visitTarget = args.select ?? args.include; + if (visitTarget) { + for (const key of Object.keys(visitTarget)) { + const field = resolveField(this.modelMeta, model, key); + if (field?.isDataModel && visitTarget[key]) { + if (typeof visitTarget[key] !== 'object') { + // v is "true", ensure it's an object + visitTarget[key] = {}; + } + this.injectReadCheckSelect(field.type, visitTarget[key]); + } } } diff --git a/packages/schema/package.json b/packages/schema/package.json index 44955cdec..2119d0fb2 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "Build scalable web apps with minimum code by defining authorization and validation rules inside the data schema that closer to the database", - "version": "2.2.1", + "version": "2.2.2", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/plugins/enhancer/policy/policy-guard-generator.ts b/packages/schema/src/plugins/enhancer/policy/policy-guard-generator.ts index ce672dcc7..0bf949329 100644 --- a/packages/schema/src/plugins/enhancer/policy/policy-guard-generator.ts +++ b/packages/schema/src/plugins/enhancer/policy/policy-guard-generator.ts @@ -241,7 +241,7 @@ export class PolicyGenerator { ? '!(' + denies .map((deny) => { - return transformer.transform(deny); + return transformer.transform(deny, false); }) .join(' || ') + ')' @@ -249,7 +249,7 @@ export class PolicyGenerator { const allowStmt = allows .map((allow) => { - return transformer.transform(allow); + return transformer.transform(allow, false); }) .join(' || '); @@ -607,79 +607,6 @@ export class PolicyGenerator { writer.writeLine(','); } - private generateFieldReadCheckerFunction( - sourceFile: SourceFile, - field: DataModelField, - allows: Expression[], - denies: Expression[] - ) { - const statements: (string | WriterFunction)[] = []; - - generateNormalizedAuthRef(field.$container as DataModel, allows, denies, statements); - - // compile rules down to typescript expressions - statements.push((writer) => { - const transformer = new TypeScriptExpressionTransformer({ - context: ExpressionContext.AccessPolicy, - fieldReferenceContext: 'input', - }); - - const denyStmt = - denies.length > 0 - ? '!(' + - denies - .map((deny) => { - return transformer.transform(deny); - }) - .join(' || ') + - ')' - : undefined; - - const allowStmt = - allows.length > 0 - ? '(' + - allows - .map((allow) => { - return transformer.transform(allow); - }) - .join(' || ') + - ')' - : undefined; - - let expr: string | undefined; - - if (denyStmt && allowStmt) { - expr = `${denyStmt} && ${allowStmt}`; - } else if (denyStmt) { - expr = denyStmt; - } else if (allowStmt) { - expr = allowStmt; - } else { - throw new Error('should not happen'); - } - - writer.write('return ' + expr); - }); - - const func = sourceFile.addFunction({ - name: `${field.$container.name}$${field.name}_read`, - returnType: 'boolean', - parameters: [ - { - name: 'input', - type: 'any', - }, - { - name: 'context', - type: 'QueryContext', - }, - ], - statements, - }); - - return func; - } - // #endregion //#region Auth selector diff --git a/packages/schema/src/plugins/enhancer/policy/utils.ts b/packages/schema/src/plugins/enhancer/policy/utils.ts index 1085a6e88..31e37e00d 100644 --- a/packages/schema/src/plugins/enhancer/policy/utils.ts +++ b/packages/schema/src/plugins/enhancer/policy/utils.ts @@ -394,12 +394,12 @@ export function generateEntityCheckerFunction( }); denies.forEach((rule) => { - const compiled = transformer.transform(rule); + const compiled = transformer.transform(rule, false); statements.push(`if (${compiled}) { return false; }`); }); allows.forEach((rule) => { - const compiled = transformer.transform(rule); + const compiled = transformer.transform(rule, false); statements.push(`if (${compiled}) { return true; }`); }); @@ -483,6 +483,11 @@ function hasCrossModelComparison(expr: Expression) { } function getSourceModelOfFieldAccess(expr: Expression) { + // `auth()` access doesn't involve db field look up so doesn't count as cross-model comparison + if (isAuthInvocation(expr)) { + return undefined; + } + // an expression that resolves to a data model and is part of a member access, return the model // e.g.: profile.age => Profile if (isDataModel(expr.$resolvedType?.decl) && isMemberAccessExpr(expr.$container)) { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index e8e474f8d..5977a3756 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "2.2.1", + "version": "2.2.2", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 1b62e2d68..3116a3714 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "2.2.1", + "version": "2.2.2", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", diff --git a/packages/testtools/package.json b/packages/testtools/package.json index a23b36293..f2aec5933 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "2.2.1", + "version": "2.2.2", "description": "ZenStack Test Tools", "main": "index.js", "private": true, diff --git a/tests/regression/tests/issue-1506.test.ts b/tests/regression/tests/issue-1506.test.ts new file mode 100644 index 000000000..b9866a6f8 --- /dev/null +++ b/tests/regression/tests/issue-1506.test.ts @@ -0,0 +1,57 @@ +import { loadSchema } from '@zenstackhq/testtools'; +describe('issue 1506', () => { + it('regression', async () => { + const { prisma, enhance } = await loadSchema( + ` + model A { + id Int @id @default(autoincrement()) + value Int + b B @relation(fields: [bId], references: [id]) + bId Int @unique + + @@allow('read', true) + } + + model B { + id Int @id @default(autoincrement()) + value Int + a A? + c C @relation(fields: [cId], references: [id]) + cId Int @unique + + @@allow('read', value > c.value) + } + + model C { + id Int @id @default(autoincrement()) + value Int + b B? + + @@allow('read', true) + } + `, + { preserveTsFiles: true, logPrismaQuery: true } + ); + + await prisma.a.create({ + data: { + value: 3, + b: { + create: { + value: 2, + c: { + create: { + value: 1, + }, + }, + }, + }, + }, + }); + + const db = enhance(); + const read = await db.a.findMany({ include: { b: true } }); + expect(read).toHaveLength(1); + expect(read[0].b).toBeTruthy(); + }); +}); diff --git a/tests/regression/tests/issue-1507.test.ts b/tests/regression/tests/issue-1507.test.ts new file mode 100644 index 000000000..49a6ee01d --- /dev/null +++ b/tests/regression/tests/issue-1507.test.ts @@ -0,0 +1,27 @@ +import { loadSchema } from '@zenstackhq/testtools'; +describe('issue 1507', () => { + it('regression', async () => { + const { prisma, enhance } = await loadSchema( + ` + model User { + id Int @id @default(autoincrement()) + age Int + } + + model Profile { + id Int @id @default(autoincrement()) + age Int + + @@allow('read', auth().age == age) + } + `, + { preserveTsFiles: true, logPrismaQuery: true } + ); + + await prisma.profile.create({ data: { age: 18 } }); + await prisma.profile.create({ data: { age: 20 } }); + const db = enhance({ id: 1, age: 18 }); + await expect(db.profile.findMany()).resolves.toHaveLength(1); + await expect(db.profile.count()).resolves.toBe(1); + }); +});