Skip to content

Commit 8af5cdd

Browse files
committed
guard checker generation with an option flag
1 parent 3c6e5ee commit 8af5cdd

File tree

5 files changed

+101
-45
lines changed

5 files changed

+101
-45
lines changed

packages/runtime/src/enhancements/policy/policy-utils.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,12 +587,17 @@ export class PolicyUtil extends QueryUtils {
587587
return provider({ user: this.user });
588588
}
589589

590-
private getModelChecker(model: string): PolicyDef['checker']['string'] {
590+
private getModelChecker(model: string) {
591591
if (this.options.kinds && !this.options.kinds.includes('policy')) {
592-
// policy enhancement not enabled, return a constant checker
592+
// policy enhancement not enabled, return a constant true checker
593593
return { create: true, read: true, update: true, delete: true };
594594
} else {
595-
return this.options.policy.checker?.[lowerCaseFirst(model)];
595+
let result = this.options.policy.checker?.[lowerCaseFirst(model)];
596+
if (!result) {
597+
// checker generation not enabled, return constant false checker
598+
result = { create: false, read: false, update: false, delete: false };
599+
}
600+
return result;
596601
}
597602
}
598603

packages/runtime/src/enhancements/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export type PolicyDef = {
122122
}
123123
>;
124124

125-
checker: Record<string, Record<PolicyCrudKind, CheckerFunc | boolean>>;
125+
checker?: Record<string, Record<PolicyCrudKind, CheckerFunc | boolean>>;
126126

127127
// tracks which models have data validation rules
128128
validation: Record<string, { hasValidation: boolean }>;

packages/schema/src/plugins/enhancer/enhance/index.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class EnhancerGenerator {
9090
const authTypes = authModel ? generateAuthType(this.model, authModel) : '';
9191
const authTypeParam = authModel ? `auth.${authModel.name}` : 'AuthUser';
9292

93-
const checkerTypes = generateCheckerType(this.model);
93+
const checkerTypes = this.generatePermissionChecker ? generateCheckerType(this.model) : '';
9494

9595
const enhanceTs = this.project.createSourceFile(
9696
path.join(this.outDir, 'enhance.ts'),
@@ -131,15 +131,16 @@ import type * as _P from '${prismaImport}';
131131
}
132132

133133
private createSimplePrismaEnhanceFunction(authTypeParam: string) {
134+
const returnType = `DbClient${this.generatePermissionChecker ? ' & ModelCheckers' : ''}`;
134135
return `
135-
export function enhance<DbClient extends object>(prisma: DbClient, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): DbClient & ModelCheckers {
136+
export function enhance<DbClient extends object>(prisma: DbClient, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): ${returnType} {
136137
return createEnhancement(prisma, {
137138
modelMeta,
138139
policy,
139140
zodSchemas: zodSchemas as unknown as (ZodSchemas | undefined),
140141
prismaModule: Prisma,
141142
...options
142-
}, context) as DbClient & ModelCheckers;
143+
}, context) as ${returnType};
143144
}
144145
`;
145146
}
@@ -162,12 +163,16 @@ import type { Prisma, PrismaClient } from '${logicalPrismaClientDir}/index-fixed
162163
// overload for plain PrismaClient
163164
export function enhance<ExtArgs extends Record<string, any> & InternalArgs>(
164165
prisma: _PrismaClient<any, any, ExtArgs>,
165-
context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): PrismaClient & ModelCheckers;
166+
context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): PrismaClient${
167+
this.generatePermissionChecker ? ' & ModelCheckers' : ''
168+
};
166169
167170
// overload for extended PrismaClient
168171
export function enhance<TypeMap extends TypeMapDef, TypeMapCb extends TypeMapCbDef, ExtArgs extends Record<string, any> & InternalArgs>(
169172
prisma: DynamicClientExtensionThis<TypeMap, TypeMapCb, ExtArgs>,
170-
context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): DynamicClientExtensionThis<Prisma.TypeMap, Prisma.TypeMapCb, ExtArgs> & ModelCheckers;
173+
context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): DynamicClientExtensionThis<Prisma.TypeMap, Prisma.TypeMapCb, ExtArgs>${
174+
this.generatePermissionChecker ? ' & ModelCheckers' : ''
175+
};
171176
172177
export function enhance(prisma: any, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): any {
173178
return createEnhancement(prisma, {
@@ -627,4 +632,8 @@ export function enhance(prisma: any, context?: EnhancementContext<${authTypePara
627632
await sf.save();
628633
}
629634
}
635+
636+
private get generatePermissionChecker() {
637+
return this.options.generatePermissionChecker === true;
638+
}
630639
}

packages/schema/src/plugins/enhancer/policy/policy-guard-generator.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,14 @@ export class PolicyGenerator {
9494
policyMap[model.name] = await this.generateQueryGuardForModel(model, sf);
9595
}
9696

97+
const generatePermissionChecker = options.generatePermissionChecker === true;
98+
9799
// CRUD checker functions
98100
const checkerMap: Record<string, Record<string, string | boolean>> = {};
99-
for (const model of models) {
100-
checkerMap[model.name] = await this.generateCheckerForModel(model, sf);
101+
if (generatePermissionChecker) {
102+
for (const model of models) {
103+
checkerMap[model.name] = await this.generateCheckerForModel(model, sf);
104+
}
101105
}
102106

103107
const authSelector = this.generateAuthSelector(models);
@@ -128,19 +132,21 @@ export class PolicyGenerator {
128132
});
129133
writer.writeLine(',');
130134

131-
writer.write('checker:');
132-
writer.inlineBlock(() => {
133-
for (const [model, map] of Object.entries(checkerMap)) {
134-
writer.write(`${lowerCaseFirst(model)}:`);
135-
writer.inlineBlock(() => {
136-
Object.entries(map).forEach(([op, func]) => {
137-
writer.write(`${op}: ${func},`);
135+
if (generatePermissionChecker) {
136+
writer.write('checker:');
137+
writer.inlineBlock(() => {
138+
for (const [model, map] of Object.entries(checkerMap)) {
139+
writer.write(`${lowerCaseFirst(model)}:`);
140+
writer.inlineBlock(() => {
141+
Object.entries(map).forEach(([op, func]) => {
142+
writer.write(`${op}: ${func},`);
143+
});
138144
});
139-
});
140-
writer.writeLine(',');
141-
}
142-
});
143-
writer.writeLine(',');
145+
writer.writeLine(',');
146+
}
147+
});
148+
writer.writeLine(',');
149+
}
144150

145151
writer.write('validation:');
146152
writer.inlineBlock(() => {

tests/integration/tests/enhancements/with-policy/checker.test.ts

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,44 @@
1-
import { loadSchema } from '@zenstackhq/testtools';
1+
import { SchemaLoadOptions, loadSchema } from '@zenstackhq/testtools';
22

33
describe('Permission checker', () => {
4-
it('empty rules', async () => {
4+
const PRELUDE = `
5+
datasource db {
6+
provider = 'sqlite'
7+
url = 'file:./dev.db'
8+
}
9+
10+
generator js {
11+
provider = 'prisma-client-js'
12+
}
13+
14+
plugin enhancer {
15+
provider = '@core/enhancer'
16+
generatePermissionChecker = true
17+
}
18+
`;
19+
20+
const load = (schema: string, options?: SchemaLoadOptions) =>
21+
loadSchema(`${PRELUDE}\n${schema}`, {
22+
...options,
23+
addPrelude: false,
24+
});
25+
26+
it('checker generation not enabled', async () => {
527
const { enhance } = await loadSchema(
28+
`
29+
model Model {
30+
id Int @id @default(autoincrement())
31+
value Int
32+
@@allow('all', true)
33+
}
34+
`
35+
);
36+
const db = enhance();
37+
await expect(db.model.check('read')).toResolveFalsy();
38+
});
39+
40+
it('empty rules', async () => {
41+
const { enhance } = await load(
642
`
743
model Model {
844
id Int @id @default(autoincrement())
@@ -16,7 +52,7 @@ describe('Permission checker', () => {
1652
});
1753

1854
it('unconditional allow', async () => {
19-
const { enhance } = await loadSchema(
55+
const { enhance } = await load(
2056
`
2157
model Model {
2258
id Int @id @default(autoincrement())
@@ -31,7 +67,7 @@ describe('Permission checker', () => {
3167
});
3268

3369
it('deny rule', async () => {
34-
const { enhance } = await loadSchema(
70+
const { enhance } = await load(
3571
`
3672
model Model {
3773
id Int @id @default(autoincrement())
@@ -49,7 +85,7 @@ describe('Permission checker', () => {
4985
});
5086

5187
it('int field condition', async () => {
52-
const { enhance } = await loadSchema(
88+
const { enhance } = await load(
5389
`
5490
model Model {
5591
id Int @id @default(autoincrement())
@@ -82,7 +118,7 @@ describe('Permission checker', () => {
82118
});
83119

84120
it('boolean field toplevel condition', async () => {
85-
const { enhance } = await loadSchema(
121+
const { enhance } = await load(
86122
`
87123
model Model {
88124
id Int @id @default(autoincrement())
@@ -99,7 +135,7 @@ describe('Permission checker', () => {
99135
});
100136

101137
it('boolean field condition', async () => {
102-
const { enhance } = await loadSchema(
138+
const { enhance } = await load(
103139
`
104140
model Model {
105141
id Int @id @default(autoincrement())
@@ -131,7 +167,7 @@ describe('Permission checker', () => {
131167
});
132168

133169
it('string field condition', async () => {
134-
const { enhance } = await loadSchema(
170+
const { enhance } = await load(
135171
`
136172
model Model {
137173
id Int @id @default(autoincrement())
@@ -148,7 +184,7 @@ describe('Permission checker', () => {
148184
});
149185

150186
it('function noop', async () => {
151-
const { enhance } = await loadSchema(
187+
const { enhance } = await load(
152188
`
153189
model Model {
154190
id Int @id @default(autoincrement())
@@ -169,7 +205,7 @@ describe('Permission checker', () => {
169205
});
170206

171207
it('relation noop', async () => {
172-
const { enhance } = await loadSchema(
208+
const { enhance } = await load(
173209
`
174210
model Model {
175211
id Int @id @default(autoincrement())
@@ -194,7 +230,7 @@ describe('Permission checker', () => {
194230
});
195231

196232
it('collection predicate noop', async () => {
197-
const { enhance } = await loadSchema(
233+
const { enhance } = await load(
198234
`
199235
model Model {
200236
id Int @id @default(autoincrement())
@@ -219,7 +255,7 @@ describe('Permission checker', () => {
219255
});
220256

221257
it('field complex condition', async () => {
222-
const { enhance } = await loadSchema(
258+
const { enhance } = await load(
223259
`
224260
model Model {
225261
id Int @id @default(autoincrement())
@@ -252,7 +288,7 @@ describe('Permission checker', () => {
252288
});
253289

254290
it('field condition unsolvable', async () => {
255-
const { enhance } = await loadSchema(
291+
const { enhance } = await load(
256292
`
257293
model Model {
258294
id Int @id @default(autoincrement())
@@ -272,7 +308,7 @@ describe('Permission checker', () => {
272308
});
273309

274310
it('simple auth condition', async () => {
275-
const { enhance } = await loadSchema(
311+
const { enhance } = await load(
276312
`
277313
model User {
278314
id Int @id @default(autoincrement())
@@ -307,7 +343,7 @@ describe('Permission checker', () => {
307343
});
308344

309345
it('auth compared with relation field', async () => {
310-
const { enhance } = await loadSchema(
346+
const { enhance } = await load(
311347
`
312348
model User {
313349
id Int @id @default(autoincrement())
@@ -349,7 +385,7 @@ describe('Permission checker', () => {
349385
});
350386

351387
it('auth null check', async () => {
352-
const { enhance } = await loadSchema(
388+
const { enhance } = await load(
353389
`
354390
model User {
355391
id Int @id @default(autoincrement())
@@ -379,7 +415,7 @@ describe('Permission checker', () => {
379415
});
380416

381417
it('auth with relation access', async () => {
382-
const { enhance } = await loadSchema(
418+
const { enhance } = await load(
383419
`
384420
model User {
385421
id Int @id @default(autoincrement())
@@ -408,7 +444,7 @@ describe('Permission checker', () => {
408444
});
409445

410446
it('nullable field', async () => {
411-
const { enhance } = await loadSchema(
447+
const { enhance } = await load(
412448
`
413449
model Model {
414450
id Int @id @default(autoincrement())
@@ -427,7 +463,7 @@ describe('Permission checker', () => {
427463
});
428464

429465
it('compilation', async () => {
430-
await loadSchema(
466+
await load(
431467
`
432468
model Model {
433469
id Int @id @default(autoincrement())
@@ -456,7 +492,7 @@ describe('Permission checker', () => {
456492
});
457493

458494
it('invalid filter', async () => {
459-
const { enhance } = await loadSchema(
495+
const { enhance } = await load(
460496
`
461497
model Model {
462498
id Int @id @default(autoincrement())
@@ -498,7 +534,7 @@ describe('Permission checker', () => {
498534
});
499535

500536
it('float field ignored', async () => {
501-
const { enhance } = await loadSchema(
537+
const { enhance } = await load(
502538
`
503539
model Model {
504540
id Int @id @default(autoincrement())
@@ -513,7 +549,7 @@ describe('Permission checker', () => {
513549
});
514550

515551
it('float value ignored', async () => {
516-
const { enhance } = await loadSchema(
552+
const { enhance } = await load(
517553
`
518554
model Model {
519555
id Int @id @default(autoincrement())

0 commit comments

Comments
 (0)