@rapidrest/service-core 1.0.0-rc.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +382 -23
- package/README.md +79 -5
- package/dist/lib/ApiErrors.js +10 -0
- package/dist/lib/ApiErrors.js.map +1 -1
- package/dist/lib/BackgroundService.js +1 -4
- package/dist/lib/BackgroundService.js.map +1 -1
- package/dist/lib/BackgroundServiceManager.js +47 -6
- package/dist/lib/BackgroundServiceManager.js.map +1 -1
- package/dist/lib/BulkError.js +4 -0
- package/dist/lib/BulkError.js.map +1 -1
- package/dist/lib/EventListenerManager.js +53 -18
- package/dist/lib/EventListenerManager.js.map +1 -1
- package/dist/lib/NetUtils.js +1 -0
- package/dist/lib/NetUtils.js.map +1 -1
- package/dist/lib/NotificationUtils.js +27 -8
- package/dist/lib/NotificationUtils.js.map +1 -1
- package/dist/lib/ObjectFactory.js +25 -44
- package/dist/lib/ObjectFactory.js.map +1 -1
- package/dist/lib/OpenApiSpec.js +31 -11
- package/dist/lib/OpenApiSpec.js.map +1 -1
- package/dist/lib/Server.js +74 -110
- package/dist/lib/Server.js.map +1 -1
- package/dist/lib/auth/AuthMiddleware.js +11 -16
- package/dist/lib/auth/AuthMiddleware.js.map +1 -1
- package/dist/lib/auth/JWTStrategy.js +68 -17
- package/dist/lib/auth/JWTStrategy.js.map +1 -1
- package/dist/lib/auth/index.js +0 -1
- package/dist/lib/auth/index.js.map +1 -1
- package/dist/lib/database/ConnectionKinds.js +15 -0
- package/dist/lib/database/ConnectionKinds.js.map +1 -1
- package/dist/lib/database/ConnectionManager.js +140 -61
- package/dist/lib/database/ConnectionManager.js.map +1 -1
- package/dist/lib/database/MongoConnection.js +13 -3
- package/dist/lib/database/MongoConnection.js.map +1 -1
- package/dist/lib/database/MongoRepository.js +61 -15
- package/dist/lib/database/MongoRepository.js.map +1 -1
- package/dist/lib/database/MongoSchemaSync.js +3 -3
- package/dist/lib/database/MongoSchemaSync.js.map +1 -1
- package/dist/lib/database/NamingUtils.js +3 -2
- package/dist/lib/database/NamingUtils.js.map +1 -1
- package/dist/lib/database/RedisCache.js +64 -0
- package/dist/lib/database/RedisCache.js.map +1 -0
- package/dist/lib/database/TypeOrmSupport.js +53 -13
- package/dist/lib/database/TypeOrmSupport.js.map +1 -1
- package/dist/lib/database/index.js +1 -0
- package/dist/lib/database/index.js.map +1 -1
- package/dist/lib/decorators/DatabaseDecorators.js +264 -20
- package/dist/lib/decorators/DatabaseDecorators.js.map +1 -1
- package/dist/lib/decorators/DocDecorators.js +30 -9
- package/dist/lib/decorators/DocDecorators.js.map +1 -1
- package/dist/lib/decorators/EventDecorators.js +1 -0
- package/dist/lib/decorators/EventDecorators.js.map +1 -1
- package/dist/lib/decorators/ModelDecorators.js +39 -23
- package/dist/lib/decorators/ModelDecorators.js.map +1 -1
- package/dist/lib/decorators/PersistenceDecorators.js +4 -3
- package/dist/lib/decorators/PersistenceDecorators.js.map +1 -1
- package/dist/lib/decorators/RouteDecorators.js +139 -36
- package/dist/lib/decorators/RouteDecorators.js.map +1 -1
- package/dist/lib/http/IWebSocketShim.js +2 -0
- package/dist/lib/http/IWebSocketShim.js.map +1 -0
- package/dist/lib/http/MiddlewareChain.js +167 -0
- package/dist/lib/http/MiddlewareChain.js.map +1 -0
- package/dist/lib/http/RuntimeDetect.js +9 -0
- package/dist/lib/http/RuntimeDetect.js.map +1 -0
- package/dist/lib/http/bun/BunAdapters.js +326 -0
- package/dist/lib/http/bun/BunAdapters.js.map +1 -0
- package/dist/lib/http/bun/BunRouter.js +391 -0
- package/dist/lib/http/bun/BunRouter.js.map +1 -0
- package/dist/lib/http/bun/BunWebSocket.js +49 -0
- package/dist/lib/http/bun/BunWebSocket.js.map +1 -0
- package/dist/lib/http/index.js +16 -3
- package/dist/lib/http/index.js.map +1 -1
- package/dist/lib/http/session/RedisSessionStore.js +30 -0
- package/dist/lib/http/session/RedisSessionStore.js.map +1 -0
- package/dist/lib/http/session/SessionManager.js +113 -0
- package/dist/lib/http/session/SessionManager.js.map +1 -0
- package/dist/lib/http/session/SessionStore.js +6 -0
- package/dist/lib/http/session/SessionStore.js.map +1 -0
- package/dist/lib/http/session/sessionMiddleware.js +42 -0
- package/dist/lib/http/session/sessionMiddleware.js.map +1 -0
- package/dist/lib/http/types.js +0 -3
- package/dist/lib/http/types.js.map +1 -1
- package/dist/lib/http/uWS/Adapters.js +421 -0
- package/dist/lib/http/uWS/Adapters.js.map +1 -0
- package/dist/lib/http/{Router.js → uWS/Router.js} +73 -173
- package/dist/lib/http/uWS/Router.js.map +1 -0
- package/dist/lib/http/{WebSocket.js → uWS/WebSocket.js} +4 -1
- package/dist/lib/http/uWS/WebSocket.js.map +1 -0
- package/dist/lib/models/BaseEntity.js +2 -1
- package/dist/lib/models/BaseEntity.js.map +1 -1
- package/dist/lib/models/BaseMongoEntity.js +2 -2
- package/dist/lib/models/BaseMongoEntity.js.map +1 -1
- package/dist/lib/models/ModelUtils.js +248 -101
- package/dist/lib/models/ModelUtils.js.map +1 -1
- package/dist/lib/models/RecoverableBaseEntity.js +6 -1
- package/dist/lib/models/RecoverableBaseEntity.js.map +1 -1
- package/dist/lib/models/RecoverableBaseMongoEntity.js +1 -0
- package/dist/lib/models/RecoverableBaseMongoEntity.js.map +1 -1
- package/dist/lib/models/RepoUtils.js +641 -213
- package/dist/lib/models/RepoUtils.js.map +1 -1
- package/dist/lib/models/SimpleEntity.js +1 -0
- package/dist/lib/models/SimpleEntity.js.map +1 -1
- package/dist/lib/models/SimpleMongoEntity.js +2 -1
- package/dist/lib/models/SimpleMongoEntity.js.map +1 -1
- package/dist/lib/models/StatusExtraData.js +4 -0
- package/dist/lib/models/StatusExtraData.js.map +1 -1
- package/dist/lib/routes/{AdminRoute.js → BaseAdminRoute.js} +98 -80
- package/dist/lib/routes/BaseAdminRoute.js.map +1 -0
- package/dist/lib/routes/{MetricsRoute.js → BaseMetricsRoute.js} +29 -15
- package/dist/lib/routes/BaseMetricsRoute.js.map +1 -0
- package/dist/lib/routes/{OpenAPIRoute.js → BaseOpenAPIRoute.js} +32 -32
- package/dist/lib/routes/BaseOpenAPIRoute.js.map +1 -0
- package/dist/lib/routes/BasePushRoute.js +305 -0
- package/dist/lib/routes/BasePushRoute.js.map +1 -0
- package/dist/lib/routes/BaseStaticRoute.js +153 -0
- package/dist/lib/routes/BaseStaticRoute.js.map +1 -0
- package/dist/lib/routes/BaseStatusRoute.js +80 -0
- package/dist/lib/routes/BaseStatusRoute.js.map +1 -0
- package/dist/lib/routes/CRUDRoute.js +284 -0
- package/dist/lib/routes/CRUDRoute.js.map +1 -0
- package/dist/lib/routes/ModelRoute.js +84 -57
- package/dist/lib/routes/ModelRoute.js.map +1 -1
- package/dist/lib/routes/RouteUtils.js +108 -20
- package/dist/lib/routes/RouteUtils.js.map +1 -1
- package/dist/lib/routes/index.js +7 -4
- package/dist/lib/routes/index.js.map +1 -1
- package/dist/lib/security/ACLUtils.js +297 -170
- package/dist/lib/security/ACLUtils.js.map +1 -1
- package/dist/lib/security/AccessControlList.js +18 -10
- package/dist/lib/security/AccessControlList.js.map +1 -1
- package/dist/lib/security/AccessControlListMongo.js +19 -68
- package/dist/lib/security/AccessControlListMongo.js.map +1 -1
- package/dist/lib/security/AccessControlListSQL.js +21 -70
- package/dist/lib/security/AccessControlListSQL.js.map +1 -1
- package/dist/lib/security/BaseACLRoute.js +239 -0
- package/dist/lib/security/BaseACLRoute.js.map +1 -0
- package/dist/lib/security/index.js +3 -0
- package/dist/lib/security/index.js.map +1 -1
- package/dist/lib/test/request.js +62 -5
- package/dist/lib/test/request.js.map +1 -1
- package/dist/lib/test/requestws.js +34 -10
- package/dist/lib/test/requestws.js.map +1 -1
- package/dist/types/ApiErrors.d.ts +8 -2
- package/dist/types/BackgroundService.d.ts +0 -5
- package/dist/types/BackgroundServiceManager.d.ts +2 -1
- package/dist/types/EventListenerManager.d.ts +7 -6
- package/dist/types/NotificationUtils.d.ts +5 -3
- package/dist/types/OpenApiSpec.d.ts +2 -2
- package/dist/types/Server.d.ts +28 -10
- package/dist/types/auth/AuthMiddleware.d.ts +3 -3
- package/dist/types/auth/AuthStrategy.d.ts +10 -7
- package/dist/types/auth/JWTStrategy.d.ts +6 -4
- package/dist/types/auth/index.d.ts +0 -1
- package/dist/types/database/ConnectionKinds.d.ts +7 -0
- package/dist/types/database/ConnectionManager.d.ts +29 -4
- package/dist/types/database/MongoConnection.d.ts +16 -4
- package/dist/types/database/MongoRepository.d.ts +32 -11
- package/dist/types/database/RedisCache.d.ts +23 -0
- package/dist/types/database/TypeOrmSupport.d.ts +4 -4
- package/dist/types/database/index.d.ts +1 -0
- package/dist/types/decorators/DatabaseDecorators.d.ts +139 -8
- package/dist/types/decorators/DocDecorators.d.ts +11 -2
- package/dist/types/decorators/ModelDecorators.d.ts +12 -5
- package/dist/types/decorators/PersistenceDecorators.d.ts +11 -4
- package/dist/types/decorators/RouteDecorators.d.ts +36 -6
- package/dist/types/http/IWebSocketShim.d.ts +11 -0
- package/dist/types/http/MiddlewareChain.d.ts +47 -0
- package/dist/types/http/RuntimeDetect.d.ts +2 -0
- package/dist/types/http/bun/BunAdapters.d.ts +139 -0
- package/dist/types/http/bun/BunRouter.d.ts +79 -0
- package/dist/types/http/bun/BunWebSocket.d.ts +35 -0
- package/dist/types/http/index.d.ts +16 -6
- package/dist/types/http/session/RedisSessionStore.d.ts +15 -0
- package/dist/types/http/session/SessionManager.d.ts +35 -0
- package/dist/types/http/session/SessionStore.d.ts +12 -0
- package/dist/types/http/session/sessionMiddleware.d.ts +10 -0
- package/dist/types/http/types.d.ts +42 -3
- package/dist/types/http/uWS/Adapters.d.ts +129 -0
- package/dist/types/http/{Router.d.ts → uWS/Router.d.ts} +14 -38
- package/dist/types/http/{WebSocket.d.ts → uWS/WebSocket.d.ts} +6 -5
- package/dist/types/models/BaseEntity.d.ts +1 -1
- package/dist/types/models/ModelUtils.d.ts +47 -14
- package/dist/types/models/RecoverableBaseEntity.d.ts +3 -1
- package/dist/types/models/RepoUtils.d.ts +84 -14
- package/dist/types/routes/BaseAdminRoute.d.ts +65 -0
- package/dist/types/routes/BaseMetricsRoute.d.ts +37 -0
- package/dist/types/routes/BaseOpenAPIRoute.d.ts +33 -0
- package/dist/types/routes/BasePushRoute.d.ts +53 -0
- package/dist/types/routes/BaseStaticRoute.d.ts +44 -0
- package/dist/types/routes/BaseStatusRoute.d.ts +37 -0
- package/dist/types/routes/CRUDRoute.d.ts +57 -0
- package/dist/types/routes/ModelRoute.d.ts +39 -36
- package/dist/types/routes/RouteUtils.d.ts +19 -0
- package/dist/types/routes/index.d.ts +7 -4
- package/dist/types/security/ACLUtils.d.ts +94 -18
- package/dist/types/security/AccessControlList.d.ts +37 -59
- package/dist/types/security/AccessControlListMongo.d.ts +3 -8
- package/dist/types/security/AccessControlListSQL.d.ts +3 -8
- package/dist/types/security/BaseACLRoute.d.ts +73 -0
- package/dist/types/security/index.d.ts +3 -0
- package/dist/types/test/request.d.ts +18 -1
- package/package.json +135 -129
- package/tsconfig.export.json +17 -0
- package/dist/lib/auth/BasicStrategy.js +0 -113
- package/dist/lib/auth/BasicStrategy.js.map +0 -1
- package/dist/lib/http/Adapters.js +0 -241
- package/dist/lib/http/Adapters.js.map +0 -1
- package/dist/lib/http/Router.js.map +0 -1
- package/dist/lib/http/WebSocket.js.map +0 -1
- package/dist/lib/routes/AdminRoute.js.map +0 -1
- package/dist/lib/routes/MetricsRoute.js.map +0 -1
- package/dist/lib/routes/OpenAPIRoute.js.map +0 -1
- package/dist/lib/routes/StatusRoute.js +0 -55
- package/dist/lib/routes/StatusRoute.js.map +0 -1
- package/dist/lib/security/ACLRouteMongo.js +0 -194
- package/dist/lib/security/ACLRouteMongo.js.map +0 -1
- package/dist/lib/security/ACLRouteSQL.js +0 -193
- package/dist/lib/security/ACLRouteSQL.js.map +0 -1
- package/dist/types/auth/BasicStrategy.d.ts +0 -37
- package/dist/types/http/Adapters.d.ts +0 -68
- package/dist/types/routes/AdminRoute.d.ts +0 -47
- package/dist/types/routes/MetricsRoute.d.ts +0 -15
- package/dist/types/routes/OpenAPIRoute.d.ts +0 -17
- package/dist/types/routes/StatusRoute.d.ts +0 -11
- package/dist/types/security/ACLRouteMongo.d.ts +0 -19
- package/dist/types/security/ACLRouteSQL.d.ts +0 -19
- package/docs/Makefile +0 -20
- package/docs/conf.py +0 -58
- package/docs/index.rst +0 -17
- package/docs/make.bat +0 -35
- package/docs/reference/@rapidrest/namespaces/DatabaseDecorators/README.md +0 -13
- package/docs/reference/@rapidrest/namespaces/DatabaseDecorators/functions/MongoRepository.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DatabaseDecorators/functions/RedisConnection.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DatabaseDecorators/functions/Repository.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DocDecorators/README.md +0 -23
- package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Default.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Description.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Document.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Example.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Format.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Returns.md +0 -28
- package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Summary.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Tags.md +0 -25
- package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/TypeInfo.md +0 -28
- package/docs/reference/@rapidrest/namespaces/DocDecorators/interfaces/DocumentsData.md +0 -57
- package/docs/reference/@rapidrest/namespaces/EventDecorators/README.md +0 -12
- package/docs/reference/@rapidrest/namespaces/EventDecorators/functions/EventListener.md +0 -17
- package/docs/reference/@rapidrest/namespaces/EventDecorators/functions/OnEvent.md +0 -26
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/README.md +0 -26
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Cache.md +0 -25
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/ChildEntity.md +0 -18
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/DataStore.md +0 -25
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Identifier.md +0 -27
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Protect.md +0 -35
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Reference.md +0 -25
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Shard.md +0 -27
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/TrackChanges.md +0 -26
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/interfaces/PendingTypeOrmColumn.md +0 -45
- package/docs/reference/@rapidrest/namespaces/ModelDecorators/variables/pendingTypeOrmColumns.md +0 -14
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/README.md +0 -27
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/Column.md +0 -25
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/Entity.md +0 -25
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/Index.md +0 -119
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/PrimaryColumn.md +0 -25
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/Unique.md +0 -68
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/getColumnMetadata.md +0 -26
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/getEntityName.md +0 -26
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/getIndexMetadata.md +0 -27
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/CollationOptions.md +0 -79
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/ColumnInfo.md +0 -41
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/ColumnOptions.md +0 -51
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/EntityOptions.md +0 -32
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/IndexInfo.md +0 -41
- package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/IndexOptions.md +0 -61
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/README.md +0 -36
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/After.md +0 -26
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Auth.md +0 -33
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/AuthResult.md +0 -31
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Before.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/ContentType.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Delete.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Get.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Head.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Header.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Method.md +0 -31
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Model.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Options.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Param.md +0 -26
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Patch.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Post.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Protect.md +0 -31
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Put.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Query.md +0 -26
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Request.md +0 -31
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/RequiresRole.md +0 -26
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Response.md +0 -31
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Route.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Socket.md +0 -33
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/User.md +0 -31
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Validate.md +0 -25
- package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/WebSocket.md +0 -25
- package/docs/reference/README.md +0 -20
- package/docs/reference/classes/ACLUtils.md +0 -263
- package/docs/reference/classes/AdminRoute.md +0 -51
- package/docs/reference/classes/AuthMiddleware.md +0 -173
- package/docs/reference/classes/BackgroundService.md +0 -117
- package/docs/reference/classes/BackgroundServiceManager.md +0 -172
- package/docs/reference/classes/BaseEntity.md +0 -82
- package/docs/reference/classes/BaseMongoEntity.md +0 -107
- package/docs/reference/classes/BasicStrategy.md +0 -131
- package/docs/reference/classes/BasicStrategyOptions.md +0 -115
- package/docs/reference/classes/BulkError.md +0 -271
- package/docs/reference/classes/ConnectionManager.md +0 -75
- package/docs/reference/classes/EventListenerManager.md +0 -88
- package/docs/reference/classes/HttpRouter.md +0 -343
- package/docs/reference/classes/JWTStrategy.md +0 -100
- package/docs/reference/classes/JWTStrategyOptions.md +0 -97
- package/docs/reference/classes/MetricsRoute.md +0 -27
- package/docs/reference/classes/ModelRoute.md +0 -527
- package/docs/reference/classes/ModelUtils.md +0 -446
- package/docs/reference/classes/MongoConnection.md +0 -218
- package/docs/reference/classes/MongoRepository.md +0 -390
- package/docs/reference/classes/MongoSchemaSync.md +0 -97
- package/docs/reference/classes/NetUtils.md +0 -90
- package/docs/reference/classes/NotificationUtils.md +0 -77
- package/docs/reference/classes/ObjectFactory.md +0 -336
- package/docs/reference/classes/OpenAPIRoute.md +0 -77
- package/docs/reference/classes/OpenApiSpec.md +0 -892
- package/docs/reference/classes/RecoverableBaseEntity.md +0 -114
- package/docs/reference/classes/RecoverableBaseMongoEntity.md +0 -124
- package/docs/reference/classes/RedisTransport.md +0 -2202
- package/docs/reference/classes/RepoUtils.md +0 -482
- package/docs/reference/classes/RouteUtils.md +0 -191
- package/docs/reference/classes/Server.md +0 -408
- package/docs/reference/classes/SimpleEntity.md +0 -48
- package/docs/reference/classes/SimpleMongoEntity.md +0 -66
- package/docs/reference/classes/StatusExtraData.md +0 -57
- package/docs/reference/classes/StatusRoute.md +0 -26
- package/docs/reference/classes/UWSRequest.md +0 -226
- package/docs/reference/classes/UWSResponse.md +0 -257
- package/docs/reference/classes/UWSWebSocketShim.md +0 -958
- package/docs/reference/enumerations/ACLAction.md +0 -63
- package/docs/reference/enumerations/ApiErrorMessages.md +0 -123
- package/docs/reference/enumerations/ApiErrors.md +0 -123
- package/docs/reference/functions/createWebSocketStream.md +0 -27
- package/docs/reference/functions/isSqlDataSource.md +0 -26
- package/docs/reference/functions/readBody.md +0 -29
- package/docs/reference/functions/resolveCollectionName.md +0 -33
- package/docs/reference/functions/runChain.md +0 -40
- package/docs/reference/functions/snakeCase.md +0 -28
- package/docs/reference/globals.md +0 -106
- package/docs/reference/interfaces/ACLRecord.md +0 -96
- package/docs/reference/interfaces/AccessControlList.md +0 -76
- package/docs/reference/interfaces/AuthResult.md +0 -55
- package/docs/reference/interfaces/AuthStrategy.md +0 -95
- package/docs/reference/interfaces/CreateRequestOptions.md +0 -121
- package/docs/reference/interfaces/DeleteRequestOptions.md +0 -137
- package/docs/reference/interfaces/FindRequestOptions.md +0 -133
- package/docs/reference/interfaces/HttpRequest.md +0 -147
- package/docs/reference/interfaces/HttpResponse.md +0 -164
- package/docs/reference/interfaces/JWTAuthResult.md +0 -84
- package/docs/reference/interfaces/RepoCreateOptions.md +0 -95
- package/docs/reference/interfaces/RepoDeleteOptions.md +0 -105
- package/docs/reference/interfaces/RepoFindOptions.md +0 -125
- package/docs/reference/interfaces/RepoOperationOptions.md +0 -69
- package/docs/reference/interfaces/RepoUpdateOptions.md +0 -101
- package/docs/reference/interfaces/RequestOptions.md +0 -112
- package/docs/reference/interfaces/RequestWS.md +0 -225
- package/docs/reference/interfaces/TruncateRequestOptions.md +0 -161
- package/docs/reference/interfaces/UpdateRequestOptions.md +0 -139
- package/docs/reference/type-aliases/ErrorHandler.md +0 -35
- package/docs/reference/type-aliases/NextFunction.md +0 -23
- package/docs/reference/type-aliases/OneOrMany.md +0 -19
- package/docs/reference/type-aliases/OneOrNull.md +0 -19
- package/docs/reference/type-aliases/PartialBaseEntity.md +0 -17
- package/docs/reference/type-aliases/PartialSimpleEntity.md +0 -17
- package/docs/reference/type-aliases/RequestHandler.md +0 -31
- package/docs/reference/type-aliases/UpdateObject.md +0 -19
- package/docs/reference/type-aliases/WsUpgradeAuth.md +0 -27
- package/docs/reference/type-aliases/WsUpgradeAuthResult.md +0 -47
|
@@ -9,6 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
};
|
|
10
10
|
///////////////////////////////////////////////////////////////////////////////
|
|
11
11
|
// Copyright (C) 2020-2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
12
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
12
13
|
///////////////////////////////////////////////////////////////////////////////
|
|
13
14
|
import { ObjectDecorators, UserUtils, sleep } from "@rapidrest/core";
|
|
14
15
|
import { AccessControlListSQL } from "./AccessControlListSQL.js";
|
|
@@ -18,20 +19,19 @@ import { ConnectionManager } from "../database/ConnectionManager.js";
|
|
|
18
19
|
import { isSqlDataSource } from "../database/ConnectionKinds.js";
|
|
19
20
|
import { MongoConnection } from "../database/MongoConnection.js";
|
|
20
21
|
import { MongoRepository } from "../database/MongoRepository.js";
|
|
22
|
+
import { RedisCache } from "../database/RedisCache.js";
|
|
23
|
+
import { Transactional, transactionContext } from "../decorators/DatabaseDecorators.js";
|
|
21
24
|
const { Config, Init, Inject, Logger } = ObjectDecorators;
|
|
22
|
-
const CACHE_BASE_KEY = "db.cache.AccessControlList";
|
|
23
25
|
/**
|
|
24
26
|
* Common utility functions for working with `AccessControlList` objects and validating user permissions.
|
|
27
|
+
*
|
|
28
|
+
* @author Jean-Philippe Steinmetz
|
|
25
29
|
*/
|
|
26
30
|
export class ACLUtils {
|
|
27
31
|
constructor() {
|
|
28
32
|
this.enabled = true;
|
|
29
|
-
this.cacheTTL = 30;
|
|
30
33
|
this.trustedRoles = ["admin"];
|
|
31
34
|
}
|
|
32
|
-
get cacheClient() {
|
|
33
|
-
return this.connMgr?.connections.get("cache");
|
|
34
|
-
}
|
|
35
35
|
get repo() {
|
|
36
36
|
const conn = this.connMgr?.connections.get("acl");
|
|
37
37
|
if (conn instanceof MongoConnection) {
|
|
@@ -42,10 +42,19 @@ export class ACLUtils {
|
|
|
42
42
|
}
|
|
43
43
|
return undefined;
|
|
44
44
|
}
|
|
45
|
-
init() {
|
|
45
|
+
async init() {
|
|
46
46
|
if (this.enabled) {
|
|
47
47
|
if (!this.repo) {
|
|
48
|
-
throw new Error("Failed to initialize ACLUtils. Did you forget to configure the `acl`
|
|
48
|
+
throw new Error("Failed to initialize ACLUtils. Did you forget to configure the `acl` datasource?");
|
|
49
|
+
}
|
|
50
|
+
// Create the cache store if caching is enabled for this entity type
|
|
51
|
+
if (!this.cache) {
|
|
52
|
+
const conn = this.connMgr?.connections.get("acl");
|
|
53
|
+
let modelClass = conn instanceof MongoConnection ? AccessControlListMongo : AccessControlListSQL;
|
|
54
|
+
this.cache = await this._objectFactory?.newInstance(RedisCache, {
|
|
55
|
+
name: modelClass.fqn ?? modelClass.name,
|
|
56
|
+
args: [modelClass],
|
|
57
|
+
});
|
|
49
58
|
}
|
|
50
59
|
this.logger?.info("RBAC system is enabled and ready.");
|
|
51
60
|
}
|
|
@@ -54,50 +63,49 @@ export class ACLUtils {
|
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
/**
|
|
57
|
-
*
|
|
66
|
+
* Classifies how specifically the given user matches the provided ACL record id. Used by `getRecord()` to
|
|
67
|
+
* prefer the most specific match among a record's siblings, rather than the first one in array order.
|
|
58
68
|
* @param user The user to check.
|
|
59
69
|
* @param userOrRoleId The ACL record id to check against.
|
|
60
|
-
* @returns `
|
|
70
|
+
* @returns `"exact"` for a direct uid/anonymous match, `"role"` for a role match, `"wildcard"` for a `.*`/`*`
|
|
71
|
+
* match, or `"none"` if the user doesn't match this id at all.
|
|
61
72
|
*/
|
|
62
|
-
|
|
73
|
+
matchSpecificity(user, userOrRoleId) {
|
|
63
74
|
if (!user?.uid) {
|
|
64
|
-
|
|
75
|
+
// Wildcards only ever match an *authenticated* user (see the check below) — an anonymous caller
|
|
76
|
+
// can only match a record explicitly keyed "anonymous".
|
|
77
|
+
return userOrRoleId === "anonymous" ? "exact" : "none";
|
|
65
78
|
}
|
|
79
|
+
if (user.uid === userOrRoleId)
|
|
80
|
+
return "exact";
|
|
81
|
+
if (user.roles?.includes(userOrRoleId))
|
|
82
|
+
return "role";
|
|
66
83
|
// Explicit wildcards — match any authenticated user; no regex engine involved
|
|
67
84
|
if (userOrRoleId === ".*" || userOrRoleId === "*")
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
return true;
|
|
71
|
-
if (user.roles) {
|
|
72
|
-
for (const role of user.roles) {
|
|
73
|
-
if (role === userOrRoleId)
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
85
|
+
return "wildcard";
|
|
86
|
+
return "none";
|
|
78
87
|
}
|
|
79
88
|
/**
|
|
80
89
|
* Validates that the user has permission to perform the request operation against the URL path for the
|
|
81
|
-
* provided request. If ACLUtils has not been initialized or the `acl`
|
|
90
|
+
* provided request. If ACLUtils has not been initialized or the `acl` datasource has not been configured
|
|
82
91
|
* then always returns `true`.
|
|
83
92
|
*
|
|
84
93
|
* @param uid The uid of the access control list to verify against.
|
|
85
94
|
* @param user The user to validate.
|
|
86
|
-
* @param req The request
|
|
95
|
+
* @param req The HTTP request to check permissions for.
|
|
87
96
|
*/
|
|
88
97
|
async checkRequestPerms(uid, user, req) {
|
|
89
|
-
let result = true;
|
|
90
98
|
// If RBAC is disabled just return
|
|
91
99
|
if (!this.enabled) {
|
|
92
|
-
return
|
|
100
|
+
return true;
|
|
93
101
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!req._aclCache) {
|
|
97
|
-
req._aclCache = new Map();
|
|
102
|
+
if (!req) {
|
|
103
|
+
throw new Error("options.request must be set to call this function.");
|
|
98
104
|
}
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
// Deny by default — if the ACL record can't be found (e.g. it failed to persist at
|
|
106
|
+
// registration time) a `@Protect`-ed route must not silently become open to everyone.
|
|
107
|
+
let result = false;
|
|
108
|
+
let acl = await this.findACL(uid, []);
|
|
101
109
|
if (acl) {
|
|
102
110
|
// Make sure all parents are populated
|
|
103
111
|
if (!acl.parent) {
|
|
@@ -109,26 +117,18 @@ export class ACLUtils {
|
|
|
109
117
|
result = true;
|
|
110
118
|
}
|
|
111
119
|
else {
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
case "post":
|
|
125
|
-
result = await this.hasPermission(user, acl, ACLAction.CREATE);
|
|
126
|
-
break;
|
|
127
|
-
case "put":
|
|
128
|
-
result = await this.hasPermission(user, acl, ACLAction.UPDATE);
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
120
|
+
// Map the request method to an ACLAction and test for permission. `hasPermission` already
|
|
121
|
+
// treats `ACLAction.FULL` as a wildcard that supersedes any specific action requested.
|
|
122
|
+
const methodToAction = {
|
|
123
|
+
delete: ACLAction.DELETE,
|
|
124
|
+
get: ACLAction.READ,
|
|
125
|
+
head: ACLAction.COUNT,
|
|
126
|
+
patch: ACLAction.UPDATE,
|
|
127
|
+
post: ACLAction.CREATE,
|
|
128
|
+
put: ACLAction.UPDATE,
|
|
129
|
+
};
|
|
130
|
+
const action = methodToAction[req.method.toLowerCase()];
|
|
131
|
+
result = action ? await this.hasPermission(user, acl, action) : false;
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
return result;
|
|
@@ -142,7 +142,6 @@ export class ACLUtils {
|
|
|
142
142
|
* @returns `true` if the user has at least one of the permissions granted for the given entity, otherwise `false`.
|
|
143
143
|
*/
|
|
144
144
|
async hasPermission(user, acl, action) {
|
|
145
|
-
let result = null;
|
|
146
145
|
// If the repo isn't available, no acl was provided or the ACL string is empty just return, assume always true
|
|
147
146
|
if (!this.enabled || !this.repo || !acl || acl === "") {
|
|
148
147
|
return true;
|
|
@@ -154,117 +153,119 @@ export class ACLUtils {
|
|
|
154
153
|
}
|
|
155
154
|
// If a uid has been given look up the ACL associated with it and then process
|
|
156
155
|
if (typeof acl === "string") {
|
|
157
|
-
const entry = await this.findACL(acl);
|
|
156
|
+
const entry = await this.findACL(acl, []);
|
|
158
157
|
return entry ? await this.hasPermission(user, entry, action) : false;
|
|
159
158
|
}
|
|
160
159
|
// Look for the first available record for the given user
|
|
161
160
|
const record = this.getRecord(acl, user);
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
// A `FULL` permission grant overrides everything else
|
|
165
|
-
result = record.full;
|
|
166
|
-
if (!result) {
|
|
167
|
-
switch (action) {
|
|
168
|
-
case ACLAction.CREATE:
|
|
169
|
-
result = record.create;
|
|
170
|
-
break;
|
|
171
|
-
case ACLAction.DELETE:
|
|
172
|
-
result = record.delete;
|
|
173
|
-
break;
|
|
174
|
-
case ACLAction.FULL:
|
|
175
|
-
result =
|
|
176
|
-
record.full ||
|
|
177
|
-
(record.create && record.delete && record.read && record.special && record.update);
|
|
178
|
-
break;
|
|
179
|
-
case ACLAction.READ:
|
|
180
|
-
result = record.read;
|
|
181
|
-
break;
|
|
182
|
-
case ACLAction.SPECIAL:
|
|
183
|
-
result = record.special;
|
|
184
|
-
break;
|
|
185
|
-
case ACLAction.UPDATE:
|
|
186
|
-
result = record.update;
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
// No matching record found — deny by default
|
|
192
|
-
return result !== null && result !== undefined ? result : false;
|
|
161
|
+
// A `FULL` ("*") grant supersedes any specific action requested.
|
|
162
|
+
return record ? record.actions.includes(ACLAction.FULL) || record.actions.includes(action) : false;
|
|
193
163
|
}
|
|
194
164
|
/**
|
|
195
165
|
* Retrieves the access control list with the associated identifier and populates the parent(s).
|
|
196
166
|
*
|
|
167
|
+
* Deliberately not `@Transactional` and never participates in a caller's transaction: it's on the hot path
|
|
168
|
+
* for every permission check (`hasPermission`/`checkRequestPerms`), not just writes, so wrapping it would
|
|
169
|
+
* add a session open/close to every authorization check. It also has no way to safely reuse a *caller's*
|
|
170
|
+
* transaction context.
|
|
171
|
+
*
|
|
197
172
|
* @param entityId The unique identifier of the ACL to retrieve.
|
|
198
173
|
* @param parentUids The list of already found parent UIDs. This is used to break circular dependencies.
|
|
174
|
+
* @param options Set `skipCache: true` to bypass the cache and always read the current database state —
|
|
175
|
+
* mirrors `RepoUtils`'s `RepoFindOptions.skipCache`. The result is still written back into the cache
|
|
176
|
+
* afterward, same as a normal (non-skipped) lookup.
|
|
199
177
|
*/
|
|
200
|
-
async findACL(entityId, parentUids = [],
|
|
178
|
+
async findACL(entityId, parentUids = [], options) {
|
|
201
179
|
if (!this.enabled || !this.repo) {
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// Retrieve the ACL from the cache if present
|
|
211
|
-
if (this.cacheClient) {
|
|
212
|
-
const json = await this.cacheClient.get(`${CACHE_BASE_KEY}.${entityId}`);
|
|
213
|
-
if (json) {
|
|
214
|
-
try {
|
|
215
|
-
acl = JSON.parse(json);
|
|
216
|
-
}
|
|
217
|
-
catch (err) {
|
|
218
|
-
// We don't care if this fails
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
// If the acl wasn't found in the cache look in the database
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
// Retrieve the ACL from the cache if present. A cache hit must still fall through to the parent-chain
|
|
183
|
+
// population below (not return early) — a cached ACL was stored via its own plain, unpopulated `.parent`
|
|
184
|
+
// (see the cache write below), so skipping that step here would silently return an ACL with no parent
|
|
185
|
+
// chain, which `hasPermission()` needs to find inherited records.
|
|
186
|
+
let acl = options?.skipCache ? undefined : await this.cache?.load(entityId);
|
|
187
|
+
// If the acl wasn't found in the cache (or the cache was skipped) look in the database
|
|
223
188
|
if (!acl) {
|
|
224
189
|
if (this.repo instanceof MongoRepository) {
|
|
225
190
|
acl = await this.repo.findOne({ uid: entityId });
|
|
226
|
-
acl = acl ? new AccessControlListMongo(acl) :
|
|
191
|
+
acl = acl ? new AccessControlListMongo(acl) : undefined;
|
|
227
192
|
}
|
|
228
193
|
else {
|
|
229
|
-
acl = await this.repo.findOne({ uid: entityId });
|
|
230
|
-
acl = acl ? new AccessControlListSQL(acl) :
|
|
231
|
-
}
|
|
232
|
-
// Store a copy in the cache for faster retrieval next time
|
|
233
|
-
if (acl && this.cacheClient) {
|
|
234
|
-
void this.cacheClient.setex(`${CACHE_BASE_KEY}.${entityId}`, this.cacheTTL, JSON.stringify(acl));
|
|
194
|
+
acl = await this.repo.findOne({ where: { uid: entityId } });
|
|
195
|
+
acl = acl ? new AccessControlListSQL(acl) : undefined;
|
|
235
196
|
}
|
|
236
197
|
}
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
198
|
+
// Store a copy in the cache for faster retrieval next time.
|
|
199
|
+
if (acl && this.cache) {
|
|
200
|
+
this.cache.save(entityId, acl).catch((err) => {
|
|
201
|
+
this.logger?.warn(`ACLUtils: Cache save failed for ACL ${entityId}.`);
|
|
202
|
+
this.logger?.debug(err);
|
|
203
|
+
});
|
|
241
204
|
}
|
|
242
205
|
// Retrieve the parent ACL and assign it if available. Don't populate parents we've
|
|
243
206
|
// already found to prevent a circular dependency.
|
|
244
207
|
if (acl && acl.parentUid && !parentUids.includes(acl.parentUid)) {
|
|
245
208
|
parentUids.push(acl.parentUid);
|
|
246
|
-
acl.parent = await this.findACL(acl.parentUid, parentUids,
|
|
209
|
+
acl.parent = await this.findACL(acl.parentUid, parentUids, options);
|
|
247
210
|
}
|
|
248
211
|
return acl;
|
|
249
212
|
}
|
|
250
213
|
/**
|
|
251
|
-
* Deletes the ACL with the given identifier from the database
|
|
214
|
+
* Deletes the ACL with the given identifier from the database, returning the document that was actually
|
|
215
|
+
* deleted (or `undefined` if none existed) — captured atomically as part of the delete itself (Mongo:
|
|
216
|
+
* `findOneAndDelete`; SQL: a `findOne` immediately followed by the `delete`, both inside this method's own
|
|
217
|
+
* `acl` transaction), not a separate, earlier read. A caller using the return value as a restore snapshot
|
|
218
|
+
* (see `RepoUtils.delete()`/`truncate()`'s `registerRollbackHook()` usage) always gets the exact row that
|
|
219
|
+
* was removed, never a possibly-stale one raced by a concurrent legitimate write between an earlier read
|
|
220
|
+
* and this delete — and restoring it later still goes through `saveACL()`'s own optimistic-lock version
|
|
221
|
+
* check, so a restore attempt can't clobber a *newer* row that appeared after this delete either.
|
|
222
|
+
*
|
|
223
|
+
* Runs in its own transaction scoped to the `acl` connection (see `Transactional`) rather than trying to
|
|
224
|
+
* join whatever transaction the caller is itself in — `acl` is frequently configured as its own, separate
|
|
225
|
+
* datastore (a documented, supported deployment shape), so a caller's session/entityManager almost never
|
|
226
|
+
* actually belongs to the same physical connection this repo does. Reusing it directly used to throw
|
|
227
|
+
* (Mongo) or silently target the wrong connection (SQL).
|
|
228
|
+
*
|
|
252
229
|
* @param uid The unique identifier of the ACL to remove.
|
|
253
230
|
*/
|
|
254
231
|
async removeACL(uid) {
|
|
255
|
-
if (this.enabled) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
232
|
+
if (!this.enabled) {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
if (!this.repo) {
|
|
236
|
+
throw new Error("repo is not set.");
|
|
237
|
+
}
|
|
238
|
+
const ctx = transactionContext.getStore();
|
|
239
|
+
let removed;
|
|
240
|
+
try {
|
|
241
|
+
if (this.repo instanceof MongoRepository) {
|
|
242
|
+
const deleted = await this.repo.findOneAndDelete({ uid }, { session: ctx?.session });
|
|
243
|
+
removed = deleted ? new AccessControlListMongo(deleted) : undefined;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
const repo = ctx?.entityManager ? ctx.entityManager.getRepository(AccessControlListSQL) : this.repo;
|
|
247
|
+
const existing = await repo.findOne({ where: { uid } });
|
|
248
|
+
if (existing) {
|
|
249
|
+
await repo.delete({ uid });
|
|
250
|
+
removed = new AccessControlListSQL(existing);
|
|
262
251
|
}
|
|
263
252
|
}
|
|
264
|
-
|
|
265
|
-
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
// The error "ns not found" occurs when the collection doesn't exist yet (e.g. a fresh deployment
|
|
256
|
+
// that has never written an ACL) - matches RepoUtils.truncate()'s handling of the same driver
|
|
257
|
+
// quirk. Any other error is a genuine failure and must propagate, not be silently treated as "this
|
|
258
|
+
// ACL is already gone".
|
|
259
|
+
if (err?.message !== "ns not found") {
|
|
260
|
+
throw err;
|
|
266
261
|
}
|
|
267
262
|
}
|
|
263
|
+
// Without this, a deleted ACL stays readable from the cache for up to `cacheTTL` seconds,
|
|
264
|
+
// so permissions the deletion was meant to revoke would continue to apply during that window.
|
|
265
|
+
if (this.cache) {
|
|
266
|
+
await this.cache.delete(uid);
|
|
267
|
+
}
|
|
268
|
+
return removed;
|
|
268
269
|
}
|
|
269
270
|
/**
|
|
270
271
|
* Compares two ACLs to see if they have been modified and returns the total number of changes between them.
|
|
@@ -290,13 +291,8 @@ export class ACLUtils {
|
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
293
|
if (foundRecord) {
|
|
293
|
-
// Check to see if
|
|
294
|
-
result += foundRecord.
|
|
295
|
-
result += foundRecord.delete !== recordA.delete ? 1 : 0;
|
|
296
|
-
result += foundRecord.full !== recordA.full ? 1 : 0;
|
|
297
|
-
result += foundRecord.read !== recordA.read ? 1 : 0;
|
|
298
|
-
result += foundRecord.special !== recordA.special ? 1 : 0;
|
|
299
|
-
result += foundRecord.update !== recordA.update ? 1 : 0;
|
|
294
|
+
// Check to see if the granted actions changed for this record
|
|
295
|
+
result += this.actionsChanged(foundRecord.actions, recordA.actions) ? 1 : 0;
|
|
300
296
|
}
|
|
301
297
|
else {
|
|
302
298
|
result++;
|
|
@@ -313,13 +309,8 @@ export class ACLUtils {
|
|
|
313
309
|
}
|
|
314
310
|
}
|
|
315
311
|
if (foundRecord) {
|
|
316
|
-
// Check to see if
|
|
317
|
-
result += foundRecord.
|
|
318
|
-
result += foundRecord.delete !== recordB.delete ? 1 : 0;
|
|
319
|
-
result += foundRecord.full !== recordB.full ? 1 : 0;
|
|
320
|
-
result += foundRecord.read !== recordB.read ? 1 : 0;
|
|
321
|
-
result += foundRecord.special !== recordB.special ? 1 : 0;
|
|
322
|
-
result += foundRecord.update !== recordB.update ? 1 : 0;
|
|
312
|
+
// Check to see if the granted actions changed for this record
|
|
313
|
+
result += this.actionsChanged(foundRecord.actions, recordB.actions) ? 1 : 0;
|
|
323
314
|
}
|
|
324
315
|
else {
|
|
325
316
|
result++;
|
|
@@ -328,55 +319,107 @@ export class ACLUtils {
|
|
|
328
319
|
return result;
|
|
329
320
|
}
|
|
330
321
|
/**
|
|
331
|
-
*
|
|
322
|
+
* Compares two lists of granted actions, ignoring order, to see if they differ.
|
|
323
|
+
*/
|
|
324
|
+
actionsChanged(a, b) {
|
|
325
|
+
if (a.length !== b.length) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
const sortedA = [...a].sort();
|
|
329
|
+
const sortedB = [...b].sort();
|
|
330
|
+
return sortedA.some((action, i) => action !== sortedB[i]);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Stores the given access control list into the ACL database. By default, bumps the version for optimistic
|
|
334
|
+
* locking (see below) — pass `preserveVersion: true` to instead write `acl` back with its own version
|
|
335
|
+
* exactly as given, for restoring a known-good snapshot (see `RepoUtils.delete()`/`truncate()`'s
|
|
336
|
+
* `registerRollbackHook()` usage) rather than applying a new change on top of whatever's currently stored.
|
|
337
|
+
* A restore only ever targets a uid with nothing currently at it (that's the point — the entity-side
|
|
338
|
+
* transaction it was tied to is being rolled back, so the row it deleted should no longer exist); if
|
|
339
|
+
* something *does* exist there already, the restore is refused rather than silently clobbering it.
|
|
340
|
+
*
|
|
341
|
+
* Runs in its own transaction scoped to the `acl` connection (see `Transactional`) rather than trying to
|
|
342
|
+
* join whatever transaction the caller is itself in — see `removeACL`'s doc comment for why. Callers that
|
|
343
|
+
* need the entity-side write and this ACL save to stay consistent should register a compensating action
|
|
344
|
+
* via `registerRollbackHook()`.
|
|
332
345
|
*
|
|
333
346
|
* @param acl The ACL to store.
|
|
347
|
+
* @param options Set `preserveVersion: true` to restore `acl` exactly as given (see above) instead of the
|
|
348
|
+
* normal optimistic-locking update semantics.
|
|
334
349
|
* @return Returns the ACL that was stored in the database.
|
|
335
350
|
*/
|
|
336
|
-
async saveACL(acl) {
|
|
351
|
+
async saveACL(acl, options) {
|
|
337
352
|
let result = null;
|
|
338
353
|
if (!this.enabled || !acl) {
|
|
339
354
|
return result;
|
|
340
355
|
}
|
|
356
|
+
if (!this.repo) {
|
|
357
|
+
throw new Error("repo is not set.");
|
|
358
|
+
}
|
|
359
|
+
const ctx = transactionContext.getStore();
|
|
360
|
+
const preserveVersion = !!options?.preserveVersion;
|
|
341
361
|
if (this.repo instanceof MongoRepository) {
|
|
342
362
|
const mACL = new AccessControlListMongo(acl);
|
|
343
|
-
const existing = await this.repo.findOne({ uid: acl.uid }
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
363
|
+
const existing = await this.repo.findOne({ uid: acl.uid }, {
|
|
364
|
+
session: ctx?.session,
|
|
365
|
+
});
|
|
366
|
+
if (preserveVersion) {
|
|
367
|
+
if (existing) {
|
|
368
|
+
throw new Error(`Cannot restore ACL ${acl.uid}: a document already exists at this uid.`);
|
|
369
|
+
}
|
|
347
370
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
371
|
+
else {
|
|
372
|
+
// If no changes have been made between versions ignore this request
|
|
373
|
+
if (existing && this.diffACL(existing, acl) === 0) {
|
|
374
|
+
return existing;
|
|
375
|
+
}
|
|
376
|
+
// Make sure that the versions match before we proceed
|
|
377
|
+
if (existing && existing.version !== mACL.version) {
|
|
378
|
+
throw new Error(`The acl to save must be of the same version. ACL=${acl.uid}, Expected=${existing.version}, Actual=${mACL.version}`);
|
|
379
|
+
}
|
|
351
380
|
}
|
|
352
381
|
const aclMongo = new AccessControlListMongo({
|
|
353
382
|
...acl,
|
|
354
383
|
dateModifed: new Date(),
|
|
355
|
-
version: existing ? mACL.version + 1 : 0,
|
|
384
|
+
version: preserveVersion ? mACL.version : existing ? mACL.version + 1 : 0,
|
|
356
385
|
});
|
|
357
|
-
|
|
386
|
+
// ACLs are single-row-per-uid (never trackChanges) - merge by `uid` rather than requiring `_id` to
|
|
387
|
+
// have survived the `{...acl, ...}` spread above, or a stale/missing `_id` would insert a
|
|
388
|
+
// duplicate document instead of updating the one `existing` just found.
|
|
389
|
+
result = await this.repo.save(aclMongo, { session: ctx?.session, mergeByUid: true });
|
|
358
390
|
}
|
|
359
|
-
else
|
|
391
|
+
else {
|
|
392
|
+
const repo = ctx?.entityManager ? ctx.entityManager.getRepository(AccessControlListSQL) : this.repo;
|
|
360
393
|
const sACL = new AccessControlListSQL(acl);
|
|
361
|
-
const existing = await
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
394
|
+
const existing = await repo.findOne({ where: { uid: acl.uid } });
|
|
395
|
+
if (preserveVersion) {
|
|
396
|
+
if (existing) {
|
|
397
|
+
throw new Error(`Cannot restore ACL ${acl.uid}: a document already exists at this uid.`);
|
|
398
|
+
}
|
|
365
399
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
400
|
+
else {
|
|
401
|
+
// If no changes have been made between versions ignore this request
|
|
402
|
+
if (existing && this.diffACL(existing, acl) === 0) {
|
|
403
|
+
return existing;
|
|
404
|
+
}
|
|
405
|
+
// Make sure that the versions match before we proceed
|
|
406
|
+
if (existing && existing.version !== sACL.version) {
|
|
407
|
+
throw new Error(`The acl to save must be of the same version. ACL=${acl.uid}, Expected=${existing.version}, Actual=${sACL.version}`);
|
|
408
|
+
}
|
|
369
409
|
}
|
|
370
410
|
const aclSQL = new AccessControlListSQL({
|
|
371
411
|
...acl,
|
|
372
412
|
dateModifed: new Date(),
|
|
373
|
-
version: existing ? sACL.version + 1 : 0,
|
|
413
|
+
version: preserveVersion ? sACL.version : existing ? sACL.version + 1 : 0,
|
|
374
414
|
});
|
|
375
|
-
result = await
|
|
415
|
+
result = await repo.save(aclSQL);
|
|
376
416
|
}
|
|
377
|
-
// Store a copy in the cache for faster retrieval next time
|
|
378
|
-
if (this.
|
|
379
|
-
|
|
417
|
+
// Store a copy in the cache for faster retrieval next time.
|
|
418
|
+
if (this.cache && result) {
|
|
419
|
+
this.cache.save(result.uid, result).catch((err) => {
|
|
420
|
+
this.logger?.warn(`ACLUtils: Cache save failed for ACL ${result?.uid}.`);
|
|
421
|
+
this.logger?.debug(err);
|
|
422
|
+
});
|
|
380
423
|
}
|
|
381
424
|
return result;
|
|
382
425
|
}
|
|
@@ -416,6 +459,9 @@ export class ACLUtils {
|
|
|
416
459
|
// Copy over the new records from code
|
|
417
460
|
existing.records = defaultAcl.records;
|
|
418
461
|
defaultAcl = existing;
|
|
462
|
+
// The user-defined override record was already created on a previous run. Look it
|
|
463
|
+
// up so callers still receive a valid ACL to register routes against instead of `null`.
|
|
464
|
+
result = (await this.findACL(acl.uid)) ?? null;
|
|
419
465
|
}
|
|
420
466
|
else {
|
|
421
467
|
// Create the user-defined override record
|
|
@@ -425,7 +471,7 @@ export class ACLUtils {
|
|
|
425
471
|
records: [],
|
|
426
472
|
});
|
|
427
473
|
}
|
|
428
|
-
// Always save the ACL into the
|
|
474
|
+
// Always save the ACL into the datasource
|
|
429
475
|
await this.saveACL(defaultAcl);
|
|
430
476
|
attempts = maxAttempts;
|
|
431
477
|
}
|
|
@@ -443,7 +489,49 @@ export class ACLUtils {
|
|
|
443
489
|
return result;
|
|
444
490
|
}
|
|
445
491
|
/**
|
|
446
|
-
*
|
|
492
|
+
* Atomic removal of multiple ACLs in a single `acl`-scoped transaction (see `@Transactional`), returning
|
|
493
|
+
* whichever of them actually existed to be deleted (see `removeACL()`'s doc comment for why the returned
|
|
494
|
+
* documents — not a separate, earlier read — are the right thing to snapshot for a possible restore).
|
|
495
|
+
*
|
|
496
|
+
* Deliberately sequential, not batched-concurrent: every `removeACL()` call below resolves to this same
|
|
497
|
+
* `acl` datasource, so `@Transactional`'s merge logic joins all of them onto the one MongoDB
|
|
498
|
+
* `ClientSession`/SQL `EntityManager` this method opened. Running these with `Promise.all`
|
|
499
|
+
* would issue overlapping commands against one session, which drivers reject or misorder.
|
|
500
|
+
*
|
|
501
|
+
* @param uids The unique identifiers of the ACLs to remove.
|
|
502
|
+
*/
|
|
503
|
+
async removeACLs(uids) {
|
|
504
|
+
const removed = [];
|
|
505
|
+
for (const uid of uids) {
|
|
506
|
+
const acl = await this.removeACL(uid);
|
|
507
|
+
if (acl) {
|
|
508
|
+
removed.push(acl);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return removed;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Atomic save of multiple ACLs in a single `acl`-scoped transaction (see `Transactional`). Used to restore
|
|
515
|
+
* a batch of ACL snapshots — e.g. by a `registerRollbackHook()` compensating action — if the entity-side
|
|
516
|
+
* transaction they were removed alongside subsequently fails. Always restores each ACL's own version as
|
|
517
|
+
* given (see `saveACL()`'s `preserveVersion` option) rather than bumping it, since this is a restore, not
|
|
518
|
+
* a new change.
|
|
519
|
+
*
|
|
520
|
+
* Deliberately sequential. See `removeACLs()`'s doc comment for why.
|
|
521
|
+
*
|
|
522
|
+
* @param acls The ACLs to store.
|
|
523
|
+
*/
|
|
524
|
+
async saveACLs(acls) {
|
|
525
|
+
for (const acl of acls) {
|
|
526
|
+
await this.saveACL(acl, { preserveVersion: true });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Retrieves the most specific record in the provided ACL associated with the provided user: an exact
|
|
531
|
+
* uid/anonymous match beats a role match, which beats a wildcard (`.*`/`*`) match — regardless of where
|
|
532
|
+
* each record sits in `acl.records`. Without this, a wildcard grant authored before a specific record
|
|
533
|
+
* (a natural authoring order) would silently shadow that more specific record. Only falls back to the
|
|
534
|
+
* parent ACL when nothing in this ACL's own records matches at all.
|
|
447
535
|
*
|
|
448
536
|
* @param acl The access control list that will be searched.
|
|
449
537
|
* @param user The user to find a record for.
|
|
@@ -453,10 +541,25 @@ export class ACLUtils {
|
|
|
453
541
|
if (!acl) {
|
|
454
542
|
return null;
|
|
455
543
|
}
|
|
544
|
+
let roleMatch = null;
|
|
545
|
+
let wildcardMatch = null;
|
|
456
546
|
for (const record of acl.records) {
|
|
457
|
-
|
|
547
|
+
const specificity = this.matchSpecificity(user, record.userOrRoleId);
|
|
548
|
+
if (specificity === "exact") {
|
|
458
549
|
return record;
|
|
459
550
|
}
|
|
551
|
+
else if (specificity === "role") {
|
|
552
|
+
roleMatch = roleMatch ?? record;
|
|
553
|
+
}
|
|
554
|
+
else if (specificity === "wildcard") {
|
|
555
|
+
wildcardMatch = wildcardMatch ?? record;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (roleMatch) {
|
|
559
|
+
return roleMatch;
|
|
560
|
+
}
|
|
561
|
+
if (wildcardMatch) {
|
|
562
|
+
return wildcardMatch;
|
|
460
563
|
}
|
|
461
564
|
return acl.parent ? this.getRecord(acl.parent, user) : null;
|
|
462
565
|
}
|
|
@@ -493,6 +596,30 @@ __decorate([
|
|
|
493
596
|
Init,
|
|
494
597
|
__metadata("design:type", Function),
|
|
495
598
|
__metadata("design:paramtypes", []),
|
|
496
|
-
__metadata("design:returntype",
|
|
599
|
+
__metadata("design:returntype", Promise)
|
|
497
600
|
], ACLUtils.prototype, "init", null);
|
|
601
|
+
__decorate([
|
|
602
|
+
Transactional("acl"),
|
|
603
|
+
__metadata("design:type", Function),
|
|
604
|
+
__metadata("design:paramtypes", [String]),
|
|
605
|
+
__metadata("design:returntype", Promise)
|
|
606
|
+
], ACLUtils.prototype, "removeACL", null);
|
|
607
|
+
__decorate([
|
|
608
|
+
Transactional("acl"),
|
|
609
|
+
__metadata("design:type", Function),
|
|
610
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
611
|
+
__metadata("design:returntype", Promise)
|
|
612
|
+
], ACLUtils.prototype, "saveACL", null);
|
|
613
|
+
__decorate([
|
|
614
|
+
Transactional("acl"),
|
|
615
|
+
__metadata("design:type", Function),
|
|
616
|
+
__metadata("design:paramtypes", [Array]),
|
|
617
|
+
__metadata("design:returntype", Promise)
|
|
618
|
+
], ACLUtils.prototype, "removeACLs", null);
|
|
619
|
+
__decorate([
|
|
620
|
+
Transactional("acl"),
|
|
621
|
+
__metadata("design:type", Function),
|
|
622
|
+
__metadata("design:paramtypes", [Array]),
|
|
623
|
+
__metadata("design:returntype", Promise)
|
|
624
|
+
], ACLUtils.prototype, "saveACLs", null);
|
|
498
625
|
//# sourceMappingURL=ACLUtils.js.map
|