@cosmicdrift/kumiko-framework 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -0
- package/package.json +91 -0
- package/src/__tests__/anonymous-access.integration.ts +325 -0
- package/src/__tests__/error-contract.integration.ts +435 -0
- package/src/__tests__/field-access.integration.ts +269 -0
- package/src/__tests__/full-stack.integration.ts +914 -0
- package/src/__tests__/ownership.integration.ts +449 -0
- package/src/__tests__/reference-data.integration.ts +198 -0
- package/src/__tests__/transition-guard.integration.ts +340 -0
- package/src/api/__tests__/api.test.ts +337 -0
- package/src/api/__tests__/auth-middleware-transport.test.ts +80 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +179 -0
- package/src/api/__tests__/batch.integration.ts +404 -0
- package/src/api/__tests__/body-limit.test.ts +88 -0
- package/src/api/__tests__/csrf-middleware.test.ts +97 -0
- package/src/api/__tests__/dispatcher-live.integration.ts +216 -0
- package/src/api/__tests__/metrics-endpoint.test.ts +126 -0
- package/src/api/__tests__/nested-write.integration.ts +213 -0
- package/src/api/__tests__/readiness.test.ts +76 -0
- package/src/api/__tests__/request-id-middleware.test.ts +72 -0
- package/src/api/__tests__/sse-broker.test.ts +58 -0
- package/src/api/__tests__/sse-route.test.ts +112 -0
- package/src/api/anonymous-cookie.ts +60 -0
- package/src/api/api-constants.ts +64 -0
- package/src/api/auth-middleware.ts +418 -0
- package/src/api/auth-routes.ts +982 -0
- package/src/api/csrf-middleware.ts +77 -0
- package/src/api/index.ts +31 -0
- package/src/api/jwt.ts +66 -0
- package/src/api/observability-middleware.ts +89 -0
- package/src/api/readiness.ts +132 -0
- package/src/api/request-context.ts +49 -0
- package/src/api/request-id-middleware.ts +50 -0
- package/src/api/route-registrars.ts +195 -0
- package/src/api/routes.ts +135 -0
- package/src/api/server.ts +640 -0
- package/src/api/sse-broker.ts +71 -0
- package/src/api/sse-route.ts +62 -0
- package/src/api/tokens.ts +16 -0
- package/src/db/__tests__/apply-entity-event-tenant.integration.ts +159 -0
- package/src/db/__tests__/compound-types.test.ts +114 -0
- package/src/db/__tests__/connection-options.test.ts +68 -0
- package/src/db/__tests__/cursor.test.ts +41 -0
- package/src/db/__tests__/db-helpers.test.ts +369 -0
- package/src/db/__tests__/dialect-instant.test.ts +50 -0
- package/src/db/__tests__/drizzle-helpers.integration.ts +186 -0
- package/src/db/__tests__/drizzle-table-types.test.ts +162 -0
- package/src/db/__tests__/encryption.test.ts +39 -0
- package/src/db/__tests__/event-store-executor-list.integration.ts +313 -0
- package/src/db/__tests__/event-store-executor.integration.ts +235 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.ts +304 -0
- package/src/db/__tests__/located-timestamp.test.ts +184 -0
- package/src/db/__tests__/money.test.ts +199 -0
- package/src/db/__tests__/multi-row-insert.integration.ts +76 -0
- package/src/db/__tests__/parse-auto-verb.test.ts +70 -0
- package/src/db/__tests__/required-not-null-migration-safety.integration.ts +105 -0
- package/src/db/__tests__/row-helpers.test.ts +59 -0
- package/src/db/__tests__/schema-migration.integration.ts +273 -0
- package/src/db/__tests__/table-builder-indexes.test.ts +153 -0
- package/src/db/__tests__/table-builder-required.test.ts +216 -0
- package/src/db/__tests__/tenant-db.integration.ts +606 -0
- package/src/db/__tests__/unique-violation-mapping.integration.ts +166 -0
- package/src/db/apply-entity-event.ts +188 -0
- package/src/db/assert-exists-in.ts +59 -0
- package/src/db/compound-types.ts +47 -0
- package/src/db/connection.ts +104 -0
- package/src/db/cursor.ts +83 -0
- package/src/db/dialect.ts +109 -0
- package/src/db/eagerload.ts +174 -0
- package/src/db/encryption.ts +39 -0
- package/src/db/event-store-executor.ts +906 -0
- package/src/db/index.ts +55 -0
- package/src/db/located-timestamp.ts +114 -0
- package/src/db/money.ts +120 -0
- package/src/db/pg-error.ts +46 -0
- package/src/db/reference-data.ts +77 -0
- package/src/db/row-helpers.ts +53 -0
- package/src/db/schema-inspection.ts +25 -0
- package/src/db/table-builder.ts +475 -0
- package/src/db/tenant-db.ts +434 -0
- package/src/engine/__tests__/auth-claims-registrar.test.ts +74 -0
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +108 -0
- package/src/engine/__tests__/boot-validator.test.ts +1865 -0
- package/src/engine/__tests__/build-app-schema.test.ts +154 -0
- package/src/engine/__tests__/claim-keys.test.ts +274 -0
- package/src/engine/__tests__/config-helpers.test.ts +236 -0
- package/src/engine/__tests__/effective-features.test.ts +86 -0
- package/src/engine/__tests__/engine.test.ts +1461 -0
- package/src/engine/__tests__/entity-handlers.test.ts +274 -0
- package/src/engine/__tests__/event-helpers.test.ts +68 -0
- package/src/engine/__tests__/extends-registrar.test.ts +159 -0
- package/src/engine/__tests__/factories-long-text.test.ts +84 -0
- package/src/engine/__tests__/factories-time.test.ts +158 -0
- package/src/engine/__tests__/field-predicates.test.ts +48 -0
- package/src/engine/__tests__/hook-phases.test.ts +132 -0
- package/src/engine/__tests__/identifiers.test.ts +35 -0
- package/src/engine/__tests__/lifecycle-hooks.test.ts +237 -0
- package/src/engine/__tests__/nav.test.ts +267 -0
- package/src/engine/__tests__/ownership.test.ts +421 -0
- package/src/engine/__tests__/parse-ref-target.test.ts +43 -0
- package/src/engine/__tests__/projection-helpers.test.ts +62 -0
- package/src/engine/__tests__/projection.test.ts +191 -0
- package/src/engine/__tests__/qualified-name.test.ts +264 -0
- package/src/engine/__tests__/resolve-config-or-param.test.ts +315 -0
- package/src/engine/__tests__/run-in.test.ts +38 -0
- package/src/engine/__tests__/schema-builder.test.ts +380 -0
- package/src/engine/__tests__/screen.test.ts +408 -0
- package/src/engine/__tests__/state-machine.test.ts +148 -0
- package/src/engine/__tests__/system-user.test.ts +57 -0
- package/src/engine/__tests__/validation-hooks.test.ts +71 -0
- package/src/engine/access.ts +23 -0
- package/src/engine/boot-validator.ts +1528 -0
- package/src/engine/build-app-schema.ts +125 -0
- package/src/engine/config-helpers.ts +115 -0
- package/src/engine/constants.ts +85 -0
- package/src/engine/create-app.ts +98 -0
- package/src/engine/define-feature.ts +702 -0
- package/src/engine/define-handler.ts +78 -0
- package/src/engine/define-roles.ts +19 -0
- package/src/engine/effective-features.ts +87 -0
- package/src/engine/entity-handlers.ts +364 -0
- package/src/engine/event-helpers.ts +73 -0
- package/src/engine/factories.ts +328 -0
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +416 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +197 -0
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +128 -0
- package/src/engine/feature-ast/__tests__/parse.test.ts +888 -0
- package/src/engine/feature-ast/__tests__/patch.test.ts +360 -0
- package/src/engine/feature-ast/__tests__/patcher.test.ts +469 -0
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +287 -0
- package/src/engine/feature-ast/extractors.ts +2562 -0
- package/src/engine/feature-ast/index.ts +105 -0
- package/src/engine/feature-ast/parse.ts +369 -0
- package/src/engine/feature-ast/patch.ts +525 -0
- package/src/engine/feature-ast/patcher.ts +518 -0
- package/src/engine/feature-ast/patterns.ts +434 -0
- package/src/engine/feature-ast/render.ts +602 -0
- package/src/engine/feature-ast/source-location.ts +45 -0
- package/src/engine/field-access.ts +120 -0
- package/src/engine/index.ts +254 -0
- package/src/engine/ownership.ts +337 -0
- package/src/engine/parse-ref-target.ts +22 -0
- package/src/engine/pattern-library/__tests__/library.test.ts +351 -0
- package/src/engine/pattern-library/index.ts +24 -0
- package/src/engine/pattern-library/library.ts +1117 -0
- package/src/engine/pattern-library/types.ts +255 -0
- package/src/engine/projection-helpers.ts +85 -0
- package/src/engine/qualified-name.ts +122 -0
- package/src/engine/read-claim.ts +31 -0
- package/src/engine/registry.ts +1325 -0
- package/src/engine/resolve-config-or-param.ts +153 -0
- package/src/engine/run-in.ts +29 -0
- package/src/engine/schema-builder.ts +175 -0
- package/src/engine/screen-filter-ops.ts +51 -0
- package/src/engine/state-machine.ts +70 -0
- package/src/engine/system-user.ts +32 -0
- package/src/engine/types/config.ts +306 -0
- package/src/engine/types/event-type-map.ts +37 -0
- package/src/engine/types/feature.ts +574 -0
- package/src/engine/types/fields.ts +422 -0
- package/src/engine/types/handlers.ts +742 -0
- package/src/engine/types/hooks.ts +142 -0
- package/src/engine/types/http-route.ts +54 -0
- package/src/engine/types/identifiers.ts +47 -0
- package/src/engine/types/index.ts +208 -0
- package/src/engine/types/nav.ts +46 -0
- package/src/engine/types/projection.ts +132 -0
- package/src/engine/types/relations.ts +51 -0
- package/src/engine/types/screen.ts +452 -0
- package/src/engine/types/workspace.ts +42 -0
- package/src/engine/validation.ts +33 -0
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.ts +173 -0
- package/src/entrypoint/__tests__/split-deploy.integration.ts +297 -0
- package/src/entrypoint/index.ts +442 -0
- package/src/errors/__tests__/classes.test.ts +371 -0
- package/src/errors/__tests__/write-failures.test.ts +109 -0
- package/src/errors/classes.ts +249 -0
- package/src/errors/i18n/de.yaml +83 -0
- package/src/errors/i18n/en.yaml +80 -0
- package/src/errors/index.ts +41 -0
- package/src/errors/kumiko-error.ts +67 -0
- package/src/errors/reasons.ts +36 -0
- package/src/errors/serialize.ts +136 -0
- package/src/errors/transition-details.ts +30 -0
- package/src/errors/write-error-info.ts +123 -0
- package/src/errors/zod-bridge.ts +49 -0
- package/src/event-store/__tests__/admin-api.integration.ts +361 -0
- package/src/event-store/__tests__/event-store.integration.ts +584 -0
- package/src/event-store/__tests__/get-stream-version-perf.integration.ts +83 -0
- package/src/event-store/__tests__/perf.integration.ts +255 -0
- package/src/event-store/__tests__/snapshot.integration.ts +267 -0
- package/src/event-store/__tests__/upcaster-dead-letter.integration.ts +204 -0
- package/src/event-store/__tests__/upcaster.integration.ts +460 -0
- package/src/event-store/admin-api.ts +257 -0
- package/src/event-store/archive.ts +106 -0
- package/src/event-store/errors.ts +35 -0
- package/src/event-store/event-store.ts +405 -0
- package/src/event-store/events-schema.ts +90 -0
- package/src/event-store/index.ts +50 -0
- package/src/event-store/snapshot.ts +210 -0
- package/src/event-store/upcaster-dead-letter.ts +119 -0
- package/src/event-store/upcaster.ts +147 -0
- package/src/files/__tests__/content-disposition.test.ts +123 -0
- package/src/files/__tests__/file-field-column.integration.ts +103 -0
- package/src/files/__tests__/file-field-pipeline.integration.ts +211 -0
- package/src/files/__tests__/file-handle.test.ts +122 -0
- package/src/files/__tests__/files.integration.ts +830 -0
- package/src/files/__tests__/storage-tracking.integration.ts +153 -0
- package/src/files/content-disposition.ts +55 -0
- package/src/files/file-handle.ts +63 -0
- package/src/files/file-ref-table.ts +22 -0
- package/src/files/file-routes.ts +353 -0
- package/src/files/in-memory-provider.ts +62 -0
- package/src/files/index.ts +29 -0
- package/src/files/local-provider.ts +35 -0
- package/src/files/storage-tracking.ts +60 -0
- package/src/files/types.ts +118 -0
- package/src/i18n/__tests__/i18n.test.ts +72 -0
- package/src/i18n/index.ts +29 -0
- package/src/jobs/__tests__/job-event-trigger.integration.ts +172 -0
- package/src/jobs/__tests__/job-multi-trigger.integration.ts +144 -0
- package/src/jobs/__tests__/jobs.integration.ts +566 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-runner.ts +574 -0
- package/src/lifecycle/__tests__/create-test-lifecycle.ts +19 -0
- package/src/lifecycle/__tests__/lifecycle-server.integration.ts +108 -0
- package/src/lifecycle/__tests__/lifecycle.test.ts +212 -0
- package/src/lifecycle/__tests__/signal-handlers.test.ts +106 -0
- package/src/lifecycle/index.ts +13 -0
- package/src/lifecycle/lifecycle.ts +160 -0
- package/src/lifecycle/signal-handlers.ts +62 -0
- package/src/logging/__tests__/pino-trace-bridge.test.ts +50 -0
- package/src/logging/index.ts +3 -0
- package/src/logging/pino-logger.ts +64 -0
- package/src/logging/types.ts +7 -0
- package/src/migrations/__tests__/compare-snapshots.test.ts +150 -0
- package/src/migrations/__tests__/detect-drift.integration.ts +320 -0
- package/src/migrations/__tests__/detect-projections-to-rebuild.integration.ts +134 -0
- package/src/migrations/__tests__/rebuild-marker.test.ts +79 -0
- package/src/migrations/index.ts +28 -0
- package/src/migrations/projection-detection.ts +149 -0
- package/src/migrations/rebuild-marker.ts +64 -0
- package/src/migrations/schema-drift.ts +395 -0
- package/src/observability/__tests__/console-provider.test.ts +67 -0
- package/src/observability/__tests__/metric-validator.test.ts +87 -0
- package/src/observability/__tests__/noop-provider.test.ts +82 -0
- package/src/observability/__tests__/observability.integration.ts +559 -0
- package/src/observability/__tests__/prometheus-meter.test.ts +144 -0
- package/src/observability/__tests__/recording-meter.test.ts +101 -0
- package/src/observability/__tests__/recording-tracer.test.ts +110 -0
- package/src/observability/__tests__/sensitive-filter.test.ts +98 -0
- package/src/observability/console-provider.ts +130 -0
- package/src/observability/context.ts +26 -0
- package/src/observability/fallback.ts +34 -0
- package/src/observability/ids.ts +25 -0
- package/src/observability/index.ts +79 -0
- package/src/observability/metric-validator.ts +86 -0
- package/src/observability/metrics-handle.ts +56 -0
- package/src/observability/noop-provider.ts +146 -0
- package/src/observability/prometheus-meter.ts +284 -0
- package/src/observability/recording-meter.ts +156 -0
- package/src/observability/recording-tracer.ts +198 -0
- package/src/observability/redis-wrapper.ts +132 -0
- package/src/observability/sensitive-filter.ts +108 -0
- package/src/observability/standard-metrics.ts +213 -0
- package/src/observability/types/index.ts +29 -0
- package/src/observability/types/metric.ts +56 -0
- package/src/observability/types/provider.ts +32 -0
- package/src/observability/types/span.ts +64 -0
- package/src/pipeline/__tests__/archive-stream.integration.ts +220 -0
- package/src/pipeline/__tests__/auth-claims-resolver.test.ts +279 -0
- package/src/pipeline/__tests__/cascade-handler.integration.ts +419 -0
- package/src/pipeline/__tests__/cascade-handler.test.ts +52 -0
- package/src/pipeline/__tests__/causation-chain.integration.ts +206 -0
- package/src/pipeline/__tests__/ctx-bridge.integration.ts +234 -0
- package/src/pipeline/__tests__/dispatcher.test.ts +379 -0
- package/src/pipeline/__tests__/distributed-lock.integration.ts +67 -0
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +323 -0
- package/src/pipeline/__tests__/event-dedup.integration.ts +153 -0
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +220 -0
- package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +423 -0
- package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +123 -0
- package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +290 -0
- package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +65 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.ts +287 -0
- package/src/pipeline/__tests__/event-retention.integration.ts +239 -0
- package/src/pipeline/__tests__/fetch-for-writing.integration.ts +281 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +430 -0
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +266 -0
- package/src/pipeline/__tests__/msp-error-mode.integration.ts +149 -0
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +228 -0
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +368 -0
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +341 -0
- package/src/pipeline/__tests__/perf-rebuild.integration.ts +147 -0
- package/src/pipeline/__tests__/projection-rebuild.integration.ts +551 -0
- package/src/pipeline/__tests__/query-projection.integration.ts +201 -0
- package/src/pipeline/__tests__/redis-pipeline.integration.ts +306 -0
- package/src/pipeline/append-event-core.ts +117 -0
- package/src/pipeline/auth-claims-resolver.ts +103 -0
- package/src/pipeline/cascade-handler.ts +113 -0
- package/src/pipeline/dispatcher.ts +1585 -0
- package/src/pipeline/distributed-lock.ts +37 -0
- package/src/pipeline/entity-cache.ts +113 -0
- package/src/pipeline/event-consumer-state.ts +108 -0
- package/src/pipeline/event-dedup.ts +23 -0
- package/src/pipeline/event-dispatcher.ts +1016 -0
- package/src/pipeline/event-retention.ts +154 -0
- package/src/pipeline/idempotency.ts +76 -0
- package/src/pipeline/index.ts +66 -0
- package/src/pipeline/lifecycle-pipeline.ts +409 -0
- package/src/pipeline/msp-rebuild.ts +242 -0
- package/src/pipeline/multi-stream-apply-context.ts +115 -0
- package/src/pipeline/projection-rebuild.ts +334 -0
- package/src/pipeline/projection-state.ts +72 -0
- package/src/pipeline/projections-runner.ts +56 -0
- package/src/pipeline/redis-keys.ts +11 -0
- package/src/pipeline/system-hooks.ts +190 -0
- package/src/random/__tests__/generate.test.ts +149 -0
- package/src/random/generate.ts +141 -0
- package/src/random/index.ts +8 -0
- package/src/random/words.ts +392 -0
- package/src/rate-limit/__tests__/dispatcher-l3.integration.ts +111 -0
- package/src/rate-limit/__tests__/middleware.integration.ts +189 -0
- package/src/rate-limit/__tests__/resolver.integration.ts +189 -0
- package/src/rate-limit/bucket.ts +36 -0
- package/src/rate-limit/index.ts +14 -0
- package/src/rate-limit/middleware.ts +152 -0
- package/src/rate-limit/resolver.ts +267 -0
- package/src/redis/__tests__/redis-options.test.ts +54 -0
- package/src/redis/index.ts +74 -0
- package/src/search/__tests__/meilisearch-adapter.integration.ts +236 -0
- package/src/search/__tests__/search-adapter.test.ts +256 -0
- package/src/search/in-memory-adapter.ts +123 -0
- package/src/search/index.ts +12 -0
- package/src/search/meilisearch-adapter.ts +106 -0
- package/src/search/types.ts +39 -0
- package/src/secrets/__tests__/dek-cache.test.ts +213 -0
- package/src/secrets/__tests__/env-master-key-provider.test.ts +119 -0
- package/src/secrets/__tests__/envelope.test.ts +74 -0
- package/src/secrets/__tests__/leak-guard.test.ts +92 -0
- package/src/secrets/__tests__/rotation.test.ts +149 -0
- package/src/secrets/dek-cache.ts +116 -0
- package/src/secrets/env-master-key-provider.ts +162 -0
- package/src/secrets/envelope.ts +55 -0
- package/src/secrets/index.ts +19 -0
- package/src/secrets/leak-guard.ts +87 -0
- package/src/secrets/rotation.ts +34 -0
- package/src/secrets/types.ts +107 -0
- package/src/stack/db.ts +104 -0
- package/src/stack/event-collector.ts +23 -0
- package/src/stack/index.ts +32 -0
- package/src/stack/redis.ts +44 -0
- package/src/stack/request-helper.ts +168 -0
- package/src/stack/table-helpers.ts +104 -0
- package/src/stack/test-stack.ts +357 -0
- package/src/stack/test-users.ts +37 -0
- package/src/testing/__tests__/e2e-generator.test.ts +230 -0
- package/src/testing/__tests__/ensure-entity-table.integration.ts +54 -0
- package/src/testing/access-assertions.ts +15 -0
- package/src/testing/assertions.ts +35 -0
- package/src/testing/e2e-generator.ts +465 -0
- package/src/testing/expect-error.ts +25 -0
- package/src/testing/handler-context.ts +125 -0
- package/src/testing/http-cookies.ts +52 -0
- package/src/testing/index.ts +41 -0
- package/src/testing/late-bound.ts +39 -0
- package/src/testing/mutable-master-key-provider.ts +31 -0
- package/src/testing/observability-recorder.ts +54 -0
- package/src/testing/shared-entities.ts +49 -0
- package/src/testing/utils.ts +1 -0
- package/src/testing/wait-for.ts +31 -0
- package/src/time/__tests__/polyfill.test.ts +73 -0
- package/src/time/__tests__/tz-context.test.ts +121 -0
- package/src/time/index.ts +21 -0
- package/src/time/polyfill.ts +70 -0
- package/src/time/tz-context.ts +107 -0
- package/src/ui-types/app-schema.ts +57 -0
- package/src/ui-types/index.ts +65 -0
- package/src/utils/__tests__/assert.test.ts +17 -0
- package/src/utils/__tests__/env-parse.test.ts +54 -0
- package/src/utils/assert.ts +18 -0
- package/src/utils/env-parse.ts +16 -0
- package/src/utils/ids.ts +16 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/safe-json.ts +30 -0
- package/src/utils/serialization.ts +7 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import type { Context, Next } from "hono";
|
|
2
|
+
import { getCookie } from "hono/cookie";
|
|
3
|
+
import { createAnonymousUser } from "../engine/system-user";
|
|
4
|
+
import type { SessionUser, TenantId } from "../engine/types";
|
|
5
|
+
import { parseTenantId } from "../engine/types/identifiers";
|
|
6
|
+
import { TENANT_COOKIE_NAME, TENANT_HEADER_NAME } from "./api-constants";
|
|
7
|
+
import type { JwtHelper } from "./jwt";
|
|
8
|
+
|
|
9
|
+
const USER_KEY = "pipelineUser";
|
|
10
|
+
const AUTH_TRANSPORT_KEY = "authTransport";
|
|
11
|
+
|
|
12
|
+
// Names used across middleware and auth-routes. Kept here so csrf-middleware
|
|
13
|
+
// and auth-routes import them from a single source of truth — renaming a
|
|
14
|
+
// cookie is a coordinated change across issuer, reader and deleter.
|
|
15
|
+
export const AUTH_COOKIE_NAME = "kumiko_auth";
|
|
16
|
+
export const CSRF_COOKIE_NAME = "kumiko_csrf";
|
|
17
|
+
export const CSRF_HEADER_NAME = "X-CSRF-Token";
|
|
18
|
+
|
|
19
|
+
// Which wire the current request authenticated over. Downstream
|
|
20
|
+
// csrf-middleware reads this: cookie-auth gets a CSRF-token check, bearer
|
|
21
|
+
// does not (headers aren't set cross-origin by browsers, so there is no
|
|
22
|
+
// CSRF vector on a bearer-only client).
|
|
23
|
+
export type AuthTransport = "cookie" | "bearer";
|
|
24
|
+
|
|
25
|
+
// Status of a sid from the server's perspective. The sessions feature owns
|
|
26
|
+
// the DB-backed implementation; middleware just consults whatever function
|
|
27
|
+
// the app wires in.
|
|
28
|
+
export type AuthSessionStatus = "live" | "revoked" | "expired" | "missing";
|
|
29
|
+
|
|
30
|
+
// Called by the middleware after JWT-verify. Gets the sid AND the expected
|
|
31
|
+
// userId from the JWT's `sub` — the checker MUST confirm the session row
|
|
32
|
+
// both exists + is live AND belongs to expectedUserId. Without the userId
|
|
33
|
+
// cross-check, a compromised-sid-but-valid-JWT combination from two
|
|
34
|
+
// different users could slip through (defense-in-depth: the JWT signing
|
|
35
|
+
// secret is the main control, but we don't want a single leaked sid to
|
|
36
|
+
// matter when the attacker already knows the userId too).
|
|
37
|
+
export type AuthSessionChecker = (
|
|
38
|
+
sid: string,
|
|
39
|
+
expectedUserId: string,
|
|
40
|
+
) => Promise<AuthSessionStatus>;
|
|
41
|
+
|
|
42
|
+
export type AuthMiddlewareOptions = {
|
|
43
|
+
// Called after JWT-verify when the token carries a sid. If the checker
|
|
44
|
+
// reports anything other than "live", the request is rejected with 401.
|
|
45
|
+
// Omit to run in stateless-JWT mode (any valid JWT is accepted).
|
|
46
|
+
readonly sessionChecker?: AuthSessionChecker;
|
|
47
|
+
// When true, a JWT WITHOUT a sid is rejected. Leave false during rollout
|
|
48
|
+
// so already-issued stateless JWTs keep working until they expire; flip
|
|
49
|
+
// to true once the server has been emitting sid for longer than the JWT
|
|
50
|
+
// TTL. Has no effect when sessionChecker is undefined.
|
|
51
|
+
readonly strictMode?: boolean;
|
|
52
|
+
// Opt-in: when set, requests without a JWT are treated as anonymous
|
|
53
|
+
// callers instead of being rejected with 401. The middleware synthesises
|
|
54
|
+
// a SessionUser with id="anonymous" and roles=["anonymous"], scoped to a
|
|
55
|
+
// tenantId resolved through the chain documented on AnonymousAccessConfig.
|
|
56
|
+
readonly anonymousAccess?: AnonymousAccessConfig;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Resolves the tenant for an unauthenticated request. Returns null when no
|
|
60
|
+
// tenant can be determined — the middleware rejects with 400 instead of
|
|
61
|
+
// silently falling through. Throw only on infrastructure failures (DB down,
|
|
62
|
+
// cache broken) — those surface as 500. "Subdomain unknown" is null, not
|
|
63
|
+
// throw.
|
|
64
|
+
export type TenantResolver = (c: Context) => Promise<TenantId | null> | TenantId | null;
|
|
65
|
+
|
|
66
|
+
// Returns true when the tenantId names an active tenant. The middleware
|
|
67
|
+
// calls this after a header/cookie/resolver supplied a candidate, before
|
|
68
|
+
// the anonymous SessionUser is synthesised. Omit to skip the existence
|
|
69
|
+
// check entirely — fine for prototypes, NOT for production multi-tenant
|
|
70
|
+
// deployments where a caller could otherwise probe arbitrary ids.
|
|
71
|
+
export type TenantExists = (tenantId: TenantId) => Promise<boolean> | boolean;
|
|
72
|
+
|
|
73
|
+
export type AnonymousAccessConfig = {
|
|
74
|
+
// Custom resolver (e.g. subdomain parser). Consulted only when neither
|
|
75
|
+
// the X-Tenant header nor the kumiko_tenant cookie are present.
|
|
76
|
+
readonly tenantResolver?: TenantResolver;
|
|
77
|
+
// Single-tenant shortcut. When set, the server runs in **locked** mode:
|
|
78
|
+
// - no client-supplied tenant: defaultTenantId is used.
|
|
79
|
+
// - client supplies a matching tenant (header/cookie/resolver): allowed.
|
|
80
|
+
// - client supplies a non-matching tenant: 400 tenant_mismatch (the
|
|
81
|
+
// server is single-tenant; rejecting protects against confused clients
|
|
82
|
+
// who think they're talking to a different deployment).
|
|
83
|
+
// The framework does NOT verify defaultTenantId against the DB at boot;
|
|
84
|
+
// the caller is responsible (see sample for the pattern).
|
|
85
|
+
readonly defaultTenantId?: TenantId;
|
|
86
|
+
// Per-request existence check for header/cookie/resolver-supplied ids.
|
|
87
|
+
// Skipped for the defaultTenantId path (the caller already vetted that
|
|
88
|
+
// value when configuring the server).
|
|
89
|
+
readonly tenantExists?: TenantExists;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Where the candidate tenant came from. Drives the validation policy:
|
|
93
|
+
// - header / cookie / resolver: untrusted, must pass tenantExists if set.
|
|
94
|
+
// - default: trusted (configured at boot), no per-request check.
|
|
95
|
+
type TenantSource = "header" | "cookie" | "resolver" | "default";
|
|
96
|
+
|
|
97
|
+
// Error-body shape matches the UnprocessableError/AccessDeniedError on the
|
|
98
|
+
// dispatcher path — clients parse `{error: {code, httpStatus, message,
|
|
99
|
+
// i18nKey, details?}}` everywhere, not a second middleware-only shape.
|
|
100
|
+
// All middleware rejects (missing/invalid/ambiguous token, session state,
|
|
101
|
+
// csrf mismatch) route through this helper so one parser covers the lot.
|
|
102
|
+
type MiddlewareRejectCode =
|
|
103
|
+
| "missing_token"
|
|
104
|
+
| "invalid_token"
|
|
105
|
+
| "ambiguous_auth"
|
|
106
|
+
| "session_invalid"
|
|
107
|
+
| "tenant_required"
|
|
108
|
+
| "tenant_not_found"
|
|
109
|
+
| "tenant_mismatch"
|
|
110
|
+
| "invalid_tenant_format";
|
|
111
|
+
|
|
112
|
+
function middlewareReject(
|
|
113
|
+
c: Context,
|
|
114
|
+
opts: {
|
|
115
|
+
code: MiddlewareRejectCode;
|
|
116
|
+
status: 400 | 401 | 403 | 404;
|
|
117
|
+
message: string;
|
|
118
|
+
i18nKey: string;
|
|
119
|
+
details?: Record<string, unknown>;
|
|
120
|
+
},
|
|
121
|
+
): Response {
|
|
122
|
+
return c.json(
|
|
123
|
+
{
|
|
124
|
+
error: {
|
|
125
|
+
code: opts.code,
|
|
126
|
+
httpStatus: opts.status,
|
|
127
|
+
message: opts.message,
|
|
128
|
+
i18nKey: opts.i18nKey,
|
|
129
|
+
...(opts.details ? { details: opts.details } : {}),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
opts.status,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function sessionInvalid(c: Context, reason: AuthSessionStatus | "no_sid"): Response {
|
|
137
|
+
return middlewareReject(c, {
|
|
138
|
+
code: "session_invalid",
|
|
139
|
+
status: 401,
|
|
140
|
+
message: `session ${reason}`,
|
|
141
|
+
i18nKey: "auth.errors.sessionInvalid",
|
|
142
|
+
details: { reason },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Extract the JWT from either the kumiko_auth cookie (web) or the
|
|
147
|
+
// Authorization Bearer header (native / server-to-server). The two paths
|
|
148
|
+
// are mutually exclusive: if both are present the request is rejected with
|
|
149
|
+
// `ambiguous_auth` to prevent a confused-deputy bug where a server-bug
|
|
150
|
+
// could authenticate via one transport while the other sat there ignored.
|
|
151
|
+
// Note: this is NOT a CSRF control — Bearer-only clients are already safe
|
|
152
|
+
// because browsers can't set Authorization headers cross-origin. The reject
|
|
153
|
+
// exists so future middleware authors can't accidentally mix transports.
|
|
154
|
+
function extractToken(
|
|
155
|
+
c: Context,
|
|
156
|
+
): { token: string; transport: AuthTransport } | { error: "both" | "missing" } {
|
|
157
|
+
const cookieToken = getCookie(c, AUTH_COOKIE_NAME);
|
|
158
|
+
const header = c.req.header("Authorization");
|
|
159
|
+
const bearerToken = header?.startsWith("Bearer ") ? header.slice(7) : undefined;
|
|
160
|
+
|
|
161
|
+
if (cookieToken && bearerToken) return { error: "both" };
|
|
162
|
+
if (cookieToken) return { token: cookieToken, transport: "cookie" };
|
|
163
|
+
if (bearerToken) return { token: bearerToken, transport: "bearer" };
|
|
164
|
+
return { error: "missing" };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function authMiddleware(jwt: JwtHelper, options: AuthMiddlewareOptions = {}) {
|
|
168
|
+
const { sessionChecker, strictMode = false, anonymousAccess } = options;
|
|
169
|
+
|
|
170
|
+
return async (c: Context, next: Next) => {
|
|
171
|
+
const extracted = extractToken(c);
|
|
172
|
+
if ("error" in extracted) {
|
|
173
|
+
if (extracted.error === "both") {
|
|
174
|
+
return middlewareReject(c, {
|
|
175
|
+
code: "ambiguous_auth",
|
|
176
|
+
status: 400,
|
|
177
|
+
message: "cookie and bearer transport presented simultaneously",
|
|
178
|
+
i18nKey: "auth.errors.ambiguousAuth",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// No JWT → either fall through as anonymous (when the server opts in)
|
|
182
|
+
// or reject with 401 (preserving the pre-anonymous default).
|
|
183
|
+
//
|
|
184
|
+
// Auth-routes-Sonderfall: `/api/auth/*`-Pfade die NICHT in
|
|
185
|
+
// PUBLIC_API_PATHS sind (tenants, switch-tenant, logout) brauchen
|
|
186
|
+
// einen JWT — aber keinen Tenant-Resolve. Würden sie durch
|
|
187
|
+
// handleAnonymous laufen, wirft resolveTenant 400 tenant_required
|
|
188
|
+
// wenn kein Tenant declared ist. Falsche Diagnose: das Problem ist
|
|
189
|
+
// missing authentication, nicht missing tenant. Daher hier direkt
|
|
190
|
+
// 401, ohne den anonymous-Tenant-Flow zu durchlaufen.
|
|
191
|
+
if (anonymousAccess) {
|
|
192
|
+
if (c.req.path.startsWith("/api/auth/")) {
|
|
193
|
+
return middlewareReject(c, {
|
|
194
|
+
code: "missing_token",
|
|
195
|
+
status: 401,
|
|
196
|
+
message: "no auth cookie or bearer token",
|
|
197
|
+
i18nKey: "auth.errors.missingToken",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return await handleAnonymous(c, anonymousAccess, next);
|
|
201
|
+
}
|
|
202
|
+
return middlewareReject(c, {
|
|
203
|
+
code: "missing_token",
|
|
204
|
+
status: 401,
|
|
205
|
+
message: "no auth cookie or bearer token",
|
|
206
|
+
i18nKey: "auth.errors.missingToken",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const { token, transport } = extracted;
|
|
210
|
+
|
|
211
|
+
let payload: Awaited<ReturnType<JwtHelper["verify"]>>;
|
|
212
|
+
try {
|
|
213
|
+
payload = await jwt.verify(token);
|
|
214
|
+
} catch {
|
|
215
|
+
return middlewareReject(c, {
|
|
216
|
+
code: "invalid_token",
|
|
217
|
+
status: 401,
|
|
218
|
+
message: "token verification failed",
|
|
219
|
+
i18nKey: "auth.errors.invalidToken",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Session liveness check — only when both a checker is wired AND the
|
|
224
|
+
// token carries a sid. strictMode governs the no-sid case below so that
|
|
225
|
+
// both old JWTs (no sid) and rolling-deploy gaps can be handled.
|
|
226
|
+
if (sessionChecker) {
|
|
227
|
+
if (payload.jti) {
|
|
228
|
+
const status = await sessionChecker(payload.jti, payload.sub);
|
|
229
|
+
if (status !== "live") {
|
|
230
|
+
return sessionInvalid(c, status);
|
|
231
|
+
}
|
|
232
|
+
} else if (strictMode) {
|
|
233
|
+
return sessionInvalid(c, "no_sid");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Tenant-mismatch guard: if the caller sends BOTH a JWT (carrying a
|
|
238
|
+
// signed tenantId) AND an X-Tenant header pointing at a different tenant,
|
|
239
|
+
// reject loudly. JWT always wins on the wire, so silent ignore would let
|
|
240
|
+
// a confused client believe it's hitting tenantB while it's actually on
|
|
241
|
+
// tenantA. Same defensive stance as ambiguous_auth (cookie + bearer).
|
|
242
|
+
const headerTenant = c.req.header(TENANT_HEADER_NAME);
|
|
243
|
+
if (headerTenant !== undefined && headerTenant !== payload.tenantId) {
|
|
244
|
+
return middlewareReject(c, {
|
|
245
|
+
code: "tenant_mismatch",
|
|
246
|
+
status: 400,
|
|
247
|
+
message: "JWT tenantId and X-Tenant header disagree",
|
|
248
|
+
i18nKey: "auth.errors.tenantMismatch",
|
|
249
|
+
details: { jwtTenantId: payload.tenantId, headerTenantId: headerTenant },
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const user: SessionUser = {
|
|
254
|
+
id: payload.sub,
|
|
255
|
+
tenantId: payload.tenantId,
|
|
256
|
+
roles: payload.roles,
|
|
257
|
+
...(payload.claims ? { claims: payload.claims } : {}),
|
|
258
|
+
...(payload.jti ? { sid: payload.jti } : {}),
|
|
259
|
+
};
|
|
260
|
+
c.set(USER_KEY, user);
|
|
261
|
+
c.set(AUTH_TRANSPORT_KEY, transport);
|
|
262
|
+
await next();
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function getUser(c: Context): SessionUser {
|
|
267
|
+
// @cast-boundary engine-bridge — Hono context.get returns unknown
|
|
268
|
+
return c.get(USER_KEY) as SessionUser;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function getAuthTransport(c: Context): AuthTransport | undefined {
|
|
272
|
+
// @cast-boundary engine-bridge — Hono context.get returns unknown
|
|
273
|
+
return c.get(AUTH_TRANSPORT_KEY) as AuthTransport | undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Anonymous request flow. Steps:
|
|
277
|
+
// 1. Read raw client-supplied tenant from X-Tenant header / cookie.
|
|
278
|
+
// 2. Validate format (UUID-shape) — junk strings get 400 right here.
|
|
279
|
+
// 3. Pick the authoritative tenant:
|
|
280
|
+
// - defaultTenantId set: locked single-tenant mode. A client tenant
|
|
281
|
+
// that disagrees with default → 400 tenant_mismatch. Otherwise
|
|
282
|
+
// default wins.
|
|
283
|
+
// - else: client tenant > resolver(req). No tenant at all → 400.
|
|
284
|
+
// 4. For non-default sources, run tenantExists if configured → 404.
|
|
285
|
+
// 5. Synthesise the anonymous SessionUser. The transport flag stays unset
|
|
286
|
+
// so csrf-middleware skips the double-submit check (no auth-cookie ⇒
|
|
287
|
+
// no CSRF vector to defend against).
|
|
288
|
+
async function handleAnonymous(
|
|
289
|
+
c: Context,
|
|
290
|
+
config: AnonymousAccessConfig,
|
|
291
|
+
next: Next,
|
|
292
|
+
): Promise<Response | undefined> {
|
|
293
|
+
// Step 1+2: parse client-supplied tenant. Reject malformed values before
|
|
294
|
+
// they touch any downstream consumer (DB, cache, audit row).
|
|
295
|
+
const headerRaw = c.req.header(TENANT_HEADER_NAME);
|
|
296
|
+
const cookieRaw = getCookie(c, TENANT_COOKIE_NAME);
|
|
297
|
+
|
|
298
|
+
const headerCheck = parseClientTenant(headerRaw, "X-Tenant header");
|
|
299
|
+
if (headerCheck.error) return middlewareReject(c, headerCheck.error);
|
|
300
|
+
const cookieCheck = parseClientTenant(cookieRaw, "kumiko_tenant cookie");
|
|
301
|
+
if (cookieCheck.error) return middlewareReject(c, cookieCheck.error);
|
|
302
|
+
|
|
303
|
+
const clientTenant: { id: TenantId; source: "header" | "cookie" } | null =
|
|
304
|
+
headerCheck.tenantId !== null
|
|
305
|
+
? { id: headerCheck.tenantId, source: "header" }
|
|
306
|
+
: cookieCheck.tenantId !== null
|
|
307
|
+
? { id: cookieCheck.tenantId, source: "cookie" }
|
|
308
|
+
: null;
|
|
309
|
+
|
|
310
|
+
// Step 3: pick the authoritative tenant.
|
|
311
|
+
const resolved = await resolveTenant(c, config, clientTenant);
|
|
312
|
+
if ("error" in resolved) return middlewareReject(c, resolved.error);
|
|
313
|
+
|
|
314
|
+
// Step 4: existence check for untrusted sources.
|
|
315
|
+
if (resolved.source !== "default" && config.tenantExists) {
|
|
316
|
+
const exists = await config.tenantExists(resolved.tenantId);
|
|
317
|
+
if (!exists) {
|
|
318
|
+
return middlewareReject(c, {
|
|
319
|
+
code: "tenant_not_found",
|
|
320
|
+
status: 404,
|
|
321
|
+
message: `tenant "${resolved.tenantId}" does not exist`,
|
|
322
|
+
i18nKey: "auth.errors.tenantNotFound",
|
|
323
|
+
details: { tenantId: resolved.tenantId },
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Step 5: synthesise + continue.
|
|
329
|
+
c.set(USER_KEY, createAnonymousUser(resolved.tenantId));
|
|
330
|
+
await next();
|
|
331
|
+
// skip: anonymous path completed — Hono middleware contract returns void
|
|
332
|
+
// when next() ran; explicit return makes the union return-type honest.
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Validates an X-Tenant / cookie value against the tenantId format. Returns
|
|
337
|
+
// `{tenantId: null}` when absent, `{tenantId: TenantId}` when valid, or
|
|
338
|
+
// `{error: …}` when the value is non-empty junk (so the caller can return
|
|
339
|
+
// 400 instead of silently treating it as "no tenant supplied").
|
|
340
|
+
function parseClientTenant(
|
|
341
|
+
raw: string | undefined,
|
|
342
|
+
source: string,
|
|
343
|
+
): { tenantId: TenantId | null; error?: never } | { tenantId?: never; error: RejectArgs } {
|
|
344
|
+
if (raw === undefined || raw === "") return { tenantId: null };
|
|
345
|
+
const parsed = parseTenantId(raw);
|
|
346
|
+
if (parsed === null) {
|
|
347
|
+
return {
|
|
348
|
+
error: {
|
|
349
|
+
code: "invalid_tenant_format",
|
|
350
|
+
status: 400,
|
|
351
|
+
message: `${source} is not a valid tenant id`,
|
|
352
|
+
i18nKey: "auth.errors.invalidTenantFormat",
|
|
353
|
+
details: { source, value: raw },
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
return { tenantId: parsed };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
type ResolvedTenant = { tenantId: TenantId; source: TenantSource };
|
|
361
|
+
type ResolveError = { error: RejectArgs };
|
|
362
|
+
|
|
363
|
+
// Implements the "client tenant vs default" precedence. Single-tenant mode
|
|
364
|
+
// (defaultTenantId set) is **locked**: the client either agrees with the
|
|
365
|
+
// default or gets tenant_mismatch — defending the deployment from confused
|
|
366
|
+
// clients that think they're talking to a different installation.
|
|
367
|
+
async function resolveTenant(
|
|
368
|
+
c: Context,
|
|
369
|
+
config: AnonymousAccessConfig,
|
|
370
|
+
clientTenant: { id: TenantId; source: "header" | "cookie" } | null,
|
|
371
|
+
): Promise<ResolvedTenant | ResolveError> {
|
|
372
|
+
if (config.defaultTenantId !== undefined) {
|
|
373
|
+
if (clientTenant && clientTenant.id !== config.defaultTenantId) {
|
|
374
|
+
return {
|
|
375
|
+
error: {
|
|
376
|
+
code: "tenant_mismatch",
|
|
377
|
+
status: 400,
|
|
378
|
+
message: `${clientTenant.source} tenant disagrees with server default`,
|
|
379
|
+
i18nKey: "auth.errors.tenantMismatch",
|
|
380
|
+
details: {
|
|
381
|
+
clientTenantId: clientTenant.id,
|
|
382
|
+
defaultTenantId: config.defaultTenantId,
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return { tenantId: config.defaultTenantId, source: "default" };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (clientTenant) {
|
|
391
|
+
return { tenantId: clientTenant.id, source: clientTenant.source };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (config.tenantResolver) {
|
|
395
|
+
const resolved = await config.tenantResolver(c);
|
|
396
|
+
if (resolved !== null && resolved !== undefined) {
|
|
397
|
+
return { tenantId: resolved, source: "resolver" };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
error: {
|
|
403
|
+
code: "tenant_required",
|
|
404
|
+
status: 400,
|
|
405
|
+
message:
|
|
406
|
+
"anonymous access requires a tenant (X-Tenant header, kumiko_tenant cookie, or server-side resolver)",
|
|
407
|
+
i18nKey: "auth.errors.tenantRequired",
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
type RejectArgs = {
|
|
413
|
+
code: MiddlewareRejectCode;
|
|
414
|
+
status: 400 | 401 | 403 | 404;
|
|
415
|
+
message: string;
|
|
416
|
+
i18nKey: string;
|
|
417
|
+
details?: Record<string, unknown>;
|
|
418
|
+
};
|