diff --git a/README.md b/README.md index c14d22687..549e01bf9 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [@app/armory](./apps/armory/README.md) | @app/armory CI status | | [@app/policy-engine](./apps/policy-engine/README.md) | @app/policy-engine CI status | +| [@app/vault](./apps/vault/README.md) | @app/vault CI status | | [@narval/encryption](./packages/encryption/README.md) | Packages CI status | | [@narval/policy-engine-shared](./packages/policy-engine-shared/README.md) | Packages CI status | | [@narval/signature](./packages/signature/README.md) | Packages CI status | diff --git a/apps/armory/src/main.ts b/apps/armory/src/main.ts index f8bf31edb..4cbfd111a 100644 --- a/apps/armory/src/main.ts +++ b/apps/armory/src/main.ts @@ -2,6 +2,7 @@ import { ClassSerializerInterceptor, INestApplication, Logger, ValidationPipe } import { ConfigService } from '@nestjs/config' import { NestFactory, Reflector } from '@nestjs/core' import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger' +import { patchNestJsSwagger } from 'nestjs-zod' import { lastValueFrom, map, of, switchMap } from 'rxjs' import { Config } from './armory.config' import { ArmoryModule } from './armory.module' @@ -16,6 +17,11 @@ import { ZodExceptionFilter } from './shared/filter/zod-exception.filter' * @returns The modified INestApplication instance. */ const withSwagger = (app: INestApplication): INestApplication => { + // IMPORTANT: This modifies the Nest Swagger module to be compatible with + // DTOs created by Zod schemas. The patch MUST be done before the + // configuration process. + patchNestJsSwagger() + const document = SwaggerModule.createDocument( app, new DocumentBuilder() @@ -24,6 +30,7 @@ const withSwagger = (app: INestApplication): INestApplication => { .setVersion('1.0') .build() ) + SwaggerModule.setup('docs', app, document, { customSiteTitle: 'Armory API' }) diff --git a/apps/armory/src/orchestration/orchestration.module.ts b/apps/armory/src/orchestration/orchestration.module.ts index 4e6888759..0e2703f69 100644 --- a/apps/armory/src/orchestration/orchestration.module.ts +++ b/apps/armory/src/orchestration/orchestration.module.ts @@ -5,6 +5,7 @@ import { BullModule } from '@nestjs/bull' import { ClassSerializerInterceptor, Module, ValidationPipe } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core' +import { ZodValidationPipe } from 'nestjs-zod' import { AUTHORIZATION_REQUEST_PROCESSING_QUEUE } from '../armory.constant' import { DataFeedModule } from '../data-feed/data-feed.module' import { PriceModule } from '../price/price.module' @@ -63,8 +64,13 @@ import { AuthorizationRequestProcessingProducer } from './queue/producer/authori useClass: ClassSerializerInterceptor }, { + // DEPRECATE: Use Zod generated DTOs to validate request and responses. provide: APP_PIPE, useClass: ValidationPipe + }, + { + provide: APP_PIPE, + useClass: ZodValidationPipe } ], exports: [AuthorizationRequestGateway] diff --git a/apps/policy-engine/src/engine/engine.module.ts b/apps/policy-engine/src/engine/engine.module.ts index 463ceed7c..bd411aa0c 100644 --- a/apps/policy-engine/src/engine/engine.module.ts +++ b/apps/policy-engine/src/engine/engine.module.ts @@ -3,6 +3,7 @@ import { EncryptionModule } from '@narval/encryption-module' import { HttpModule } from '@nestjs/axios' import { Module, ValidationPipe } from '@nestjs/common' import { APP_PIPE } from '@nestjs/core' +import { ZodValidationPipe } from 'nestjs-zod' import { load } from '../policy-engine.config' import { EncryptionModuleOptionFactory } from '../shared/factory/encryption-module-option.factory' import { AdminApiKeyGuard } from '../shared/guard/admin-api-key.guard' @@ -52,8 +53,13 @@ import { TenantRepository } from './persistence/repository/tenant.repository' TenantService, EvaluationService, { + // DEPRECATE: Use Zod generated DTOs to validate request and responses. provide: APP_PIPE, useClass: ValidationPipe + }, + { + provide: APP_PIPE, + useClass: ZodValidationPipe } ], exports: [EngineService, ProvisionService, BootstrapService] diff --git a/apps/policy-engine/src/main.ts b/apps/policy-engine/src/main.ts index 0b2e8d420..88acf066f 100644 --- a/apps/policy-engine/src/main.ts +++ b/apps/policy-engine/src/main.ts @@ -2,6 +2,7 @@ import { ConfigService } from '@narval/config-module' import { INestApplication, Logger, ValidationPipe } from '@nestjs/common' import { NestFactory } from '@nestjs/core' import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger' +import { patchNestJsSwagger } from 'nestjs-zod' import { lastValueFrom, map, of, switchMap } from 'rxjs' import { Config } from './policy-engine.config' import { PolicyEngineModule } from './policy-engine.module' @@ -15,6 +16,11 @@ import { HttpExceptionFilter } from './shared/filter/http-exception.filter' * @returns The modified INestApplication instance. */ const withSwagger = (app: INestApplication): INestApplication => { + // IMPORTANT: This modifies the Nest Swagger module to be compatible with + // DTOs created by Zod schemas. The patch MUST be done before the + // configuration process. + patchNestJsSwagger() + const document = SwaggerModule.createDocument( app, new DocumentBuilder() @@ -23,6 +29,7 @@ const withSwagger = (app: INestApplication): INestApplication => { .setVersion('1.0') .build() ) + SwaggerModule.setup('docs', app, document) return app diff --git a/apps/policy-engine/src/policy-engine.module.ts b/apps/policy-engine/src/policy-engine.module.ts index e2f624fed..75cf2a95d 100644 --- a/apps/policy-engine/src/policy-engine.module.ts +++ b/apps/policy-engine/src/policy-engine.module.ts @@ -2,6 +2,7 @@ import { ConfigModule, ConfigService } from '@narval/config-module' import { EncryptionModule } from '@narval/encryption-module' import { Module, OnApplicationBootstrap, ValidationPipe } from '@nestjs/common' import { APP_PIPE } from '@nestjs/core' +import { ZodValidationPipe } from 'nestjs-zod' import { BootstrapService } from './engine/core/service/bootstrap.service' import { EngineService } from './engine/core/service/engine.service' import { ProvisionService } from './engine/core/service/provision.service' @@ -27,8 +28,13 @@ import { EncryptionModuleOptionFactory } from './shared/factory/encryption-modul ], providers: [ { + // DEPRECATE: Use Zod generated DTOs to validate request and responses. provide: APP_PIPE, useClass: ValidationPipe + }, + { + provide: APP_PIPE, + useClass: ZodValidationPipe } ] }) diff --git a/apps/vault/src/main.module.ts b/apps/vault/src/main.module.ts index 002e0ca6e..db88dc197 100644 --- a/apps/vault/src/main.module.ts +++ b/apps/vault/src/main.module.ts @@ -2,6 +2,7 @@ import { EncryptionModule } from '@narval/encryption-module' import { Module, ValidationPipe, forwardRef } from '@nestjs/common' import { ConfigModule, ConfigService } from '@nestjs/config' import { APP_PIPE } from '@nestjs/core' +import { ZodValidationPipe } from 'nestjs-zod' import { load } from './main.config' import { EncryptionModuleOptionFactory } from './shared/factory/encryption-module-option.factory' import { TenantModule } from './tenant/tenant.module' @@ -27,8 +28,13 @@ import { VaultModule } from './vault/vault.module' ], providers: [ { + // DEPRECATE: Use Zod generated DTOs to validate request and responses. provide: APP_PIPE, useClass: ValidationPipe + }, + { + provide: APP_PIPE, + useClass: ZodValidationPipe } ] }) diff --git a/apps/vault/src/main.ts b/apps/vault/src/main.ts index 1e86aa6b6..1f7a2c5ea 100644 --- a/apps/vault/src/main.ts +++ b/apps/vault/src/main.ts @@ -2,6 +2,7 @@ import { INestApplication, Logger, ValidationPipe } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import { NestFactory } from '@nestjs/core' import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger' +import { patchNestJsSwagger } from 'nestjs-zod' import { lastValueFrom, map, of, switchMap } from 'rxjs' import { MainModule } from './main.module' @@ -12,6 +13,11 @@ import { MainModule } from './main.module' * @returns The modified INestApplication instance. */ const withSwagger = (app: INestApplication): INestApplication => { + // IMPORTANT: This modifies the Nest Swagger module to be compatible with + // DTOs created by Zod schemas. The patch MUST be done before the + // configuration process. + patchNestJsSwagger() + const document = SwaggerModule.createDocument( app, new DocumentBuilder() @@ -20,6 +26,7 @@ const withSwagger = (app: INestApplication): INestApplication => { .setVersion('1.0') .build() ) + SwaggerModule.setup('docs', app, document) return app diff --git a/apps/vault/src/tenant/tenant.module.ts b/apps/vault/src/tenant/tenant.module.ts index b532b6372..225873045 100644 --- a/apps/vault/src/tenant/tenant.module.ts +++ b/apps/vault/src/tenant/tenant.module.ts @@ -1,6 +1,7 @@ import { HttpModule } from '@nestjs/axios' import { Module, OnApplicationBootstrap, ValidationPipe, forwardRef } from '@nestjs/common' import { APP_PIPE } from '@nestjs/core' +import { ZodValidationPipe } from 'nestjs-zod' import { AdminApiKeyGuard } from '../shared/guard/admin-api-key.guard' import { KeyValueModule } from '../shared/module/key-value/key-value.module' import { VaultModule } from '../vault/vault.module' @@ -19,8 +20,13 @@ import { TenantRepository } from './persistence/repository/tenant.repository' TenantRepository, TenantService, { + // DEPRECATE: Use Zod generated DTOs to validate request and responses. provide: APP_PIPE, useClass: ValidationPipe + }, + { + provide: APP_PIPE, + useClass: ZodValidationPipe } ], exports: [TenantService, TenantRepository] diff --git a/package-lock.json b/package-lock.json index f53cc3cc0..261c7def0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "lodash": "^4.17.21", "lowdb": "^7.0.1", "nest-commander": "^3.12.5", + "nestjs-zod": "^3.0.0", "next": "14.0.4", "prism-react-renderer": "^2.3.1", "react": "18.2.0", @@ -17882,6 +17883,14 @@ "deep-equal": "^2.0.5" } }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -24299,6 +24308,25 @@ "is-callable": "^1.1.3" } }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -26202,6 +26230,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -28055,6 +28088,14 @@ "shell-quote": "^1.8.1" } }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/less": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", @@ -29541,6 +29582,89 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "peer": true }, + "node_modules/merge-deep": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", + "dependencies": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-deep/node_modules/clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", + "dependencies": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-deep/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-deep/node_modules/shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", + "dependencies": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-deep/node_modules/shallow-clone/node_modules/kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", + "dependencies": { + "is-buffer": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-deep/node_modules/shallow-clone/node_modules/lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -31945,6 +32069,26 @@ } } }, + "node_modules/mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", + "dependencies": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object/node_modules/for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -32237,6 +32381,34 @@ } } }, + "node_modules/nestjs-zod": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/nestjs-zod/-/nestjs-zod-3.0.0.tgz", + "integrity": "sha512-vL9CHShCVj6TmjCVPOd4my46D8d7FdoB4nQvvh+lmVTuzvnwuD+slSxjT4EDdPDWDFtjhfpvQnnkr55/80KHEQ==", + "dependencies": { + "merge-deep": "^3.0.3" + }, + "peerDependencies": { + "@nestjs/common": ">= 8.0.0", + "@nestjs/core": ">= 8.0.0", + "@nestjs/swagger": ">= 5.0.0", + "zod": ">= 3.14.3" + }, + "peerDependenciesMeta": { + "@nestjs/common": { + "optional": true + }, + "@nestjs/core": { + "optional": true + }, + "@nestjs/swagger": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, "node_modules/next": { "version": "14.0.4", "resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz", diff --git a/package.json b/package.json index bc98f450e..56489f987 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "lodash": "^4.17.21", "lowdb": "^7.0.1", "nest-commander": "^3.12.5", + "nestjs-zod": "^3.0.0", "next": "14.0.4", "prism-react-renderer": "^2.3.1", "react": "18.2.0",