From 8ec7029449096f29a124d2eecd4187f46df797ea Mon Sep 17 00:00:00 2001 From: Manuel Herrera Date: Tue, 4 Oct 2022 20:53:32 -0300 Subject: [PATCH] base --- docker-compose.yml | 17 +- jest.config.ts | 17 ++ jest.e2e.ts | 10 + jest.env.ts | 4 + package-lock.json | 267 ++++++++++++++++++++++--- package.json | 8 +- src/app.module.ts | 46 ++++- src/coms/app.controller.ts | 28 --- src/coms/app.gateway.ts | 19 -- src/coms/events.gateway.ts | 40 ++++ src/coms/events.ts | 6 +- src/coms/index.ts | 2 + src/coms/rest.controller.ts | 36 ++++ src/config/broker.config.ts | 8 + src/data/index.ts | 3 + src/data/principal.repo.ts | 27 +++ src/data/role.repo.ts | 27 +++ src/data/scope.repo.ts | 27 +++ src/domain/blocs/app.service.ts | 8 - src/domain/blocs/authz.service.ts | 23 +++ src/domain/blocs/index.ts | 2 + src/domain/blocs/management.service.ts | 21 ++ src/domain/entities/index.ts | 3 + src/domain/entities/scope.entity.ts | 6 +- src/domain/model/index.ts | 3 + src/domain/model/principal.model.ts | 38 +++- src/domain/model/role.model.ts | 21 +- src/domain/model/scope.model.ts | 23 ++- src/main.ts | 16 +- test/app.e2e-spec.ts | 119 ++++++++++- test/jest-e2e.json | 9 - tsconfig.json | 6 +- 32 files changed, 739 insertions(+), 151 deletions(-) create mode 100644 jest.config.ts create mode 100644 jest.e2e.ts create mode 100644 jest.env.ts delete mode 100644 src/coms/app.controller.ts delete mode 100644 src/coms/app.gateway.ts create mode 100644 src/coms/events.gateway.ts create mode 100644 src/coms/index.ts create mode 100644 src/coms/rest.controller.ts create mode 100644 src/config/broker.config.ts create mode 100644 src/data/index.ts create mode 100644 src/data/principal.repo.ts create mode 100644 src/data/role.repo.ts create mode 100644 src/data/scope.repo.ts delete mode 100644 src/domain/blocs/app.service.ts create mode 100644 src/domain/blocs/authz.service.ts create mode 100644 src/domain/blocs/index.ts create mode 100644 src/domain/blocs/management.service.ts create mode 100644 src/domain/entities/index.ts create mode 100644 src/domain/model/index.ts delete mode 100644 test/jest-e2e.json diff --git a/docker-compose.yml b/docker-compose.yml index 02e5d96..931c52b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,15 @@ services: broker: - image: nats - command: --debug - expose: - - '4222' + image: bitnami/rabbitmq:latest ports: - - '8222:8222' + - '4369:4369' + - '5551:5551' + - '5552:5552' + - '5672:5672' + - '25672:25672' + - '15672:15672' + volumes: + - 'rabbitmq_data:/bitnami/rabbitmq/mnesia' +volumes: + rabbitmq_data: + driver: local diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..b4a75e2 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,17 @@ +module.exports = { + displayName: 'authorization', + moduleFileExtensions: ['ts', 'js', 'html'], + rootDir: '.', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': 'ts-jest', + }, + testMatch: ['/src/**/*spec.ts'], + // preset: '../../jest.preset.ts', + // globals: { + // 'ts-jest': { + // tsconfig: '/tsconfig.spec.json', + // }, + // }, + // coverageDirectory: '../../coverage/apps/authorization', +}; diff --git a/jest.e2e.ts b/jest.e2e.ts new file mode 100644 index 0000000..e4379a8 --- /dev/null +++ b/jest.e2e.ts @@ -0,0 +1,10 @@ +const baseConfig = require('./jest.config'); + +module.exports = { + ...baseConfig, + testMatch: ['/test/**/*.e2e-spec.ts'], + setupFiles: ['/jest.env.ts'], + moduleNameMapper: { + '@authz/(.*)': '/src/$1', + }, +}; diff --git a/jest.env.ts b/jest.env.ts new file mode 100644 index 0000000..07c8899 --- /dev/null +++ b/jest.env.ts @@ -0,0 +1,4 @@ +process.env.PORT = '9990'; +process.env.BROKER_PORT = '5672'; +process.env.BROKER_URI = 'amqp://user:bitnami@localhost:5672'; +process.env.QUEUE = 'authz'; diff --git a/package-lock.json b/package-lock.json index ca6711d..ddceb09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,21 @@ { - "name": "nestjs-authz-example", + "name": "authorization", "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "nestjs-authz-example", + "name": "authorization", "version": "0.0.1", "license": "UNLICENSED", "dependencies": { "@nestjs/common": "^9.0.0", + "@nestjs/config": "^2.2.0", "@nestjs/core": "^9.0.0", "@nestjs/microservices": "^9.0.0", "@nestjs/platform-express": "^9.0.0", - "nats": "^2.7.1", + "amqp-connection-manager": "^4.1.7", + "amqplib": "^0.10.3", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", @@ -44,6 +46,24 @@ "typescript": "^4.3.5" } }, + "node_modules/@acuminous/bitsyntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", + "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "debug": "^4.3.4", + "safe-buffer": "~5.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@acuminous/bitsyntax/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -1553,6 +1573,30 @@ } } }, + "node_modules/@nestjs/config": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.2.0.tgz", + "integrity": "sha512-78Eg6oMbCy3D/YvqeiGBTOWei1Jwi3f2pSIZcZ1QxY67kYsJzTRTkwRT8Iv30DbK0sGKc1mcloDLD5UXgZAZtg==", + "dependencies": { + "dotenv": "16.0.1", + "dotenv-expand": "8.0.3", + "lodash": "4.17.21", + "uuid": "8.3.2" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^6.0.0 || ^7.2.0" + } + }, + "node_modules/@nestjs/config/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@nestjs/core": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.1.1.tgz", @@ -2620,6 +2664,56 @@ } } }, + "node_modules/amqp-connection-manager": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/amqp-connection-manager/-/amqp-connection-manager-4.1.7.tgz", + "integrity": "sha512-CbNev3Nr8rIzngwrpfWlgUqgJzSwA2cUNe6vMfNTzeKfShpkr4ZsUDadKY4nzbBrZC0s+lravXmlNg6eEzP7EQ==", + "dependencies": { + "promise-breaker": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0", + "npm": ">5.0.0" + }, + "peerDependencies": { + "amqplib": "*" + } + }, + "node_modules/amqplib": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz", + "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==", + "dependencies": { + "@acuminous/bitsyntax": "^0.1.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/amqplib/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/amqplib/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/amqplib/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -3043,6 +3137,11 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3483,7 +3582,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -3640,6 +3738,22 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/dotenv-expand": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz", + "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==", + "engines": { + "node": ">=12" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6285,8 +6399,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -6534,8 +6647,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multer": { "version": "1.4.4-lts.1", @@ -6564,6 +6676,8 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/nats/-/nats-2.7.1.tgz", "integrity": "sha512-aH0OXxasfLCTG+LQCFRaWoL1kqejCQg7B+t4z++JgLPgfdpQMET1Rqo95I06DEQyIJGTTgYpxkI/zC0ul8V3pw==", + "optional": true, + "peer": true, "dependencies": { "nkeys.js": "^1.0.0-9" }, @@ -6595,6 +6709,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/nkeys.js/-/nkeys.js-1.0.3.tgz", "integrity": "sha512-p5Bpb/acPaQmCrbe4gNmMBY/naZJV8Q7m2B9UkXT8BQRC6wjX8zqD2ya8eZu9mpSXQffodV46HCP9OckmxcwYA==", + "optional": true, + "peer": true, "dependencies": { "tweetnacl": "1.0.3" }, @@ -7123,6 +7239,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/promise-breaker": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/promise-breaker/-/promise-breaker-6.0.0.tgz", + "integrity": "sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA==" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -7190,8 +7311,7 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -7331,8 +7451,7 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { "version": "1.22.1", @@ -8419,7 +8538,9 @@ "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "optional": true, + "peer": true }, "node_modules/type-check": { "version": "0.4.0", @@ -8549,7 +8670,6 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -8995,6 +9115,23 @@ } }, "dependencies": { + "@acuminous/bitsyntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", + "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "requires": { + "buffer-more-ints": "~1.0.0", + "debug": "^4.3.4", + "safe-buffer": "~5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -10142,6 +10279,24 @@ "uuid": "9.0.0" } }, + "@nestjs/config": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.2.0.tgz", + "integrity": "sha512-78Eg6oMbCy3D/YvqeiGBTOWei1Jwi3f2pSIZcZ1QxY67kYsJzTRTkwRT8Iv30DbK0sGKc1mcloDLD5UXgZAZtg==", + "requires": { + "dotenv": "16.0.1", + "dotenv-expand": "8.0.3", + "lodash": "4.17.21", + "uuid": "8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, "@nestjs/core": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.1.1.tgz", @@ -10942,6 +11097,48 @@ "ajv": "^8.0.0" } }, + "amqp-connection-manager": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/amqp-connection-manager/-/amqp-connection-manager-4.1.7.tgz", + "integrity": "sha512-CbNev3Nr8rIzngwrpfWlgUqgJzSwA2cUNe6vMfNTzeKfShpkr4ZsUDadKY4nzbBrZC0s+lravXmlNg6eEzP7EQ==", + "requires": { + "promise-breaker": "^6.0.0" + } + }, + "amqplib": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz", + "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==", + "requires": { + "@acuminous/bitsyntax": "^0.1.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "url-parse": "~1.5.10" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + } + } + }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -11256,6 +11453,11 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, "busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -11595,7 +11797,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -11712,6 +11913,16 @@ } } }, + "dotenv": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" + }, + "dotenv-expand": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz", + "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -13712,8 +13923,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.memoize": { "version": "4.1.2", @@ -13899,8 +14109,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multer": { "version": "1.4.4-lts.1", @@ -13926,6 +14135,8 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/nats/-/nats-2.7.1.tgz", "integrity": "sha512-aH0OXxasfLCTG+LQCFRaWoL1kqejCQg7B+t4z++JgLPgfdpQMET1Rqo95I06DEQyIJGTTgYpxkI/zC0ul8V3pw==", + "optional": true, + "peer": true, "requires": { "nkeys.js": "^1.0.0-9" } @@ -13951,6 +14162,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/nkeys.js/-/nkeys.js-1.0.3.tgz", "integrity": "sha512-p5Bpb/acPaQmCrbe4gNmMBY/naZJV8Q7m2B9UkXT8BQRC6wjX8zqD2ya8eZu9mpSXQffodV46HCP9OckmxcwYA==", + "optional": true, + "peer": true, "requires": { "tweetnacl": "1.0.3" } @@ -14332,6 +14545,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "promise-breaker": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/promise-breaker/-/promise-breaker-6.0.0.tgz", + "integrity": "sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA==" + }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -14384,8 +14602,7 @@ "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "queue-microtask": { "version": "1.2.3", @@ -14489,8 +14706,7 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "resolve": { "version": "1.22.1", @@ -15281,7 +15497,9 @@ "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "optional": true, + "peer": true }, "type-check": { "version": "0.4.0", @@ -15367,7 +15585,6 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" diff --git a/package.json b/package.json index 81a73b9..7a40144 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "nestjs-authz-example", + "name": "authorization", "version": "0.0.1", "description": "", "author": "", @@ -18,14 +18,16 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./jest.e2e.ts --runInBand" }, "dependencies": { "@nestjs/common": "^9.0.0", + "@nestjs/config": "^2.2.0", "@nestjs/core": "^9.0.0", "@nestjs/microservices": "^9.0.0", "@nestjs/platform-express": "^9.0.0", - "nats": "^2.7.1", + "amqp-connection-manager": "^4.1.7", + "amqplib": "^0.10.3", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", diff --git a/src/app.module.ts b/src/app.module.ts index 5625a9a..ae9fb8c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,11 +1,45 @@ import { Module } from '@nestjs/common'; -import { AppController } from './coms/app.controller'; -import { GatewayController } from './coms/app.gateway'; -import { AppService } from './domain/blocs/app.service'; +import { ClientProxyFactory, Transport } from '@nestjs/microservices'; +import { ConfigModule, ConfigType } from '@nestjs/config'; +import { EventsGatewayController, RestController } from './coms/'; +import { AuthzService, ManagementService } from './domain/blocs/'; +import { PrincipalModel, RoleModel, ScopeModel } from './domain/model'; +import BrokerConfig from './config/broker.config'; +import { PrincipalRepo, RoleRepo, ScopeRepo } from './data'; @Module({ - imports: [], - controllers: [AppController, GatewayController], - providers: [AppService], + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [BrokerConfig], + }), + ], + controllers: [RestController, EventsGatewayController], + providers: [ + { + provide: 'BROKER_CLIENT', + inject: [BrokerConfig.KEY], + useFactory: (brokerConfig: ConfigType) => { + return ClientProxyFactory.create({ + transport: Transport.RMQ, + options: { + urls: [brokerConfig.brokerUri], + queue: brokerConfig.queue, + queueOptions: { + durable: false, + }, + }, + }); + }, + }, + AuthzService, + ManagementService, + PrincipalModel, + RoleModel, + ScopeModel, + PrincipalRepo, + RoleRepo, + ScopeRepo, + ], }) export class AppModule {} diff --git a/src/coms/app.controller.ts b/src/coms/app.controller.ts deleted file mode 100644 index 040febc..0000000 --- a/src/coms/app.controller.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Controller, Post } from '@nestjs/common'; -import { AppService } from '../domain/blocs/app.service'; - -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} - - @Post() - createPrincipal(): string { - return this.appService.getHello(); - } - @Post() - updatePrincipal(): string { - return ''; - } - @Post() - createRole(): string { - return this.appService.getHello(); - } - @Post() - registerScopes(): string { - return this.appService.getHello(); - } - @Post() - authorize(): string { - return this.appService.getHello(); - } -} diff --git a/src/coms/app.gateway.ts b/src/coms/app.gateway.ts deleted file mode 100644 index 574ce4c..0000000 --- a/src/coms/app.gateway.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Controller } from '@nestjs/common'; -import { EventPattern } from '@nestjs/microservices'; -import { AppService } from '../domain/blocs/app.service'; -import { PRINCIPAL_CREATED, REGISTER_SCOPES } from './events'; - -@Controller() -export class GatewayController { - constructor(private readonly appService: AppService) {} - - @EventPattern(PRINCIPAL_CREATED) - async createAuthUser(msg): Promise { - return this.appService.getHello(); - } - - @EventPattern(REGISTER_SCOPES) - async registerScopes(msg): Promise { - return this.appService.getHello(); - } -} diff --git a/src/coms/events.gateway.ts b/src/coms/events.gateway.ts new file mode 100644 index 0000000..75f82fe --- /dev/null +++ b/src/coms/events.gateway.ts @@ -0,0 +1,40 @@ +import { Controller } from '@nestjs/common'; +import { EventPattern } from '@nestjs/microservices'; +import { AuthzService, ManagementService } from '../domain/blocs/'; +import { + PRINCIPAL_CREATED, + SCOPES_CREATED, + ROLE_CREATED, + REQUESTED_AUTHORIZATION, +} from './events'; + +@Controller() +export class EventsGatewayController { + constructor( + private readonly authzService: AuthzService, + private readonly mgmtService: ManagementService, + ) {} + + @EventPattern(PRINCIPAL_CREATED) + async createPrincipal(msg: Record): Promise { + return this.mgmtService.createPrincipal(msg.rolesIds); + } + @EventPattern(ROLE_CREATED) + async createRole(msg: Record): Promise { + return this.mgmtService.createRole(msg.name, msg.scopesIds); + } + @EventPattern(SCOPES_CREATED) + async registerScopes(msg: { scopes: any[] }): Promise { + console.log('even', msg); + return this.mgmtService.createScopes(msg.scopes); + } + + @EventPattern(REQUESTED_AUTHORIZATION) + async authorize(msg): Promise { + const { id, urns } = msg; + const authorizationMap = await this.authzService.diffScopes(id, urns); + return { + ...authorizationMap, + }; + } +} diff --git a/src/coms/events.ts b/src/coms/events.ts index 44b1701..4eef979 100644 --- a/src/coms/events.ts +++ b/src/coms/events.ts @@ -1,2 +1,4 @@ -export const REGISTER_SCOPES = 'authz.scopes.register'; -export const PRINCIPAL_CREATED = 'authz.principal.create'; +export const SCOPES_CREATED = 'authz.scopes.created'; +export const PRINCIPAL_CREATED = 'authz.principal.created'; +export const ROLE_CREATED = 'authz.role.created'; +export const REQUESTED_AUTHORIZATION = 'authz.requested'; diff --git a/src/coms/index.ts b/src/coms/index.ts new file mode 100644 index 0000000..1243f9f --- /dev/null +++ b/src/coms/index.ts @@ -0,0 +1,2 @@ +export * from './events.gateway'; +export * from './rest.controller'; diff --git a/src/coms/rest.controller.ts b/src/coms/rest.controller.ts new file mode 100644 index 0000000..3b89e9f --- /dev/null +++ b/src/coms/rest.controller.ts @@ -0,0 +1,36 @@ +import { Controller, Post, HttpCode, Body } from '@nestjs/common'; +import { ManagementService, AuthzService } from '../domain/blocs'; +@Controller() +export class RestController { + constructor( + private readonly mgmtService: ManagementService, + private readonly authzService: AuthzService, + ) {} + + @Post('principal/create') + async createPrincipal(@Body() body: { roles: string[] }): Promise { + return this.mgmtService.createPrincipal(body.roles); + } + @Post('role/create') + async createRole( + @Body() body: { name: string; scopesIds: string[] }, + ): Promise { + return this.mgmtService.createRole(body.name, body.scopesIds); + } + @Post('scopes/register') + async registerScopes( + @Body() body: { scopes: { urn: string; namespace: string }[] }, + ): Promise { + return this.mgmtService.createScopes(body.scopes); + } + @HttpCode(200) + @Post('authorize') + async authorize(@Body() body: any): Promise { + const { id, urns } = body; + const authorizationMap = await this.authzService.diffScopes(id, urns); + + return { + ...authorizationMap, + }; + } +} diff --git a/src/config/broker.config.ts b/src/config/broker.config.ts new file mode 100644 index 0000000..fdf50bc --- /dev/null +++ b/src/config/broker.config.ts @@ -0,0 +1,8 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('broker', () => { + return { + brokerUri: process.env.BROKER_URI, + queue: process.env.QUEUE, + }; +}); diff --git a/src/data/index.ts b/src/data/index.ts new file mode 100644 index 0000000..e4aac49 --- /dev/null +++ b/src/data/index.ts @@ -0,0 +1,3 @@ +export * from './principal.repo'; +export * from './role.repo'; +export * from './scope.repo'; diff --git a/src/data/principal.repo.ts b/src/data/principal.repo.ts new file mode 100644 index 0000000..3b8fd71 --- /dev/null +++ b/src/data/principal.repo.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { Principal } from '../domain/entities/'; + +@Injectable() +export class PrincipalRepo { + principalsDb: Record; + constructor() { + this.principalsDb = {}; + } + + async write(data: Principal) { + this.principalsDb[data.id] = data; + return true; + } + async writeMany(data: Principal[]) { + for (const principal of data) { + this.principalsDb[principal.id] = principal; + } + return true; + } + async read(id: string) { + return this.principalsDb[id]; + } + async readAll() { + return Object.values(this.principalsDb); + } +} diff --git a/src/data/role.repo.ts b/src/data/role.repo.ts new file mode 100644 index 0000000..b7eef77 --- /dev/null +++ b/src/data/role.repo.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { Role } from '../domain/entities/'; + +@Injectable() +export class RoleRepo { + rolesDb: Record; + constructor() { + this.rolesDb = {}; + } + + async write(data: Role) { + this.rolesDb[data.id] = data; + return true; + } + async writeMany(data: Role[]) { + for (const role of data) { + this.rolesDb[role.id] = role; + } + return true; + } + async read(id: string) { + return this.rolesDb[id]; + } + async readAll() { + return Object.values(this.rolesDb); + } +} diff --git a/src/data/scope.repo.ts b/src/data/scope.repo.ts new file mode 100644 index 0000000..e14cea6 --- /dev/null +++ b/src/data/scope.repo.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { Scope } from '../domain/entities/'; + +@Injectable() +export class ScopeRepo { + scopesDb: Record; + constructor() { + this.scopesDb = {}; + } + + async write(data: Scope) { + this.scopesDb[data.id] = data; + return true; + } + async writeMany(data: Scope[]) { + for (const scope of data) { + this.scopesDb[scope.id] = scope; + } + return true; + } + async read(id: string) { + return this.scopesDb[id]; + } + async readAll() { + return Object.values(this.scopesDb); + } +} diff --git a/src/domain/blocs/app.service.ts b/src/domain/blocs/app.service.ts deleted file mode 100644 index 927d7cc..0000000 --- a/src/domain/blocs/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getHello(): string { - return 'Hello World!'; - } -} diff --git a/src/domain/blocs/authz.service.ts b/src/domain/blocs/authz.service.ts new file mode 100644 index 0000000..28270c6 --- /dev/null +++ b/src/domain/blocs/authz.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { PrincipalModel } from '../model/principal.model'; + +@Injectable() +export class AuthzService { + constructor(private readonly principalModel: PrincipalModel) {} + async diffScopes(principalId: string, scopesUrns: string[]) { + const principalScopes = await this.principalModel.getScopes(principalId); + const urnsDiff: Record = {}; + for (const urn of scopesUrns) { + if ( + principalScopes.findIndex( + (principalScope) => principalScope.urn === urn, + ) === -1 + ) { + urnsDiff[urn] = false; + } else { + urnsDiff[urn] = true; + } + } + return urnsDiff; + } +} diff --git a/src/domain/blocs/index.ts b/src/domain/blocs/index.ts new file mode 100644 index 0000000..3623304 --- /dev/null +++ b/src/domain/blocs/index.ts @@ -0,0 +1,2 @@ +export * from './authz.service'; +export * from './management.service'; diff --git a/src/domain/blocs/management.service.ts b/src/domain/blocs/management.service.ts new file mode 100644 index 0000000..8ad6cbb --- /dev/null +++ b/src/domain/blocs/management.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; +import { RoleModel, ScopeModel } from '../model'; +import { PrincipalModel } from '../model/principal.model'; + +@Injectable() +export class ManagementService { + constructor( + private readonly principalModel: PrincipalModel, + private readonly scopeModel: ScopeModel, + private readonly roleModel: RoleModel, + ) {} + async createPrincipal(rolesIds: string[]) { + return this.principalModel.create(rolesIds); + } + async createRole(name: string, scopesIds: string[]) { + return this.roleModel.create(name, scopesIds); + } + async createScopes(scopes: { urn: string; namespace: string }[]) { + return this.scopeModel.create(scopes); + } +} diff --git a/src/domain/entities/index.ts b/src/domain/entities/index.ts new file mode 100644 index 0000000..4fb874d --- /dev/null +++ b/src/domain/entities/index.ts @@ -0,0 +1,3 @@ +export * from './principal.entity'; +export * from './role.entity'; +export * from './scope.entity'; diff --git a/src/domain/entities/scope.entity.ts b/src/domain/entities/scope.entity.ts index f7a8757..8836062 100644 --- a/src/domain/entities/scope.entity.ts +++ b/src/domain/entities/scope.entity.ts @@ -3,10 +3,10 @@ import { v4 as uuid } from 'uuid'; export class Scope { id!: string; urn!: string; - context!: string; - constructor(urn: string, context: string) { + namespace!: string; + constructor(urn: string, namespace: string) { this.id = uuid(); this.urn = urn; - this.context = context; + this.namespace = namespace; } } diff --git a/src/domain/model/index.ts b/src/domain/model/index.ts new file mode 100644 index 0000000..815da23 --- /dev/null +++ b/src/domain/model/index.ts @@ -0,0 +1,3 @@ +export * from './principal.model'; +export * from './role.model'; +export * from './scope.model'; diff --git a/src/domain/model/principal.model.ts b/src/domain/model/principal.model.ts index 818ed0c..6fd272d 100644 --- a/src/domain/model/principal.model.ts +++ b/src/domain/model/principal.model.ts @@ -1,21 +1,41 @@ import { Injectable } from '@nestjs/common'; import { Principal } from '../entities/principal.entity'; +import { PrincipalRepo, RoleRepo, ScopeRepo } from '../../data/'; @Injectable() export class PrincipalModel { - principalsDb: Record; - constructor() {} + constructor( + private readonly principalRepo: PrincipalRepo, + private readonly roleRepo: RoleRepo, + private readonly scopeRepo: ScopeRepo, + ) {} - create(roles: string[]) { + async create(roles: string[]) { const newPrincipal = new Principal(roles); - this.principalsDb[newPrincipal.id] = newPrincipal; + this.principalRepo.write(newPrincipal); return newPrincipal; } - findById(id: string) { - return this.principalsDb[id]; + async findById(id: string) { + return this.principalRepo.read(id); } - assignRoles(id: string, roles: string[]) { - this.principalsDb[id].roles = roles; - return this.principalsDb[id]; + async getScopes(principalId: string) { + const rolesIds = (await this.principalRepo.read(principalId)).roles; + let scopesIds = []; + for (const role of rolesIds) { + scopesIds = scopesIds.concat((await this.roleRepo.read(role)).scopes); + } + // eliminate dups + const scopes = []; + for (const id of [...new Set(scopesIds)]) { + scopes.push(await this.scopeRepo.read(id)); + } + return scopes; + } + + async assignRoles(id: string, roles: string[]) { + const principal = await this.principalRepo.read(id); + principal.roles = roles; + await this.principalRepo.write(principal); + return principal; } } diff --git a/src/domain/model/role.model.ts b/src/domain/model/role.model.ts index 414921d..762bb24 100644 --- a/src/domain/model/role.model.ts +++ b/src/domain/model/role.model.ts @@ -1,18 +1,23 @@ import { Injectable } from '@nestjs/common'; +import { RoleRepo } from '../../data'; import { Role } from '../entities/role.entity'; -import { ScopeModel } from './scope.model'; - @Injectable() export class RoleModel { - rolesDb: Record; - constructor(private readonly scopeModel: ScopeModel) {} + constructor(private readonly roleRepo: RoleRepo) {} - create(name: string, scopes: string[]) { + async create(name: string, scopes: string[]) { const newRole = new Role(name, scopes); - this.rolesDb[newRole.id] = newRole; + await this.roleRepo.write(newRole); return newRole; } - findById(ids: string[]) { - return ids.map((id) => this.rolesDb[id]); + async update(role: Role) { + await this.roleRepo.write(role); + return role; + } + async findById(ids: string[]) { + const roles = await Promise.all( + ids.map(async (id) => await this.roleRepo.read(id)), + ); + return roles; } } diff --git a/src/domain/model/scope.model.ts b/src/domain/model/scope.model.ts index a169140..90ac254 100644 --- a/src/domain/model/scope.model.ts +++ b/src/domain/model/scope.model.ts @@ -1,19 +1,22 @@ import { Injectable } from '@nestjs/common'; import { Scope } from '../entities/scope.entity'; +import { ScopeRepo } from '../../data'; @Injectable() export class ScopeModel { - scopesDb: Record; - constructor() {} + constructor(private readonly scopeRepo: ScopeRepo) {} - create(scopes: { urn: string; context: string }[]) { - return scopes.map((scope) => { - const newScope = new Scope(scope.urn, scope.context); - this.scopesDb[newScope.id] = newScope; - return newScope; - }); + async create(scopes: { urn: string; namespace: string }[]) { + const scopesToWrite = scopes.map( + (scope) => new Scope(scope.urn, scope.namespace), + ); + await this.scopeRepo.writeMany(scopesToWrite); + return scopesToWrite; } - findById(ids: string[]) { - return ids.map((id) => this.scopesDb[id]); + async findById(ids: string[]) { + const scopes = await Promise.all( + ids.map(async (id) => await this.scopeRepo.read(id)), + ); + return scopes; } } diff --git a/src/main.ts b/src/main.ts index c1773d9..475129c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,22 @@ import { NestFactory } from '@nestjs/core'; -import { Transport, MicroserviceOptions } from '@nestjs/microservices'; +import { Transport, RmqOptions } from '@nestjs/microservices'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); - app.connectMicroservice({ - transport: Transport.NATS, + app.connectMicroservice({ + transport: Transport.RMQ, options: { - servers: [process.env.NATS_SERVER], + urls: [ + `amqp://${process.env.BROKER_USER}:${process.env.BROKER_PASSWORD}@localhost:5672`, + ], queue: 'authz', + queueOptions: { + durable: false, + }, }, }); - await app.listen(9990); + await app.startAllMicroservices(); + await app.listen(process.env.PORT); } bootstrap(); diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index 50cda62..d162335 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -1,24 +1,123 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; +import { Transport, ClientProxy, ClientsModule } from '@nestjs/microservices'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; +jest.setTimeout(50000); describe('AppController (e2e)', () => { let app: INestApplication; + let producer: INestApplication; + let client: ClientProxy; + let scope: Record; + let anotherScope: Record; + let role: Record; + let principal: Record; - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ + beforeAll(async () => { + const appFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); + app = appFixture.createNestApplication(); + app.connectMicroservice({ + transport: Transport.RMQ, + options: { + urls: [process.env.BROKER_URI], + queue: process.env.QUEUE, + queueOptions: { + durable: false, + }, + }, + }); + await app.listen(process.env.PORT || 3000); + await app.startAllMicroservices(); + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + ClientsModule.register([ + { + name: 'RMQ_PRODUCER', + transport: Transport.RMQ, + options: { + urls: [process.env.BROKER_URI], + queue: process.env.QUEUE, + queueOptions: { + durable: false, + }, + }, + }, + ]), + ], + }).compile(); + producer = moduleFixture.createNestApplication(); + await producer.listen(3009); + client = await producer.get('RMQ_PRODUCER'); + }); + afterAll(async () => { + await Promise.all([app.close(), client.close(), producer.close()]); }); - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); + // it('should consume scopes created event ', async () => { + // const obs = client + // .emit('authz.scopes.created', { + // scopes: [ + // { urn: 'unique.resource.name.one', namespace: 'service.name.one' }, + // ], + // }) + // .forEach((next) => { + // console.log('next is,', next); + // }); + // const sleep = new Promise((resolve) => { + // setTimeout(resolve, 20000); + // }); + + // await sleep; + // return true; + // }); + + it('should create scope POST /scope/create ', async () => { + const scopeResponse = await request(app.getHttpServer()) + .post('/scopes/register') + .send({ + scopes: [ + { urn: 'unique.resource.name.one', namespace: 'service.name.one' }, + { urn: 'unique.resource.name.two', namespace: 'service.name.two' }, + ], + }); + + scope = scopeResponse.body[0]; + anotherScope = scopeResponse.body[1]; + expect(anotherScope).toHaveProperty('id'); + return expect(scope).toHaveProperty('id'); + }); + it('should create role POST /role/create ', async () => { + const roleResponse = await request(app.getHttpServer()) + .post('/role/create') + .send({ + name: 'role_one', + scopesIds: [scope.id], + }); + role = roleResponse.body; + return expect(role).toHaveProperty('scopes', [scope.id]); + }); + it('should create role with scope POST /principal/create ', async () => { + const principalResponse = await request(app.getHttpServer()) + .post('/principal/create') + .send({ roles: [role.id] }); + principal = principalResponse.body; + return expect(principal).toHaveProperty('roles', [role.id]); + }); + it('should return authorization map scope: true POST /authorize ', async () => { + const authzResponse = await request(app.getHttpServer()) + .post('/authorize') + .send({ id: principal.id, urns: [scope.urn] }); + const authzMap = authzResponse.body; + return expect(authzMap[scope.urn]).toBeTruthy(); + }); + it('should return authorization map anotherScope: false POST /authorize ', async () => { + const authzResponse = await request(app.getHttpServer()) + .post('/authorize') + .send({ id: principal.id, urns: [anotherScope.urn] }); + const authzMap = authzResponse.body; + return expect(authzMap[anotherScope.urn]).toBeFalsy(); }); }); diff --git a/test/jest-e2e.json b/test/jest-e2e.json deleted file mode 100644 index e9d912f..0000000 --- a/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -} diff --git a/tsconfig.json b/tsconfig.json index adb614c..784cf57 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,12 +10,16 @@ "sourceMap": true, "outDir": "./dist", "baseUrl": "./", + "rootDir": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "paths": { + "@authz/*": ["src/*"] + } } }