bazis 0.96.1
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 +21 -0
- package/README.md +146 -0
- package/cli/build.ts +147 -0
- package/cli/codegen.ts +40 -0
- package/cli/generateModule.ts +187 -0
- package/cli/generateProject.ts +183 -0
- package/cli/main.ts +125 -0
- package/cli/moduleRegistration.ts +127 -0
- package/cli/naming.ts +124 -0
- package/cli/parseCli.ts +151 -0
- package/cli/templates/module.ts +497 -0
- package/cli/templates/pack.ts +76 -0
- package/cli/templates/passport.ts +178 -0
- package/core/agent/AgentContextBuilder.ts +567 -0
- package/core/agent/AgentDataDefinition.ts +8 -0
- package/core/agent/AgentRegistry.ts +539 -0
- package/core/agent/AgentRuntime.ts +47 -0
- package/core/agent/AgentToolExecutor.ts +2130 -0
- package/core/agent/AgentToolHooks.ts +39 -0
- package/core/agent/ModelProvider.ts +21 -0
- package/core/agent/README.md +229 -0
- package/core/agent/Tool.contract.ts +20 -0
- package/core/agent/decorators/Agent.ts +11 -0
- package/core/agent/decorators/Prompt.ts +11 -0
- package/core/agent/decorators/Task.ts +29 -0
- package/core/agent/decorators/Tool.ts +12 -0
- package/core/agent/errors.ts +43 -0
- package/core/agent/generated/catalog.ts +26 -0
- package/core/agent/index.ts +170 -0
- package/core/agent/internal/AgentExecutionDriver.ts +1497 -0
- package/core/agent/internal/AgentExecutionState.ts +775 -0
- package/core/agent/internal/AgentJsonSchema.ts +150 -0
- package/core/agent/internal/AgentModelBinding.ts +57 -0
- package/core/agent/internal/Schema.contract.ts +36 -0
- package/core/agent/internal/ToolAudit.projector.ts +163 -0
- package/core/agent/internal/ToolContract.validator.ts +102 -0
- package/core/agent/metadata.ts +398 -0
- package/core/agent/moduleContributions-v1.ts +113 -0
- package/core/agent/semantic.ts +903 -0
- package/core/agent/session/codec.ts +33 -0
- package/core/agent/session/contracts.ts +96 -0
- package/core/agent/session/entities.ts +84 -0
- package/core/agent/session/protector.ts +92 -0
- package/core/app/index.ts +5 -0
- package/core/app/runApp.ts +293 -0
- package/core/app/ui/index.ts +29 -0
- package/core/app/ui/uiProfileResolver.ts +966 -0
- package/core/app/ui/uiSurfaceHosting.ts +38 -0
- package/core/app/ui/uiSurfaceHttp.ts +476 -0
- package/core/background/BackgroundService.ts +223 -0
- package/core/background/backgroundModule.ts +42 -0
- package/core/background/decorator.ts +43 -0
- package/core/background/delay.ts +27 -0
- package/core/background/index.ts +15 -0
- package/core/cache/COMPATIBILITY.md +13 -0
- package/core/cache/ICache.ts +71 -0
- package/core/cache/MemoryCache.ts +394 -0
- package/core/cache/README.md +83 -0
- package/core/cache/SPEC.md +1355 -0
- package/core/cache/cacheModule.ts +21 -0
- package/core/cache/decorators/Cacheable.ts +38 -0
- package/core/cache/decorators/CacheableRedis.ts +39 -0
- package/core/cache/decorators/OutputCache.ts +33 -0
- package/core/cache/decorators/OutputRedisCache.ts +33 -0
- package/core/cache/decorators/cacheableMetadata.ts +78 -0
- package/core/cache/decorators/cacheableRedisMetadata.ts +81 -0
- package/core/cache/decorators/outputCacheMetadata.ts +71 -0
- package/core/cache/decorators/outputRedisCacheMetadata.ts +77 -0
- package/core/cache/di/classProviderHook.ts +26 -0
- package/core/cache/distributed/CacheCodec.ts +5 -0
- package/core/cache/distributed/DistributedCache.ts +403 -0
- package/core/cache/distributed/DistributedCacheDriver.ts +83 -0
- package/core/cache/distributed/DistributedCacheStores.ts +28 -0
- package/core/cache/distributed/IDistributedCache.ts +40 -0
- package/core/cache/distributed/NamedCacheRegistry.ts +23 -0
- package/core/cache/distributed/codecs.ts +32 -0
- package/core/cache/distributed/index.ts +19 -0
- package/core/cache/errors/CacheError.ts +16 -0
- package/core/cache/http/CachedHttpPayload.ts +213 -0
- package/core/cache/http/applyClientCacheHeaders.ts +61 -0
- package/core/cache/http/buildOutputCacheKey.ts +154 -0
- package/core/cache/http/buildRedisOutputCacheKey.ts +16 -0
- package/core/cache/http/composeOutputCache.ts +130 -0
- package/core/cache/http/outputCacheMiddleware.ts +111 -0
- package/core/cache/http/outputCacheSecurityWarning.ts +58 -0
- package/core/cache/http/outputRedisCacheMiddleware.ts +118 -0
- package/core/cache/index.ts +97 -0
- package/core/cache/internal/AutomaticCacheKey.ts +106 -0
- package/core/cache/internal/CacheKeyGuard.ts +56 -0
- package/core/cache/internal/buildCacheProviders.ts +146 -0
- package/core/cache/internal/normalizeCacheKey.ts +9 -0
- package/core/cache/internal/resolveCachePolicy.ts +32 -0
- package/core/cache/internal/resolveCacheableRedisPolicy.ts +17 -0
- package/core/cache/internal/resolveOutputRedisCachePolicy.ts +17 -0
- package/core/cache/providers/cachedProviders.ts +68 -0
- package/core/cache/services/cacheProxy.ts +243 -0
- package/core/cache/tokens/CACHE_POLICIES.ts +5 -0
- package/core/cache/tokens/DISTRIBUTED_CACHE.ts +30 -0
- package/core/cache/types/CacheEntry.ts +7 -0
- package/core/cache/types/CacheOptions.ts +60 -0
- package/core/cache/types/CachePolicy.ts +74 -0
- package/core/cache/types/CacheableRedisOptions.ts +23 -0
- package/core/cache/types/OutputRedisCacheOptions.ts +25 -0
- package/core/di/README.md +104 -0
- package/core/di/ServiceCollection.ts +185 -0
- package/core/di/ServiceProvider.ts +689 -0
- package/core/di/ServiceScope.ts +61 -0
- package/core/di/container.ts +38 -0
- package/core/di/errors/AmbiguousNamedDependencyError.ts +10 -0
- package/core/di/errors/AsyncResolutionRequiredError.ts +12 -0
- package/core/di/errors/CircularDependencyError.ts +10 -0
- package/core/di/errors/ClassDependenciesMismatchError.ts +15 -0
- package/core/di/errors/DiError.ts +6 -0
- package/core/di/errors/InvalidProviderError.ts +9 -0
- package/core/di/errors/ModuleEncapsulationError.ts +7 -0
- package/core/di/errors/ModuleOwnedProviderConflictError.ts +8 -0
- package/core/di/errors/NamedDependencyNotFoundError.ts +7 -0
- package/core/di/errors/OptionsValidationError.ts +7 -0
- package/core/di/errors/ProviderNotFoundError.ts +10 -0
- package/core/di/errors/ScopeDisposedError.ts +7 -0
- package/core/di/errors/ScopedServiceFromRootError.ts +9 -0
- package/core/di/errors/ServiceValidationError.ts +7 -0
- package/core/di/errors/index.ts +14 -0
- package/core/di/extensions/activator.ts +39 -0
- package/core/di/extensions/application.ts +117 -0
- package/core/di/extensions/hosted-service.ts +124 -0
- package/core/di/extensions/index.ts +23 -0
- package/core/di/extensions/options-reloadable.ts +137 -0
- package/core/di/extensions/options.ts +91 -0
- package/core/di/extensions/service-provider-token.ts +21 -0
- package/core/di/index.ts +97 -0
- package/core/di/internal/GraphValidator.ts +119 -0
- package/core/di/internal/HostedServicePlan.ts +158 -0
- package/core/di/internal/NamedTokenIndex.ts +55 -0
- package/core/di/internal/OpenGenericRegistration.ts +14 -0
- package/core/di/internal/ResolutionPlan.ts +18 -0
- package/core/di/internal/ResolutionScopeState.ts +19 -0
- package/core/di/internal/ResolutionTracker.ts +153 -0
- package/core/di/internal/ScopeLifecycle.ts +159 -0
- package/core/di/internal/ServiceRegistration.ts +14 -0
- package/core/di/internal/ServiceRegistry.ts +252 -0
- package/core/di/internal/classDeps.ts +38 -0
- package/core/di/internal/disposal.ts +58 -0
- package/core/di/internal/providerDeps.ts +26 -0
- package/core/di/module/DI.ts +146 -0
- package/core/di/module/Global.ts +25 -0
- package/core/di/module/Module.ts +44 -0
- package/core/di/module/ModuleRegistrar.ts +69 -0
- package/core/di/module/autoDeps.ts +39 -0
- package/core/di/module/classProviderHooks.ts +66 -0
- package/core/di/module/collectModuleConfigs.ts +57 -0
- package/core/di/module/collectModuleControllers.ts +53 -0
- package/core/di/module/collectModuleUiProfiles.ts +53 -0
- package/core/di/module/createContainer.ts +261 -0
- package/core/di/module/encapsulation.ts +416 -0
- package/core/di/module/encapsulationHooks.ts +36 -0
- package/core/di/module/generatedProviderAttachments.ts +102 -0
- package/core/di/module/index.ts +41 -0
- package/core/di/module/moduleExtensions.ts +35 -0
- package/core/di/module/moduleNameBinding.ts +136 -0
- package/core/di/module/moduleOwnedProviderContributors.ts +409 -0
- package/core/di/module/providerSelection.ts +90 -0
- package/core/di/module/shortcuts.ts +111 -0
- package/core/di/module/types/BazisModule.ts +71 -0
- package/core/di/module/types/DiRegistrar.ts +24 -0
- package/core/di/module/types/index.ts +2 -0
- package/core/di/provider/index.ts +26 -0
- package/core/di/provider/providerGuards.ts +17 -0
- package/core/di/provider/types/AsyncFactoryProvider.ts +15 -0
- package/core/di/provider/types/ClassProvider.ts +18 -0
- package/core/di/provider/types/FactoryProvider.ts +20 -0
- package/core/di/provider/types/KeyedDependency.ts +20 -0
- package/core/di/provider/types/LazyDependency.ts +31 -0
- package/core/di/provider/types/NamedDependency.ts +16 -0
- package/core/di/provider/types/Provider.ts +10 -0
- package/core/di/provider/types/ProviderDefinition.ts +11 -0
- package/core/di/provider/types/ProviderDependencyList.ts +11 -0
- package/core/di/provider/types/ProviderLifetime.ts +1 -0
- package/core/di/provider/types/ProviderOwnership.ts +7 -0
- package/core/di/provider/types/ResolvedDeps.ts +18 -0
- package/core/di/provider/types/ValueProvider.ts +6 -0
- package/core/di/provider/types/index.ts +13 -0
- package/core/di/token.ts +84 -0
- package/core/di/types/BuildServiceProviderOptions.ts +4 -0
- package/core/di/types/ServiceKey.ts +1 -0
- package/core/di/types/ServiceResolver.ts +16 -0
- package/core/di/types/index.ts +3 -0
- package/core/generatedFingerprint.ts +60 -0
- package/core/generatedRuntime.ts +187 -0
- package/core/grpc/Grpc.module.ts +23 -0
- package/core/grpc/GrpcBinding.contract.ts +35 -0
- package/core/grpc/GrpcCall.ts +73 -0
- package/core/grpc/GrpcClient.ts +172 -0
- package/core/grpc/GrpcClientCall.ts +267 -0
- package/core/grpc/GrpcError.ts +19 -0
- package/core/grpc/GrpcRequest.binding.ts +45 -0
- package/core/grpc/GrpcServer.ts +204 -0
- package/core/grpc/GrpcStatus.ts +8 -0
- package/core/grpc/GrpcTransport.ts +174 -0
- package/core/grpc/Metadata.ts +73 -0
- package/core/grpc/ProtoCodec.ts +225 -0
- package/core/grpc/ProtoParser.ts +192 -0
- package/core/grpc/ProtoReader.ts +34 -0
- package/core/grpc/ProtoSchema.ts +52 -0
- package/core/grpc/ServerCredentials.ts +30 -0
- package/core/grpc/clientContracts.ts +39 -0
- package/core/grpc/contracts.ts +45 -0
- package/core/grpc/decorators.ts +82 -0
- package/core/grpc/grpcClientProvider.ts +9 -0
- package/core/grpc/index.ts +17 -0
- package/core/grpc/moduleControllers.ts +38 -0
- package/core/grpc/protobuf.ts +80 -0
- package/core/grpc/serviceDefinition.ts +20 -0
- package/core/http/Authorization/Authorize.ts +52 -0
- package/core/http/Authorization/authorizeComposer.ts +31 -0
- package/core/http/Authorization/metadata.ts +158 -0
- package/core/http/Binding/ParameterBinder.ts +109 -0
- package/core/http/Binding/RequestModel.ts +27 -0
- package/core/http/Binding/autoBindings.ts +125 -0
- package/core/http/Binding/bindings.ts +19 -0
- package/core/http/Binding/convert.ts +48 -0
- package/core/http/Binding/generatedSpec.ts +18 -0
- package/core/http/Binding/listModelRegistry.ts +47 -0
- package/core/http/Binding/modelBinder.ts +345 -0
- package/core/http/Binding/modelValidator.ts +30 -0
- package/core/http/Binding/requestModelRegistry.ts +117 -0
- package/core/http/Decorators/attributes.ts +92 -0
- package/core/http/Decorators/controller.ts +24 -0
- package/core/http/Decorators/metadata.ts +107 -0
- package/core/http/Decorators/routes.ts +131 -0
- package/core/http/Errors/HttpError.ts +92 -0
- package/core/http/HttpContext/HttpContext.ts +148 -0
- package/core/http/HttpContext/ResponseBuilder.ts +37 -0
- package/core/http/HttpContext/inspectableRedirects.ts +47 -0
- package/core/http/HttpContext/principal.ts +24 -0
- package/core/http/HttpContext/responseLifetime.ts +45 -0
- package/core/http/HttpServer.ts +627 -0
- package/core/http/Middleware/accessLog.ts +90 -0
- package/core/http/Middleware/cors.ts +160 -0
- package/core/http/Middleware/errorHandler.ts +100 -0
- package/core/http/Middleware/pipeline.ts +21 -0
- package/core/http/Middleware/rateLimit.ts +104 -0
- package/core/http/Middleware/securityHeaders.ts +118 -0
- package/core/http/Middleware/types.ts +19 -0
- package/core/http/OpenApi/generatedOpenApiRegistry.ts +102 -0
- package/core/http/OpenApi/openApiDocs.ts +75 -0
- package/core/http/OpenApi/openApiDocument.ts +356 -0
- package/core/http/README.md +139 -0
- package/core/http/Results/HttpResult.ts +19 -0
- package/core/http/Results/normalize.ts +93 -0
- package/core/http/Results/results.ts +70 -0
- package/core/http/Routing/Router.ts +201 -0
- package/core/http/Routing/RouterBuilder.ts +184 -0
- package/core/http/Routing/template.ts +112 -0
- package/core/http/SPEC.md +981 -0
- package/core/http/Versioning/VersionReader.ts +20 -0
- package/core/http/WebSocketUpgrade.ts +29 -0
- package/core/http/composeRouteMiddlewareComposers.ts +21 -0
- package/core/http/correlation/createCorrelationIdMiddleware.ts +79 -0
- package/core/http/httpModule.ts +48 -0
- package/core/http/index.ts +140 -0
- package/core/http/middlewareTokens.ts +48 -0
- package/core/http/options.ts +134 -0
- package/core/http-client/index.ts +105 -0
- package/core/infra/Infra.ts +158 -0
- package/core/infra/InfraConnector.ts +86 -0
- package/core/infra/InfraLifecycle.ts +86 -0
- package/core/infra/cache/RedisDistributedCacheBackend.ts +88 -0
- package/core/infra/cache/RedisDistributedCacheDriver.ts +186 -0
- package/core/infra/cache/index.ts +7 -0
- package/core/infra/connectorConfig.ts +29 -0
- package/core/infra/connectorIdentity.ts +39 -0
- package/core/infra/connectors/codex/CodexAppServer.ts +146 -0
- package/core/infra/connectors/codex/CodexAppServerClient.ts +258 -0
- package/core/infra/connectors/codex/CodexPolicy.ts +114 -0
- package/core/infra/connectors/codex/CodexTurn.ts +133 -0
- package/core/infra/connectors/codex/contracts.ts +82 -0
- package/core/infra/connectors/codex.ts +26 -0
- package/core/infra/connectors/llm.ts +78 -0
- package/core/infra/connectors/llmRouter.ts +443 -0
- package/core/infra/connectors/openaiCompatible.ts +590 -0
- package/core/infra/connectors/openaiTextStream.ts +134 -0
- package/core/infra/connectors/opensearch.ts +307 -0
- package/core/infra/connectors/postgres.ts +105 -0
- package/core/infra/connectors/redis.ts +132 -0
- package/core/infra/index.ts +58 -0
- package/core/kernel/ApplicationLifetime.ts +91 -0
- package/core/kernel/Bazis.ts +73 -0
- package/core/kernel/Environment.ts +53 -0
- package/core/kernel/Kernel.ts +391 -0
- package/core/kernel/KernelBuilder.ts +172 -0
- package/core/kernel/LifecycleCoordinator.ts +204 -0
- package/core/kernel/README.md +66 -0
- package/core/kernel/SupervisedHostedService.ts +147 -0
- package/core/kernel/config/ConfigRegistry.ts +48 -0
- package/core/kernel/config/Configuration.ts +110 -0
- package/core/kernel/config/README.md +62 -0
- package/core/kernel/config/Secret.ts +29 -0
- package/core/kernel/config/addConfigOptions.ts +63 -0
- package/core/kernel/config/defineConfig.ts +211 -0
- package/core/kernel/config/sources.ts +123 -0
- package/core/kernel/correlation/requestContext.ts +57 -0
- package/core/kernel/errors/ConfigKeyMissingError.ts +7 -0
- package/core/kernel/errors/KernelError.ts +7 -0
- package/core/kernel/errors/ShutdownTimeoutError.ts +7 -0
- package/core/kernel/errors/StartupAbortedError.ts +7 -0
- package/core/kernel/errors/StartupTimeoutError.ts +7 -0
- package/core/kernel/errors/index.ts +5 -0
- package/core/kernel/events/EventBus.ts +140 -0
- package/core/kernel/events/EventToken.ts +17 -0
- package/core/kernel/events/eventSubscribers.ts +126 -0
- package/core/kernel/events/kernelEvents.ts +16 -0
- package/core/kernel/events/onEvent.ts +32 -0
- package/core/kernel/health/HealthCheckContracts.ts +42 -0
- package/core/kernel/health/HealthService.ts +93 -0
- package/core/kernel/index.ts +96 -0
- package/core/kernel/internal/awaitAbortable.ts +18 -0
- package/core/kernel/internal/awaitShutdown.ts +21 -0
- package/core/kernel/internal/reportDiagnosticFailure.ts +8 -0
- package/core/kernel/internal/validateTimeout.ts +8 -0
- package/core/kernel/lifecycleHooks.ts +18 -0
- package/core/kernel/logging/ConsoleLogger.ts +68 -0
- package/core/kernel/logging/Logger.ts +22 -0
- package/core/kernel/types/ConfigSource.ts +9 -0
- package/core/kernel/types/EnvironmentName.ts +1 -0
- package/core/kernel/types/KernelOptions.ts +23 -0
- package/core/kernel/types/LifecycleHook.ts +18 -0
- package/core/kernel/types/index.ts +4 -0
- package/core/orm/DATABASE_PROVIDER.ts +5 -0
- package/core/orm/OrmHostedPlan.validator.ts +55 -0
- package/core/orm/OrmLifecycle.ts +146 -0
- package/core/orm/OrmOwnedStoreLifecycle.ts +96 -0
- package/core/orm/buildOrmProviders.ts +152 -0
- package/core/orm/databaseConnector.ts +108 -0
- package/core/orm/encapsulationHook.ts +19 -0
- package/core/orm/index.ts +95 -0
- package/core/orm/listQuery.ts +198 -0
- package/core/orm/ormModule.ts +173 -0
- package/core/orm/ownedStoreContributions.ts +195 -0
- package/core/orm/registerRepositories.ts +27 -0
- package/core/orm/repository.ts +45 -0
- package/core/orm/strictSchemaHostedIdentity.ts +17 -0
- package/core/scripts/agent-codegen.ts +1166 -0
- package/core/scripts/di-generate-target.ts +90 -0
- package/core/scripts/di-generate.ts +1837 -0
- package/core/scripts/orm-predicate-codegen.ts +33 -0
- package/core/scripts/request-model-codegen.ts +326 -0
- package/core/websocket/DESIGN.md +297 -0
- package/core/websocket/WsRateLimiter.ts +101 -0
- package/core/websocket/adapter/adapter.interface.ts +45 -0
- package/core/websocket/adapter/in-memory.adapter.ts +190 -0
- package/core/websocket/adapter/redis-delivery-scripts.ts +135 -0
- package/core/websocket/adapter/redis-delivery-store.ts +86 -0
- package/core/websocket/adapter/redis-operations.ts +33 -0
- package/core/websocket/adapter/redis.adapter.ts +218 -0
- package/core/websocket/codec/binary.codec.ts +87 -0
- package/core/websocket/codec/json.codec.ts +34 -0
- package/core/websocket/codec/packet-codec.interface.ts +17 -0
- package/core/websocket/decorators.ts +165 -0
- package/core/websocket/explorer.ts +126 -0
- package/core/websocket/index.ts +78 -0
- package/core/websocket/middleware.ts +146 -0
- package/core/websocket/packet-codec.ts +75 -0
- package/core/websocket/reliable-delivery.ts +56 -0
- package/core/websocket/session-creation.ts +16 -0
- package/core/websocket/session-manager.ts +856 -0
- package/core/websocket/socket-wrapper.ts +314 -0
- package/core/websocket/topic-cache.ts +59 -0
- package/core/websocket/types.ts +122 -0
- package/core/websocket/websocketModule.ts +96 -0
- package/core/websocket/ws-dispatch.ts +123 -0
- package/core/websocket/ws-server.ts +1707 -0
- package/index.ts +281 -0
- package/library/boundary/diagnostics-v1.ts +136 -0
- package/library/boundary/index.ts +73 -0
- package/library/boundary/json/decodeBoundedJsonV1.ts +518 -0
- package/library/boundary/json/limits-v1.ts +46 -0
- package/library/boundary/json/normalizeBoundedJsonV1.ts +256 -0
- package/library/boundary/json/types-v1.ts +11 -0
- package/library/boundary/schema/boundarySchemaHashV1.ts +6 -0
- package/library/boundary/schema/collectBoundaryValueDiagnosticsV1.ts +325 -0
- package/library/boundary/schema/decodeBoundarySchemaV1.ts +646 -0
- package/library/boundary/schema/index.ts +8 -0
- package/library/boundary/schema/types-v1.ts +99 -0
- package/library/boundary/schema/validateBoundaryValueV1.ts +58 -0
- package/library/boundary/serialization/canonicalJsonV1.ts +66 -0
- package/library/http-client/HttpClient.ts +969 -0
- package/library/http-client/HttpClientFactory.ts +45 -0
- package/library/http-client/errors.ts +68 -0
- package/library/http-client/index.ts +43 -0
- package/library/http-client/interceptors.ts +40 -0
- package/library/http-client/progress.ts +62 -0
- package/library/http-client/redirectProtocol.ts +6 -0
- package/library/http-client/serialize.ts +133 -0
- package/library/http-client/types.ts +174 -0
- package/library/jsonapi/ListQueryError.ts +25 -0
- package/library/jsonapi/ListRequest.ts +37 -0
- package/library/jsonapi/decorators.ts +161 -0
- package/library/jsonapi/document.ts +124 -0
- package/library/jsonapi/index.ts +40 -0
- package/library/jsonapi/parseListQuery.ts +291 -0
- package/library/jsonapi/types.ts +125 -0
- package/library/jwt/JwtEncoder.ts +40 -0
- package/library/jwt/JwtKeyRing.ts +164 -0
- package/library/jwt/JwtValidator.ts +210 -0
- package/library/jwt/TokenIssuer.ts +149 -0
- package/library/jwt/TokenService.ts +51 -0
- package/library/jwt/base64url.ts +61 -0
- package/library/jwt/claims.ts +36 -0
- package/library/jwt/errors.ts +44 -0
- package/library/jwt/index.ts +59 -0
- package/library/jwt/limits.ts +8 -0
- package/library/jwt/signing/Hs256Algorithm.ts +62 -0
- package/library/jwt/signing/Rs256Algorithm.ts +133 -0
- package/library/jwt/signing/SigningAlgorithm.ts +18 -0
- package/library/jwt/signing/pem.ts +30 -0
- package/library/openapi/codegen.ts +875 -0
- package/library/openapi/document.ts +326 -0
- package/library/openapi/index.ts +13 -0
- package/library/openapi/types.ts +69 -0
- package/library/openapi/ui.ts +182 -0
- package/library/orm/DatabaseFacade.ts +209 -0
- package/library/orm/DbContext.ts +107 -0
- package/library/orm/DbContextFactory.ts +25 -0
- package/library/orm/DbContextOptions.ts +33 -0
- package/library/orm/Metadata/DynamicModelBuilder.ts +497 -0
- package/library/orm/Metadata/KeyTuple.ts +36 -0
- package/library/orm/Metadata/ModelBuilder.ts +299 -0
- package/library/orm/Metadata/OrmModel.ts +80 -0
- package/library/orm/Metadata/ValueConverter.ts +57 -0
- package/library/orm/Metadata/conventions.ts +54 -0
- package/library/orm/Metadata/decorators.ts +396 -0
- package/library/orm/Metadata/types.ts +140 -0
- package/library/orm/Providers/OwnedStoreCatalog.reader.ts +1802 -0
- package/library/orm/Providers/PostgresDialect.ts +234 -0
- package/library/orm/Providers/PostgresProvider.ts +1494 -0
- package/library/orm/Providers/PostgresSchema.decoder.ts +107 -0
- package/library/orm/Providers/limits.ts +29 -0
- package/library/orm/Providers/operationDeadline.ts +43 -0
- package/library/orm/Providers/ormOwnedStoreRuntime.ts +19 -0
- package/library/orm/Providers/ormTransactionRuntime.ts +117 -0
- package/library/orm/Providers/propertyConversion.ts +14 -0
- package/library/orm/Providers/resolvedForeignKey.ts +16 -0
- package/library/orm/Providers/transactionCallbacks.ts +45 -0
- package/library/orm/Providers/transactionOutcome.ts +82 -0
- package/library/orm/Providers/transactionScopes.ts +78 -0
- package/library/orm/Providers/types.ts +207 -0
- package/library/orm/Query/DbSet.ts +78 -0
- package/library/orm/Query/EntityQuery.ts +247 -0
- package/library/orm/Query/ImmediateMutations.ts +226 -0
- package/library/orm/Query/IncludeLoader.ts +200 -0
- package/library/orm/Query/QueryPlan.ts +55 -0
- package/library/orm/Query/SqlTranslator.ts +195 -0
- package/library/orm/Query/conditions.ts +127 -0
- package/library/orm/Query/materialize.ts +39 -0
- package/library/orm/README.md +256 -0
- package/library/orm/Repository/README.md +684 -0
- package/library/orm/Repository/Repository.ts +68 -0
- package/library/orm/Saving/CommandBuilder.ts +139 -0
- package/library/orm/Saving/ExecutionStrategy.ts +128 -0
- package/library/orm/Saving/SaveExecutor.ts +413 -0
- package/library/orm/Saving/applyConventions.ts +64 -0
- package/library/orm/Schema/CheckExpression.ts +327 -0
- package/library/orm/Schema/ExactSchemaVerifier.ts +168 -0
- package/library/orm/Schema/ExpectedSchema.ts +43 -0
- package/library/orm/Schema/MigrationRunner.ts +146 -0
- package/library/orm/Schema/Migrator.ts +73 -0
- package/library/orm/Schema/OrmOwnedStore.ts +123 -0
- package/library/orm/Schema/OwnedStoreAdmission.ts +752 -0
- package/library/orm/Schema/OwnedStoreCanonical.ts +273 -0
- package/library/orm/Schema/OwnedStoreCatalog.ts +1325 -0
- package/library/orm/Schema/SafeAdditiveSchema.ts +100 -0
- package/library/orm/Schema/SchemaAdmission.ts +62 -0
- package/library/orm/Schema/SchemaDiffer.ts +78 -0
- package/library/orm/Schema/formatSchemaDifferences.ts +62 -0
- package/library/orm/Schema/introspection.ts +52 -0
- package/library/orm/Schema/physicalColumnTypes.ts +37 -0
- package/library/orm/Schema/tableKey.ts +26 -0
- package/library/orm/Tracking/ChangeTracker.ts +448 -0
- package/library/orm/Tracking/EntityState.ts +13 -0
- package/library/orm/Transactions/OrmTransaction.ts +30 -0
- package/library/orm/Transactions/TransactionScopeCoordinator.ts +377 -0
- package/library/orm/errors.ts +190 -0
- package/library/orm/index.ts +162 -0
- package/library/orm/runtime.ts +14 -0
- package/library/redaction/index.ts +166 -0
- package/library/ui/UiProfile.ts +77 -0
- package/library/ui/compiled-v1.ts +131 -0
- package/library/ui/compiler/compileUiSurfaceV1.ts +717 -0
- package/library/ui/compiler/contracts-v1.ts +25 -0
- package/library/ui/compiler/index.ts +12 -0
- package/library/ui/compiler/openApiIndex.ts +246 -0
- package/library/ui/defineUiProfile.ts +340 -0
- package/library/ui/diagnostics-v1.ts +33 -0
- package/library/ui/index.ts +129 -0
- package/library/ui/profile-v1.ts +191 -0
- package/library/ui/serialization.ts +130 -0
- package/library/ui/uiProfileAuthoring.ts +289 -0
- package/library/ui/uiProfileAuthoringMetadata.ts +78 -0
- package/library/validation/MessageRegistry.ts +144 -0
- package/library/validation/README.md +62 -0
- package/library/validation/RuleCompiler.ts +183 -0
- package/library/validation/RuleEngine.ts +401 -0
- package/library/validation/SPEC.md +579 -0
- package/library/validation/ValidationError.ts +37 -0
- package/library/validation/ValidationResult.ts +63 -0
- package/library/validation/Validator.ts +101 -0
- package/library/validation/index.ts +16 -0
- package/library/validation/metadata.ts +69 -0
- package/library/validation/modelValidatorAdapter.ts +119 -0
- package/library/validation/types/ValidationCode.ts +69 -0
- package/library/validation/types/ValidatorOptions.ts +153 -0
- package/package.json +80 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types of the universal list request in the style of the JSON:API specification.
|
|
3
|
+
*
|
|
4
|
+
* The request is described by query parameters:
|
|
5
|
+
* - `?sort=name,-createdAt`: sorting (minus = descending);
|
|
6
|
+
* - `?filter[age][gte]=18&filter[name]=Bob`: filtering (no operator = `eq`),
|
|
7
|
+
* top-level conditions are joined with AND;
|
|
8
|
+
* - `?filter[or][0][age][lte]=19&filter[or][1][age][gte]=41`: OR groups:
|
|
9
|
+
* AND inside a group, OR between groups (`filters AND (g0 OR g1 ...)`);
|
|
10
|
+
* - `?page[number]=2&page[size]=20` or `?page[offset]=40&page[limit]=20`: paging;
|
|
11
|
+
* - `?include=posts,author`: eager loading of relations;
|
|
12
|
+
* - `?fields[users]=name,age`: sparse fieldsets (projection).
|
|
13
|
+
*
|
|
14
|
+
* The types are pure (no dependencies on the kernel/environment): the parser and
|
|
15
|
+
* the response builder use only Web APIs (`URLSearchParams`).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Sort direction. */
|
|
19
|
+
export type SortDirection = "asc" | "desc";
|
|
20
|
+
|
|
21
|
+
/** One sort key. */
|
|
22
|
+
export interface SortField {
|
|
23
|
+
readonly field: string;
|
|
24
|
+
readonly dir: SortDirection;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Filter operator. The names match the comparison operators of most ORMs:
|
|
29
|
+
* - `eq`/`ne`: equal/not equal (a `null` value means IS [NOT] NULL);
|
|
30
|
+
* - `gt`/`gte`/`lt`/`lte`: comparisons;
|
|
31
|
+
* - `like`: LIKE with a user pattern; `contains`/`startsWith`/`endsWith`:
|
|
32
|
+
* LIKE with escaped wildcards;
|
|
33
|
+
* - `in`/`nin`: in/not in a list (comma-separated value);
|
|
34
|
+
* - `isNull`/`isNotNull`: NULL check (no value needed).
|
|
35
|
+
*/
|
|
36
|
+
export type FilterOperator =
|
|
37
|
+
| "eq"
|
|
38
|
+
| "ne"
|
|
39
|
+
| "gt"
|
|
40
|
+
| "gte"
|
|
41
|
+
| "lt"
|
|
42
|
+
| "lte"
|
|
43
|
+
| "like"
|
|
44
|
+
| "contains"
|
|
45
|
+
| "startsWith"
|
|
46
|
+
| "endsWith"
|
|
47
|
+
| "in"
|
|
48
|
+
| "nin"
|
|
49
|
+
| "isNull"
|
|
50
|
+
| "isNotNull";
|
|
51
|
+
|
|
52
|
+
/** One filter condition on a field. */
|
|
53
|
+
export interface FilterRule {
|
|
54
|
+
readonly field: string;
|
|
55
|
+
readonly op: FilterOperator;
|
|
56
|
+
/** An array for `in`/`nin`; an empty string for `isNull`/`isNotNull`. */
|
|
57
|
+
readonly value: string | readonly string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A group of conditions joined with **AND**: one branch of an OR expression.
|
|
62
|
+
* Several groups are joined with **OR** (see {@link ListQuery.or}).
|
|
63
|
+
*/
|
|
64
|
+
export type FilterGroup = readonly FilterRule[];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Normalized paging. Either JSON:API style (`page[number]/size` or
|
|
68
|
+
* `page[offset]/limit`) is brought to one form: `offset`/`limit` for the
|
|
69
|
+
* database, `number`/`size` for the response.
|
|
70
|
+
*/
|
|
71
|
+
export interface PageParams {
|
|
72
|
+
/** Page number (1-based). */
|
|
73
|
+
readonly number: number;
|
|
74
|
+
/** Page size. */
|
|
75
|
+
readonly size: number;
|
|
76
|
+
/** Offset (0-based) = `(number - 1) * size`. */
|
|
77
|
+
readonly offset: number;
|
|
78
|
+
/** Limit = `size`. */
|
|
79
|
+
readonly limit: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A fully parsed and validated list request. */
|
|
83
|
+
export interface ListQuery {
|
|
84
|
+
readonly sort: readonly SortField[];
|
|
85
|
+
/**
|
|
86
|
+
* Top-level conditions (`filter[field][op]=v`) joined with **AND**.
|
|
87
|
+
* Always applied.
|
|
88
|
+
*/
|
|
89
|
+
readonly filters: readonly FilterRule[];
|
|
90
|
+
/**
|
|
91
|
+
* OR groups (`filter[or][N][field][op]=v`): conditions inside a group are joined
|
|
92
|
+
* with AND, different groups with OR. The whole OR expression is joined with
|
|
93
|
+
* {@link filters} by AND: `filters AND (group0 OR group1 OR ...)`. Empty means
|
|
94
|
+
* no OR logic.
|
|
95
|
+
*/
|
|
96
|
+
readonly or: readonly FilterGroup[];
|
|
97
|
+
readonly page: PageParams;
|
|
98
|
+
readonly include: readonly string[];
|
|
99
|
+
/** Sparse fieldsets per resource type (`fields[type]=a,b`). */
|
|
100
|
+
readonly fields: Readonly<Record<string, readonly string[]>>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Paging limits and defaults. */
|
|
104
|
+
export interface PageOptions {
|
|
105
|
+
/** Default page size (if the client did not set one). Defaults to 20. */
|
|
106
|
+
readonly defaultSize?: number;
|
|
107
|
+
/** Maximum allowed page size. Defaults to 100. */
|
|
108
|
+
readonly maxSize?: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Contract of what the request may use. Secure by default: anything not listed
|
|
113
|
+
* explicitly is forbidden (sorting/filtering/include by unlisted fields results
|
|
114
|
+
* in 400), so the client cannot reach internal columns.
|
|
115
|
+
*/
|
|
116
|
+
export interface ListQueryOptions {
|
|
117
|
+
/** Fields that allow sorting (`?sort=`). Not set: sorting is forbidden. */
|
|
118
|
+
readonly sort?: readonly string[];
|
|
119
|
+
/** Fields → allowed filter operators. Not set: filtering is forbidden. */
|
|
120
|
+
readonly filter?: Readonly<Record<string, readonly FilterOperator[]>>;
|
|
121
|
+
/** Allowed include paths (`?include=`). Not set: include is forbidden. */
|
|
122
|
+
readonly include?: readonly string[];
|
|
123
|
+
/** Paging limits. */
|
|
124
|
+
readonly page?: PageOptions;
|
|
125
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { base64UrlEncode, base64UrlEncodeString } from "./base64url";
|
|
2
|
+
import type { JwtHeader, JwtPayload } from "./claims";
|
|
3
|
+
import type { SigningAlgorithm } from "./signing/SigningAlgorithm";
|
|
4
|
+
import { JwtKeyRing } from "./JwtKeyRing";
|
|
5
|
+
import { resolveMaxTokenLength } from "./limits";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Serialises a claim set into a signed compact JWS (`header.payload.signature`).
|
|
9
|
+
*
|
|
10
|
+
* Stateless and bound to a single {@link SigningAlgorithm}; the caller owns
|
|
11
|
+
* claim construction (timestamps, issuer, audience). One encoder per algorithm.
|
|
12
|
+
*/
|
|
13
|
+
export class JwtEncoder {
|
|
14
|
+
private readonly maxTokenLength: number;
|
|
15
|
+
|
|
16
|
+
public constructor(private readonly algorithm: SigningAlgorithm | JwtKeyRing, options: { readonly maxTokenLength?: number } = {}) {
|
|
17
|
+
this.maxTokenLength = resolveMaxTokenLength(options.maxTokenLength);
|
|
18
|
+
if (!(algorithm instanceof JwtKeyRing) && !algorithm.canSign) {
|
|
19
|
+
throw new Error(`Signing algorithm '${algorithm.alg}' has no signing key`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public async encode(payload: JwtPayload): Promise<string> {
|
|
24
|
+
const algorithm = this.algorithm instanceof JwtKeyRing ? this.algorithm.signingKey() : this.algorithm;
|
|
25
|
+
const header: JwtHeader = {
|
|
26
|
+
alg: algorithm.alg,
|
|
27
|
+
typ: "JWT",
|
|
28
|
+
...(algorithm.keyId === undefined ? {} : { kid: algorithm.keyId }),
|
|
29
|
+
};
|
|
30
|
+
const signingInput = `${base64UrlEncodeString(JSON.stringify(header))}.${base64UrlEncodeString(
|
|
31
|
+
JSON.stringify(payload),
|
|
32
|
+
)}`;
|
|
33
|
+
if (signingInput.length >= this.maxTokenLength) throw new RangeError("JWT exceeds maxTokenLength");
|
|
34
|
+
const signature = await algorithm.sign(signingInput);
|
|
35
|
+
if (this.algorithm instanceof JwtKeyRing) this.algorithm.assertCurrent(algorithm);
|
|
36
|
+
const token = `${signingInput}.${base64UrlEncode(signature)}`;
|
|
37
|
+
if (token.length > this.maxTokenLength) throw new RangeError("JWT exceeds maxTokenLength");
|
|
38
|
+
return token;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { JwtAlgorithmError, JwtClaimError } from "./errors";
|
|
2
|
+
import type { SigningAlgorithm } from "./signing/SigningAlgorithm";
|
|
3
|
+
|
|
4
|
+
export interface JwtKeyEntry {
|
|
5
|
+
readonly keyId: string;
|
|
6
|
+
readonly algorithm: SigningAlgorithm;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface JwtKeyRingConfig {
|
|
10
|
+
readonly keys: readonly JwtKeyEntry[];
|
|
11
|
+
/** Omit for a verifier-only ring. */
|
|
12
|
+
readonly activeKeyId?: string;
|
|
13
|
+
/** Explicit, time-bounded migration of tokens issued without kid. Never used for unknown ids. */
|
|
14
|
+
readonly legacy?: { readonly keyId: string; readonly acceptUntil: number };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A prepared, immutable binding between an id and one crypto strategy. */
|
|
18
|
+
export interface SelectedJwtKey extends SigningAlgorithm {
|
|
19
|
+
readonly keyId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface KeySnapshot {
|
|
23
|
+
readonly keys: ReadonlyMap<string, SelectedJwtKey>;
|
|
24
|
+
readonly sources: ReadonlyMap<string, SigningAlgorithm>;
|
|
25
|
+
readonly activeKeyId?: string;
|
|
26
|
+
readonly legacy?: { readonly keyId: string; readonly acceptUntil: number };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Trusted local key lifecycle. Never resolves token-provided URLs or key material. */
|
|
30
|
+
export class JwtKeyRing {
|
|
31
|
+
private revision = 0;
|
|
32
|
+
private readonly revoked = new Set<string>();
|
|
33
|
+
|
|
34
|
+
private constructor(private snapshot: KeySnapshot) {}
|
|
35
|
+
|
|
36
|
+
/** Checks all keys before exposing the ring to token consumers. */
|
|
37
|
+
public static async create(config: JwtKeyRingConfig): Promise<JwtKeyRing> {
|
|
38
|
+
return new JwtKeyRing(await prepare(config));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Prepare, then publish once. A concurrent replacement/revocation wins over stale work. */
|
|
42
|
+
public async replace(config: JwtKeyRingConfig): Promise<void> {
|
|
43
|
+
const revision = this.revision;
|
|
44
|
+
const candidate = await prepare(config, this.snapshot);
|
|
45
|
+
if (this.revision !== revision) throw new Error("JWT key ring changed during preparation; retry with current configuration");
|
|
46
|
+
for (const id of candidate.keys.keys()) {
|
|
47
|
+
if (this.revoked.has(id)) throw new Error("A revoked JWT key id cannot be restored");
|
|
48
|
+
}
|
|
49
|
+
// Retired ids are never reused during this ring's lifetime.
|
|
50
|
+
for (const id of this.snapshot.keys.keys()) {
|
|
51
|
+
if (!candidate.keys.has(id)) this.revoked.add(id);
|
|
52
|
+
}
|
|
53
|
+
this.snapshot = candidate;
|
|
54
|
+
this.revision += 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Immediately distrust an id, including any in-flight use. No crypto or I/O. */
|
|
58
|
+
public revoke(keyId: string): boolean {
|
|
59
|
+
requireKeyId(keyId);
|
|
60
|
+
const keys = new Map(this.snapshot.keys);
|
|
61
|
+
const sources = new Map(this.snapshot.sources);
|
|
62
|
+
const existed = keys.delete(keyId);
|
|
63
|
+
sources.delete(keyId);
|
|
64
|
+
this.revoked.add(keyId);
|
|
65
|
+
this.snapshot = { keys, sources, activeKeyId: this.snapshot.activeKeyId === keyId ? undefined : this.snapshot.activeKeyId,
|
|
66
|
+
legacy: this.snapshot.legacy?.keyId === keyId ? undefined : this.snapshot.legacy };
|
|
67
|
+
this.revision += 1;
|
|
68
|
+
return existed;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Safe operational metadata; no secrets, PEM, or token values. */
|
|
72
|
+
public status() {
|
|
73
|
+
return Object.freeze({
|
|
74
|
+
revision: this.revision,
|
|
75
|
+
activeKeyId: this.snapshot.activeKeyId,
|
|
76
|
+
...(this.snapshot.legacy === undefined ? {} : { legacy: this.snapshot.legacy }),
|
|
77
|
+
keys: Object.freeze([...this.snapshot.keys.values()].map(key => Object.freeze({ keyId: key.keyId, alg: key.alg, canSign: key.canSign }))),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @internal Captured once by JwtEncoder, before its first await. */
|
|
82
|
+
public signingKey(): SelectedJwtKey {
|
|
83
|
+
const key = this.snapshot.activeKeyId === undefined ? undefined : this.snapshot.keys.get(this.snapshot.activeKeyId);
|
|
84
|
+
if (key === undefined) throw new Error("JWT key ring has no active signing key");
|
|
85
|
+
return key;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @internal The untrusted id selects only an explicitly configured key. */
|
|
89
|
+
public verificationKey(keyId: string | undefined, alg: string): SelectedJwtKey {
|
|
90
|
+
const legacy = this.snapshot.legacy;
|
|
91
|
+
const selectedId = keyId === undefined && legacy !== undefined && Date.now() / 1000 < legacy.acceptUntil
|
|
92
|
+
? legacy.keyId : keyId;
|
|
93
|
+
const key = selectedId === undefined ? undefined : this.snapshot.keys.get(selectedId);
|
|
94
|
+
if (key === undefined) throw new JwtClaimError("kid", "unknown or missing key id");
|
|
95
|
+
if (alg !== key.alg) throw new JwtAlgorithmError("JWT algorithm does not match the selected key");
|
|
96
|
+
return key;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** @internal Linearization check after asynchronous crypto. */
|
|
100
|
+
public assertCurrent(key: SigningAlgorithm): void {
|
|
101
|
+
if (key.keyId === undefined || this.snapshot.keys.get(key.keyId) !== key) throw new JwtClaimError("kid", "key was removed during the operation");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function prepare(config: JwtKeyRingConfig, previous?: KeySnapshot): Promise<KeySnapshot> {
|
|
106
|
+
if (config === null || typeof config !== "object" || Array.isArray(config) ||
|
|
107
|
+
!Array.isArray(config.keys) || config.keys.length < 1 || config.keys.length > 32) {
|
|
108
|
+
throw new TypeError("JWT key ring requires between 1 and 32 keys");
|
|
109
|
+
}
|
|
110
|
+
const activeKeyId = config.activeKeyId;
|
|
111
|
+
if (activeKeyId !== undefined) requireKeyId(activeKeyId);
|
|
112
|
+
let legacy: KeySnapshot["legacy"];
|
|
113
|
+
if (config.legacy !== undefined) {
|
|
114
|
+
if (config.legacy === null || typeof config.legacy !== "object" || Array.isArray(config.legacy)) throw new TypeError("Invalid JWT legacy key policy");
|
|
115
|
+
const { keyId, acceptUntil } = config.legacy;
|
|
116
|
+
requireKeyId(keyId);
|
|
117
|
+
if (typeof acceptUntil !== "number" || !Number.isFinite(acceptUntil) || acceptUntil <= 0) throw new TypeError("JWT legacy acceptUntil must be a positive finite NumericDate");
|
|
118
|
+
legacy = Object.freeze({ keyId, acceptUntil });
|
|
119
|
+
}
|
|
120
|
+
const keys = new Map<string, SelectedJwtKey>();
|
|
121
|
+
const sources = new Map<string, SigningAlgorithm>();
|
|
122
|
+
// Take all metadata snapshots synchronously before calling user crypto strategies.
|
|
123
|
+
for (const entry of [...config.keys]) {
|
|
124
|
+
if (entry === null || typeof entry !== "object") throw new TypeError("Invalid JWT key entry");
|
|
125
|
+
const { keyId, algorithm } = entry;
|
|
126
|
+
requireKeyId(keyId);
|
|
127
|
+
if (keys.has(keyId)) throw new TypeError("Duplicate JWT key id");
|
|
128
|
+
if (algorithm === null || typeof algorithm !== "object" || typeof algorithm.alg !== "string" ||
|
|
129
|
+
algorithm.alg.length === 0 || algorithm.alg.toLowerCase() === "none" ||
|
|
130
|
+
typeof algorithm.canSign !== "boolean" || typeof algorithm.sign !== "function" || typeof algorithm.verify !== "function") {
|
|
131
|
+
throw new TypeError("Invalid JWT signing algorithm");
|
|
132
|
+
}
|
|
133
|
+
if (algorithm.keyId !== undefined && algorithm.keyId !== keyId) throw new TypeError("JWT key id conflicts with algorithm key id");
|
|
134
|
+
const prior = previous?.keys.get(keyId);
|
|
135
|
+
if (prior !== undefined && prior.alg !== algorithm.alg) throw new TypeError("Use a new key id when changing the algorithm");
|
|
136
|
+
const key = prior !== undefined && previous?.sources.get(keyId) === algorithm ? prior : Object.freeze({
|
|
137
|
+
keyId, alg: algorithm.alg, canSign: algorithm.canSign,
|
|
138
|
+
sign: algorithm.sign.bind(algorithm), verify: algorithm.verify.bind(algorithm),
|
|
139
|
+
});
|
|
140
|
+
keys.set(keyId, key);
|
|
141
|
+
sources.set(keyId, algorithm);
|
|
142
|
+
}
|
|
143
|
+
if (activeKeyId !== undefined && !keys.get(activeKeyId)?.canSign) throw new TypeError("Active JWT key must exist and support signing");
|
|
144
|
+
if (legacy !== undefined && !keys.has(legacy.keyId)) throw new TypeError("Legacy JWT key must exist in the trusted set");
|
|
145
|
+
for (const key of keys.values()) {
|
|
146
|
+
if (key === previous?.keys.get(key.keyId)) continue;
|
|
147
|
+
const challenge = `bazis-key-check.${crypto.randomUUID()}`;
|
|
148
|
+
if (key.canSign) {
|
|
149
|
+
const signature = await key.sign(challenge);
|
|
150
|
+
if (!await key.verify(challenge, signature) || await key.verify(`${challenge}.changed`, signature)) {
|
|
151
|
+
throw new Error("JWT signing key self-test failed");
|
|
152
|
+
}
|
|
153
|
+
} else if (await key.verify(challenge, new Uint8Array(0))) {
|
|
154
|
+
throw new Error("JWT verification key accepted an empty signature");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { keys, sources, activeKeyId, legacy };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function requireKeyId(value: string): void {
|
|
161
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 128 || /[^A-Za-z0-9._-]/.test(value)) {
|
|
162
|
+
throw new TypeError("JWT key id must contain 1 to 128 ASCII letters, digits, dots, underscores or hyphens");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { base64UrlDecode, base64UrlDecodeToString } from "./base64url";
|
|
2
|
+
import { RegisteredClaims, TOKEN_USE_CLAIM, type JwtHeader, type JwtPayload, type TokenUse } from "./claims";
|
|
3
|
+
import {
|
|
4
|
+
JwtAlgorithmError,
|
|
5
|
+
JwtClaimError,
|
|
6
|
+
JwtExpiredError,
|
|
7
|
+
JwtMalformedError,
|
|
8
|
+
JwtNotYetValidError,
|
|
9
|
+
JwtSignatureError,
|
|
10
|
+
} from "./errors";
|
|
11
|
+
import type { SigningAlgorithm } from "./signing/SigningAlgorithm";
|
|
12
|
+
import { JwtKeyRing } from "./JwtKeyRing";
|
|
13
|
+
import { resolveMaxTokenLength } from "./limits";
|
|
14
|
+
|
|
15
|
+
/** Default leeway (seconds) for `exp`/`nbf` to tolerate clock drift. */
|
|
16
|
+
export const DEFAULT_CLOCK_SKEW_SECONDS = 60;
|
|
17
|
+
|
|
18
|
+
export interface JwtValidationOptions {
|
|
19
|
+
/** Require `iss` to equal this value. */
|
|
20
|
+
readonly issuer?: string;
|
|
21
|
+
/** Require `aud` to contain at least one of these values. */
|
|
22
|
+
readonly audience?: string | readonly string[];
|
|
23
|
+
/** Require the `token_use` claim to equal this value. */
|
|
24
|
+
readonly expectedTokenUse?: TokenUse;
|
|
25
|
+
/** Finite, non-negative leeway in seconds (default {@link DEFAULT_CLOCK_SKEW_SECONDS}). */
|
|
26
|
+
readonly clockSkewSeconds?: number;
|
|
27
|
+
/** Require `exp` to be present as a NumericDate. Default true. */
|
|
28
|
+
readonly requireExpiration?: boolean;
|
|
29
|
+
/** Maximum compact JWS length, checked before parsing or crypto. Default 16384. */
|
|
30
|
+
readonly maxTokenLength?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A successfully verified token: decoded header and payload. */
|
|
34
|
+
export interface VerifiedToken {
|
|
35
|
+
readonly header: JwtHeader;
|
|
36
|
+
readonly payload: JwtPayload;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Verifies a compact JWS and validates its registered claims.
|
|
41
|
+
*
|
|
42
|
+
* Invalid tokens throw a {@link JwtError} subclass. Operational failures of
|
|
43
|
+
* the configured signing strategy propagate unchanged. The `alg` header must match the configured
|
|
44
|
+
* algorithm, which blocks algorithm-confusion attacks. Bound to one
|
|
45
|
+
* {@link SigningAlgorithm}; create separate validators for separate keys.
|
|
46
|
+
*/
|
|
47
|
+
export class JwtValidator {
|
|
48
|
+
private readonly clockSkewSeconds: number;
|
|
49
|
+
private readonly options: JwtValidationOptions;
|
|
50
|
+
private readonly maxTokenLength: number;
|
|
51
|
+
|
|
52
|
+
public constructor(
|
|
53
|
+
private readonly algorithm: SigningAlgorithm | JwtKeyRing,
|
|
54
|
+
options: JwtValidationOptions = {},
|
|
55
|
+
) {
|
|
56
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
57
|
+
throw new TypeError("JWT validation options must be an object");
|
|
58
|
+
}
|
|
59
|
+
const snapshot = { ...options };
|
|
60
|
+
if (snapshot.issuer !== undefined && (typeof snapshot.issuer !== "string" || snapshot.issuer.length === 0)) {
|
|
61
|
+
throw new TypeError("JWT issuer must be a non-empty string");
|
|
62
|
+
}
|
|
63
|
+
if (snapshot.audience !== undefined) {
|
|
64
|
+
const values = typeof snapshot.audience === "string" ? [snapshot.audience]
|
|
65
|
+
: Array.isArray(snapshot.audience) ? [...snapshot.audience] : snapshot.audience;
|
|
66
|
+
if (!Array.isArray(values) || values.length === 0 || values.some(value => typeof value !== "string" || value.length === 0)) {
|
|
67
|
+
throw new TypeError("JWT audience must be a non-empty string or array of non-empty strings");
|
|
68
|
+
}
|
|
69
|
+
if (Array.isArray(snapshot.audience)) snapshot.audience = Object.freeze(values);
|
|
70
|
+
}
|
|
71
|
+
if (snapshot.expectedTokenUse !== undefined && snapshot.expectedTokenUse !== "access" && snapshot.expectedTokenUse !== "refresh") {
|
|
72
|
+
throw new TypeError("JWT expectedTokenUse must be access or refresh");
|
|
73
|
+
}
|
|
74
|
+
if (snapshot.requireExpiration !== undefined && typeof snapshot.requireExpiration !== "boolean") {
|
|
75
|
+
throw new TypeError("JWT requireExpiration must be a boolean");
|
|
76
|
+
}
|
|
77
|
+
const skew = snapshot.clockSkewSeconds === undefined ? DEFAULT_CLOCK_SKEW_SECONDS : snapshot.clockSkewSeconds;
|
|
78
|
+
if (typeof skew !== "number" || !Number.isFinite(skew) || skew < 0) {
|
|
79
|
+
throw new RangeError("JWT clockSkewSeconds must be finite and non-negative");
|
|
80
|
+
}
|
|
81
|
+
this.options = Object.freeze(snapshot);
|
|
82
|
+
this.clockSkewSeconds = skew;
|
|
83
|
+
this.maxTokenLength = resolveMaxTokenLength(snapshot.maxTokenLength);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public async validate(token: string): Promise<VerifiedToken> {
|
|
87
|
+
if (typeof token !== "string") {
|
|
88
|
+
throw new JwtMalformedError("Malformed JWT: expected a string");
|
|
89
|
+
}
|
|
90
|
+
if (token.length > this.maxTokenLength) throw new JwtMalformedError("JWT exceeds maxTokenLength");
|
|
91
|
+
const parts = token.split(".");
|
|
92
|
+
if (parts.length !== 3 || parts.some(part => part.length === 0)) {
|
|
93
|
+
throw new JwtMalformedError("Malformed JWT: expected three non-empty segments");
|
|
94
|
+
}
|
|
95
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts as [string, string, string];
|
|
96
|
+
|
|
97
|
+
const header = this.parseSegment<JwtHeader & Record<string, unknown>>(encodedHeader, "header");
|
|
98
|
+
if (typeof header.alg !== "string" || header.alg.length === 0) {
|
|
99
|
+
throw new JwtMalformedError("Malformed JWT algorithm");
|
|
100
|
+
}
|
|
101
|
+
for (const field of ["kid", "typ", "cty"] as const) {
|
|
102
|
+
if (header[field] !== undefined && typeof header[field] !== "string") {
|
|
103
|
+
throw new JwtMalformedError(`Malformed JWT ${field}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (header.crit !== undefined) {
|
|
107
|
+
throw new JwtMalformedError("JWT critical extensions are not supported");
|
|
108
|
+
}
|
|
109
|
+
if (header.b64 !== undefined && header.b64 !== true) {
|
|
110
|
+
throw new JwtMalformedError("JWT unencoded payloads are not supported");
|
|
111
|
+
}
|
|
112
|
+
const algorithm = this.algorithm instanceof JwtKeyRing ? this.algorithm.verificationKey(header.kid, header.alg) : this.algorithm;
|
|
113
|
+
if (header.alg !== algorithm.alg) {
|
|
114
|
+
throw new JwtAlgorithmError(`Unexpected algorithm '${header.alg}', expected '${algorithm.alg}'`);
|
|
115
|
+
}
|
|
116
|
+
if (!(this.algorithm instanceof JwtKeyRing) && algorithm.keyId !== undefined && header.kid !== algorithm.keyId) {
|
|
117
|
+
throw new JwtClaimError("kid", "key id mismatch");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
121
|
+
let signature: Uint8Array;
|
|
122
|
+
try {
|
|
123
|
+
signature = base64UrlDecode(encodedSignature);
|
|
124
|
+
} catch {
|
|
125
|
+
throw new JwtMalformedError("Malformed JWT signature encoding");
|
|
126
|
+
}
|
|
127
|
+
if (!(await algorithm.verify(signingInput, signature))) {
|
|
128
|
+
throw new JwtSignatureError();
|
|
129
|
+
}
|
|
130
|
+
if (this.algorithm instanceof JwtKeyRing) {
|
|
131
|
+
this.algorithm.assertCurrent(algorithm);
|
|
132
|
+
// Recheck migration cutoff/policy after crypto, including a concurrent policy change.
|
|
133
|
+
if (this.algorithm.verificationKey(header.kid, header.alg) !== algorithm) throw new JwtClaimError("kid", "key policy changed during the operation");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const payload = this.parseSegment<JwtPayload>(encodedPayload, "payload");
|
|
137
|
+
this.validateClaims(payload);
|
|
138
|
+
return { header, payload };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private parseSegment<T>(segment: string, label: string): T {
|
|
142
|
+
try {
|
|
143
|
+
const value: unknown = JSON.parse(base64UrlDecodeToString(segment));
|
|
144
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
145
|
+
throw new TypeError("Expected a JSON object");
|
|
146
|
+
}
|
|
147
|
+
return value as T;
|
|
148
|
+
} catch {
|
|
149
|
+
throw new JwtMalformedError(`Malformed JWT ${label}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private validateClaims(payload: JwtPayload): void {
|
|
154
|
+
const now = Date.now() / 1000;
|
|
155
|
+
|
|
156
|
+
for (const claim of [RegisteredClaims.Issuer, RegisteredClaims.Subject, RegisteredClaims.JwtId]) {
|
|
157
|
+
if (payload[claim] !== undefined && typeof payload[claim] !== "string") {
|
|
158
|
+
throw new JwtClaimError(claim, "must be a string");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const audience = payload[RegisteredClaims.Audience];
|
|
162
|
+
if (audience !== undefined && typeof audience !== "string" &&
|
|
163
|
+
(!Array.isArray(audience) || audience.length === 0 || audience.some(value => typeof value !== "string"))) {
|
|
164
|
+
throw new JwtClaimError(RegisteredClaims.Audience, "must be a string or non-empty array of strings");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (this.options.issuer !== undefined && payload[RegisteredClaims.Issuer] !== this.options.issuer) {
|
|
168
|
+
throw new JwtClaimError(RegisteredClaims.Issuer, "issuer mismatch");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (this.options.audience !== undefined) {
|
|
172
|
+
this.assertAudience(payload[RegisteredClaims.Audience]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (this.options.expectedTokenUse !== undefined && payload[TOKEN_USE_CLAIM] !== this.options.expectedTokenUse) {
|
|
176
|
+
throw new JwtClaimError(TOKEN_USE_CLAIM, `expected '${this.options.expectedTokenUse}'`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const exp = this.numericDate(payload, RegisteredClaims.ExpiresAt);
|
|
180
|
+
if (exp === undefined) {
|
|
181
|
+
if (this.options.requireExpiration !== false) {
|
|
182
|
+
throw new JwtClaimError(RegisteredClaims.ExpiresAt, "expiration is required");
|
|
183
|
+
}
|
|
184
|
+
} else if (now - this.clockSkewSeconds >= exp) {
|
|
185
|
+
throw new JwtExpiredError();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const nbf = this.numericDate(payload, RegisteredClaims.NotBefore);
|
|
189
|
+
if (nbf !== undefined && now + this.clockSkewSeconds < nbf) {
|
|
190
|
+
throw new JwtNotYetValidError();
|
|
191
|
+
}
|
|
192
|
+
this.numericDate(payload, RegisteredClaims.IssuedAt);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private numericDate(payload: JwtPayload, claim: string): number | undefined {
|
|
196
|
+
const value = payload[claim];
|
|
197
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value))) {
|
|
198
|
+
throw new JwtClaimError(claim, "must be a finite NumericDate");
|
|
199
|
+
}
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private assertAudience(actual: JwtPayload[string]): void {
|
|
204
|
+
const expected = typeof this.options.audience === "string" ? [this.options.audience] : this.options.audience!;
|
|
205
|
+
const present = typeof actual === "string" ? [actual] : Array.isArray(actual) ? actual : [];
|
|
206
|
+
if (!present.some((value) => expected.includes(value))) {
|
|
207
|
+
throw new JwtClaimError(RegisteredClaims.Audience, "audience mismatch");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|