aws-service-stack 0.18.372 β†’ 0.18.374

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.
Files changed (64) hide show
  1. package/README.md +150 -0
  2. package/dist/_examples/controller/property/property-crud.d.ts +4 -0
  3. package/dist/_examples/controller/property/property-crud.js +58 -0
  4. package/dist/_examples/controller/property/property-crud.js.map +1 -0
  5. package/dist/_examples/controller/property/property.config.d.ts +10 -0
  6. package/dist/_examples/controller/property/property.config.js +53 -0
  7. package/dist/_examples/controller/property/property.config.js.map +1 -0
  8. package/dist/_examples/controller/property/property.controller.d.ts +14 -0
  9. package/dist/_examples/controller/property/property.controller.js +72 -0
  10. package/dist/_examples/controller/property/property.controller.js.map +1 -0
  11. package/dist/_examples/controller/property/property.permissions.d.ts +2 -0
  12. package/dist/_examples/controller/property/property.permissions.js +19 -0
  13. package/dist/_examples/controller/property/property.permissions.js.map +1 -0
  14. package/dist/controller/controller-api.d.ts +5 -0
  15. package/dist/controller/controller-api.js +29 -1
  16. package/dist/controller/controller-api.js.map +1 -1
  17. package/dist/controller/controller-role.d.ts +53 -0
  18. package/dist/controller/controller-role.js +216 -0
  19. package/dist/controller/controller-role.js.map +1 -0
  20. package/dist/controller/index.d.ts +1 -0
  21. package/dist/controller/index.js +1 -0
  22. package/dist/controller/index.js.map +1 -1
  23. package/dist/function/cognito/cognito.function.d.ts +15 -0
  24. package/dist/function/cognito/cognito.function.js +45 -0
  25. package/dist/function/cognito/cognito.function.js.map +1 -1
  26. package/dist/function/cognito/index.d.ts +5 -1
  27. package/dist/function/cognito/index.js +4 -0
  28. package/dist/function/cognito/index.js.map +1 -1
  29. package/dist/function/index.d.ts +4 -0
  30. package/dist/model/base.config.d.ts +5 -1
  31. package/dist/model/base.config.js +7 -0
  32. package/dist/model/base.config.js.map +1 -1
  33. package/dist/model/base.model.d.ts +15 -0
  34. package/dist/model/base.model.js +4 -1
  35. package/dist/model/base.model.js.map +1 -1
  36. package/dist/model/index.d.ts +1 -0
  37. package/dist/model/index.js.map +1 -1
  38. package/dist/model/role.model.d.ts +20 -0
  39. package/dist/model/role.model.js +12 -0
  40. package/dist/model/role.model.js.map +1 -0
  41. package/dist/model/validation.model.d.ts +1 -1
  42. package/dist/model/validation.model.js.map +1 -1
  43. package/dist/service/index.d.ts +3 -0
  44. package/dist/service/index.js +3 -0
  45. package/dist/service/index.js.map +1 -1
  46. package/dist/service/permission.cache.d.ts +24 -0
  47. package/dist/service/permission.cache.js +61 -0
  48. package/dist/service/permission.cache.js.map +1 -0
  49. package/dist/service/permission.repo.d.ts +16 -0
  50. package/dist/service/permission.repo.js +63 -0
  51. package/dist/service/permission.repo.js.map +1 -0
  52. package/dist/service/permission.service.d.ts +39 -0
  53. package/dist/service/permission.service.js +151 -0
  54. package/dist/service/permission.service.js.map +1 -0
  55. package/dist/utils/date.util.d.ts +0 -13
  56. package/dist/utils/date.util.js +0 -35
  57. package/dist/utils/date.util.js.map +1 -1
  58. package/dist/utils/index.d.ts +0 -1
  59. package/dist/utils/index.js +0 -1
  60. package/dist/utils/index.js.map +1 -1
  61. package/package.json +31 -31
  62. package/dist/utils/data.util.d.ts +0 -20
  63. package/dist/utils/data.util.js +0 -73
  64. package/dist/utils/data.util.js.map +0 -1
package/README.md CHANGED
@@ -420,6 +420,156 @@ new Function(this, "MyFunction", {
420
420
  });
421
421
  ```
422
422
 
423
+ ## πŸ” Role-Based Access Control (RBAC)
424
+
425
+ The framework provides a centralized RBAC system through `ControllerRole`, which manages Cognito user pool groups, DynamoDB-backed permissions, and scope-based data filtering β€” all integrated directly into `ControllerApi`.
426
+
427
+ ### Architecture
428
+
429
+ ```
430
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
431
+ β”‚ ControllerApi β”‚ ← Delegates RBAC check automatically
432
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
433
+ β”‚ ControllerRole β”‚ ← Permission CRUD, Cognito groups, scope enforcement
434
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
435
+ β”‚PermissionService β”‚ ← Read-through cache (15-min TTL) + orchestration
436
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
437
+ β”‚ PermissionRepo β”‚ ← DynamoDB via BaseRepoDBImpl
438
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
439
+ ```
440
+
441
+ ### Permission Model
442
+
443
+ Each permission is stored in DynamoDB with the composite key `role#resource#scope`:
444
+
445
+ ```typescript
446
+ interface Permission {
447
+ id: string;
448
+ role: string; // Cognito group name: "Manager", "Agent"
449
+ resource: string; // API resource: "property", "order"
450
+ scope: string; // Data scope: "Organization", "Branch", "Agent"
451
+ method: {
452
+ get?: boolean;
453
+ post?: boolean;
454
+ patch?: boolean;
455
+ put?: boolean;
456
+ delete?: boolean;
457
+ };
458
+ permissionKey: string; // "Manager#property#Branch"
459
+ }
460
+ ```
461
+
462
+ ### Configuration
463
+
464
+ Enable RBAC by adding `ROLE_TABLE`, `ROLE_PATH`, and `SCOPE_MAP` to your entity config:
465
+
466
+ ```typescript
467
+ import { EntityConfigImpl, ScopeMap } from "@chinggis/core";
468
+
469
+ const scopeMap: ScopeMap = new Map([
470
+ ["Organization", { filterField: "orgId", claimKey: "custom:orgId" }],
471
+ ["Branch", { filterField: "branchId", claimKey: "custom:branch" }],
472
+ ["Agent", { filterField: "agentId", claimKey: "custom:agent" }],
473
+ ]);
474
+
475
+ export class PropertyConfig extends EntityConfigImpl {
476
+ constructor() {
477
+ super("/properties", ["adminUsers"]);
478
+
479
+ this.setDynamoDB("property-dev", "owner", indexMap)
480
+ .setOpenSearch(domain, "property")
481
+ .setPolicies(policyList)
482
+ .setScopes(scopeMap)
483
+ .setRoleTable("role-permissions-dev")
484
+ .setRolePath("/properties/role");
485
+ }
486
+ }
487
+ ```
488
+
489
+ When both `ROLE_TABLE` and `SCOPE_MAP` are configured, `ControllerApi` automatically creates a `ControllerRole` instance and enforces RBAC on every request.
490
+
491
+ ### How It Works
492
+
493
+ 1. **Request arrives** at `ControllerApi.resolveCrudRequest()`
494
+ 2. If RBAC is configured, `ControllerRole.checkRbacAccess()` is called
495
+ 3. User's **role** is extracted from JWT `groups[0]`
496
+ 4. **Scope** is read from `?scope=Branch` query parameter and validated against `ScopeMap`
497
+ 5. Permission is checked via `PermissionService` (with in-memory cache)
498
+ 6. On success, **scope filter** is applied (e.g., `filter.branchId = identity["custom:branch"]`)
499
+ 7. On failure, `403 PermissionDenied` is thrown
500
+
501
+ ### Permission CRUD Endpoints
502
+
503
+ When `ROLE_PATH` is configured, permission management endpoints are automatically available:
504
+
505
+ ```
506
+ GET /properties/role # List all permissions
507
+ POST /properties/role # Create a permission
508
+ PATCH /properties/role # Update a permission
509
+ DELETE /properties/role # Delete a permission
510
+ POST /properties/role/add-role # Create a Cognito user pool group
511
+ POST /properties/role/add-user-role # Add a User to cognito group
512
+ ```
513
+
514
+ #### Create a Permission
515
+
516
+ ```json
517
+ POST /properties/role
518
+ {
519
+ "role": "Manager",
520
+ "resource": "property",
521
+ "scope": "Branch",
522
+ "method": { "get": true, "post": true, "patch": true, "delete": false }
523
+ }
524
+ ```
525
+
526
+ #### Create a Cognito Group (Role)
527
+
528
+ ```json
529
+ POST /properties/role/add-role
530
+ {
531
+ "groupName": "Manager",
532
+ "description": "Branch-level managers"
533
+ }
534
+ ```
535
+
536
+ ### Programmatic Usage
537
+
538
+ `ControllerRole` can also be used directly in custom controllers or services:
539
+
540
+ ```typescript
541
+ import { ControllerRole } from "@chinggis/core";
542
+
543
+ const roleController = new ControllerRole("role-permissions-dev");
544
+
545
+ // Permission CRUD
546
+ await roleController.addPermission({
547
+ role: "Manager",
548
+ resource: "property",
549
+ scope: "Branch",
550
+ method: { get: true, post: true, patch: true },
551
+ });
552
+
553
+ await roleController.listPermissions();
554
+ await roleController.updatePermission("perm-id", { method: { delete: true } });
555
+ await roleController.deletePermission("perm-id");
556
+
557
+ // Permission check
558
+ const allowed = await roleController.hasPermission("Manager", "property", "Branch", "GET");
559
+
560
+ // Cognito group management
561
+ await roleController.addRole("us-east-1_PoolId", "Manager", "Branch-level managers");
562
+ await roleController.assignRole("us-east-1_PoolId", "john@example.com", "Manager");
563
+ ```
564
+
565
+ ### Caching
566
+
567
+ `PermissionService` uses a read-through cache with **15-minute TTL** optimized for Lambda warm invocations:
568
+
569
+ - Cache hits return instantly without DB calls
570
+ - Concurrent requests for the same key are deduplicated (single in-flight fetch)
571
+ - Cache is automatically invalidated on create, update, and delete operations
572
+
423
573
  ## 🀝 Contributing
424
574
 
425
575
  1. Fork the repository
@@ -0,0 +1,4 @@
1
+ import "reflect-metadata";
2
+ import { APIGatewayProxyEvent } from "aws-lambda";
3
+ import "./property.controller";
4
+ export declare const handler: (event: APIGatewayProxyEvent) => Promise<import("src/utils").APIResponse>;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.handler = void 0;
43
+ require("reflect-metadata");
44
+ const typedi_1 = __importStar(require("typedi"));
45
+ require("./property.controller");
46
+ const handler = (event) => typedi_1.default.get(HelperCDI).process(event);
47
+ exports.handler = handler;
48
+ let HelperCDI = class HelperCDI {
49
+ controller = typedi_1.default.get("PropertyController");
50
+ async process(event) {
51
+ console.log("example Processing...");
52
+ return this.controller.resolveCrudRequest(event);
53
+ }
54
+ };
55
+ HelperCDI = __decorate([
56
+ (0, typedi_1.Service)()
57
+ ], HelperCDI);
58
+ //# sourceMappingURL=property-crud.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"property-crud.js","sourceRoot":"","sources":["../../../../src/_examples/controller/property/property-crud.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4BAA0B;AAC1B,iDAA4C;AAG5C,iCAA+B;AAExB,MAAM,OAAO,GAAG,CAAC,KAA2B,EAAE,EAAE,CAAC,gBAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAAnF,QAAA,OAAO,WAA4E;AAGhG,IAAM,SAAS,GAAf,MAAM,SAAS;IACL,UAAU,GAAuB,gBAAS,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAE7E,KAAK,CAAC,OAAO,CAAC,KAA2B;QACvC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;CACF,CAAA;AAPK,SAAS;IADd,IAAA,gBAAO,GAAE;GACJ,SAAS,CAOd","sourcesContent":["import \"reflect-metadata\";\nimport Container, { Service } from \"typedi\";\nimport { APIGatewayProxyEvent } from \"aws-lambda\";\nimport { PropertyController } from \"./property.controller\";\nimport \"./property.controller\";\n\nexport const handler = (event: APIGatewayProxyEvent) => Container.get(HelperCDI).process(event);\n\n@Service()\nclass HelperCDI {\n private controller: PropertyController = Container.get(\"PropertyController\");\n\n async process(event: APIGatewayProxyEvent) {\n console.log(\"example Processing...\");\n return this.controller.resolveCrudRequest(event);\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import { EntityConfigImpl } from "../../../model/base.config";
2
+ export declare const openSearch_order: {
3
+ domain: string;
4
+ index: string;
5
+ };
6
+ export declare const path = "/property";
7
+ export declare class PropertyConfig extends EntityConfigImpl {
8
+ constructor();
9
+ }
10
+ export declare const CONFIG_PROPERTY: PropertyConfig;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONFIG_PROPERTY = exports.PropertyConfig = exports.path = exports.openSearch_order = void 0;
4
+ const base_config_1 = require("../../../model/base.config");
5
+ const core_1 = require("../../../index.js");
6
+ const order_repo_db_interface_1 = require("../../repositories/order/order-repo-db.interface");
7
+ // OpenSearch configuration
8
+ exports.openSearch_order = {
9
+ domain: "https://search-amplify-opense-1ddfdekgbbpwe-kgxh6aum57h2wc4moozm2jcvom.ap-southeast-1.es.amazonaws.com",
10
+ index: "order",
11
+ };
12
+ exports.path = "/property"; // url path base
13
+ // Order configuration
14
+ class PropertyConfig extends base_config_1.EntityConfigImpl {
15
+ constructor() {
16
+ // DYNAMODB
17
+ const tableName = "Property-dev";
18
+ const ownerFieldName = "ownerId";
19
+ const indexMap = new core_1.DynamoIndexMap()
20
+ .setFields("ownerId")
21
+ .set("byAgent", { field: "agentId", rFields: ["agentId"] })
22
+ .set("propertyByBranch", { field: "branchId", rFields: ["branchId"] })
23
+ .set("byOrg", { field: "organizationId", rFields: ["organizationId"] });
24
+ // PERMISSIONS
25
+ const adminGroupNames = ["adminUsers"];
26
+ const policyList = [
27
+ { method: core_1.HttpMethod.GET, path: `${exports.path}`, access: [core_1.Access.USER, core_1.Access.PUBLIC, core_1.Access.ADMIN] },
28
+ { method: core_1.HttpMethod.GET, path: `${exports.path}/search`, access: [core_1.Access.USER], response: order_repo_db_interface_1.RESPONSE_FIELDS_LIST },
29
+ { method: core_1.HttpMethod.GET, path: `${exports.path}/search/query`, access: [core_1.Access.USER], response: order_repo_db_interface_1.RESPONSE_FIELDS_LIST },
30
+ { method: core_1.HttpMethod.GET, path: `${exports.path}/search/query/total-count`, access: [core_1.Access.USER] },
31
+ { method: core_1.HttpMethod.GET, path: `${exports.path}/{id}`, access: [core_1.Access.USER], response: order_repo_db_interface_1.RESPONSE_FIELDS_DETAILS },
32
+ { method: core_1.HttpMethod.POST, path: `${exports.path}`, access: [core_1.Access.USER], validator: order_repo_db_interface_1.CREATE, response: order_repo_db_interface_1.RESPONSE_FIELDS_DETAILS },
33
+ { method: core_1.HttpMethod.PUT, path: `${exports.path}/{id}`, access: [core_1.Access.USER], validator: order_repo_db_interface_1.REPLACE, response: order_repo_db_interface_1.RESPONSE_FIELDS_DETAILS },
34
+ { method: core_1.HttpMethod.PATCH, path: `${exports.path}/{id}`, access: [core_1.Access.USER], validator: order_repo_db_interface_1.UPDATE, response: order_repo_db_interface_1.RESPONSE_FIELDS_DETAILS },
35
+ { method: core_1.HttpMethod.DELETE, path: `${exports.path}/{id}`, access: [core_1.Access.USER] },
36
+ ];
37
+ // SCOPES
38
+ const scopeMap = new core_1.ScopeMap()
39
+ .set("branch", { filterField: "branchId", claimKey: "sub" })
40
+ .set("agent", { filterField: "agentId", claimKey: "custom:agent" })
41
+ .set("org", { filterField: "organizationId", claimKey: "custom:org" });
42
+ // INIT
43
+ super(exports.path, adminGroupNames);
44
+ this.setDynamoDB(tableName, ownerFieldName, indexMap)
45
+ .setOpenSearch(exports.openSearch_order.domain, exports.openSearch_order.index)
46
+ .setPolicies(policyList)
47
+ .setPermissionMap({ scopeMap, roleTable: "Permission-dev", rolePath: "/permission/plp" });
48
+ }
49
+ }
50
+ exports.PropertyConfig = PropertyConfig;
51
+ // Export default Order configuration
52
+ exports.CONFIG_PROPERTY = new PropertyConfig();
53
+ //# sourceMappingURL=property.config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"property.config.js","sourceRoot":"","sources":["../../../../src/_examples/controller/property/property.config.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityConfigImpl } from \"../../../model/base.config\";\nimport { Access as a, DynamoIndexMap, EndpointPolicy, HttpMethod as m, ScopeMap } from \"@chinggis/core\";\nimport {\n CREATE,\n REPLACE,\n RESPONSE_FIELDS_DETAILS as FIELDS_D,\n RESPONSE_FIELDS_LIST as FIELDS_L,\n UPDATE,\n} from \"../../repositories/order/order-repo-db.interface\";\n\n// OpenSearch configuration\nexport const openSearch_order = {\n domain: \"https://search-amplify-opense-1ddfdekgbbpwe-kgxh6aum57h2wc4moozm2jcvom.ap-southeast-1.es.amazonaws.com\",\n index: \"order\",\n};\nexport const path = \"/property\"; // url path base\n\n// Order configuration\nexport class PropertyConfig extends EntityConfigImpl {\n constructor() {\n // DYNAMODB\n const tableName = \"Property-dev\";\n const ownerFieldName = \"ownerId\";\n const indexMap = new DynamoIndexMap()\n .setFields(\"ownerId\")\n .set(\"byAgent\", { field: \"agentId\", rFields: [\"agentId\"] })\n .set(\"propertyByBranch\", { field: \"branchId\", rFields: [\"branchId\"] })\n .set(\"byOrg\", { field: \"organizationId\", rFields: [\"organizationId\"] });\n\n // PERMISSIONS\n const adminGroupNames = [\"adminUsers\"];\n\n const policyList: EndpointPolicy[] = [\n { method: m.GET, path: `${path}`, access: [a.USER, a.PUBLIC, a.ADMIN] },\n { method: m.GET, path: `${path}/search`, access: [a.USER], response: FIELDS_L },\n { method: m.GET, path: `${path}/search/query`, access: [a.USER], response: FIELDS_L },\n { method: m.GET, path: `${path}/search/query/total-count`, access: [a.USER] },\n { method: m.GET, path: `${path}/{id}`, access: [a.USER], response: FIELDS_D },\n { method: m.POST, path: `${path}`, access: [a.USER], validator: CREATE, response: FIELDS_D },\n { method: m.PUT, path: `${path}/{id}`, access: [a.USER], validator: REPLACE, response: FIELDS_D },\n { method: m.PATCH, path: `${path}/{id}`, access: [a.USER], validator: UPDATE, response: FIELDS_D },\n { method: m.DELETE, path: `${path}/{id}`, access: [a.USER] },\n ];\n\n // SCOPES\n const scopeMap = new ScopeMap()\n .set(\"branch\", { filterField: \"branchId\", claimKey: \"sub\" })\n .set(\"agent\", { filterField: \"agentId\", claimKey: \"custom:agent\" })\n .set(\"org\", { filterField: \"organizationId\", claimKey: \"custom:org\" });\n\n // INIT\n super(path, adminGroupNames);\n this.setDynamoDB(tableName, ownerFieldName, indexMap)\n .setOpenSearch(openSearch_order.domain, openSearch_order.index)\n .setPolicies(policyList)\n .setPermissionMap({ scopeMap, roleTable: \"Permission-dev\", rolePath: \"/permission/plp\" });\n }\n}\n\n// Export default Order configuration\nexport const CONFIG_PROPERTY = new PropertyConfig();\n"]}
@@ -0,0 +1,14 @@
1
+ import { OrderService } from "../../service/order-service.interface";
2
+ import "../../service/order-service";
3
+ import { Order } from "src/_examples/model-shared/example.model";
4
+ import { HttpRequest } from "src/utils";
5
+ import { ControllerApi } from "src/controller";
6
+ /**
7
+ * Example Controller for Profile: Validator (Zod) is set centrally.
8
+ * Scopes are configured in PropertyConfig via ScopeMap.
9
+ */
10
+ export declare class PropertyController extends ControllerApi<Order, OrderService> {
11
+ protected processCrudRequest(event: HttpRequest): Promise<any>;
12
+ constructor();
13
+ getPermission(permission: string): Promise<boolean>;
14
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.PropertyController = void 0;
46
+ const typedi_1 = __importStar(require("typedi"));
47
+ const property_config_1 = require("./property.config");
48
+ require("../../service/order-service");
49
+ const controller_1 = require("src/controller");
50
+ const property_permissions_1 = require("./property.permissions");
51
+ /**
52
+ * Example Controller for Profile: Validator (Zod) is set centrally.
53
+ * Scopes are configured in PropertyConfig via ScopeMap.
54
+ */
55
+ let PropertyController = class PropertyController extends controller_1.ControllerApi {
56
+ processCrudRequest(event) {
57
+ return undefined;
58
+ }
59
+ constructor() {
60
+ const service = typedi_1.default.get("OrderService");
61
+ super(service, property_config_1.CONFIG_PROPERTY.toObject());
62
+ }
63
+ async getPermission(permission) {
64
+ return (0, property_permissions_1.hasPropertyPermission)(permission);
65
+ }
66
+ };
67
+ exports.PropertyController = PropertyController;
68
+ exports.PropertyController = PropertyController = __decorate([
69
+ (0, typedi_1.Service)("PropertyController"),
70
+ __metadata("design:paramtypes", [])
71
+ ], PropertyController);
72
+ //# sourceMappingURL=property.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"property.controller.js","sourceRoot":"","sources":["../../../../src/_examples/controller/property/property.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iDAA4C;AAG5C,uDAAoD;AAEpD,uCAAqC;AAGrC,+CAA+C;AAC/C,iEAA+D;AAE/D;;;GAGG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,0BAAkC;IAC9D,kBAAkB,CAAC,KAAkB;QAC7C,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;QACE,MAAM,OAAO,GAAiB,gBAAS,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC5D,KAAK,CAAC,OAAO,EAAE,iCAAe,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,UAAkB;QACpC,OAAO,IAAA,4CAAqB,EAAC,UAAU,CAAC,CAAC;IAC3C,CAAC;CACF,CAAA;AAbY,gDAAkB;6BAAlB,kBAAkB;IAD9B,IAAA,gBAAO,EAAC,oBAAoB,CAAC;;GACjB,kBAAkB,CAa9B","sourcesContent":["import Container, { Service } from \"typedi\";\nimport {} from \"../../../controller/base-controller\";\n\nimport { CONFIG_PROPERTY } from \"./property.config\";\nimport { OrderService } from \"../../service/order-service.interface\";\nimport \"../../service/order-service\";\nimport { Order } from \"src/_examples/model-shared/example.model\";\nimport { HttpRequest } from \"src/utils\";\nimport { ControllerApi } from \"src/controller\";\nimport { hasPropertyPermission } from \"./property.permissions\";\n\n/**\n * Example Controller for Profile: Validator (Zod) is set centrally.\n * Scopes are configured in PropertyConfig via ScopeMap.\n */\n@Service(\"PropertyController\")\nexport class PropertyController extends ControllerApi<Order, OrderService> {\n protected processCrudRequest(event: HttpRequest): Promise<any> {\n return undefined;\n }\n\n constructor() {\n const service: OrderService = Container.get(\"OrderService\");\n super(service, CONFIG_PROPERTY.toObject());\n }\n\n async getPermission(permission: string): Promise<boolean> {\n return hasPropertyPermission(permission);\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const PROPERTY_PERMISSION_KEYS: Set<string>;
2
+ export declare function hasPropertyPermission(permission: string): boolean;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROPERTY_PERMISSION_KEYS = void 0;
4
+ exports.hasPropertyPermission = hasPropertyPermission;
5
+ exports.PROPERTY_PERMISSION_KEYS = new Set([
6
+ "adminusers.property.branch.GET",
7
+ "adminusers.property.branch.POST",
8
+ "user.property.branch.GET",
9
+ "user.property.agent.GET",
10
+ "user.property.agent.POST",
11
+ "owner.property.branch.GET",
12
+ "owner.property.branch.POST",
13
+ "owner.property.agent.GET",
14
+ "owner.property.agent.POST",
15
+ ]);
16
+ function hasPropertyPermission(permission) {
17
+ return exports.PROPERTY_PERMISSION_KEYS.has(permission);
18
+ }
19
+ //# sourceMappingURL=property.permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"property.permissions.js","sourceRoot":"","sources":["../../../../src/_examples/controller/property/property.permissions.ts"],"names":[],"mappings":";;;AAYA,sDAEC;AAdY,QAAA,wBAAwB,GAAG,IAAI,GAAG,CAAS;IACtD,gCAAgC;IAChC,iCAAiC;IACjC,0BAA0B;IAC1B,yBAAyB;IACzB,0BAA0B;IAC1B,2BAA2B;IAC3B,4BAA4B;IAC5B,0BAA0B;IAC1B,2BAA2B;CAC5B,CAAC,CAAC;AAEH,SAAgB,qBAAqB,CAAC,UAAkB;IACtD,OAAO,gCAAwB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAClD,CAAC","sourcesContent":["export const PROPERTY_PERMISSION_KEYS = new Set<string>([\n \"adminusers.property.branch.GET\",\n \"adminusers.property.branch.POST\",\n \"user.property.branch.GET\",\n \"user.property.agent.GET\",\n \"user.property.agent.POST\",\n \"owner.property.branch.GET\",\n \"owner.property.branch.POST\",\n \"owner.property.agent.GET\",\n \"owner.property.agent.POST\",\n]);\n\nexport function hasPropertyPermission(permission: string): boolean {\n return PROPERTY_PERMISSION_KEYS.has(permission);\n}\n"]}
@@ -6,18 +6,23 @@ export declare abstract class ControllerApi<R extends BaseEntity, T extends Crud
6
6
  protected readonly service: T;
7
7
  protected config: EntityConfig;
8
8
  protected adminGroupNames: string[];
9
+ private roleController;
9
10
  protected constructor(baseService: T, config: EntityConfig);
11
+ /** Return constructor-defined resource name */
12
+ protected getResource(): string;
10
13
  resolveCrudRequest(event: APIGatewayProxyEvent): Promise<APIResponse>;
11
14
  setConfig(config: EntityConfig): void;
12
15
  processCrudRequestPre(req: HttpRequest): Promise<HttpRequest>;
13
16
  processCrudRequestPost(request: HttpRequest, response: R | List<R>): Promise<R | List<R>>;
14
17
  protected handleList(methode: HttpMethod, path: string, request: HttpRequest): Promise<any>;
18
+ protected handlePermission(req: HttpRequest, path: string): Promise<any>;
15
19
  protected handleUpdate(entityId: string, requestBody: any, requestedUser?: CognitoUser): Promise<R>;
16
20
  protected handleDelete(entityId: string, requestedUser?: CognitoUser): Promise<boolean>;
17
21
  protected handleFetch(entityId: string, requestedUser?: CognitoUser): Promise<R>;
18
22
  protected handleReplace(entityId: string, entity: any, requestedUser?: CognitoUser): Promise<R>;
19
23
  protected handlePostCreate(entity: R, cognitoUser: CognitoUser): Promise<R>;
20
24
  protected abstract processCrudRequest(event: HttpRequest): Promise<any>;
25
+ protected isPermissionRequest(path: string): boolean;
21
26
  protected isListRequest(methode: HttpMethod, path: string): boolean;
22
27
  protected isUpdateRequest(methode: HttpMethod, path: string): boolean;
23
28
  protected isDeleteRequest(methode: HttpMethod, path: string): boolean;
@@ -4,10 +4,12 @@ exports.ControllerApi = void 0;
4
4
  const index_1 = require("../index");
5
5
  const exception_1 = require("../exception");
6
6
  const string_util_1 = require("../utils/string.util");
7
+ const controller_role_1 = require("./controller-role");
7
8
  class ControllerApi {
8
9
  service;
9
10
  config;
10
11
  adminGroupNames;
12
+ roleController;
11
13
  constructor(baseService, config) {
12
14
  this.service = baseService;
13
15
  if (!config)
@@ -16,13 +18,26 @@ class ControllerApi {
16
18
  if (config.ADMIN_GROUP_NAME) {
17
19
  this.adminGroupNames = config.ADMIN_GROUP_NAME;
18
20
  }
21
+ if (config.PERMISSION_MAP) {
22
+ this.roleController = new controller_role_1.ControllerRole(config.PERMISSION_MAP.roleTable);
23
+ }
19
24
  this.service.setConfig(config);
20
25
  }
26
+ /** Return constructor-defined resource name */
27
+ getResource() {
28
+ return this.config.BASE_PATH.replace("/", "");
29
+ }
21
30
  async resolveCrudRequest(event) {
22
31
  try {
23
32
  let req = (0, index_1.parseHttpRequest)(event, this.adminGroupNames);
24
33
  const policy = (0, index_1.findMatchedPolicy)(req.methode, event?.requestContext?.resourcePath, this.config.ENDPOINT_POLICY);
25
- this.checkPermission(policy?.access, req.requestType);
34
+ if (this.config.PERMISSION_MAP && this.roleController) {
35
+ const resource = this.getResource();
36
+ await this.roleController.checkRbacAccess(req, resource, this.config.PERMISSION_MAP.scopeMap, this.adminGroupNames);
37
+ }
38
+ else {
39
+ this.checkPermission(policy?.access, req.requestType);
40
+ }
26
41
  this.validateRequest(policy?.validator, req.body);
27
42
  if (req.identity) {
28
43
  log.debug("groups: " + JSON.stringify(req.identity.groups, null, 2));
@@ -78,6 +93,9 @@ class ControllerApi {
78
93
  return await this.service.scan(request?.filter || {});
79
94
  }
80
95
  }
96
+ async handlePermission(req, path) {
97
+ return this.roleController.handlePermissionRequest(req, path, this.config.PERMISSION_MAP, this.adminGroupNames);
98
+ }
81
99
  async handleUpdate(entityId, requestBody, requestedUser) {
82
100
  if (!entityId)
83
101
  throw new exception_1.ErrorHttp({ code: 400, error: "BadRequest" }, "[CORE] Cannot PATCH resource without id field");
@@ -149,6 +167,14 @@ class ControllerApi {
149
167
  // Save entity
150
168
  return this.service.save(entity, profileId, parentId, cognitoUser);
151
169
  }
170
+ isPermissionRequest(path) {
171
+ if (this.config.PERMISSION_MAP && this.roleController) {
172
+ const rolePath = (0, string_util_1.trimSpecialChar)(this.config.PERMISSION_MAP.rolePath);
173
+ const normalizedPath = (0, string_util_1.trimSpecialChar)(path);
174
+ return normalizedPath.includes(rolePath);
175
+ }
176
+ return false;
177
+ }
152
178
  isListRequest(methode, path) {
153
179
  const basePath = (0, string_util_1.trimSpecialChar)(this.config.BASE_PATH);
154
180
  const allowedPaths = [
@@ -195,6 +221,8 @@ class ControllerApi {
195
221
  async handleCrudByMethod(req) {
196
222
  const path = req.event?.requestContext?.resourcePath;
197
223
  const entity = this.parseEntity(req.body);
224
+ if (this.isPermissionRequest(path))
225
+ return this.handlePermission(req, path);
198
226
  if (this.isUpdateRequest(req.methode, path))
199
227
  return this.handleUpdate(req.entityId, entity, req.identity);
200
228
  if (this.isDeleteRequest(req.methode, path))