@appxdigital/appx-core 0.1.121-beta.11 → 0.1.121-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/config/permissionsConfigTypes.d.ts +0 -8
- package/dist/common/config/permissionsConfigTypes.d.ts.map +1 -1
- package/dist/common/config/permissionsConfigTypes.js +1 -13
- package/dist/common/decorators/permission.decorator.d.ts +0 -14
- package/dist/common/decorators/permission.decorator.d.ts.map +1 -1
- package/dist/common/decorators/permission.decorator.js +1 -16
- package/dist/common/guards/rbac.guard.d.ts.map +1 -1
- package/dist/common/guards/rbac.guard.js +1 -8
- package/dist/common/interceptors/prisma.interceptor.d.ts.map +1 -1
- package/dist/common/interceptors/prisma.interceptor.js +2 -6
- package/dist/config/generate-dtos.js +15 -101
- package/dist/prisma/prisma.service.d.ts +2 -49
- package/dist/prisma/prisma.service.d.ts.map +1 -1
- package/dist/prisma/prisma.service.js +5 -99
- package/package.json +2 -6
- package/CHANGELOG.md +0 -123
- package/PRERELEASES.md +0 -151
- package/RELEASING.md +0 -168
- package/docs/README.md +0 -21
- package/docs/data-access.md +0 -108
- package/docs/dtos.md +0 -163
- package/docs/graphql.md +0 -72
- package/docs/limitations.md +0 -59
- package/docs/permissions.md +0 -155
|
@@ -3,14 +3,6 @@ import { Operation } from '@prisma/client/runtime/library';
|
|
|
3
3
|
export declare const PermissionPlaceholder: {
|
|
4
4
|
USER_ID: any;
|
|
5
5
|
};
|
|
6
|
-
/**
|
|
7
|
-
* Batch (`*Many`) actions inherit the singular rule when not declared
|
|
8
|
-
* explicitly, so a config can define just `create` / `update` / `delete`.
|
|
9
|
-
* One-directional: the singular is canonical (declaring only the `*Many`
|
|
10
|
-
* variant does not enable the singular). Consumed by the Prisma proxy
|
|
11
|
-
* (`selectPermission`) and the `RbacGuard`.
|
|
12
|
-
*/
|
|
13
|
-
export declare const SINGULAR_ACTION: Record<string, string>;
|
|
14
6
|
/** Detect delegate keys on an arbitrary Prisma client type C */
|
|
15
7
|
type DelegateKeyOf<C> = {
|
|
16
8
|
[K in keyof C]: C[K] extends {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissionsConfigTypes.d.ts","sourceRoot":"","sources":["../../../src/common/config/permissionsConfigTypes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,MAAM,EAAE,YAAY,IAAI,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAC,SAAS,EAAC,MAAM,gCAAgC,CAAC;AAEzD,eAAO,MAAM,qBAAqB,EAAE;IAChC,OAAO,EAAE,GAAG,CAAC;CAGhB,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"permissionsConfigTypes.d.ts","sourceRoot":"","sources":["../../../src/common/config/permissionsConfigTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.PermissionPlaceholder = void 0;
|
|
4
4
|
exports.PermissionPlaceholder = {
|
|
5
5
|
USER_ID: '$USER_ID',
|
|
6
6
|
};
|
|
7
|
-
/**
|
|
8
|
-
* Batch (`*Many`) actions inherit the singular rule when not declared
|
|
9
|
-
* explicitly, so a config can define just `create` / `update` / `delete`.
|
|
10
|
-
* One-directional: the singular is canonical (declaring only the `*Many`
|
|
11
|
-
* variant does not enable the singular). Consumed by the Prisma proxy
|
|
12
|
-
* (`selectPermission`) and the `RbacGuard`.
|
|
13
|
-
*/
|
|
14
|
-
exports.SINGULAR_ACTION = {
|
|
15
|
-
createMany: 'create',
|
|
16
|
-
updateMany: 'update',
|
|
17
|
-
deleteMany: 'delete',
|
|
18
|
-
};
|
|
@@ -1,17 +1,3 @@
|
|
|
1
1
|
export declare const PERMISSION_METADATA_KEY = "permission";
|
|
2
|
-
export declare const EXPOSE_MODELS_METADATA_KEY = "expose_models";
|
|
3
2
|
export declare const Permission: (action: string, expose_models?: string[]) => import("@nestjs/common").CustomDecorator<string>;
|
|
4
|
-
/**
|
|
5
|
-
* Expose one or more models for the duration of the request — WITHOUT requiring
|
|
6
|
-
* a permission action. Unlike `@Permission(action, models)`, this sets no
|
|
7
|
-
* action, so `RbacGuard` does not demand a role permission for the route. Use it
|
|
8
|
-
* for a public / GUEST endpoint that must read a model the caller isn't
|
|
9
|
-
* otherwise granted (e.g. an email-availability check) without having to add a
|
|
10
|
-
* `GUEST` rule for that model. Exposure is scoped to the request: the listed
|
|
11
|
-
* models skip row / field filtering only inside this handler.
|
|
12
|
-
*
|
|
13
|
-
* Combine with `@Permission(...)` if you also want an action check; both
|
|
14
|
-
* sources of exposed models are merged.
|
|
15
|
-
*/
|
|
16
|
-
export declare const ExposeModels: (...models: string[]) => import("@nestjs/common").CustomDecorator<string>;
|
|
17
3
|
//# sourceMappingURL=permission.decorator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission.decorator.d.ts","sourceRoot":"","sources":["../../../src/common/decorators/permission.decorator.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"permission.decorator.d.ts","sourceRoot":"","sources":["../../../src/common/decorators/permission.decorator.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB,eAAe,CAAC;AAEpD,eAAO,MAAM,UAAU,WAAY,MAAM,kBAAiB,MAAM,EAAE,qDACD,CAAC"}
|
|
@@ -1,22 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.Permission = exports.PERMISSION_METADATA_KEY = void 0;
|
|
4
4
|
const common_1 = require("@nestjs/common");
|
|
5
5
|
exports.PERMISSION_METADATA_KEY = 'permission';
|
|
6
|
-
exports.EXPOSE_MODELS_METADATA_KEY = 'expose_models';
|
|
7
6
|
const Permission = (action, expose_models = []) => (0, common_1.SetMetadata)(exports.PERMISSION_METADATA_KEY, { action, expose_models });
|
|
8
7
|
exports.Permission = Permission;
|
|
9
|
-
/**
|
|
10
|
-
* Expose one or more models for the duration of the request — WITHOUT requiring
|
|
11
|
-
* a permission action. Unlike `@Permission(action, models)`, this sets no
|
|
12
|
-
* action, so `RbacGuard` does not demand a role permission for the route. Use it
|
|
13
|
-
* for a public / GUEST endpoint that must read a model the caller isn't
|
|
14
|
-
* otherwise granted (e.g. an email-availability check) without having to add a
|
|
15
|
-
* `GUEST` rule for that model. Exposure is scoped to the request: the listed
|
|
16
|
-
* models skip row / field filtering only inside this handler.
|
|
17
|
-
*
|
|
18
|
-
* Combine with `@Permission(...)` if you also want an action check; both
|
|
19
|
-
* sources of exposed models are merged.
|
|
20
|
-
*/
|
|
21
|
-
const ExposeModels = (...models) => (0, common_1.SetMetadata)(exports.EXPOSE_MODELS_METADATA_KEY, models);
|
|
22
|
-
exports.ExposeModels = ExposeModels;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rbac.guard.d.ts","sourceRoot":"","sources":["../../../src/common/guards/rbac.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,WAAW,EACX,gBAAgB,EAInB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAC,qBAAqB,
|
|
1
|
+
{"version":3,"file":"rbac.guard.d.ts","sourceRoot":"","sources":["../../../src/common/guards/rbac.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,WAAW,EACX,gBAAgB,EAInB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAC,qBAAqB,EAAkB,MAAM,kCAAkC,CAAC;AAExF,qBACa,SAAU,YAAW,WAAW;IAErC,OAAO,CAAC,SAAS;IACiB,OAAO,CAAC,iBAAiB;gBADnD,SAAS,EAAE,SAAS,EACc,iBAAiB,EAAE,qBAAqB;IAGhF,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;CAoCjE"}
|
|
@@ -17,7 +17,6 @@ const common_1 = require("@nestjs/common");
|
|
|
17
17
|
const core_1 = require("@nestjs/core");
|
|
18
18
|
const permission_decorator_1 = require("../decorators/permission.decorator");
|
|
19
19
|
const permissions_config_provider_1 = require("../config/permissions.config.provider");
|
|
20
|
-
const permissionsConfigTypes_1 = require("../config/permissionsConfigTypes");
|
|
21
20
|
let RbacGuard = exports.RbacGuard = class RbacGuard {
|
|
22
21
|
constructor(reflector, permissionsConfig) {
|
|
23
22
|
this.reflector = reflector;
|
|
@@ -39,13 +38,7 @@ let RbacGuard = exports.RbacGuard = class RbacGuard {
|
|
|
39
38
|
if (!rolePermissions) {
|
|
40
39
|
throw new common_1.ForbiddenException(`No permissions defined for role ${user.role} on model ${model}`);
|
|
41
40
|
}
|
|
42
|
-
|
|
43
|
-
// (createMany → create, updateMany → update, deleteMany → delete) — mirrors
|
|
44
|
-
// the data-access proxy, so a config can declare just the singular form.
|
|
45
|
-
let permission = rolePermissions[action];
|
|
46
|
-
if (!permission && permissionsConfigTypes_1.SINGULAR_ACTION[action]) {
|
|
47
|
-
permission = rolePermissions[permissionsConfigTypes_1.SINGULAR_ACTION[action]];
|
|
48
|
-
}
|
|
41
|
+
const permission = rolePermissions[action];
|
|
49
42
|
if (!permission) {
|
|
50
43
|
throw new common_1.ForbiddenException(`Action ${action} not allowed for role ${user.role} on model ${model}`);
|
|
51
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma.interceptor.d.ts","sourceRoot":"","sources":["../../../src/common/interceptors/prisma.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,gBAAgB,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAoB,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAC,UAAU,EAAa,MAAM,MAAM,CAAC;AAK5C,qBACa,iBAAkB,YAAW,eAAe;IAIjD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,aAAa;IALzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;gBAG1B,aAAa,EAAE,aAAa,EACrC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,aAAa;IAKxC,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"prisma.interceptor.d.ts","sourceRoot":"","sources":["../../../src/common/interceptors/prisma.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,gBAAgB,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAoB,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAC,UAAU,EAAa,MAAM,MAAM,CAAC;AAK5C,qBACa,iBAAkB,YAAW,eAAe;IAIjD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,aAAa;IALzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;gBAG1B,aAAa,EAAE,aAAa,EACrC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,aAAa;IAKxC,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC;CA+E3E"}
|
|
@@ -31,15 +31,11 @@ let PrismaInterceptor = exports.PrismaInterceptor = class PrismaInterceptor {
|
|
|
31
31
|
const req = handlerType === 'graphql'
|
|
32
32
|
? context.getArgByIndex(2) // GraphQL context
|
|
33
33
|
: context.switchToHttp().getRequest(); // HTTP context
|
|
34
|
-
// Attach expose_models metadata if needed
|
|
35
|
-
// @Permission(action, models) or the standalone @ExposeModels(...); both
|
|
36
|
-
// are merged so a route can expose models with or without a permission.
|
|
34
|
+
// Attach expose_models metadata if needed
|
|
37
35
|
const permissionMetadata = this.reflector.get(permission_decorator_1.PERMISSION_METADATA_KEY, context.getHandler()) || {};
|
|
38
|
-
const standaloneExposed = this.reflector.get(permission_decorator_1.EXPOSE_MODELS_METADATA_KEY, context.getHandler()) || [];
|
|
39
|
-
const exposedModels = [...(permissionMetadata['expose_models'] || []), ...standaloneExposed];
|
|
40
36
|
return new rxjs_1.Observable((observer) => {
|
|
41
37
|
prisma_service_1.CorePrismaContext.run({
|
|
42
|
-
exposedModels,
|
|
38
|
+
exposedModels: permissionMetadata['expose_models'] || [],
|
|
43
39
|
}, () => {
|
|
44
40
|
const useTransaction = this.reflector.get('useTransaction', context.getHandler()) ?? this.defaultUseTransaction === 'true';
|
|
45
41
|
if (useTransaction) {
|
|
@@ -45,14 +45,7 @@ const utils_1 = require("./utils");
|
|
|
45
45
|
* - a server-managed timestamp (`@default(now())` or `@updatedAt`),
|
|
46
46
|
* - annotated `/// @NoWrite`, or
|
|
47
47
|
* - annotated `/// @Role(none)` (readable by nobody ⇒ not CRUD-writable).
|
|
48
|
-
*
|
|
49
|
-
* Nested relation writes (CREATE DTOs only — `updateMany` takes scalar data):
|
|
50
|
-
* Each relation gets a nested-write member exposing ONLY the operators the
|
|
51
|
-
* data-access proxy authorizes: `create` (validated against the related
|
|
52
|
-
* model's writable scalars) and `connect` (validated against the related
|
|
53
|
-
* model's unique fields). Other operators (`set`/`disconnect`/`update`/…) are
|
|
54
|
-
* deliberately NOT emitted, so the whitelist pipe rejects them. The proxy
|
|
55
|
-
* enforces the matching ABAC rule for each (`create` rule / `connect` rule).
|
|
48
|
+
* Relation navigation fields are excluded; their scalar foreign keys are kept.
|
|
56
49
|
*/
|
|
57
50
|
const cwd = process.cwd();
|
|
58
51
|
// Read the consumer's generated Prisma client for the DMMF (authoritative
|
|
@@ -61,9 +54,6 @@ const cwd = process.cwd();
|
|
|
61
54
|
const { Prisma } = require(path.join(cwd, 'node_modules', '@prisma/client'));
|
|
62
55
|
const generatedDtoRoot = path.join(cwd, 'src/generated/dto');
|
|
63
56
|
const modulesRoot = path.join(cwd, 'src/modules');
|
|
64
|
-
const modelsByName = {};
|
|
65
|
-
for (const m of Prisma.dmmf.datamodel.models)
|
|
66
|
-
modelsByName[m.name] = m;
|
|
67
57
|
function isNoWrite(doc) {
|
|
68
58
|
if (!doc)
|
|
69
59
|
return false;
|
|
@@ -108,7 +98,7 @@ function mapField(field) {
|
|
|
108
98
|
return { validator: 'Allow', tsType: 'any' };
|
|
109
99
|
}
|
|
110
100
|
}
|
|
111
|
-
function writableFields(model
|
|
101
|
+
function writableFields(model) {
|
|
112
102
|
return model.fields.filter((f) => {
|
|
113
103
|
if (f.kind === 'object')
|
|
114
104
|
return false; // relation navigation
|
|
@@ -118,32 +108,25 @@ function writableFields(model, omit = []) {
|
|
|
118
108
|
return false;
|
|
119
109
|
if (isNoWrite(f.documentation))
|
|
120
110
|
return false;
|
|
121
|
-
if (omit.includes(f.name))
|
|
122
|
-
return false;
|
|
123
111
|
return true;
|
|
124
112
|
});
|
|
125
113
|
}
|
|
126
|
-
/**
|
|
127
|
-
function
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
/** The scalar FK column(s) the parent sets automatically — omit from a nested create. */
|
|
131
|
-
function backFkColumns(relatedModel, relationName) {
|
|
132
|
-
const inverse = relatedModel.fields.find((f) => f.kind === 'object' && f.relationName === relationName && (f.relationFromFields || []).length > 0);
|
|
133
|
-
return inverse ? inverse.relationFromFields || [] : [];
|
|
134
|
-
}
|
|
135
|
-
/** Renders class-body lines for a set of scalar/enum fields; collects imports. */
|
|
136
|
-
function renderScalarLines(fields, mode, validatorImports, enumImports) {
|
|
114
|
+
/** Renders a base DTO class body + its import block. */
|
|
115
|
+
function renderBaseDto(className, fields, mode) {
|
|
116
|
+
const validatorImports = new Set();
|
|
117
|
+
const enumImports = new Set();
|
|
137
118
|
const lines = [];
|
|
138
119
|
for (const field of fields) {
|
|
139
120
|
const m = mapField(field);
|
|
140
121
|
validatorImports.add(m.validator);
|
|
141
122
|
if (m.enumArg)
|
|
142
123
|
enumImports.add(m.enumArg);
|
|
143
|
-
// update
|
|
144
|
-
const optional = mode
|
|
124
|
+
// update: everything optional. create: optional if not required or has a default.
|
|
125
|
+
const optional = mode === 'update' || !field.isRequired || field.hasDefaultValue;
|
|
145
126
|
const isList = field.isList;
|
|
146
127
|
const tsType = isList ? `${m.tsType}[]` : m.tsType;
|
|
128
|
+
// Compose the validator decorator. @Allow takes no options; for other
|
|
129
|
+
// validators on a list field, add `{ each: true }` (+ @IsArray).
|
|
147
130
|
let validatorDecorator;
|
|
148
131
|
if (m.validator === 'Allow') {
|
|
149
132
|
validatorDecorator = '@Allow()';
|
|
@@ -170,89 +153,19 @@ function renderScalarLines(fields, mode, validatorImports, enumImports) {
|
|
|
170
153
|
lines.push(` ${field.name}${optional ? '?' : '!'}: ${tsType};`);
|
|
171
154
|
lines.push('');
|
|
172
155
|
}
|
|
173
|
-
return lines;
|
|
174
|
-
}
|
|
175
|
-
function renderClass(className, bodyLines) {
|
|
176
|
-
return `export class ${className} {\n` + (bodyLines.length ? bodyLines.join('\n') : '') + `}\n`;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* For a model's CREATE DTO, render the nested-write classes for each relation
|
|
180
|
-
* plus the property lines to add to the parent class. Nested create classes are
|
|
181
|
-
* scalars-only (no deeper relations) so there are no circular imports.
|
|
182
|
-
*/
|
|
183
|
-
function renderRelationNestedWrites(model, validatorImports, enumImports, transformerImports) {
|
|
184
|
-
const classes = [];
|
|
185
|
-
const parentLines = [];
|
|
186
|
-
const relations = model.fields.filter((f) => f.kind === 'object');
|
|
187
|
-
for (const rel of relations) {
|
|
188
|
-
const related = modelsByName[rel.type];
|
|
189
|
-
if (!related)
|
|
190
|
-
continue;
|
|
191
|
-
const relCap = rel.name.charAt(0).toUpperCase() + rel.name.slice(1);
|
|
192
|
-
const createCls = `${model.name}${relCap}CreateNestedDto`;
|
|
193
|
-
const connectCls = `${model.name}${relCap}ConnectNestedDto`;
|
|
194
|
-
const writeCls = `${model.name}${relCap}NestedWriteDto`;
|
|
195
|
-
// create: related writable scalars minus the back-reference FK.
|
|
196
|
-
const omit = backFkColumns(related, rel.relationName);
|
|
197
|
-
classes.push(renderClass(createCls, renderScalarLines(writableFields(related, omit), 'create', validatorImports, enumImports)));
|
|
198
|
-
// connect: related unique fields (all optional).
|
|
199
|
-
const conn = connectFields(related);
|
|
200
|
-
classes.push(renderClass(connectCls, renderScalarLines(conn, 'connect', validatorImports, enumImports)));
|
|
201
|
-
// nested-write: create? / connect? only.
|
|
202
|
-
transformerImports.add('Type');
|
|
203
|
-
validatorImports.add('IsOptional');
|
|
204
|
-
validatorImports.add('ValidateNested');
|
|
205
|
-
const eachArg = rel.isList ? '{ each: true }' : '';
|
|
206
|
-
const arrSuffix = rel.isList ? '[]' : '';
|
|
207
|
-
if (rel.isList)
|
|
208
|
-
validatorImports.add('IsArray');
|
|
209
|
-
const opLines = [];
|
|
210
|
-
for (const [op, cls] of [['create', createCls], ['connect', connectCls]]) {
|
|
211
|
-
if (rel.isList)
|
|
212
|
-
opLines.push(' @IsArray()');
|
|
213
|
-
opLines.push(' @IsOptional()');
|
|
214
|
-
opLines.push(` @ValidateNested(${eachArg})`);
|
|
215
|
-
opLines.push(` @Type(() => ${cls})`);
|
|
216
|
-
opLines.push(` ${op}?: ${cls}${arrSuffix};`);
|
|
217
|
-
opLines.push('');
|
|
218
|
-
}
|
|
219
|
-
classes.push(renderClass(writeCls, opLines));
|
|
220
|
-
// property on the parent CREATE DTO.
|
|
221
|
-
parentLines.push(' @IsOptional()');
|
|
222
|
-
parentLines.push(' @ValidateNested()');
|
|
223
|
-
parentLines.push(` @Type(() => ${writeCls})`);
|
|
224
|
-
parentLines.push(` ${rel.name}?: ${writeCls};`);
|
|
225
|
-
parentLines.push('');
|
|
226
|
-
}
|
|
227
|
-
return { classes, parentLines };
|
|
228
|
-
}
|
|
229
|
-
/** Renders a base DTO file (nested relation writes on create only). */
|
|
230
|
-
function renderBaseDto(className, model, mode) {
|
|
231
|
-
const validatorImports = new Set();
|
|
232
|
-
const enumImports = new Set();
|
|
233
|
-
const transformerImports = new Set();
|
|
234
|
-
const scalarLines = renderScalarLines(writableFields(model), mode, validatorImports, enumImports);
|
|
235
|
-
let nested = { classes: [], parentLines: [] };
|
|
236
|
-
if (mode === 'create') {
|
|
237
|
-
nested = renderRelationNestedWrites(model, validatorImports, enumImports, transformerImports);
|
|
238
|
-
}
|
|
239
|
-
const parentClass = renderClass(className, [...scalarLines, ...nested.parentLines]);
|
|
240
156
|
const importLines = [];
|
|
241
157
|
if (validatorImports.size > 0) {
|
|
242
158
|
importLines.push(`import { ${[...validatorImports].sort().join(', ')} } from 'class-validator';`);
|
|
243
159
|
}
|
|
244
|
-
if (transformerImports.size > 0) {
|
|
245
|
-
importLines.push(`import { ${[...transformerImports].sort().join(', ')} } from 'class-transformer';`);
|
|
246
|
-
}
|
|
247
160
|
if (enumImports.size > 0) {
|
|
248
161
|
importLines.push(`import { ${[...enumImports].sort().join(', ')} } from '@prisma/client';`);
|
|
249
162
|
}
|
|
250
163
|
return (utils_1.GENERATED_BANNER +
|
|
251
164
|
'\n' +
|
|
252
165
|
(importLines.length ? importLines.join('\n') + '\n\n' : '') +
|
|
253
|
-
|
|
254
|
-
(
|
|
255
|
-
|
|
166
|
+
`export class ${className} {\n` +
|
|
167
|
+
(lines.length ? lines.map((l) => l).join('\n') : '') +
|
|
168
|
+
`}\n`);
|
|
256
169
|
}
|
|
257
170
|
/** Renders the hand-owned subclass (generated once, then customizable). */
|
|
258
171
|
function renderSubclassDto(subclass, baseClass, importPath) {
|
|
@@ -269,13 +182,14 @@ for (const model of Prisma.dmmf.datamodel.models) {
|
|
|
269
182
|
const folder = (0, utils_1.pascalToKebabCase)(modelName); // kebab, e.g. project-member
|
|
270
183
|
if (utils_1.IGNORE_FOLDERS.includes(folder))
|
|
271
184
|
continue;
|
|
185
|
+
const fields = writableFields(model);
|
|
272
186
|
for (const mode of ['create', 'update']) {
|
|
273
187
|
const capMode = mode.charAt(0).toUpperCase() + mode.slice(1);
|
|
274
188
|
const baseClass = `${capMode}${modelName}GeneratedDto`;
|
|
275
189
|
const subclass = `${capMode}${modelName}Dto`;
|
|
276
190
|
// Base (overwritten, gitignored).
|
|
277
191
|
const basePath = path.join(generatedDtoRoot, folder, `${mode}-${folder}.generated.dto.ts`);
|
|
278
|
-
(0, utils_1.writeGeneratedFile)(basePath, renderBaseDto(baseClass,
|
|
192
|
+
(0, utils_1.writeGeneratedFile)(basePath, renderBaseDto(baseClass, fields, mode));
|
|
279
193
|
// Subclass (once, hand-owned, committed).
|
|
280
194
|
const subPath = path.join(modulesRoot, folder, 'dto', `${mode}-${folder}.dto.ts`);
|
|
281
195
|
const importPath = `../../../generated/dto/${folder}/${mode}-${folder}.generated.dto`;
|
|
@@ -12,34 +12,7 @@ export type CorePrismaOptions = {
|
|
|
12
12
|
BYPASS_OMISSION?: boolean;
|
|
13
13
|
BYPASS_FILTERING?: boolean;
|
|
14
14
|
};
|
|
15
|
-
|
|
16
|
-
type MethodOf<D, K extends string> = K extends keyof D ? D[K] : never;
|
|
17
|
-
/**
|
|
18
|
-
* Re-aligns a Prisma model delegate `D` to the proxy's real contract. Single-
|
|
19
|
-
* record methods that require a unique `where` can't carry the injected ABAC
|
|
20
|
-
* conditions, so each is re-aliased to its filter-compatible equivalent and the
|
|
21
|
-
* proxy transparently redirects the call at runtime:
|
|
22
|
-
* - `findUnique` takes `findFirst`'s params/return; `findUniqueOrThrow` takes
|
|
23
|
-
* `findFirstOrThrow`'s.
|
|
24
|
-
* - `update` takes `updateMany`'s params and returns its result
|
|
25
|
-
* (`BatchPayload`); `delete` takes `deleteMany`'s.
|
|
26
|
-
*/
|
|
27
|
-
export type ProxiedDelegate<D> = Omit<D, 'findUnique' | 'findUniqueOrThrow' | 'update' | 'delete'> & {
|
|
28
|
-
findUnique: MethodOf<D, 'findFirst'>;
|
|
29
|
-
findUniqueOrThrow: MethodOf<D, 'findFirstOrThrow'>;
|
|
30
|
-
update: MethodOf<D, 'updateMany'>;
|
|
31
|
-
delete: MethodOf<D, 'deleteMany'>;
|
|
32
|
-
};
|
|
33
|
-
export type CorePrismaModel<M extends ModelKey> = ProxiedDelegate<RuntimeClient[M]>;
|
|
34
|
-
/**
|
|
35
|
-
* Applies {@link ProxiedDelegate} across every model delegate on a Prisma
|
|
36
|
-
* client, leaving non-delegate members (`$transaction`, `$connect`, …) intact.
|
|
37
|
-
*/
|
|
38
|
-
export type CorePrismaClient<C> = {
|
|
39
|
-
[K in keyof C]: C[K] extends {
|
|
40
|
-
findMany: (...args: any[]) => any;
|
|
41
|
-
} ? ProxiedDelegate<C[K]> : C[K];
|
|
42
|
-
};
|
|
15
|
+
export type CorePrismaModel<M extends ModelKey> = Exclude<RuntimeClient[M], 'findUnique' | 'findUniqueOrThrow' | 'delete' | 'update'>;
|
|
43
16
|
export declare class PrismaService {
|
|
44
17
|
private readonly permissionsConfig;
|
|
45
18
|
private fieldConfigs;
|
|
@@ -63,7 +36,7 @@ export declare class PrismaService {
|
|
|
63
36
|
* @param model
|
|
64
37
|
*/
|
|
65
38
|
getModelDelegate<M extends ModelKey>(model: M): CorePrismaModel<M>;
|
|
66
|
-
get model():
|
|
39
|
+
get model(): RuntimeClient;
|
|
67
40
|
get user(): CorePrismaModel<'user'>;
|
|
68
41
|
get session(): CorePrismaModel<'session'>;
|
|
69
42
|
get userRefreshToken(): any;
|
|
@@ -114,26 +87,6 @@ export declare class PrismaService {
|
|
|
114
87
|
* cannot evaluate, rather than silently allowing the insert.
|
|
115
88
|
*/
|
|
116
89
|
private enforceCreateConditions;
|
|
117
|
-
/**
|
|
118
|
-
* Recursively authorizes nested relation writes in a `create` payload. Only
|
|
119
|
-
* an explicit allowlist of operators is permitted, each checked against ABAC;
|
|
120
|
-
* every other operator is rejected (fail closed).
|
|
121
|
-
*
|
|
122
|
-
* Runs on the create path only — `updateMany` / `createMany` (what update /
|
|
123
|
-
* createMany resolve to) accept scalar data, so nested relation writes reach
|
|
124
|
-
* Prisma solely via `create()`.
|
|
125
|
-
*
|
|
126
|
-
* - `create` → the child must satisfy the related model's `create` rule
|
|
127
|
-
* (recurses, so deeper nesting is authorized too).
|
|
128
|
-
* - `connect` → the target must satisfy the related model's `connect` rule.
|
|
129
|
-
* `connect` is a DEDICATED action: being allowed to READ a record does not
|
|
130
|
-
* authorize associating it.
|
|
131
|
-
*/
|
|
132
|
-
private enforceNestedWrites;
|
|
133
|
-
/** Authorizes each operator inside one relation's nested-write object. */
|
|
134
|
-
private enforceNestedRelationOps;
|
|
135
|
-
/** A `connect` requires an explicit `connect` permission on the target model. */
|
|
136
|
-
private assertConnectAllowed;
|
|
137
90
|
/**
|
|
138
91
|
* True only in an explicit local-development environment. Deliberately NOT
|
|
139
92
|
* `!== 'production'` — staging and other envs should behave like production
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma.service.d.ts","sourceRoot":"","sources":["../../src/prisma/prisma.service.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,MAAM,EAAE,YAAY,EAAC,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAC,qBAAqB,
|
|
1
|
+
{"version":3,"file":"prisma.service.d.ts","sourceRoot":"","sources":["../../src/prisma/prisma.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
16
16
|
exports.PrismaService = exports.CorePrismaContext = void 0;
|
|
17
17
|
const common_1 = require("@nestjs/common");
|
|
18
18
|
const client_1 = require("@prisma/client");
|
|
19
|
-
const permissionsConfigTypes_1 = require("../common/config/permissionsConfigTypes");
|
|
20
19
|
const nestjs_request_context_1 = require("nestjs-request-context");
|
|
21
20
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
22
21
|
exports.CorePrismaContext = new node_async_hooks_1.AsyncLocalStorage();
|
|
@@ -73,19 +72,16 @@ let PrismaService = exports.PrismaService = PrismaService_1 = class PrismaServic
|
|
|
73
72
|
this.debug(`Proxying ${String(propKey)}.${String(methodKey)}() for role ${userRole}`);
|
|
74
73
|
const contextModel = nestjs_request_context_1.RequestContext.currentContext?.req.prisma?.[propKey] || model;
|
|
75
74
|
return async (params = {}, options) => {
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
// equivalents. The proxied types remove/re-alias these, so this
|
|
79
|
-
// matches what a typed caller can express: update() runs
|
|
80
|
-
// updateMany, delete() runs deleteMany, and a findUnique lookup
|
|
81
|
-
// becomes a findFirst with conditions applied.
|
|
82
|
-
const redirect = {
|
|
75
|
+
// Blacklisted methods, should not be used to ensure permission filtering is applied
|
|
76
|
+
let blacklist = {
|
|
83
77
|
findUnique: 'findFirst',
|
|
84
78
|
findUniqueOrThrow: 'findFirstOrThrow',
|
|
85
79
|
delete: 'deleteMany',
|
|
86
80
|
update: 'updateMany',
|
|
87
81
|
};
|
|
88
|
-
|
|
82
|
+
if (blacklist[methodKey.toString()]) {
|
|
83
|
+
throw new Error(`The method ${methodKey.toString()} is not compatible with permission filtering and is not allowed. Please use ${blacklist[methodKey.toString()]}} instead.`);
|
|
84
|
+
}
|
|
89
85
|
// delete, deleteMany, update and updateMany methods should not apply field omission because they are not selecting fields
|
|
90
86
|
if (!options?.BYPASS_OMISSION && !['delete', 'deleteMany', 'update', 'updateMany', 'create', 'createMany'].includes(methodKey.toString())) {
|
|
91
87
|
params = this.applyFieldOmission(String(propKey), userRole, params);
|
|
@@ -98,9 +94,6 @@ let PrismaService = exports.PrismaService = PrismaService_1 = class PrismaServic
|
|
|
98
94
|
// Conditions can't be pushed into a WHERE on insert, so
|
|
99
95
|
// validate the incoming data satisfies them before insert.
|
|
100
96
|
await this.enforceCreateConditions(String(propKey), userRole, params, user, methodKey.toString());
|
|
101
|
-
// Authorize any nested relation writes (create/connect) in
|
|
102
|
-
// the payload; reject every other nested operator.
|
|
103
|
-
await this.enforceNestedWrites(String(propKey), params.data, user, userRole);
|
|
104
97
|
}
|
|
105
98
|
else {
|
|
106
99
|
params = this.applyWhereConditions(String(propKey), userRole, params, user, methodKey);
|
|
@@ -482,85 +475,6 @@ let PrismaService = exports.PrismaService = PrismaService_1 = class PrismaServic
|
|
|
482
475
|
}
|
|
483
476
|
}
|
|
484
477
|
}
|
|
485
|
-
/**
|
|
486
|
-
* Recursively authorizes nested relation writes in a `create` payload. Only
|
|
487
|
-
* an explicit allowlist of operators is permitted, each checked against ABAC;
|
|
488
|
-
* every other operator is rejected (fail closed).
|
|
489
|
-
*
|
|
490
|
-
* Runs on the create path only — `updateMany` / `createMany` (what update /
|
|
491
|
-
* createMany resolve to) accept scalar data, so nested relation writes reach
|
|
492
|
-
* Prisma solely via `create()`.
|
|
493
|
-
*
|
|
494
|
-
* - `create` → the child must satisfy the related model's `create` rule
|
|
495
|
-
* (recurses, so deeper nesting is authorized too).
|
|
496
|
-
* - `connect` → the target must satisfy the related model's `connect` rule.
|
|
497
|
-
* `connect` is a DEDICATED action: being allowed to READ a record does not
|
|
498
|
-
* authorize associating it.
|
|
499
|
-
*/
|
|
500
|
-
async enforceNestedWrites(modelName, data, user, userRole) {
|
|
501
|
-
if (!data || typeof data !== 'object')
|
|
502
|
-
return;
|
|
503
|
-
const rows = Array.isArray(data) ? data : [data];
|
|
504
|
-
for (const row of rows) {
|
|
505
|
-
if (!row || typeof row !== 'object')
|
|
506
|
-
continue;
|
|
507
|
-
for (const key of Object.keys(row)) {
|
|
508
|
-
const relation = this.getRelation(modelName, key);
|
|
509
|
-
if (!relation)
|
|
510
|
-
continue; // scalar / FK field — not a relation nav key
|
|
511
|
-
const ops = row[key];
|
|
512
|
-
if (!ops || typeof ops !== 'object')
|
|
513
|
-
continue;
|
|
514
|
-
await this.enforceNestedRelationOps(relation.model, key, ops, user, userRole, modelName);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
/** Authorizes each operator inside one relation's nested-write object. */
|
|
519
|
-
async enforceNestedRelationOps(relatedModel, field, ops, user, userRole, parentModel) {
|
|
520
|
-
const ALLOWED = ['create', 'connect'];
|
|
521
|
-
for (const op of Object.keys(ops)) {
|
|
522
|
-
if (!ALLOWED.includes(op)) {
|
|
523
|
-
throw new common_1.ForbiddenException(`Nested '${op}' on relation '${field}' of ${parentModel} is not supported through the data-access proxy. ` +
|
|
524
|
-
`Only ${ALLOWED.join(' / ')} are permitted; perform other nested writes in an explicit endpoint with its own authorization.`);
|
|
525
|
-
}
|
|
526
|
-
const value = ops[op];
|
|
527
|
-
const items = Array.isArray(value) ? value : [value];
|
|
528
|
-
if (op === 'create') {
|
|
529
|
-
for (const child of items) {
|
|
530
|
-
await this.enforceCreateConditions(relatedModel, userRole, { data: child }, user, 'create');
|
|
531
|
-
await this.enforceNestedWrites(relatedModel, child, user, userRole);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
for (const target of items) {
|
|
536
|
-
await this.assertConnectAllowed(relatedModel, target, user, userRole, field);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
/** A `connect` requires an explicit `connect` permission on the target model. */
|
|
542
|
-
async assertConnectAllowed(relatedModel, where, user, userRole, field) {
|
|
543
|
-
const normalizedName = relatedModel.toLowerCase().trim();
|
|
544
|
-
const exposedModels = (exports.CorePrismaContext.getStore()?.exposedModels || []).map((m) => m.toLowerCase());
|
|
545
|
-
if (exposedModels.includes(normalizedName))
|
|
546
|
-
return;
|
|
547
|
-
const connectPerm = this.normalizedPermissionsConfig[normalizedName]?.[userRole]?.['connect'];
|
|
548
|
-
if (!connectPerm) {
|
|
549
|
-
throw new common_1.ForbiddenException(`Not allowed to connect ${relatedModel} (via '${field}'): role ${userRole} has no 'connect' permission on ${relatedModel}.`);
|
|
550
|
-
}
|
|
551
|
-
if (connectPerm === 'ALL')
|
|
552
|
-
return;
|
|
553
|
-
const conditions = connectPerm.conditions;
|
|
554
|
-
const resolved = conditions
|
|
555
|
-
? PrismaService_1._buildConditions(Array.isArray(conditions) ? { AND: conditions } : conditions, user)
|
|
556
|
-
: {};
|
|
557
|
-
const delegate = relatedModel.charAt(0).toLowerCase() + relatedModel.slice(1);
|
|
558
|
-
const client = nestjs_request_context_1.RequestContext.currentContext?.req.prisma || this.rawClient;
|
|
559
|
-
const found = await client[delegate].findFirst({ where: { AND: [where || {}, resolved] } });
|
|
560
|
-
if (!found) {
|
|
561
|
-
throw new common_1.ForbiddenException(`Not allowed to connect ${relatedModel} (via '${field}'): the target does not satisfy the 'connect' permission conditions for role ${userRole}.`);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
478
|
/**
|
|
565
479
|
* True only in an explicit local-development environment. Deliberately NOT
|
|
566
480
|
* `!== 'production'` — staging and other envs should behave like production
|
|
@@ -854,14 +768,6 @@ let PrismaService = exports.PrismaService = PrismaService_1 = class PrismaServic
|
|
|
854
768
|
actionPermissions = permissions['findFirst'];
|
|
855
769
|
}
|
|
856
770
|
}
|
|
857
|
-
// A `*Many` action inherits the singular rule when not set explicitly
|
|
858
|
-
// (updateMany → update, deleteMany → delete). `update()` / `delete()`
|
|
859
|
-
// redirect to their `*Many` form, so this is what makes a config that
|
|
860
|
-
// declares only `update` / `delete` apply to them.
|
|
861
|
-
if (!actionPermissions && permissionsConfigTypes_1.SINGULAR_ACTION[action] && permissions[permissionsConfigTypes_1.SINGULAR_ACTION[action]]) {
|
|
862
|
-
this.debug(`Using '${permissionsConfigTypes_1.SINGULAR_ACTION[action]}' permissions for '${action}' action on model '${modelName}' and role ${userRole}`, 'info');
|
|
863
|
-
actionPermissions = permissions[permissionsConfigTypes_1.SINGULAR_ACTION[action]];
|
|
864
|
-
}
|
|
865
771
|
return actionPermissions;
|
|
866
772
|
}
|
|
867
773
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appxdigital/appx-core",
|
|
3
|
-
"version": "0.1.121-beta.
|
|
3
|
+
"version": "0.1.121-beta.5",
|
|
4
4
|
"description": "Appx Core is a library that provides a set of tools to help you build your application faster.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -99,11 +99,7 @@
|
|
|
99
99
|
"rxjs": "~7.8.1"
|
|
100
100
|
},
|
|
101
101
|
"files": [
|
|
102
|
-
"dist"
|
|
103
|
-
"docs",
|
|
104
|
-
"CHANGELOG.md",
|
|
105
|
-
"PRERELEASES.md",
|
|
106
|
-
"RELEASING.md"
|
|
102
|
+
"dist"
|
|
107
103
|
],
|
|
108
104
|
"engines": {
|
|
109
105
|
"npm": ">=10.0.0",
|