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,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RegisteredClaims,
|
|
3
|
+
TOKEN_USE_CLAIM,
|
|
4
|
+
type CustomClaims,
|
|
5
|
+
type JwtPayload,
|
|
6
|
+
type TokenUse,
|
|
7
|
+
} from "./claims";
|
|
8
|
+
import { JwtEncoder } from "./JwtEncoder";
|
|
9
|
+
import { JwtValidator, type VerifiedToken } from "./JwtValidator";
|
|
10
|
+
import { JwtClaimError } from "./errors";
|
|
11
|
+
import type { SigningAlgorithm } from "./signing/SigningAlgorithm";
|
|
12
|
+
import type { JwtKeyRing } from "./JwtKeyRing";
|
|
13
|
+
|
|
14
|
+
export interface TokenIssuerConfig {
|
|
15
|
+
/** `iss` claim written to every token of this kind. */
|
|
16
|
+
readonly issuer: string;
|
|
17
|
+
/**
|
|
18
|
+
* `aud` claim binding tokens to this kind (e.g. `user`, `admin`, `employee`).
|
|
19
|
+
* Tokens of one kind never validate against another kind's audience.
|
|
20
|
+
*/
|
|
21
|
+
readonly audience: string;
|
|
22
|
+
/** Signs and verifies tokens of this kind. Use a distinct key per kind. */
|
|
23
|
+
readonly algorithm: SigningAlgorithm | JwtKeyRing;
|
|
24
|
+
/** Optional separate algorithm for refresh tokens (defaults to {@link algorithm}). */
|
|
25
|
+
readonly refreshAlgorithm?: SigningAlgorithm | JwtKeyRing;
|
|
26
|
+
/** Finite, positive access token lifetime in seconds. */
|
|
27
|
+
readonly accessTtlSeconds: number;
|
|
28
|
+
/** Finite, positive refresh token lifetime in seconds. */
|
|
29
|
+
readonly refreshTtlSeconds: number;
|
|
30
|
+
/** Finite, non-negative clock skew leeway for validation (seconds). */
|
|
31
|
+
readonly clockSkewSeconds?: number;
|
|
32
|
+
/** Bound both issued and received compact JWS tokens. Default 16384. */
|
|
33
|
+
readonly maxTokenLength?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** An access + refresh token pair, shaped for an OAuth-style response. */
|
|
37
|
+
export interface TokenPair {
|
|
38
|
+
readonly accessToken: string;
|
|
39
|
+
readonly refreshToken: string;
|
|
40
|
+
readonly tokenType: "Bearer";
|
|
41
|
+
/** Access token lifetime in seconds. */
|
|
42
|
+
readonly expiresIn: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Issues and verifies access/refresh tokens for a single audience ("kind").
|
|
47
|
+
*
|
|
48
|
+
* Access and refresh tokens are both signed JWTs (stateless): they carry a
|
|
49
|
+
* `token_use` claim so a refresh token can never be presented as an access
|
|
50
|
+
* token and vice versa. Each kind has its own signing key and `aud`, so a
|
|
51
|
+
* token minted for one kind (e.g. `user`) is cryptographically rejected by
|
|
52
|
+
* another kind's validator (e.g. `admin`).
|
|
53
|
+
*
|
|
54
|
+
* Rotation here is stateless — {@link rotate} verifies a refresh token and
|
|
55
|
+
* mints a fresh pair. Reuse detection / revocation requires a store and is
|
|
56
|
+
* intentionally left to the application layer.
|
|
57
|
+
*/
|
|
58
|
+
export class TokenIssuer {
|
|
59
|
+
private readonly accessEncoder: JwtEncoder;
|
|
60
|
+
private readonly refreshEncoder: JwtEncoder;
|
|
61
|
+
private readonly accessValidator: JwtValidator;
|
|
62
|
+
private readonly refreshValidator: JwtValidator;
|
|
63
|
+
private readonly config: TokenIssuerConfig;
|
|
64
|
+
|
|
65
|
+
public constructor(config: TokenIssuerConfig) {
|
|
66
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) {
|
|
67
|
+
throw new TypeError("TokenIssuer config must be an object");
|
|
68
|
+
}
|
|
69
|
+
this.config = Object.freeze({ ...config });
|
|
70
|
+
for (const field of ["issuer", "audience"] as const) {
|
|
71
|
+
if (typeof this.config[field] !== "string" || this.config[field].length === 0) {
|
|
72
|
+
throw new TypeError(`TokenIssuer ${field} must be a non-empty string`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const field of ["accessTtlSeconds", "refreshTtlSeconds"] as const) {
|
|
76
|
+
const value = this.config[field];
|
|
77
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
78
|
+
throw new RangeError(`TokenIssuer ${field} must be finite and positive`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const refreshAlgorithm = this.config.refreshAlgorithm === undefined ? this.config.algorithm : this.config.refreshAlgorithm;
|
|
82
|
+
this.accessEncoder = new JwtEncoder(this.config.algorithm, this.config);
|
|
83
|
+
this.refreshEncoder = new JwtEncoder(refreshAlgorithm, this.config);
|
|
84
|
+
this.accessValidator = this.buildValidator(this.config.algorithm, "access");
|
|
85
|
+
this.refreshValidator = this.buildValidator(refreshAlgorithm, "refresh");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Mints a fresh access + refresh pair for the given subject. */
|
|
89
|
+
public async issue(subject: string, claims?: CustomClaims): Promise<TokenPair> {
|
|
90
|
+
if (typeof subject !== "string" || subject.length === 0) {
|
|
91
|
+
throw new JwtClaimError(RegisteredClaims.Subject, "must be a non-empty string");
|
|
92
|
+
}
|
|
93
|
+
if (claims !== undefined && (claims === null || typeof claims !== "object" || Array.isArray(claims))) {
|
|
94
|
+
throw new TypeError("Custom claims must be an object");
|
|
95
|
+
}
|
|
96
|
+
const [accessToken, refreshToken] = await Promise.all([
|
|
97
|
+
this.accessEncoder.encode(this.buildPayload(subject, "access", this.config.accessTtlSeconds, claims)),
|
|
98
|
+
this.refreshEncoder.encode(this.buildPayload(subject, "refresh", this.config.refreshTtlSeconds)),
|
|
99
|
+
]);
|
|
100
|
+
return { accessToken, refreshToken, tokenType: "Bearer", expiresIn: this.config.accessTtlSeconds };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Verifies an access token; throws a {@link JwtError} on any problem. */
|
|
104
|
+
public verifyAccess(token: string): Promise<VerifiedToken> {
|
|
105
|
+
return this.accessValidator.validate(token);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Verifies a refresh token; throws a {@link JwtError} on any problem. */
|
|
109
|
+
public verifyRefresh(token: string): Promise<VerifiedToken> {
|
|
110
|
+
return this.refreshValidator.validate(token);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Verifies a refresh token and issues a brand-new pair for the same subject.
|
|
115
|
+
* Optionally refreshes the access-token claims (e.g. updated roles).
|
|
116
|
+
*/
|
|
117
|
+
public async rotate(refreshToken: string, claims?: CustomClaims): Promise<TokenPair> {
|
|
118
|
+
const { payload } = await this.verifyRefresh(refreshToken);
|
|
119
|
+
const subject = payload[RegisteredClaims.Subject];
|
|
120
|
+
if (typeof subject !== "string" || subject.length === 0) {
|
|
121
|
+
throw new JwtClaimError(RegisteredClaims.Subject, "refresh subject is required");
|
|
122
|
+
}
|
|
123
|
+
return this.issue(subject, claims);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private buildValidator(algorithm: SigningAlgorithm | JwtKeyRing, use: TokenUse): JwtValidator {
|
|
127
|
+
return new JwtValidator(algorithm, {
|
|
128
|
+
issuer: this.config.issuer,
|
|
129
|
+
audience: this.config.audience,
|
|
130
|
+
expectedTokenUse: use,
|
|
131
|
+
clockSkewSeconds: this.config.clockSkewSeconds,
|
|
132
|
+
maxTokenLength: this.config.maxTokenLength,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private buildPayload(subject: string, use: TokenUse, ttlSeconds: number, claims?: CustomClaims): JwtPayload {
|
|
137
|
+
const now = Date.now() / 1000;
|
|
138
|
+
return {
|
|
139
|
+
...claims,
|
|
140
|
+
[RegisteredClaims.Subject]: subject,
|
|
141
|
+
[RegisteredClaims.Issuer]: this.config.issuer,
|
|
142
|
+
[RegisteredClaims.Audience]: this.config.audience,
|
|
143
|
+
[RegisteredClaims.IssuedAt]: now,
|
|
144
|
+
[RegisteredClaims.ExpiresAt]: now + ttlSeconds,
|
|
145
|
+
[RegisteredClaims.JwtId]: crypto.randomUUID(),
|
|
146
|
+
[TOKEN_USE_CLAIM]: use,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { TokenIssuer, type TokenIssuerConfig } from "./TokenIssuer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Registry of {@link TokenIssuer}s keyed by token kind.
|
|
5
|
+
*
|
|
6
|
+
* Each kind (e.g. `user`, `admin`, `employee`) is fully isolated: its own
|
|
7
|
+
* signing key and audience. Adding a new kind is purely additive — extend the
|
|
8
|
+
* config map, no existing code changes. The generic key type `K` gives callers
|
|
9
|
+
* compile-time safety over which kinds exist.
|
|
10
|
+
*
|
|
11
|
+
* The concrete kinds are defined by the application, not this library. Pass a
|
|
12
|
+
* string-literal union as `K` for compile-time safety over which kinds exist:
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* type Kind = "user" | "admin";
|
|
16
|
+
* const tokens = new TokenService<Kind>({
|
|
17
|
+
* user: { issuer, audience: "user", algorithm: userKey, accessTtlSeconds: 900, refreshTtlSeconds: 1_209_600 },
|
|
18
|
+
* admin: { issuer, audience: "admin", algorithm: adminKey, accessTtlSeconds: 600, refreshTtlSeconds: 86_400 },
|
|
19
|
+
* });
|
|
20
|
+
* const pair = await tokens.forKind("user").issue("user-42");
|
|
21
|
+
*/
|
|
22
|
+
export class TokenService<K extends string = string> {
|
|
23
|
+
private readonly issuers: ReadonlyMap<K, TokenIssuer>;
|
|
24
|
+
|
|
25
|
+
public constructor(kinds: Readonly<Record<K, TokenIssuerConfig>>) {
|
|
26
|
+
const entries = (Object.entries(kinds) as Array<[K, TokenIssuerConfig]>).map(
|
|
27
|
+
([kind, config]) => [kind, new TokenIssuer(config)] as const,
|
|
28
|
+
);
|
|
29
|
+
if (entries.length === 0) {
|
|
30
|
+
throw new Error("TokenService requires at least one token kind");
|
|
31
|
+
}
|
|
32
|
+
this.issuers = new Map(entries);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Returns the issuer for a kind; throws if the kind is not registered. */
|
|
36
|
+
public forKind(kind: K): TokenIssuer {
|
|
37
|
+
const issuer = this.issuers.get(kind);
|
|
38
|
+
if (issuer === undefined) {
|
|
39
|
+
throw new Error(`Unknown token kind '${kind}'`);
|
|
40
|
+
}
|
|
41
|
+
return issuer;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public has(kind: K): boolean {
|
|
45
|
+
return this.issuers.has(kind);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public kinds(): readonly K[] {
|
|
49
|
+
return [...this.issuers.keys()];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const encoder = new TextEncoder();
|
|
2
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
3
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4
|
+
|
|
5
|
+
/** RFC 4648 base64url encode without padding. Hot path — reuses TextEncoder. */
|
|
6
|
+
export function base64UrlEncode(bytes: Uint8Array): string {
|
|
7
|
+
let binary = "";
|
|
8
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
9
|
+
binary += String.fromCharCode(bytes[index]!);
|
|
10
|
+
}
|
|
11
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Strict, canonical RFC 4648 base64url without padding. Empty input decodes to empty bytes. */
|
|
15
|
+
export function base64UrlDecode(value: string): Uint8Array {
|
|
16
|
+
if (typeof value !== "string" || /[^A-Za-z0-9_-]/.test(value) || value.length % 4 === 1) {
|
|
17
|
+
throw new TypeError("Malformed base64url");
|
|
18
|
+
}
|
|
19
|
+
const remainder = value.length % 4;
|
|
20
|
+
const last = alphabet.indexOf(value.at(-1) ?? "");
|
|
21
|
+
if ((remainder === 2 && (last & 15) !== 0) || (remainder === 3 && (last & 3) !== 0)) {
|
|
22
|
+
throw new TypeError("Non-canonical base64url padding bits");
|
|
23
|
+
}
|
|
24
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
25
|
+
const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - (normalized.length % 4));
|
|
26
|
+
const binary = atob(normalized + padding);
|
|
27
|
+
const bytes = new Uint8Array(binary.length);
|
|
28
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
29
|
+
bytes[index] = binary.charCodeAt(index);
|
|
30
|
+
}
|
|
31
|
+
return bytes;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function base64UrlEncodeString(value: string): string {
|
|
35
|
+
return base64UrlEncode(encoder.encode(value));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function base64UrlDecodeToString(value: string): string {
|
|
39
|
+
return decoder.decode(base64UrlDecode(value));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Returns the bytes as a standalone ArrayBuffer (strict BufferSource for Web Crypto). */
|
|
43
|
+
export function toArrayBuffer(view: Uint8Array): ArrayBuffer {
|
|
44
|
+
return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength) as ArrayBuffer;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Byte comparison without a match-position-dependent early return.
|
|
49
|
+
* JavaScript execution has no constant-time guarantee; JWT HMAC verification
|
|
50
|
+
* uses Web Crypto instead. Kept as a compatible public helper.
|
|
51
|
+
*/
|
|
52
|
+
export function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
53
|
+
if (a.length !== b.length) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
let diff = 0;
|
|
57
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
58
|
+
diff |= a[index]! ^ b[index]!;
|
|
59
|
+
}
|
|
60
|
+
return diff === 0;
|
|
61
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Registered claim names defined by RFC 7519. */
|
|
2
|
+
export const RegisteredClaims = {
|
|
3
|
+
Subject: "sub",
|
|
4
|
+
Issuer: "iss",
|
|
5
|
+
Audience: "aud",
|
|
6
|
+
ExpiresAt: "exp",
|
|
7
|
+
NotBefore: "nbf",
|
|
8
|
+
IssuedAt: "iat",
|
|
9
|
+
JwtId: "jti",
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
/** Custom claim distinguishing access tokens from refresh tokens. */
|
|
13
|
+
export const TOKEN_USE_CLAIM = "token_use";
|
|
14
|
+
|
|
15
|
+
/** Whether a token authorizes API calls (`access`) or rotation only (`refresh`). */
|
|
16
|
+
export type TokenUse = "access" | "refresh";
|
|
17
|
+
|
|
18
|
+
/** A JSON-serialisable claim value. */
|
|
19
|
+
export type ClaimValue = string | number | boolean | readonly string[] | null;
|
|
20
|
+
|
|
21
|
+
/** A decoded JWT payload — claim name to value. */
|
|
22
|
+
export interface JwtPayload {
|
|
23
|
+
readonly [claim: string]: ClaimValue | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Custom claims supplied by the caller when issuing a token. */
|
|
27
|
+
export interface CustomClaims {
|
|
28
|
+
readonly [claim: string]: ClaimValue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Compact JWS header. */
|
|
32
|
+
export interface JwtHeader {
|
|
33
|
+
readonly alg: string;
|
|
34
|
+
readonly typ: "JWT";
|
|
35
|
+
readonly kid?: string;
|
|
36
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Base class for every JWT failure. Maps cleanly to HTTP 401 at the edge. */
|
|
2
|
+
export class JwtError extends Error {
|
|
3
|
+
public constructor(message: string) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = new.target.name;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Token is structurally invalid (wrong segment count, bad base64, bad JSON). */
|
|
10
|
+
export class JwtMalformedError extends JwtError {}
|
|
11
|
+
|
|
12
|
+
/** Header `alg` does not match the verifying algorithm (blocks `alg` confusion). */
|
|
13
|
+
export class JwtAlgorithmError extends JwtError {}
|
|
14
|
+
|
|
15
|
+
/** Cryptographic signature check failed. */
|
|
16
|
+
export class JwtSignatureError extends JwtError {
|
|
17
|
+
public constructor() {
|
|
18
|
+
super("Invalid token signature");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** `exp` claim is in the past (accounting for clock skew). */
|
|
23
|
+
export class JwtExpiredError extends JwtError {
|
|
24
|
+
public constructor() {
|
|
25
|
+
super("Token has expired");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `nbf` claim is in the future (accounting for clock skew). */
|
|
30
|
+
export class JwtNotYetValidError extends JwtError {
|
|
31
|
+
public constructor() {
|
|
32
|
+
super("Token is not yet valid");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A registered claim (`iss`, `aud`, `token_use`, …) failed validation. */
|
|
37
|
+
export class JwtClaimError extends JwtError {
|
|
38
|
+
public constructor(
|
|
39
|
+
public readonly claim: string,
|
|
40
|
+
reason: string,
|
|
41
|
+
) {
|
|
42
|
+
super(`Invalid '${claim}' claim: ${reason}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT library for Bazis.
|
|
3
|
+
*
|
|
4
|
+
* Pure JWT logic without services, controllers or kernel dependencies: only
|
|
5
|
+
* Web Crypto and standard APIs, compatible with binary compilation
|
|
6
|
+
* (`bun build --compile`).
|
|
7
|
+
*
|
|
8
|
+
* Layers:
|
|
9
|
+
* - {@link SigningAlgorithm}: signing strategy (HS256 / RS256), caches keys.
|
|
10
|
+
* - {@link JwtKeyRing}: a set of keys addressed by `kid`, for key rotation.
|
|
11
|
+
* - {@link JwtEncoder} / {@link JwtValidator}: encoding and strict validation.
|
|
12
|
+
* - {@link TokenIssuer}: issues an access/refresh pair for one token kind.
|
|
13
|
+
* - {@link TokenService}: registry of token kinds (for example `user` / `admin`),
|
|
14
|
+
* each with its own key and `aud`; tokens of different kinds reject each other.
|
|
15
|
+
*
|
|
16
|
+
* Secure by default: fail-closed validation, an `alg` check (protection against
|
|
17
|
+
* algorithm confusion), HMAC verification through Web Crypto, and a mandatory
|
|
18
|
+
* `token_use` binding in TokenIssuer (access ≠ refresh). Contracts: MODULE.md.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export { base64UrlEncode, base64UrlDecode, base64UrlEncodeString, base64UrlDecodeToString, timingSafeEqual } from "./base64url";
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
RegisteredClaims,
|
|
25
|
+
TOKEN_USE_CLAIM,
|
|
26
|
+
type TokenUse,
|
|
27
|
+
type ClaimValue,
|
|
28
|
+
type JwtPayload,
|
|
29
|
+
type CustomClaims,
|
|
30
|
+
type JwtHeader,
|
|
31
|
+
} from "./claims";
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
JwtError,
|
|
35
|
+
JwtMalformedError,
|
|
36
|
+
JwtAlgorithmError,
|
|
37
|
+
JwtSignatureError,
|
|
38
|
+
JwtExpiredError,
|
|
39
|
+
JwtNotYetValidError,
|
|
40
|
+
JwtClaimError,
|
|
41
|
+
} from "./errors";
|
|
42
|
+
|
|
43
|
+
export type { SigningAlgorithm } from "./signing/SigningAlgorithm";
|
|
44
|
+
export { Hs256Algorithm, hs256, HS256_MIN_KEY_BYTES } from "./signing/Hs256Algorithm";
|
|
45
|
+
export { Rs256Algorithm, rs256, generateRsaKeyPairPem, type Rs256KeyMaterial } from "./signing/Rs256Algorithm";
|
|
46
|
+
export { decodePem, decodePemToBuffer, encodePem } from "./signing/pem";
|
|
47
|
+
|
|
48
|
+
export { JwtEncoder } from "./JwtEncoder";
|
|
49
|
+
export { JwtKeyRing, type JwtKeyEntry, type JwtKeyRingConfig } from "./JwtKeyRing";
|
|
50
|
+
export { DEFAULT_MAX_TOKEN_LENGTH } from "./limits";
|
|
51
|
+
export {
|
|
52
|
+
JwtValidator,
|
|
53
|
+
DEFAULT_CLOCK_SKEW_SECONDS,
|
|
54
|
+
type JwtValidationOptions,
|
|
55
|
+
type VerifiedToken,
|
|
56
|
+
} from "./JwtValidator";
|
|
57
|
+
|
|
58
|
+
export { TokenIssuer, type TokenIssuerConfig, type TokenPair } from "./TokenIssuer";
|
|
59
|
+
export { TokenService } from "./TokenService";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Compact JWS is ASCII. Bound work before splitting, decoding or cryptography. */
|
|
2
|
+
export const DEFAULT_MAX_TOKEN_LENGTH = 16_384;
|
|
3
|
+
|
|
4
|
+
export function resolveMaxTokenLength(value: number | undefined): number {
|
|
5
|
+
if (value === undefined) return DEFAULT_MAX_TOKEN_LENGTH;
|
|
6
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new RangeError("JWT maxTokenLength must be a positive safe integer");
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { toArrayBuffer } from "../base64url";
|
|
2
|
+
import type { SigningAlgorithm } from "./SigningAlgorithm";
|
|
3
|
+
|
|
4
|
+
const encoder = new TextEncoder();
|
|
5
|
+
|
|
6
|
+
/** Minimum key length for HS256 — matches the SHA-256 output size (RFC 2104). */
|
|
7
|
+
export const HS256_MIN_KEY_BYTES = 32;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Symmetric HMAC-SHA256 (HS256).
|
|
11
|
+
*
|
|
12
|
+
* The same key both signs and verifies, so it is suitable when a single
|
|
13
|
+
* trusted party issues and validates tokens. The imported {@link CryptoKey}
|
|
14
|
+
* is cached after first use. Caller-owned key bytes are copied immediately.
|
|
15
|
+
*/
|
|
16
|
+
export class Hs256Algorithm implements SigningAlgorithm {
|
|
17
|
+
public readonly alg = "HS256";
|
|
18
|
+
public readonly canSign = true;
|
|
19
|
+
|
|
20
|
+
private readonly keyBytes: Uint8Array;
|
|
21
|
+
private cryptoKey?: CryptoKey;
|
|
22
|
+
|
|
23
|
+
public constructor(secret: string | Uint8Array) {
|
|
24
|
+
if (typeof secret !== "string" && !(secret instanceof Uint8Array)) {
|
|
25
|
+
throw new TypeError("HS256 secret must be a string or Uint8Array");
|
|
26
|
+
}
|
|
27
|
+
this.keyBytes = typeof secret === "string" ? encoder.encode(secret) : new Uint8Array(secret);
|
|
28
|
+
if (this.keyBytes.length < HS256_MIN_KEY_BYTES) {
|
|
29
|
+
throw new RangeError(`HS256 secret must be at least ${HS256_MIN_KEY_BYTES} bytes`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public async sign(signingInput: string): Promise<Uint8Array> {
|
|
34
|
+
const key = await this.importKey();
|
|
35
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(signingInput));
|
|
36
|
+
return new Uint8Array(signature);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public async verify(signingInput: string, signature: Uint8Array): Promise<boolean> {
|
|
40
|
+
const key = await this.importKey();
|
|
41
|
+
return crypto.subtle.verify("HMAC", key, toArrayBuffer(signature), encoder.encode(signingInput));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private async importKey(): Promise<CryptoKey> {
|
|
45
|
+
if (this.cryptoKey !== undefined) {
|
|
46
|
+
return this.cryptoKey;
|
|
47
|
+
}
|
|
48
|
+
this.cryptoKey = await crypto.subtle.importKey(
|
|
49
|
+
"raw",
|
|
50
|
+
toArrayBuffer(this.keyBytes),
|
|
51
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
52
|
+
false,
|
|
53
|
+
["sign", "verify"],
|
|
54
|
+
);
|
|
55
|
+
return this.cryptoKey;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Convenience factory for {@link Hs256Algorithm}. */
|
|
60
|
+
export function hs256(secret: string | Uint8Array): Hs256Algorithm {
|
|
61
|
+
return new Hs256Algorithm(secret);
|
|
62
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { toArrayBuffer } from "../base64url";
|
|
2
|
+
import { decodePemToBuffer, encodePem } from "./pem";
|
|
3
|
+
import type { SigningAlgorithm } from "./SigningAlgorithm";
|
|
4
|
+
|
|
5
|
+
const RSA_PARAMS = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as const;
|
|
6
|
+
const encoder = new TextEncoder();
|
|
7
|
+
|
|
8
|
+
export interface Rs256KeyMaterial {
|
|
9
|
+
/** PKCS#8 private key PEM — required to sign, optional for verify-only. */
|
|
10
|
+
readonly privateKeyPem?: string;
|
|
11
|
+
/** SPKI public key PEM — required to verify. */
|
|
12
|
+
readonly publicKeyPem: string;
|
|
13
|
+
/** `kid` header value and verify-time match (default: no `kid`). */
|
|
14
|
+
readonly keyId?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Asymmetric RS256 (RSASSA-PKCS1-v1.5 + SHA-256).
|
|
19
|
+
*
|
|
20
|
+
* The private key signs; the public key verifies. Verifiers can hold only the
|
|
21
|
+
* public key, which is the recommended setup for separating an auth issuer
|
|
22
|
+
* from resource services. Imported keys are cached for the hot path.
|
|
23
|
+
*/
|
|
24
|
+
export class Rs256Algorithm implements SigningAlgorithm {
|
|
25
|
+
public readonly alg = "RS256";
|
|
26
|
+
public readonly canSign: boolean;
|
|
27
|
+
public readonly keyId?: string;
|
|
28
|
+
|
|
29
|
+
private cachedPrivate?: CryptoKey;
|
|
30
|
+
private cachedPublic?: CryptoKey;
|
|
31
|
+
private readonly keys: Rs256KeyMaterial;
|
|
32
|
+
|
|
33
|
+
public constructor(keys: Rs256KeyMaterial) {
|
|
34
|
+
if (keys === null || typeof keys !== "object" || Array.isArray(keys)) {
|
|
35
|
+
throw new TypeError("RS256 key material must be an object");
|
|
36
|
+
}
|
|
37
|
+
this.keys = Object.freeze({ ...keys });
|
|
38
|
+
for (const field of ["publicKeyPem", "privateKeyPem", "keyId"] as const) {
|
|
39
|
+
const value = this.keys[field];
|
|
40
|
+
if (value === undefined && field !== "publicKeyPem") continue;
|
|
41
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
42
|
+
throw new TypeError(`RS256 ${field} must be a non-empty string`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
this.canSign = this.keys.privateKeyPem !== undefined;
|
|
46
|
+
this.keyId = this.keys.keyId;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public async sign(signingInput: string): Promise<Uint8Array> {
|
|
50
|
+
const key = await this.importPrivateKey();
|
|
51
|
+
const signature = await crypto.subtle.sign(RSA_PARAMS, key, encoder.encode(signingInput));
|
|
52
|
+
return new Uint8Array(signature);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public async verify(signingInput: string, signature: Uint8Array): Promise<boolean> {
|
|
56
|
+
const key = await this.importPublicKey();
|
|
57
|
+
return crypto.subtle.verify(RSA_PARAMS, key, toArrayBuffer(signature), encoder.encode(signingInput));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Exports the public key as a JWK (for serving `/.well-known/jwks.json`). */
|
|
61
|
+
public async exportPublicJwk(): Promise<Record<string, string>> {
|
|
62
|
+
const key = await this.importPublicKey();
|
|
63
|
+
const jwk = (await crypto.subtle.exportKey("jwk", key)) as { n?: string; e?: string };
|
|
64
|
+
return {
|
|
65
|
+
kty: "RSA",
|
|
66
|
+
use: "sig",
|
|
67
|
+
alg: "RS256",
|
|
68
|
+
n: jwk.n!,
|
|
69
|
+
e: jwk.e!,
|
|
70
|
+
...(this.keyId === undefined ? {} : { kid: this.keyId }),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async importPrivateKey(): Promise<CryptoKey> {
|
|
75
|
+
if (this.cachedPrivate !== undefined) {
|
|
76
|
+
return this.cachedPrivate;
|
|
77
|
+
}
|
|
78
|
+
if (this.keys.privateKeyPem === undefined) {
|
|
79
|
+
throw new Error("RS256 private key PEM is required for signing");
|
|
80
|
+
}
|
|
81
|
+
const key = await crypto.subtle.importKey(
|
|
82
|
+
"pkcs8",
|
|
83
|
+
decodePemToBuffer(this.keys.privateKeyPem),
|
|
84
|
+
RSA_PARAMS,
|
|
85
|
+
false,
|
|
86
|
+
["sign"],
|
|
87
|
+
);
|
|
88
|
+
return this.cachedPrivate = this.requireStrongKey(key);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async importPublicKey(): Promise<CryptoKey> {
|
|
92
|
+
if (this.cachedPublic !== undefined) {
|
|
93
|
+
return this.cachedPublic;
|
|
94
|
+
}
|
|
95
|
+
const key = await crypto.subtle.importKey(
|
|
96
|
+
"spki",
|
|
97
|
+
decodePemToBuffer(this.keys.publicKeyPem),
|
|
98
|
+
RSA_PARAMS,
|
|
99
|
+
true,
|
|
100
|
+
["verify"],
|
|
101
|
+
);
|
|
102
|
+
return this.cachedPublic = this.requireStrongKey(key);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private requireStrongKey(key: CryptoKey): CryptoKey {
|
|
106
|
+
const parameters = key.algorithm;
|
|
107
|
+
if (!("modulusLength" in parameters) || typeof parameters.modulusLength !== "number" ||
|
|
108
|
+
!Number.isInteger(parameters.modulusLength) || parameters.modulusLength < 2048) {
|
|
109
|
+
throw new RangeError("RS256 keys must be at least 2048 bits");
|
|
110
|
+
}
|
|
111
|
+
return key;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Convenience factory for {@link Rs256Algorithm}. */
|
|
116
|
+
export function rs256(keys: Rs256KeyMaterial): Rs256Algorithm {
|
|
117
|
+
return new Rs256Algorithm(keys);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Generates an RSA-2048 key pair as PEM strings (bootstrap / tests). */
|
|
121
|
+
export async function generateRsaKeyPairPem(): Promise<{ privateKeyPem: string; publicKeyPem: string }> {
|
|
122
|
+
const pair = await crypto.subtle.generateKey(
|
|
123
|
+
{ name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
|
|
124
|
+
true,
|
|
125
|
+
["sign", "verify"],
|
|
126
|
+
);
|
|
127
|
+
const privateDer = new Uint8Array(await crypto.subtle.exportKey("pkcs8", pair.privateKey));
|
|
128
|
+
const publicDer = new Uint8Array(await crypto.subtle.exportKey("spki", pair.publicKey));
|
|
129
|
+
return {
|
|
130
|
+
privateKeyPem: encodePem("PRIVATE KEY", privateDer),
|
|
131
|
+
publicKeyPem: encodePem("PUBLIC KEY", publicDer),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pluggable JWT signing/verification strategy (HS256, RS256, …).
|
|
3
|
+
*
|
|
4
|
+
* Implementations must be stateless from the caller's perspective and may
|
|
5
|
+
* cache imported {@link CryptoKey}s internally to keep the verify path fast.
|
|
6
|
+
*/
|
|
7
|
+
export interface SigningAlgorithm {
|
|
8
|
+
/** JWS `alg` header value (e.g. `HS256`, `RS256`). */
|
|
9
|
+
readonly alg: string;
|
|
10
|
+
/** Optional `kid` written to the header and matched on verify. */
|
|
11
|
+
readonly keyId?: string;
|
|
12
|
+
/** Whether this instance holds the secret/private key needed to sign. */
|
|
13
|
+
readonly canSign: boolean;
|
|
14
|
+
/** Produces the signature bytes for the `header.payload` signing input. */
|
|
15
|
+
sign(signingInput: string): Promise<Uint8Array>;
|
|
16
|
+
/** Constant-time / cryptographic verification of a detached signature. */
|
|
17
|
+
verify(signingInput: string, signature: Uint8Array): Promise<boolean>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Strips PEM armor and decodes the base64 payload to DER bytes. */
|
|
2
|
+
export function decodePem(pem: string): Uint8Array {
|
|
3
|
+
const body = pem
|
|
4
|
+
.trim()
|
|
5
|
+
.split("\n")
|
|
6
|
+
.filter((line) => !line.startsWith("-----"))
|
|
7
|
+
.join("");
|
|
8
|
+
const binary = atob(body);
|
|
9
|
+
const bytes = new Uint8Array(binary.length);
|
|
10
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
11
|
+
bytes[index] = binary.charCodeAt(index);
|
|
12
|
+
}
|
|
13
|
+
return bytes;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Returns a standalone ArrayBuffer (strict BufferSource for Web Crypto import). */
|
|
17
|
+
export function decodePemToBuffer(pem: string): ArrayBuffer {
|
|
18
|
+
const bytes = decodePem(pem);
|
|
19
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Wraps DER bytes in PEM armor with the given label. */
|
|
23
|
+
export function encodePem(label: string, der: Uint8Array): string {
|
|
24
|
+
let binary = "";
|
|
25
|
+
for (let index = 0; index < der.length; index += 1) {
|
|
26
|
+
binary += String.fromCharCode(der[index]!);
|
|
27
|
+
}
|
|
28
|
+
const lines = btoa(binary).match(/.{1,64}/g) ?? [];
|
|
29
|
+
return `-----BEGIN ${label}-----\n${lines.join("\n")}\n-----END ${label}-----\n`;
|
|
30
|
+
}
|