@appxdigital/appx-core 0.1.91 → 0.1.92

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.
@@ -1,3 +1,3 @@
1
1
  export declare const PERMISSION_METADATA_KEY = "permission";
2
- export declare const Permission: (action: string) => import("@nestjs/common").CustomDecorator<string>;
2
+ export declare const Permission: (action: string, expose_models?: string[]) => import("@nestjs/common").CustomDecorator<string>;
3
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;AAEpD,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,qDACS,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,GAAI,QAAQ,MAAM,EAAE,gBAAe,MAAM,EAAO,qDACN,CAAC"}
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
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
- const Permission = (action) => (0, common_1.SetMetadata)(exports.PERMISSION_METADATA_KEY, { action });
6
+ const Permission = (action, expose_models = []) => (0, common_1.SetMetadata)(exports.PERMISSION_METADATA_KEY, { action, expose_models });
7
7
  exports.Permission = Permission;
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.interceptor.d.ts","sourceRoot":"","sources":["../../../src/common/interceptors/prisma.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,WAAW,EACX,gBAAgB,EAEhB,eAAe,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAC,UAAU,EAAa,MAAM,MAAM,CAAC;AAI5C,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;CAqD3E"}
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,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC1D,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;CAyD3E"}
@@ -18,6 +18,7 @@ const nestjs_request_context_1 = require("nestjs-request-context");
18
18
  const operators_1 = require("rxjs/operators");
19
19
  const rxjs_1 = require("rxjs");
20
20
  const error_handler_1 = require("../utils/error-handler");
21
+ const permission_decorator_1 = require("../decorators/permission.decorator");
21
22
  let PrismaInterceptor = class PrismaInterceptor {
22
23
  constructor(prismaService, reflector, configService) {
23
24
  this.prismaService = prismaService;
@@ -30,6 +31,9 @@ let PrismaInterceptor = class PrismaInterceptor {
30
31
  const req = handlerType === 'graphql'
31
32
  ? context.getArgByIndex(2) // GraphQL context
32
33
  : context.switchToHttp().getRequest(); // HTTP context
34
+ // Attach expose_models metadata if needed
35
+ const permissionMetadata = this.reflector.get(permission_decorator_1.PERMISSION_METADATA_KEY, context.getHandler()) || {};
36
+ nestjs_request_context_1.RequestContext.currentContext.req.prismaExposedModels = permissionMetadata['expose_models'] || [];
33
37
  const useTransaction = this.reflector.get('useTransaction', context.getHandler()) ?? this.defaultUseTransaction === 'true';
34
38
  if (useTransaction) {
35
39
  return new rxjs_1.Observable((observer) => {
@@ -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,EAAC,MAAM,yCAAyC,CAAC;AAE9E,OAAO,KAAK,EAAC,YAAY,IAAI,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAGlE,KAAK,QAAQ,GAAG,MAAM,aAAa,CAAC;AAEpC,oDAAoD;AACpD,MAAM,MAAM,iBAAiB,GAAG;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAGF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC;AAEtI,qBACa,aAAa;IAMY,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IALpE,OAAO,CAAC,YAAY,CAA2B;IAC/C,YAAY,EAAE,YAAY,CAAC;gBAGvB,YAAY,EAAE,YAAY,EACqB,iBAAiB,EAAE,qBAAqB;IAO3F,YAAY,CAAC,MAAM,EAAE,OAAO;IAI5B,OAAO,CAAC,KAAK;IAQb,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,iBAAiB,KAAK,OAAO,CAAC,CAAC,CAAC;IAIpE,WAAW;IAuDX;;;;OAIG;IACH,gBAAgB,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC;IAYlE,IAAI,KAAK,IAAI,aAAa,CAUzB;IAED,IAAI,IAAI,IAAI,eAAe,CAAC,MAAM,CAAC,CAElC;IAED,IAAI,OAAO,IAAI,eAAe,CAAC,SAAS,CAAC,CAExC;IAED,IAAI,gBAAgB,QAEnB;IAED;;;OAGG;IACH,WAAW;IAoDX;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAmC1B,IAAI,2BAA2B,QAM9B;IAED;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,oBAAoB;IAsH5B;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAgBvB;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IA2B3B;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAUvB;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB;IAkDzB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,IAAI,GAAG;QAC5E,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC9B;IAkBD,gBAAgB,CAAC,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAgBzF"}
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,EAAC,MAAM,yCAAyC,CAAC;AAE9E,OAAO,KAAK,EAAC,YAAY,IAAI,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAGlE,KAAK,QAAQ,GAAG,MAAM,aAAa,CAAC;AAEpC,oDAAoD;AACpD,MAAM,MAAM,iBAAiB,GAAG;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAGF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC;AAEtI,qBACa,aAAa;IAMY,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IALpE,OAAO,CAAC,YAAY,CAA2B;IAC/C,YAAY,EAAE,YAAY,CAAC;gBAGvB,YAAY,EAAE,YAAY,EACqB,iBAAiB,EAAE,qBAAqB;IAO3F,YAAY,CAAC,MAAM,EAAE,OAAO;IAI5B,OAAO,CAAC,KAAK;IAQb,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,iBAAiB,KAAK,OAAO,CAAC,CAAC,CAAC;IAIpE,WAAW;IAuDX;;;;OAIG;IACH,gBAAgB,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC;IAYlE,IAAI,KAAK,IAAI,aAAa,CAUzB;IAED,IAAI,IAAI,IAAI,eAAe,CAAC,MAAM,CAAC,CAElC;IAED,IAAI,OAAO,IAAI,eAAe,CAAC,SAAS,CAAC,CAExC;IAED,IAAI,gBAAgB,QAEnB;IAED;;;OAGG;IACH,WAAW;IAoDX;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAmC1B,IAAI,2BAA2B,QAM9B;IAED;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,oBAAoB;IAwI5B;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAgBvB;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IA2B3B;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAUvB;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB;IAkDzB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,IAAI,GAAG;QAC5E,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC9B;IAkBD,gBAAgB,CAAC,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAgBzF"}
@@ -229,13 +229,20 @@ let PrismaService = class PrismaService {
229
229
  const permissionsConfig = this.normalizedPermissionsConfig;
230
230
  const normalizedName = modelName.toLowerCase().trim();
231
231
  const permissions = permissionsConfig[normalizedName]?.[userRole];
232
- if (!permissions) {
233
- throw new common_1.HttpException(`No permissions found for model '${modelName}' and role ${userRole}`, common_1.HttpStatus.FORBIDDEN);
232
+ let actionPermissions;
233
+ // If model is exposed, permissions is ALL
234
+ if (nestjs_request_context_1.RequestContext.currentContext.req.prismaExposedModels.map((m) => m.toLowerCase()).includes(modelName.toLowerCase())) {
235
+ actionPermissions = 'ALL';
234
236
  }
235
- let actionPermissions = this.selectPermission(permissions, action.toString(), modelName, userRole);
236
- if (!actionPermissions) {
237
- console.debug(`No permissions found for action '${modelName}.${String(action)}()' on role ${userRole}`);
238
- throw new common_1.HttpException('Missing permissions on model ' + modelName, common_1.HttpStatus.FORBIDDEN);
237
+ else {
238
+ if (!permissions) {
239
+ throw new common_1.HttpException(`No permissions found for model '${modelName}' and role ${userRole}`, common_1.HttpStatus.FORBIDDEN);
240
+ }
241
+ actionPermissions = this.selectPermission(permissions, action.toString(), modelName, userRole);
242
+ if (!actionPermissions) {
243
+ console.debug(`No permissions found for action '${modelName}.${String(action)}()' on role ${userRole}`);
244
+ throw new common_1.HttpException('Missing permissions on model ' + modelName, common_1.HttpStatus.FORBIDDEN);
245
+ }
239
246
  }
240
247
  if (args.select) {
241
248
  for (const field of Object.keys(args.select)) {
@@ -247,10 +254,19 @@ let PrismaService = class PrismaService {
247
254
  if (relation.relation === 'belongsTo') {
248
255
  this.debug(`Found 1:1 / *:1 (belongsTo) relation to model '${relation.model}' from model '${modelName}' via field '${field}'. Filter will be applied to main conditions...`);
249
256
  const relatedPermissions = this.selectPermission(permissionsConfig[relation.model.toLowerCase()]?.[userRole] || {}, action.toString(), relation.model, userRole);
257
+ // If model is exposed, do not apply conditions
258
+ if (nestjs_request_context_1.RequestContext.currentContext.req.prismaExposedModels.map((m) => m.toLowerCase()).includes(relation.model.toLowerCase())) {
259
+ this.debug(`Related model '${relation.model}' is exposed via @Permission() decorator. Skipping conditions for action '${String(action)}' on role ${userRole}.`);
260
+ continue;
261
+ }
250
262
  if (!relatedPermissions) {
251
263
  console.debug(`No permissions found for action '${relation.model}.${String(action)}()' on role ${userRole}`);
252
264
  throw new common_1.HttpException('Missing permissions on model ' + relation.model, common_1.HttpStatus.FORBIDDEN);
253
265
  }
266
+ if (relatedPermissions === 'ALL') {
267
+ this.debug(`Related model '${relation.model}' has 'ALL' permissions for action '${String(action)}' on role ${userRole}. No conditions to apply.`);
268
+ continue;
269
+ }
254
270
  belongsToQueue.push({
255
271
  field,
256
272
  relation,
@@ -277,23 +293,6 @@ let PrismaService = class PrismaService {
277
293
  delete args.select[field].select.where;
278
294
  }
279
295
  }
280
- if (actionPermissions === 'ALL' && belongsToQueue.length === 0 || action.toString().startsWith('create')) {
281
- this.debug(`No conditions to apply for '${modelName}.${String(action)}()' on role ${userRole}`);
282
- return args;
283
- }
284
- const whereClause = this.buildConditions(actionPermissions.conditions, user);
285
- this.debug(`Applying where conditions for '${modelName}.${String(action)}()' on role ${userRole}: ${JSON.stringify(whereClause)}`);
286
- if (!args.where) {
287
- args.where = whereClause;
288
- }
289
- else {
290
- args.where = { AND: [args.where, whereClause] };
291
- }
292
- // Failsafe: If there are no conditions at all and there were supposed to be, block access
293
- if (Object.keys(args.where).length === 0 && !belongsToQueue?.length) {
294
- this.debug(`Found a weird edge case. Contact Manuel Olveira @ AppX.`);
295
- throw new common_1.ForbiddenException(`You are not authorized to access this record`);
296
- }
297
296
  if (belongsToQueue?.length > 0) {
298
297
  this.debug(`Merging belongsTo relation conditions into main ${modelName}.`, 'warn');
299
298
  }
@@ -301,7 +300,7 @@ let PrismaService = class PrismaService {
301
300
  const { relatedPermissions, field } = entry;
302
301
  this.debug(`Merging conditions for belongsTo relation field '${field}': ${JSON.stringify(relatedPermissions?.conditions)}`, 'info');
303
302
  args.where = {
304
- ...args.where,
303
+ ...(args.where || {}),
305
304
  [field]: {
306
305
  AND: [
307
306
  args?.where?.[field] ?? {},
@@ -310,6 +309,23 @@ let PrismaService = class PrismaService {
310
309
  }
311
310
  };
312
311
  }
312
+ // If model is exposed, do not apply conditions
313
+ if (nestjs_request_context_1.RequestContext.currentContext.req.prismaExposedModels.map((m) => m.toLowerCase()).includes(modelName.toLowerCase())) {
314
+ this.debug(`Model '${modelName}' is exposed via @Permission() decorator. Skipping conditions for action '${String(action)}' on role ${userRole}.`);
315
+ return args;
316
+ }
317
+ if (actionPermissions === 'ALL') {
318
+ this.debug(`No conditions to apply for '${modelName}.${String(action)}()' on role ${userRole}`);
319
+ return args;
320
+ }
321
+ const whereClause = this.buildConditions(actionPermissions.conditions, user);
322
+ this.debug(`Applying where conditions for '${modelName}.${String(action)}()' on role ${userRole}: ${JSON.stringify(whereClause)}`);
323
+ if (!args.where) {
324
+ args.where = whereClause;
325
+ }
326
+ else {
327
+ args.where = { AND: [args.where, whereClause] };
328
+ }
313
329
  return args;
314
330
  }
315
331
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appxdigital/appx-core",
3
- "version": "0.1.91",
3
+ "version": "0.1.92",
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",