@starci/hfs 4.2.17 → 4.4.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/CHANGELOG.md +28 -0
- package/README.md +7 -0
- package/db/migrate.mjs +361 -0
- package/db/post-cutover.mjs +136 -0
- package/emit/db-types.mjs +10 -1
- package/lint/run.mjs +1 -1
- package/package.json +4 -1
- package/release/gate.mjs +298 -0
- package/release/index.mjs +103 -0
- package/release/push.mjs +110 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
- package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
- package/runtime/knowledge/hfs/rules.yaml +3437 -160
- package/runtime/knowledge/hfs/slots.yaml +430 -92
- package/runtime/knowledge/patterns/be/api.yaml +92 -2
- package/runtime/knowledge/patterns/be/cli.yaml +37 -9
- package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
- package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
- package/runtime/knowledge/patterns/be/projections.yaml +4 -2
- package/runtime/knowledge/patterns/be/queues.yaml +11 -3
- package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
- package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
- package/runtime/knowledge/patterns/be/saga.yaml +20 -3
- package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
- package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/failure-codes.yaml +3128 -47
- package/runtime/modules/models/runtimes.yaml +1 -1
- package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
- package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
- package/runtime/packages/cli/src/validate-args.mjs +213 -0
- package/runtime/scripts/api/git/add.mjs +5 -0
- package/runtime/scripts/api/git/cat-file.mjs +5 -0
- package/runtime/scripts/api/git/init.mjs +7 -0
- package/runtime/scripts/api/git/lib.mjs +3 -0
- package/runtime/scripts/api/git/tag.mjs +5 -0
- package/runtime/scripts/api/node/lib.mjs +20 -0
- package/runtime/scripts/api/node/run-node.mjs +6 -0
- package/runtime/scripts/api/sops/lib.mjs +41 -0
- package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
- package/runtime/scripts/api/sops/seal.mjs +1 -1
- package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
- package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
- package/runtime/scripts/api/supabase/database-query.mjs +5 -0
- package/runtime/scripts/api/supabase/lib.mjs +44 -0
- package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
- package/runtime/scripts/hfs/app-wiring.mjs +201 -0
- package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
- package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
- package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
- package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
- package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
- package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
- package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
- package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
- package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
- package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
- package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
- package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
- package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
- package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
- package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
- package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
- package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
- package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
- package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
- package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
- package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
- package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
- package/runtime/scripts/hfs/check-results.mjs +28 -0
- package/runtime/scripts/hfs/check.mjs +63 -72
- package/runtime/scripts/hfs/coverage-from.mjs +70 -0
- package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
- package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
- package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
- package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
- package/runtime/scripts/hfs/doc-commands.mjs +367 -0
- package/runtime/scripts/hfs/edition-slots.mjs +2 -2
- package/runtime/scripts/hfs/example-secrets.mjs +158 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
- package/runtime/scripts/hfs/path-findings.mjs +32 -2
- package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
- package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
- package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
- package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
- package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
- package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
- package/runtime/scripts/hfs/rules/assets.mjs +79 -0
- package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
- package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
- package/runtime/scripts/hfs/rules/contract.mjs +66 -5
- package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
- package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
- package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
- package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
- package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
- package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
- package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
- package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
- package/runtime/scripts/hfs/rules/database.mjs +11 -2
- package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
- package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
- package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
- package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
- package/runtime/scripts/hfs/rules/docker.mjs +24 -7
- package/runtime/scripts/hfs/rules/edition.mjs +11 -10
- package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
- package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
- package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
- package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
- package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
- package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
- package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
- package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
- package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
- package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
- package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
- package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
- package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
- package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
- package/runtime/scripts/hfs/rules/saga.mjs +8 -16
- package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
- package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
- package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
- package/runtime/scripts/hfs/rules/services.mjs +4 -4
- package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
- package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
- package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
- package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
- package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
- package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
- package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
- package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
- package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
- package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
- package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
- package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
- package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
- package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
- package/runtime/scripts/hfs/sealed-members.mjs +101 -0
- package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
- package/runtime/scripts/hfs/secret-document.mjs +97 -0
- package/runtime/scripts/hfs/secret-edit.mjs +170 -0
- package/runtime/scripts/hfs/secret-errors.mjs +20 -0
- package/runtime/scripts/hfs/secret-files.mjs +124 -0
- package/runtime/scripts/hfs/secret-init.mjs +300 -0
- package/runtime/scripts/hfs/secret-output.mjs +100 -0
- package/runtime/scripts/hfs/secret-sops.mjs +130 -0
- package/runtime/scripts/hfs/secret-store.mjs +163 -0
- package/runtime/scripts/hfs/secret.mjs +162 -193
- package/runtime/scripts/hfs/slot-imports.mjs +18 -3
- package/runtime/scripts/hfs/slot-required.mjs +13 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
- package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
- package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
- package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
- package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
- package/runtime/scripts/hfs/tree-findings.mjs +22 -0
- package/runtime/scripts/lib/command-tokens.mjs +126 -0
- package/runtime/scripts/lib/compose-values.mjs +11 -0
- package/runtime/scripts/lib/deployment-findings.mjs +7 -0
- package/runtime/scripts/lib/json-clone.mjs +61 -0
- package/runtime/scripts/lib/list.mjs +4 -0
- package/runtime/scripts/lib/roots.mjs +59 -0
- package/runtime/scripts/lib/runtime-example.mjs +13 -0
- package/runtime/scripts/lib/secret-patterns.mjs +60 -5
- package/runtime/scripts/lib/sql-ast.mjs +32 -0
- package/runtime/scripts/lib/stack-services.mjs +1 -1
- package/runtime/scripts/lib/swarm-stack.mjs +126 -0
- package/runtime/scripts/lib/trim-end.mjs +8 -0
- package/runtime/scripts/lib/ts-analysis.mjs +23 -0
- package/runtime/scripts/lib/ts-ast.mjs +27 -2
- package/runtime/scripts/lib/workflow-events.mjs +8 -0
- package/scaffold/add-app.mjs +77 -59
- package/scaffold/add-cli-lite.mjs +24 -17
- package/scaffold/add-compose.mjs +79 -0
- package/scaffold/add-database-lite.mjs +14 -0
- package/scaffold/add-fe-overlay.mjs +122 -0
- package/scaffold/add-fe-page.mjs +269 -0
- package/scaffold/add-inbox-lite.mjs +87 -124
- package/scaffold/add-plan.mjs +183 -0
- package/scaffold/add-table.mjs +132 -70
- package/scaffold/add.mjs +186 -204
- package/scaffold/app.mjs +128 -43
- package/scaffold/edition-gate.mjs +3 -3
- package/scaffold/image.mjs +2 -2
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +2 -2
- package/src/main.mjs +238 -119
- package/stack/age-install.mjs +37 -0
- package/stack/backup.mjs +100 -0
- package/stack/build-args.mjs +42 -0
- package/stack/build.mjs +173 -0
- package/stack/cli.mjs +130 -0
- package/stack/config.mjs +57 -0
- package/stack/context.mjs +57 -0
- package/stack/converge.mjs +107 -0
- package/stack/deploy.mjs +301 -0
- package/stack/digest.mjs +16 -0
- package/stack/docker-transport.mjs +53 -0
- package/stack/edge-alias.mjs +118 -0
- package/stack/errors.mjs +21 -0
- package/stack/image-scan.mjs +152 -0
- package/stack/ingress-create.mjs +114 -0
- package/stack/ingress-publish.mjs +273 -0
- package/stack/ingress.mjs +706 -0
- package/stack/jobs.mjs +51 -0
- package/stack/live-restore.mjs +177 -0
- package/stack/prepare.mjs +266 -0
- package/stack/private-file.mjs +22 -0
- package/stack/provision.mjs +90 -0
- package/stack/prune.mjs +46 -0
- package/stack/release-store.mjs +87 -0
- package/stack/remote.mjs +205 -0
- package/stack/render.mjs +149 -0
- package/stack/retire.mjs +47 -0
- package/stack/sealed.mjs +149 -0
- package/stack/task.mjs +42 -0
- package/stack/verify.mjs +99 -0
- package/sync/ci-commands.mjs +13 -0
- package/sync/index.mjs +127 -64
- package/sync/stack-images.mjs +127 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
- package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
- package/templates/app/codegen-graphql.mjs.partial +13 -2
- package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
- package/templates/app/codegen-prelude.mjs.partial +8 -0
- package/templates/app/gitignore +4 -0
- package/templates/app/gitignore-lite +4 -0
- package/templates/app/hooks/husky/pre-commit +7 -12
- package/templates/app/package-scripts/package.json +1 -0
- package/templates/app/package-scripts/supabase.json +7 -0
- package/templates/app/package-scripts-lite/package.json +1 -7
- package/templates/app/skeleton/.editorconfig +1 -0
- package/templates/app/skeleton/.gitattributes +7 -1
- package/templates/app/skeleton/.github/dependabot.yml +17 -0
- package/templates/app/skeleton/.nvmrc +1 -1
- package/templates/app/skeleton/.sops.yaml +1 -1
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
- package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
- package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
- package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
- package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
- package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
- package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
- package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
- package/templates/app/skeleton/README.md +6 -6
- package/templates/app/skeleton/scripts/codegen.mjs +1 -2
- package/templates/app/skeleton-lite/.editorconfig +16 -0
- package/templates/app/skeleton-lite/.gitattributes +8 -0
- package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
- package/templates/app/skeleton-lite/.nvmrc +1 -0
- package/templates/app/skeleton-lite/.sops.yaml +3 -2
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
- package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
- package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
- package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
- package/templates/app/skeleton-lite/README.md +8 -4
- package/templates/app/skeleton-lite/supabase/config.toml +8 -1
- package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
- package/templates/app/starciwork.gitignore-lite +6 -0
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
- package/templates/be/inbox/database-client.ts.tpl +14 -28
- package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
- package/templates/be/inbox/event-index.ts.tpl +1 -0
- package/templates/be/inbox/events.json.tpl +10 -0
- package/templates/be/inbox/migration.sql.tpl +1 -0
- package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
- package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
- package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
- package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
- package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
- package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
- package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
- package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
- package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
- package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
- package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
- package/templates/be/inbox/platform/migration.sql.tpl +48 -0
- package/templates/be/inbox/service.spec.ts.tpl +49 -20
- package/templates/be/inbox/service.ts.tpl +27 -16
- package/templates/be/inbox/sql.ts.tpl +2 -2
- package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
- package/templates/be/inbox/webhook-door.ts.tpl +4 -3
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
- package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
- package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
- package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
- package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
- package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
- package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
- package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
- package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
- package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
- package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
- package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
- package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
- package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
- package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
- package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
- package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
- package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
- package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
- package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
- package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
- package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
- package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
- package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
- package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
- package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
- package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
- package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
- package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
- package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
- package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
- package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
- package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
- package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
- package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
- package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
- package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
- package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
- package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
- package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
- package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
- package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
- package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
- package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
- package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
- package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
- package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
- package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
- package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
- package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
- package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
- package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
- package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
- package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
- package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
- package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
- package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
- package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
- package/templates/be/table/api/feature.module.ts.tpl +2 -1
- package/templates/be/table/api/feature.options.ts.tpl +2 -0
- package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
- package/templates/be/table/api/graphql.module.ts.tpl +3 -2
- package/templates/be/table/api/graphql.options.ts.tpl +2 -0
- package/templates/be/table/migration.sql.tpl +1 -0
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
- package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
- package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
- package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
- package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
- package/templates/fe/auth/supabase-client.ts.tpl +34 -0
- package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
- package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
- package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
- package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
- package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
- package/upgrade/check.mjs +3 -1
- package/upgrade/file-state.mjs +68 -0
- package/upgrade/index.mjs +58 -94
- package/runtime/scripts/api/git/log.mjs +0 -5
- package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common"
|
|
2
2
|
import { SystemHealthModule } from "../../system-health.module"
|
|
3
3
|
import { LiveController } from "./live.controller"
|
|
4
|
+
import { ReadyController } from "./ready.controller"
|
|
5
|
+
import { ConfigurableModuleClass } from "./system-health-http.module-definition"
|
|
4
6
|
|
|
5
|
-
@Module({
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
@Module({
|
|
8
|
+
imports: [SystemHealthModule.register({})],
|
|
9
|
+
controllers: [LiveController, ReadyController],
|
|
10
|
+
})
|
|
11
|
+
/** The HTTP transport of the health feature: independent liveness and required-dependency readiness doors. */
|
|
12
|
+
export class SystemHealthHttpModule extends ConfigurableModuleClass {}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
2
|
+
import type { CliOptions } from "./cli.options"
|
|
3
|
+
|
|
4
|
+
/** The configurable-module base of the cli module. */
|
|
5
|
+
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
|
|
6
|
+
new ConfigurableModuleBuilder<CliOptions>().setClassMethodName("register").build()
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common"
|
|
2
|
+
import { ConfigurableModuleClass } from "./cli.module-definition"
|
|
2
3
|
import { MigrateModule } from "./migrate/migrate.module"
|
|
3
4
|
import { SeedModule } from "./seed/seed.module"
|
|
4
5
|
|
|
5
|
-
@Module({ imports: [MigrateModule, SeedModule] })
|
|
6
|
+
@Module({ imports: [MigrateModule.register({}), SeedModule.register({})] })
|
|
6
7
|
/** The cli feature root: every command group of the back end, compiled only into apps/cli. */
|
|
7
|
-
export class CliModule {}
|
|
8
|
+
export class CliModule extends ConfigurableModuleClass {}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
2
|
+
import type { MigrateOptions } from "./migrate.options"
|
|
3
|
+
|
|
4
|
+
/** The configurable-module base of the migrate module. */
|
|
5
|
+
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
|
|
6
|
+
new ConfigurableModuleBuilder<MigrateOptions>().setClassMethodName("register").build()
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common"
|
|
2
2
|
import { MigrateCli } from "./migrate.cli"
|
|
3
|
+
import { ConfigurableModuleClass } from "./migrate.module-definition"
|
|
3
4
|
import { RunCli } from "./subs/run.cli"
|
|
4
5
|
|
|
5
6
|
@Module({ providers: [MigrateCli, RunCli] })
|
|
6
7
|
/** The migrate group: its command and its sub-commands; the connections come from the database capability. */
|
|
7
|
-
export class MigrateModule {}
|
|
8
|
+
export class MigrateModule extends ConfigurableModuleClass {}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
2
|
+
import type { SeedOptions } from "./seed.options"
|
|
3
|
+
|
|
4
|
+
/** The configurable-module base of the seed module. */
|
|
5
|
+
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
|
|
6
|
+
new ConfigurableModuleBuilder<SeedOptions>().setClassMethodName("register").build()
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common"
|
|
2
2
|
import { SeedCli } from "./seed.cli"
|
|
3
|
+
import { ConfigurableModuleClass } from "./seed.module-definition"
|
|
3
4
|
import { RunSeedsCli } from "./subs/run.cli"
|
|
4
5
|
|
|
5
6
|
@Module({ providers: [SeedCli, RunSeedsCli] })
|
|
6
7
|
/** The seed group: its command and its sub-commands; the connections and the seed file reader come from the database capability. */
|
|
7
|
-
export class SeedModule {}
|
|
8
|
+
export class SeedModule extends ConfigurableModuleClass {}
|
|
@@ -3,17 +3,17 @@ import { Test } from "@nestjs/testing"
|
|
|
3
3
|
import { SeedRunnerService } from "@modules/platform/database"
|
|
4
4
|
import { RunSeedsCli } from "./run.cli"
|
|
5
5
|
|
|
6
|
-
const build = async (
|
|
6
|
+
const build = async () => {
|
|
7
|
+
const seeds = mock<SeedRunnerService>()
|
|
7
8
|
const moduleRef = await Test.createTestingModule({
|
|
8
9
|
providers: [RunSeedsCli, { provide: SeedRunnerService, useValue: seeds }],
|
|
9
10
|
}).compile()
|
|
10
|
-
return moduleRef.get(RunSeedsCli)
|
|
11
|
+
return { command: moduleRef.get(RunSeedsCli), seeds }
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
describe("RunSeedsCli", () => {
|
|
14
15
|
it("delegates the seed run of the named environment to the seed runner once", async () => {
|
|
15
|
-
const seeds =
|
|
16
|
-
const command = await build(seeds)
|
|
16
|
+
const { command, seeds } = await build()
|
|
17
17
|
|
|
18
18
|
await command.run([], { env: "staging" })
|
|
19
19
|
|
|
@@ -22,8 +22,7 @@ describe("RunSeedsCli", () => {
|
|
|
22
22
|
})
|
|
23
23
|
|
|
24
24
|
it("delegates with no environment when none is named, and takes --env as given", async () => {
|
|
25
|
-
const seeds =
|
|
26
|
-
const command = await build(seeds)
|
|
25
|
+
const { command, seeds } = await build()
|
|
27
26
|
|
|
28
27
|
await command.run([])
|
|
29
28
|
|
|
@@ -1,13 +1,65 @@
|
|
|
1
|
+
import { Test } from "@nestjs/testing"
|
|
1
2
|
import { admit } from "./admission.policy"
|
|
2
|
-
import { IdentityErrorCode } from "./errors/identity.error"
|
|
3
|
+
import { IdentityError, IdentityErrorCode } from "./errors/identity.error"
|
|
4
|
+
import type { PublicMetadata } from "./identity.contracts"
|
|
3
5
|
import { PublicReason } from "./identity.contracts"
|
|
4
6
|
|
|
7
|
+
const ADMISSION: unique symbol = Symbol("identity.admission")
|
|
8
|
+
|
|
9
|
+
const runtimeMetadata = (source: string): PublicMetadata => {
|
|
10
|
+
try {
|
|
11
|
+
const metadata: PublicMetadata = JSON.parse(source)
|
|
12
|
+
return metadata
|
|
13
|
+
} catch (cause) {
|
|
14
|
+
throw new IdentityError({ code: IdentityErrorCode.Unauthenticated, cause })
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
5
18
|
describe("admit", () => {
|
|
6
|
-
|
|
7
|
-
|
|
19
|
+
let subject: typeof admit
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
const moduleRef = await Test.createTestingModule({
|
|
23
|
+
providers: [{ provide: ADMISSION, useFactory: () => admit }],
|
|
24
|
+
}).compile()
|
|
25
|
+
subject = moduleRef.get<typeof admit>(ADMISSION)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it.each([PublicReason.Health, PublicReason.AuthHandshake, PublicReason.SignedWebhook, PublicReason.CatalogRead])(
|
|
29
|
+
"admits a door with the canonical public reason %s",
|
|
30
|
+
(reason) => {
|
|
31
|
+
expect(subject({ reason })).toSucceedWith(reason)
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
it("admits a credential door whose principal has the required kind", () => {
|
|
36
|
+
const principal = { kind: "api-key", subject: "key-1", workspaceId: "ws-1" } as const
|
|
37
|
+
|
|
38
|
+
expect(subject(undefined, { required: { kind: "api-key" }, principal })).toSucceedWith(principal)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("refuses a credential door without a principal of the required kind", () => {
|
|
42
|
+
const principal = { kind: "tool-token", subject: "tool-1", workspaceId: "ws-1" } as const
|
|
43
|
+
|
|
44
|
+
expect(subject(undefined, { required: { kind: "api-key" }, principal })).toBeRefused(
|
|
45
|
+
IdentityErrorCode.Unauthenticated,
|
|
46
|
+
)
|
|
47
|
+
expect(subject(undefined, { required: { kind: "api-key" }, principal: undefined })).toBeRefused(
|
|
48
|
+
IdentityErrorCode.Unauthenticated,
|
|
49
|
+
)
|
|
8
50
|
})
|
|
9
51
|
|
|
10
52
|
it("refuses a door that is not public", () => {
|
|
11
|
-
expect(
|
|
53
|
+
expect(subject(undefined)).toBeRefused(IdentityErrorCode.Unauthenticated)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("refuses an unknown public reason supplied as runtime metadata", () => {
|
|
57
|
+
const metadata = runtimeMetadata('{"reason":"tool-token"}')
|
|
58
|
+
expect(subject(metadata)).toBeRefused(IdentityErrorCode.Unauthenticated)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it("refuses runtime metadata without a public reason", () => {
|
|
62
|
+
const metadata = runtimeMetadata("{}")
|
|
63
|
+
expect(subject(metadata)).toBeRefused(IdentityErrorCode.Unauthenticated)
|
|
12
64
|
})
|
|
13
65
|
})
|
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
import { ok, refused } from "@modules/platform/primitives"
|
|
2
2
|
import type { Outcome } from "@modules/platform/primitives"
|
|
3
|
+
import type { CredentialMetadata, CredentialPrincipal } from "./credential.contracts"
|
|
3
4
|
import { IdentityErrorCode } from "./errors/identity.error"
|
|
4
|
-
import type { PublicMetadata
|
|
5
|
+
import type { PublicMetadata } from "./identity.contracts"
|
|
6
|
+
import { PublicReason } from "./identity.contracts"
|
|
7
|
+
|
|
8
|
+
/** What a credential-marked door requires and what `CredentialGuard` established for it. */
|
|
9
|
+
export interface CredentialAdmission {
|
|
10
|
+
/** The `@Credential` metadata of the door. */
|
|
11
|
+
readonly required: CredentialMetadata
|
|
12
|
+
/** The principal `CredentialGuard` set on the request, when the credential authenticated. */
|
|
13
|
+
readonly principal: CredentialPrincipal | undefined
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const admitCredential = ({
|
|
17
|
+
required,
|
|
18
|
+
principal,
|
|
19
|
+
}: CredentialAdmission): Outcome<CredentialPrincipal, IdentityErrorCode> =>
|
|
20
|
+
principal?.kind === required.kind ? ok(principal) : refused(IdentityErrorCode.Unauthenticated)
|
|
5
21
|
|
|
6
22
|
/**
|
|
7
|
-
* The default-deny decision of one door: a door
|
|
8
|
-
* until the design that adds
|
|
23
|
+
* The default-deny decision of one door: a credential-marked door is admitted only with its authenticated credential
|
|
24
|
+
* principal; a door with a canonical public reason is admitted; every other door is refused until the design that adds
|
|
25
|
+
* sign-in establishes a session principal here.
|
|
9
26
|
*/
|
|
10
|
-
export const admit = (
|
|
11
|
-
metadata
|
|
27
|
+
export const admit = (
|
|
28
|
+
metadata: PublicMetadata | undefined,
|
|
29
|
+
credential?: CredentialAdmission,
|
|
30
|
+
): Outcome<PublicReason | CredentialPrincipal, IdentityErrorCode> => {
|
|
31
|
+
if (credential !== undefined) return admitCredential(credential)
|
|
32
|
+
switch (metadata?.reason) {
|
|
33
|
+
case PublicReason.Health:
|
|
34
|
+
case PublicReason.AuthHandshake:
|
|
35
|
+
case PublicReason.SignedWebhook:
|
|
36
|
+
case PublicReason.CatalogRead:
|
|
37
|
+
return ok(metadata.reason)
|
|
38
|
+
case undefined:
|
|
39
|
+
default:
|
|
40
|
+
return refused(IdentityErrorCode.Unauthenticated)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,37 +1,68 @@
|
|
|
1
1
|
import type { ExecutionContext } from "@nestjs/common"
|
|
2
|
+
import type { HttpArgumentsHost } from "@nestjs/common/interfaces"
|
|
2
3
|
import { Reflector } from "@nestjs/core"
|
|
3
4
|
import { Test } from "@nestjs/testing"
|
|
4
5
|
import { mock } from "@starci/jest-preset"
|
|
5
6
|
import { AuthGuard } from "./auth.guard"
|
|
7
|
+
import type { CredentialMetadata, CredentialPrincipal } from "./credential.contracts"
|
|
6
8
|
import { IdentityErrorCode } from "./errors/identity.error"
|
|
7
9
|
import type { PublicMetadata } from "./identity.contracts"
|
|
8
10
|
import { PublicReason } from "./identity.contracts"
|
|
11
|
+
import { CREDENTIAL_KEY, PUBLIC_KEY } from "./identity.decorators"
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
interface Door {
|
|
14
|
+
readonly credential?: CredentialMetadata
|
|
15
|
+
readonly metadata?: PublicMetadata
|
|
16
|
+
readonly principal?: CredentialPrincipal
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const build = async ({ credential, metadata }: Door) => {
|
|
11
20
|
const reflector = mock<Reflector>()
|
|
12
|
-
reflector.getAllAndOverride.
|
|
21
|
+
reflector.getAllAndOverride.mockImplementation((key: unknown) =>
|
|
22
|
+
key === CREDENTIAL_KEY ? credential : key === PUBLIC_KEY ? metadata : undefined,
|
|
23
|
+
)
|
|
13
24
|
const moduleRef = await Test.createTestingModule({
|
|
14
25
|
providers: [AuthGuard, { provide: Reflector, useValue: reflector }],
|
|
15
26
|
}).compile()
|
|
16
|
-
return
|
|
27
|
+
return moduleRef.get(AuthGuard)
|
|
17
28
|
}
|
|
18
29
|
|
|
19
|
-
const contextOf = () => {
|
|
30
|
+
const contextOf = (principal?: CredentialPrincipal) => {
|
|
20
31
|
const context = mock<ExecutionContext>()
|
|
21
32
|
context.getHandler.mockReturnValue(() => undefined)
|
|
22
|
-
context.getClass.mockReturnValue(class
|
|
33
|
+
context.getClass.mockReturnValue(class DoorClass {})
|
|
34
|
+
context.getType.mockReturnValue("http")
|
|
35
|
+
const host = mock<HttpArgumentsHost>()
|
|
36
|
+
host.getRequest.mockReturnValue({ credentialPrincipal: principal })
|
|
37
|
+
context.switchToHttp.mockReturnValue(host)
|
|
23
38
|
return context
|
|
24
39
|
}
|
|
25
40
|
|
|
41
|
+
const API_KEY: CredentialPrincipal = { kind: "api-key", subject: "key-1", workspaceId: "ws-1" }
|
|
42
|
+
|
|
26
43
|
describe("AuthGuard", () => {
|
|
27
44
|
it("lets a door through when it states why it is public", async () => {
|
|
28
|
-
const
|
|
45
|
+
const guard = await build({ metadata: { reason: PublicReason.Health } })
|
|
29
46
|
|
|
30
47
|
expect(guard.canActivate(contextOf())).toBe(true)
|
|
31
48
|
})
|
|
32
49
|
|
|
33
|
-
it("refuses every door that is
|
|
34
|
-
const
|
|
50
|
+
it("refuses every door that is neither public nor credential-marked", async () => {
|
|
51
|
+
const guard = await build({})
|
|
52
|
+
|
|
53
|
+
expect(() => guard.canActivate(contextOf())).toThrow(
|
|
54
|
+
expect.objectContaining({ code: IdentityErrorCode.Unauthenticated }),
|
|
55
|
+
)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it("lets a credential door through once the credential principal is set", async () => {
|
|
59
|
+
const guard = await build({ credential: { kind: "api-key" } })
|
|
60
|
+
|
|
61
|
+
expect(guard.canActivate(contextOf(API_KEY))).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("refuses a credential door without a credential principal", async () => {
|
|
65
|
+
const guard = await build({ credential: { kind: "api-key" } })
|
|
35
66
|
|
|
36
67
|
expect(() => guard.canActivate(contextOf())).toThrow(
|
|
37
68
|
expect.objectContaining({ code: IdentityErrorCode.Unauthenticated }),
|
|
@@ -2,24 +2,30 @@ import { Injectable } from "@nestjs/common"
|
|
|
2
2
|
import type { CanActivate, ExecutionContext } from "@nestjs/common"
|
|
3
3
|
import type { Reflector } from "@nestjs/core"
|
|
4
4
|
import { InjectReflector } from "@modules/platform/composition"
|
|
5
|
+
import { requestOf } from "@modules/platform/http-security"
|
|
5
6
|
import { unwrapOutcome } from "@modules/platform/primitives"
|
|
6
7
|
import { admit } from "./admission.policy"
|
|
8
|
+
import type { CredentialMetadata } from "./credential.contracts"
|
|
7
9
|
import { IdentityError } from "./errors/identity.error"
|
|
8
10
|
import type { PublicMetadata } from "./identity.contracts"
|
|
9
|
-
import { PUBLIC_KEY } from "./identity.decorators"
|
|
11
|
+
import { CREDENTIAL_KEY, PUBLIC_KEY } from "./identity.decorators"
|
|
10
12
|
|
|
11
13
|
@Injectable()
|
|
12
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The fourth app guard and the default-deny gate: a door is open only when it says `@Public({ reason })`, or when it is
|
|
16
|
+
* marked `@Credential` and `CredentialGuard` has set the credential principal. It never calls another guard.
|
|
17
|
+
*/
|
|
13
18
|
export class AuthGuard implements CanActivate {
|
|
14
19
|
constructor(@InjectReflector() private readonly reflector: Reflector) {}
|
|
15
20
|
|
|
16
|
-
/** Lets public doors through and refuses every other door. */
|
|
21
|
+
/** Lets public and credential-authenticated doors through and refuses every other door. */
|
|
17
22
|
canActivate(context: ExecutionContext): boolean {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
const targets = [context.getHandler(), context.getClass()]
|
|
24
|
+
const required = this.reflector.getAllAndOverride<CredentialMetadata | undefined>(CREDENTIAL_KEY, targets)
|
|
25
|
+
const metadata = this.reflector.getAllAndOverride<PublicMetadata | undefined>(PUBLIC_KEY, targets)
|
|
26
|
+
const credential =
|
|
27
|
+
required === undefined ? undefined : { required, principal: requestOf(context).credentialPrincipal }
|
|
28
|
+
unwrapOutcome(admit(metadata, credential), IdentityError)
|
|
23
29
|
return true
|
|
24
30
|
}
|
|
25
31
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The closed set of alternate credential families a door can accept. */
|
|
2
|
+
export type CredentialKind = "api-key" | "run-token" | "tool-token" | "link-token" | "posting"
|
|
3
|
+
|
|
4
|
+
/** Alternate credentials accepted only by doors marked with `@Credential`. */
|
|
5
|
+
export interface CredentialMetadata {
|
|
6
|
+
/** The credential family this door accepts. */
|
|
7
|
+
readonly kind: CredentialKind
|
|
8
|
+
/** An optional workflow constraint the authenticator may enforce for the credential. */
|
|
9
|
+
readonly workflow?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A credential identity is deliberately separate from a signed-in user. */
|
|
13
|
+
export interface CredentialPrincipal {
|
|
14
|
+
/** The credential family that established this identity. */
|
|
15
|
+
readonly kind: CredentialKind
|
|
16
|
+
/** The key, job, run or link the credential identifies. */
|
|
17
|
+
readonly subject: string
|
|
18
|
+
/** The only workspace this credential can act for. */
|
|
19
|
+
readonly workspaceId: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The port the app binds to the capability that owns credential verification. */
|
|
23
|
+
export interface CredentialAuthenticator {
|
|
24
|
+
/** Authenticates the actual credential, throwing the owning domain's refusal on failure. */
|
|
25
|
+
authenticate(metadata: CredentialMetadata, authorization: string, resourceId?: string): Promise<CredentialPrincipal>
|
|
26
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ExecutionContext } from "@nestjs/common"
|
|
2
|
+
import type { HttpArgumentsHost } from "@nestjs/common/interfaces"
|
|
3
|
+
import { Reflector } from "@nestjs/core"
|
|
4
|
+
import { Test } from "@nestjs/testing"
|
|
5
|
+
import { mock } from "@starci/jest-preset"
|
|
6
|
+
import { CredentialGuard } from "./credential.guard"
|
|
7
|
+
import type { CredentialAuthenticator, CredentialMetadata, CredentialPrincipal } from "./credential.contracts"
|
|
8
|
+
import { IdentityError, IdentityErrorCode } from "./errors/identity.error"
|
|
9
|
+
import { CREDENTIAL_AUTHENTICATOR } from "./identity.decorators"
|
|
10
|
+
|
|
11
|
+
const PRINCIPAL: CredentialPrincipal = { kind: "tool-token", subject: "tool-1", workspaceId: "ws-1" }
|
|
12
|
+
|
|
13
|
+
interface FakeHeaders {
|
|
14
|
+
readonly authorization?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface FakeParams {
|
|
18
|
+
readonly id?: unknown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface FakeRequest {
|
|
22
|
+
credentialPrincipal?: CredentialPrincipal
|
|
23
|
+
readonly headers: FakeHeaders
|
|
24
|
+
readonly params?: FakeParams
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const build = async (metadata: CredentialMetadata | undefined, principal: CredentialPrincipal = PRINCIPAL) => {
|
|
28
|
+
const reflector = mock<Reflector>()
|
|
29
|
+
reflector.getAllAndOverride.mockReturnValue(metadata)
|
|
30
|
+
const authenticator = mock<CredentialAuthenticator>()
|
|
31
|
+
authenticator.authenticate.mockResolvedValue(principal)
|
|
32
|
+
const moduleRef = await Test.createTestingModule({
|
|
33
|
+
providers: [
|
|
34
|
+
CredentialGuard,
|
|
35
|
+
{ provide: Reflector, useValue: reflector },
|
|
36
|
+
{ provide: CREDENTIAL_AUTHENTICATOR, useValue: authenticator },
|
|
37
|
+
],
|
|
38
|
+
}).compile()
|
|
39
|
+
return { guard: moduleRef.get(CredentialGuard), authenticator }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const contextOf = (request: FakeRequest) => {
|
|
43
|
+
const context = mock<ExecutionContext>()
|
|
44
|
+
context.getHandler.mockReturnValue(() => undefined)
|
|
45
|
+
context.getClass.mockReturnValue(class DoorClass {})
|
|
46
|
+
context.getType.mockReturnValue("http")
|
|
47
|
+
const host = mock<HttpArgumentsHost>()
|
|
48
|
+
host.getRequest.mockReturnValue(request)
|
|
49
|
+
context.switchToHttp.mockReturnValue(host)
|
|
50
|
+
return context
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const requestOf = (): FakeRequest => ({ headers: { authorization: "Bearer secret" }, params: { id: "run-1" } })
|
|
54
|
+
|
|
55
|
+
describe("CredentialGuard", () => {
|
|
56
|
+
it("passes a door without credential metadata and authenticates nothing", async () => {
|
|
57
|
+
const { guard, authenticator } = await build(undefined)
|
|
58
|
+
const request = { ...requestOf(), credentialPrincipal: PRINCIPAL }
|
|
59
|
+
|
|
60
|
+
await expect(guard.canActivate(contextOf(request))).resolves.toBe(true)
|
|
61
|
+
expect(request.credentialPrincipal).toEqual(PRINCIPAL)
|
|
62
|
+
expect(authenticator.authenticate).not.toHaveBeenCalled()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("authenticates a credential door and sets the credential principal", async () => {
|
|
66
|
+
const { guard, authenticator } = await build({ kind: "tool-token" }, PRINCIPAL)
|
|
67
|
+
const request = requestOf()
|
|
68
|
+
|
|
69
|
+
await expect(guard.canActivate(contextOf(request))).resolves.toBe(true)
|
|
70
|
+
expect(request.credentialPrincipal).toEqual(PRINCIPAL)
|
|
71
|
+
expect(authenticator.authenticate).toHaveBeenCalledWith({ kind: "tool-token" }, "Bearer secret", "run-1")
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it("refuses a principal of another credential kind", async () => {
|
|
75
|
+
const { guard } = await build({ kind: "api-key" }, PRINCIPAL)
|
|
76
|
+
const request = requestOf()
|
|
77
|
+
|
|
78
|
+
await expect(guard.canActivate(contextOf(request))).rejects.toMatchObject({
|
|
79
|
+
code: IdentityErrorCode.Unauthenticated,
|
|
80
|
+
})
|
|
81
|
+
expect(request.credentialPrincipal).toBeUndefined()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it.each([
|
|
85
|
+
{ ...PRINCIPAL, subject: "" },
|
|
86
|
+
{ ...PRINCIPAL, workspaceId: "" },
|
|
87
|
+
])("refuses an incomplete credential principal (%o)", async (principal) => {
|
|
88
|
+
const { guard } = await build({ kind: "tool-token" }, principal)
|
|
89
|
+
const request = requestOf()
|
|
90
|
+
|
|
91
|
+
await expect(guard.canActivate(contextOf(request))).rejects.toMatchObject({
|
|
92
|
+
code: IdentityErrorCode.Unauthenticated,
|
|
93
|
+
})
|
|
94
|
+
expect(request.credentialPrincipal).toBeUndefined()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it("clears an earlier credential principal when authentication refuses", async () => {
|
|
98
|
+
const { guard, authenticator } = await build({ kind: "tool-token" }, PRINCIPAL)
|
|
99
|
+
const refusal = new IdentityError({ code: IdentityErrorCode.Unauthenticated })
|
|
100
|
+
authenticator.authenticate.mockRejectedValue(refusal)
|
|
101
|
+
const request = { ...requestOf(), credentialPrincipal: PRINCIPAL }
|
|
102
|
+
|
|
103
|
+
await expect(guard.canActivate(contextOf(request))).rejects.toBe(refusal)
|
|
104
|
+
expect(request.credentialPrincipal).toBeUndefined()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it.each([undefined, { id: 1 }])(
|
|
108
|
+
"passes absent authorization and no string resource id to the authenticator (%o)",
|
|
109
|
+
async (params) => {
|
|
110
|
+
const metadata = { kind: "tool-token", workflow: "checkout" } as const
|
|
111
|
+
const { guard, authenticator } = await build(metadata, PRINCIPAL)
|
|
112
|
+
const request: FakeRequest = { headers: {}, params }
|
|
113
|
+
|
|
114
|
+
await expect(guard.canActivate(contextOf(request))).resolves.toBe(true)
|
|
115
|
+
expect(authenticator.authenticate).toHaveBeenCalledWith(metadata, "", undefined)
|
|
116
|
+
expect(request.credentialPrincipal).toEqual(PRINCIPAL)
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Injectable } from "@nestjs/common"
|
|
2
|
+
import type { CanActivate, ExecutionContext } from "@nestjs/common"
|
|
3
|
+
import type { Reflector } from "@nestjs/core"
|
|
4
|
+
import { InjectReflector } from "@modules/platform/composition"
|
|
5
|
+
import { requestOf } from "@modules/platform/http-security"
|
|
6
|
+
import type { CredentialAuthenticator, CredentialMetadata } from "./credential.contracts"
|
|
7
|
+
import { IdentityError, IdentityErrorCode } from "./errors/identity.error"
|
|
8
|
+
import { CREDENTIAL_KEY, InjectCredentialAuthenticator } from "./identity.decorators"
|
|
9
|
+
|
|
10
|
+
@Injectable()
|
|
11
|
+
/**
|
|
12
|
+
* The third app guard: it acts only on a door marked `@Credential({ kind })`, authenticates the credential through the
|
|
13
|
+
* identity-owned port and sets the request's credential principal. Every other door passes through untouched and
|
|
14
|
+
* `AuthGuard` decides it.
|
|
15
|
+
*/
|
|
16
|
+
export class CredentialGuard implements CanActivate {
|
|
17
|
+
constructor(
|
|
18
|
+
@InjectReflector() private readonly reflector: Reflector,
|
|
19
|
+
@InjectCredentialAuthenticator() private readonly authenticator: CredentialAuthenticator,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/** Authenticates a credential door and leaves every other door to `AuthGuard`. */
|
|
23
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
24
|
+
const metadata = this.reflector.getAllAndOverride<CredentialMetadata | undefined>(CREDENTIAL_KEY, [
|
|
25
|
+
context.getHandler(),
|
|
26
|
+
context.getClass(),
|
|
27
|
+
])
|
|
28
|
+
if (metadata === undefined) return true
|
|
29
|
+
const request = requestOf(context)
|
|
30
|
+
request.credentialPrincipal = undefined
|
|
31
|
+
const id: unknown = request.params?.id
|
|
32
|
+
const principal = await this.authenticator.authenticate(
|
|
33
|
+
metadata,
|
|
34
|
+
request.headers.authorization ?? "",
|
|
35
|
+
typeof id === "string" ? id : undefined,
|
|
36
|
+
)
|
|
37
|
+
if (principal.kind !== metadata.kind || !principal.subject || !principal.workspaceId) {
|
|
38
|
+
throw new IdentityError({ code: IdentityErrorCode.Unauthenticated })
|
|
39
|
+
}
|
|
40
|
+
request.credentialPrincipal = principal
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
import type { CredentialPrincipal } from "./credential.contracts"
|
|
2
|
+
|
|
3
|
+
declare module "express-serve-static-core" {
|
|
4
|
+
/** The request carries what the credential guard established. */
|
|
5
|
+
interface Request {
|
|
6
|
+
/** The identity `CredentialGuard` authenticated on a door marked `@Credential`; never a session principal. */
|
|
7
|
+
credentialPrincipal?: CredentialPrincipal
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
/** Why a door is open to anonymous callers; the vocabulary is closed. */
|
|
2
12
|
export enum PublicReason {
|
|
3
13
|
/** Liveness and readiness probes. */
|
|
4
14
|
Health = "health",
|
|
15
|
+
/** The authentication protocol's anonymous handshake. */
|
|
16
|
+
AuthHandshake = "auth-handshake",
|
|
17
|
+
/** A webhook whose signature is verified before its domain intake runs. */
|
|
18
|
+
SignedWebhook = "signed-webhook",
|
|
19
|
+
/** A declared public catalog read. */
|
|
20
|
+
CatalogRead = "catalog-read",
|
|
5
21
|
}
|
|
6
22
|
|
|
7
23
|
/** The metadata `@Public` attaches to a door. */
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { SetMetadata } from "@nestjs/common"
|
|
2
|
+
import { injector } from "@modules/platform/composition"
|
|
3
|
+
import type { TypedParameterDecorator } from "@modules/platform/composition"
|
|
4
|
+
import type { CredentialAuthenticator, CredentialMetadata } from "./credential.contracts"
|
|
2
5
|
import type { PublicMetadata } from "./identity.contracts"
|
|
3
6
|
|
|
4
7
|
/** Metadata key of `@Public`. */
|
|
5
8
|
export const PUBLIC_KEY = "domain.identity.public"
|
|
6
9
|
|
|
7
|
-
/** Opens a door to anonymous callers, stating why; every other door needs a signed-in caller. */
|
|
10
|
+
/** Opens a door to anonymous callers, stating why; every other door needs a credential or a signed-in caller. */
|
|
8
11
|
export const Public = (metadata: PublicMetadata): ReturnType<typeof SetMetadata> => SetMetadata(PUBLIC_KEY, metadata)
|
|
12
|
+
|
|
13
|
+
/** Metadata key of `@Credential`. */
|
|
14
|
+
export const CREDENTIAL_KEY: unique symbol = Symbol("domain.identity.credential")
|
|
15
|
+
|
|
16
|
+
/** Token of the identity-owned port that authenticates an API key, run token, tool token or link token. */
|
|
17
|
+
export const CREDENTIAL_AUTHENTICATOR: unique symbol = Symbol("domain.identity.credential-authenticator")
|
|
18
|
+
|
|
19
|
+
/** Injects the credential authenticator identity was registered with. Parameter type: CredentialAuthenticator. */
|
|
20
|
+
export const InjectCredentialAuthenticator = (): TypedParameterDecorator<CredentialAuthenticator> =>
|
|
21
|
+
injector<CredentialAuthenticator>(CREDENTIAL_AUTHENTICATOR)
|
|
22
|
+
|
|
23
|
+
/** Marks a door authenticated by its own credential; `CredentialGuard` authenticates it and `AuthGuard` admits it. */
|
|
24
|
+
export const Credential = (metadata: CredentialMetadata): ReturnType<typeof SetMetadata> =>
|
|
25
|
+
SetMetadata(CREDENTIAL_KEY, metadata)
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common"
|
|
2
|
-
import type { DynamicModule } from "@nestjs/common"
|
|
2
|
+
import type { DynamicModule, Provider } from "@nestjs/common"
|
|
3
3
|
import { AuthGuard } from "./auth.guard"
|
|
4
|
+
import { CredentialGuard } from "./credential.guard"
|
|
5
|
+
import { IdentityError, IdentityErrorCode } from "./errors/identity.error"
|
|
6
|
+
import { CREDENTIAL_AUTHENTICATOR } from "./identity.decorators"
|
|
4
7
|
import { ConfigurableModuleClass, OPTIONS_TYPE } from "./identity.module-definition"
|
|
5
8
|
|
|
9
|
+
const refusingAuthenticator: Provider = {
|
|
10
|
+
provide: CREDENTIAL_AUTHENTICATOR,
|
|
11
|
+
useValue: {
|
|
12
|
+
authenticate: () => Promise.reject(new IdentityError({ code: IdentityErrorCode.Unauthenticated })),
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
@Module({})
|
|
7
|
-
/** The identity capability: the guard that closes every door by default. The app registers
|
|
17
|
+
/** The identity capability: the credential guard and the guard that closes every door by default. The app registers them as APP_GUARDs after the throttler and the origin guard. */
|
|
8
18
|
export class IdentityModule extends ConfigurableModuleClass {
|
|
9
|
-
/** Registers the capability once per app. */
|
|
19
|
+
/** Registers the capability once per app; the credential authenticator is the port the app binds. */
|
|
10
20
|
static register(options: typeof OPTIONS_TYPE): DynamicModule {
|
|
11
21
|
const base = super.register(options)
|
|
12
|
-
|
|
22
|
+
const authenticator: Provider =
|
|
23
|
+
options.credentialAuthenticator === undefined
|
|
24
|
+
? refusingAuthenticator
|
|
25
|
+
: { provide: CREDENTIAL_AUTHENTICATOR, useClass: options.credentialAuthenticator }
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
providers: [...(base.providers ?? []), authenticator, CredentialGuard, AuthGuard],
|
|
29
|
+
exports: [CredentialGuard, CREDENTIAL_AUTHENTICATOR, AuthGuard],
|
|
30
|
+
}
|
|
13
31
|
}
|
|
14
32
|
}
|