@starci/hfs 5.0.8 → 5.2.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 +30 -0
- package/README.md +10 -1
- package/cache/import-graph.mjs +6 -61
- package/cache/lint-family.d.mts +10 -1
- package/cache/lint-family.mjs +12 -5
- package/codemod/directive-first.d.mts +48 -0
- package/codemod/directive-first.mjs +136 -0
- package/codemod/explicit-surface-edit.d.mts +14 -0
- package/codemod/explicit-surface-edit.mjs +57 -0
- package/codemod/explicit-surface-names.d.mts +30 -0
- package/codemod/explicit-surface-names.mjs +135 -0
- package/codemod/explicit-surface-render.d.mts +29 -0
- package/codemod/explicit-surface-render.mjs +197 -0
- package/codemod/explicit-surface.d.mts +50 -0
- package/codemod/explicit-surface.mjs +374 -0
- package/codemod/main.d.mts +7 -5
- package/codemod/main.mjs +37 -16
- package/codemod/source-tree.d.mts +8 -0
- package/codemod/source-tree.mjs +23 -0
- package/db/postgres.mjs +9 -1
- package/lint/eslint-list.cjs +29 -0
- package/lint/run.d.mts +19 -5
- package/lint/run.mjs +47 -14
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +31 -10
- package/runtime/knowledge/hfs/fe-stack.yaml +7 -7
- package/runtime/knowledge/hfs/fork-images.yaml +7 -0
- package/runtime/knowledge/hfs/rules.yaml +107 -8
- package/runtime/knowledge/hfs/slots.yaml +75 -24
- package/runtime/modules/kernel/failure-codes.yaml +62 -0
- package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
- package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
- package/runtime/scripts/hfs/architecture/test-world-files.d.mts +4 -3
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +26 -7
- package/runtime/scripts/hfs/architecture/transport-owner.d.mts +1 -2
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -6
- package/runtime/scripts/hfs/explicit-surface-rule.d.mts +58 -0
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
- package/runtime/scripts/hfs/manifest-shape.d.mts +1 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +3 -1
- package/runtime/scripts/hfs/rules/a15-findings.mjs +5 -0
- package/runtime/scripts/hfs/rules/stacks-local.d.mts +7 -0
- package/runtime/scripts/hfs/rules/stacks-local.mjs +71 -0
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +6 -1
- package/runtime/scripts/hfs/rules/stacks-service-images.d.mts +12 -0
- package/runtime/scripts/hfs/rules/stacks-service-images.mjs +120 -0
- package/runtime/scripts/hfs/secret-dev-init.d.mts +11 -0
- package/runtime/scripts/hfs/secret-dev-init.mjs +79 -0
- package/runtime/scripts/hfs/secret.mjs +10 -7
- package/runtime/scripts/hfs/slots.mjs +1 -1
- package/runtime/scripts/hfs/stack-local-config.d.mts +101 -0
- package/runtime/scripts/hfs/stack-local-config.mjs +320 -0
- package/runtime/scripts/hfs/stacks-gate.mjs +1 -1
- package/runtime/scripts/lib/declaration-surface.d.mts +35 -0
- package/runtime/scripts/lib/declaration-surface.mjs +112 -0
- package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
- package/runtime/scripts/lib/swarm-stack.mjs +2 -2
- package/scaffold/add-module.d.mts +16 -0
- package/scaffold/add-module.mjs +232 -0
- package/scaffold/add-queue-lite.mjs +49 -0
- package/scaffold/add.mjs +5 -0
- package/scaffold/app.mjs +7 -7
- package/scaffold/module-registry.d.mts +19 -0
- package/scaffold/module-registry.mjs +84 -0
- package/src/main.mjs +23 -2
- package/stack/cli.mjs +6 -1
- package/stack/context.d.mts +15 -0
- package/stack/context.mjs +1 -0
- package/stack/local-cli.d.mts +25 -0
- package/stack/local-cli.mjs +89 -0
- package/stack/local-config.d.mts +17 -0
- package/stack/local-config.mjs +37 -0
- package/stack/local-custody.d.mts +24 -0
- package/stack/local-custody.mjs +109 -0
- package/stack/local-doctor.d.mts +6 -0
- package/stack/local-doctor.mjs +49 -0
- package/stack/local-down.d.mts +8 -0
- package/stack/local-down.mjs +20 -0
- package/stack/local-host.d.mts +42 -0
- package/stack/local-host.mjs +163 -0
- package/stack/local-logs.d.mts +8 -0
- package/stack/local-logs.mjs +32 -0
- package/stack/local-ports.d.mts +7 -0
- package/stack/local-ports.mjs +39 -0
- package/stack/local-prepare.d.mts +22 -0
- package/stack/local-prepare.mjs +62 -0
- package/stack/local-run.d.mts +71 -0
- package/stack/local-run.mjs +200 -0
- package/stack/local-state.d.mts +35 -0
- package/stack/local-state.mjs +29 -0
- package/stack/local-status.d.mts +6 -0
- package/stack/local-status.mjs +36 -0
- package/stack/local-up.d.mts +11 -0
- package/stack/local-up.mjs +166 -0
- package/stack/local-verify.d.mts +6 -0
- package/stack/local-verify.mjs +21 -0
- package/sync/index.d.mts +13 -12
- package/sync/index.mjs +83 -28
- package/templates/app/module/jest.config.js.tpl +8 -0
- package/templates/app/module/src/index.ts.tpl +2 -0
- package/templates/app/module/src/manifest/declaration.spec.ts.tpl +11 -0
- package/templates/app/module/src/manifest/declaration.ts.tpl +13 -0
- package/templates/app/module/src/manifest/index.ts.tpl +1 -0
- package/templates/app/module/src/public/index.ts.tpl +2 -0
- package/templates/app/module/src/template/__name__.template.spec.ts.tpl +13 -0
- package/templates/app/module/src/template/__name__.template.ts.tpl +41 -0
- package/templates/app/module/src/template/index.ts.tpl +1 -0
- package/templates/app/module/src/workbench/components/__pascal__WorkbenchView.tsx.tpl +16 -0
- package/templates/app/module/src/workbench/index.ts.tpl +2 -0
- package/templates/app/package-scripts/package.json +1 -1
- package/templates/app/package-scripts-lite/package.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +76 -70
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +23 -13
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +25 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -26
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +1 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +2 -1
- package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +57 -14
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.query.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-begin.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-begin.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-complete.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-complete.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/login.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/login.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/logout.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/logout.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/refresh.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/refresh.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/session.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/session.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +13 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.options.ts +3 -0
- package/templates/be/auth-lite/src/features/api/auth/index.ts +2 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +13 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module.ts +9 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.options.ts +3 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +90 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/google-start.request.ts +17 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/login.request.ts +16 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +46 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +28 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +145 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +95 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +38 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +54 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts +18 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +34 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-session.entity.ts +30 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +17 -0
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +86 -0
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +45 -0
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +97 -0
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +81 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +78 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +87 -0
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +44 -0
- package/templates/be/inbox/delivery-recorded.event.ts.tpl +2 -2
- package/templates/be/inbox/entities.index.ts.tpl +1 -1
- package/templates/be/inbox/module-definition.ts.tpl +8 -2
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +10 -2
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +8 -2
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -3
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +7 -6
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/job.processor.ts.tpl +1 -1
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +5 -4
- package/templates/be/patterns/projections/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -3
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/consumer.ts.tpl +1 -1
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +9 -3
- package/templates/be/platform/graphql/graphql.module-definition.ts +8 -3
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +9 -3
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +2 -1
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +8 -4
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +8 -3
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +6 -5
- package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +3 -2
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +10 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +34 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +47 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +3 -1
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +8 -4
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts +29 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +6 -31
- package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +18 -9
- package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +35 -11
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +44 -8
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +40 -18
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +18 -6
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.spec.ts +73 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +24 -53
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.registration.ts +31 -0
- package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts +1 -1
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +7 -1
- package/templates/be/table/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/table/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/table/entities.index.ts.tpl +1 -1
- package/templates/be/table/module-definition.ts.tpl +8 -3
- package/templates/be/test-world/e2e/serves-once-migrated.e2e-spec.ts.tpl +9 -9
- package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +4 -3
- package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
- package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
- package/templates/fe/common/app-layout.tsx.partial +1 -1
- package/templates/fe/common/site-shell.tsx.partial +3 -3
- package/templates/fe/skeleton/apps/__app__/src/components/providers/AppProviders/index.tsx +33 -10
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignIn.ts +11 -3
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +8 -2
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/hooks.ts +1 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +18 -12
- package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/hooks.ts +3 -2
- package/templates/fe/skeleton/apps/__app__/src/redux/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +12 -4
- package/templates/fe/skeleton/apps/__app__/src/redux/store.ts +8 -3
- package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +1 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +20 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +5 -1
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +4 -7
- package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/index.tsx +2 -3
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +4 -7
- package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +3 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +5 -1
- package/templates/fe/table/graphql/useMutateSwr.ts.tpl +2 -1
- package/templates/fe/table/graphql/useQuerySwr.ts.tpl +2 -1
- package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/clients/no-auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/attach-access-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/error.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/http.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/locale.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/refresh-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/retry.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/timeout.ts +1 -1
- package/templates/fe/units/page/index.tsx.tpl +3 -2
- package/templates/fe/units/slot/SlotView.tsx.tpl +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +0 -16
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +0 -11
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +0 -7
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +0 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +0 -8
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +0 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -186
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +0 -22
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +0 -25
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +0 -104
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +0 -34
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts +0 -28
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +0 -11
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +0 -88
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +0 -69
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +0 -37
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -36
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +0 -63
- package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/classNames.ts +0 -4
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +0 -30
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +0 -50
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +0 -31
- package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/classNames.ts +0 -4
|
@@ -2,7 +2,7 @@ import { SetContextLink } from "@apollo/client/link/context"
|
|
|
2
2
|
import { contextHeaders } from "./context-headers"
|
|
3
3
|
|
|
4
4
|
/** Attaches `Authorization: Bearer <access token>` from modules/auth when a session exists; an anonymous call goes out without it. */
|
|
5
|
-
export const createAttachAccessTokenLink = (accessToken: () => Promise<string | undefined>) =>
|
|
5
|
+
export const createAttachAccessTokenLink = (accessToken: () => Promise<string | undefined>): SetContextLink =>
|
|
6
6
|
new SetContextLink(async (context) => {
|
|
7
7
|
const token = await accessToken()
|
|
8
8
|
if (token === undefined) return {}
|
|
@@ -15,7 +15,7 @@ const codeOf = (error: GraphQLErrorEntry): string =>
|
|
|
15
15
|
* Reports what came back from downstream to `report` when the app passes one. The operation maps every error to its
|
|
16
16
|
* GraphQLResponse, so this link never swallows or rewrites one.
|
|
17
17
|
*/
|
|
18
|
-
export const createErrorLink = (report?: ErrorReporter) =>
|
|
18
|
+
export const createErrorLink = (report?: ErrorReporter): ErrorLink =>
|
|
19
19
|
new ErrorLink(({ error, operation }) => {
|
|
20
20
|
if (report === undefined) return
|
|
21
21
|
if (CombinedGraphQLErrors.is(error)) {
|
|
@@ -8,7 +8,7 @@ export interface CreateHttpLinkParams {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/** The terminal link: POSTs the operation to the back end's GraphQL endpoint (modules/config). */
|
|
11
|
-
export const createHttpLink = ({ withCredentials = false, signal }: CreateHttpLinkParams) =>
|
|
11
|
+
export const createHttpLink = ({ withCredentials = false, signal }: CreateHttpLinkParams): HttpLink =>
|
|
12
12
|
new HttpLink({
|
|
13
13
|
uri: graphqlUrl(),
|
|
14
14
|
credentials: withCredentials ? "include" : "same-origin",
|
|
@@ -6,7 +6,7 @@ const documentLocale = (): string | undefined =>
|
|
|
6
6
|
typeof document === "undefined" || document.documentElement.lang === "" ? undefined : document.documentElement.lang
|
|
7
7
|
|
|
8
8
|
/** The one writer of `Accept-Language`: every operation declares the language the back end answers and localizes its errors in. */
|
|
9
|
-
export const createAttachLocaleLink = (locale: () => string | undefined = documentLocale) =>
|
|
9
|
+
export const createAttachLocaleLink = (locale: () => string | undefined = documentLocale): SetContextLink =>
|
|
10
10
|
new SetContextLink((context) => {
|
|
11
11
|
const value = locale()
|
|
12
12
|
if (value === undefined) return {}
|
|
@@ -15,7 +15,7 @@ const isUnauthenticated = (error: unknown): boolean =>
|
|
|
15
15
|
* On an UNAUTHENTICATED answer, asks modules/auth for a fresh access token once and replays the operation with it. When no
|
|
16
16
|
* token comes back (the session ended) the original error travels on.
|
|
17
17
|
*/
|
|
18
|
-
export const createRefreshTokenLink = (refreshAccessToken: () => Promise<string | undefined>) =>
|
|
18
|
+
export const createRefreshTokenLink = (refreshAccessToken: () => Promise<string | undefined>): ErrorLink =>
|
|
19
19
|
new ErrorLink(({ error, operation, forward }) => {
|
|
20
20
|
if (!isUnauthenticated(error) || operation.getContext()[REFRESHED] === true) return
|
|
21
21
|
return new Observable((observer) => {
|
|
@@ -6,7 +6,7 @@ import { RETRY_POLICY } from "./options"
|
|
|
6
6
|
* Retries a transient network failure with jittered exponential backoff. A GraphQL error is an answer of the back end and
|
|
7
7
|
* is never retried. First in the chain, so every other link sees the operation once per attempt.
|
|
8
8
|
*/
|
|
9
|
-
export const createRetryLink = () =>
|
|
9
|
+
export const createRetryLink = (): RetryLink =>
|
|
10
10
|
new RetryLink({
|
|
11
11
|
delay: { initial: RETRY_POLICY.initialDelayMs, max: RETRY_POLICY.maxDelayMs, jitter: true },
|
|
12
12
|
attempts: {
|
|
@@ -2,7 +2,7 @@ import { ApolloLink, Observable } from "@apollo/client"
|
|
|
2
2
|
import { OPERATION_TIMEOUT_MS } from "./options"
|
|
3
3
|
|
|
4
4
|
/** Fails the operation when no answer arrived within the timeout; the upstream links see the timeout as an error. */
|
|
5
|
-
export const createTimeoutLink = (timeoutMs = OPERATION_TIMEOUT_MS) =>
|
|
5
|
+
export const createTimeoutLink = (timeoutMs: number = OPERATION_TIMEOUT_MS): ApolloLink =>
|
|
6
6
|
new ApolloLink(
|
|
7
7
|
(operation, forward) =>
|
|
8
8
|
new Observable((observer) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Metadata } from "next"
|
|
2
|
+
import type { ReactElement } from "react"
|
|
2
3
|
import { getTranslations } from "next-intl/server"
|
|
3
4
|
import { APP_ROUTES } from "@/modules/routes"
|
|
4
5
|
import { {{Name}}Base } from "./component"
|
|
@@ -23,7 +24,7 @@ const copyOf = async (isLoading: boolean): Promise<{{Name}}BaseProps["props"]> =
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/** The {{route}} page: a server owner that resolves its copy and composes its blocks; it reads no data. */
|
|
26
|
-
export const {{Name}} = async () => <{{Name}}Base props={await copyOf(false)} />
|
|
27
|
+
export const {{Name}} = async (): Promise<ReactElement> => <{{Name}}Base props={await copyOf(false)} />
|
|
27
28
|
|
|
28
29
|
/** The {{route}} route's loading view: the same page drawn in skeleton mode, never a separate layout (STATE-8). */
|
|
29
|
-
export const {{LoadingName}} = async () => <{{Name}}Base props={await copyOf(true)} />
|
|
30
|
+
export const {{LoadingName}} = async (): Promise<ReactElement> => <{{Name}}Base props={await copyOf(true)} />
|
|
@@ -15,7 +15,7 @@ type SlotViewProps<T> = {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/** The one status view (FE-ERROR-1): every region renders its loading, forbidden, error and empty status through here. */
|
|
18
|
-
export const SlotView = <T,>(props: SlotViewProps<T>) => {
|
|
18
|
+
export const SlotView = <T,>(props: SlotViewProps<T>): ReactNode => {
|
|
19
19
|
const { slot } = props
|
|
20
20
|
if (slot.isLoading === true) return props.render(props.placeholder, true)
|
|
21
21
|
if (slot.isForbidden === true) return props.forbidden === "hide" ? null : <EmptyNotice message={props.labels.forbidden} />
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Development prerequisite record
|
|
2
|
-
|
|
3
|
-
Status: pending owner execution. This record states intended configuration and contains no completed operational receipt.
|
|
4
|
-
|
|
5
|
-
## DNS
|
|
6
|
-
|
|
7
|
-
The development operator replaces `dev.{{project}}.example.invalid` with a locally owned development name, updates the manifest, Caddyfile and public configuration together, and resolves the name to the Swarm node. Record the actual resolver mapping and dated verification here.
|
|
8
|
-
|
|
9
|
-
## TLS
|
|
10
|
-
|
|
11
|
-
The local development route uses HTTP on ingress port 80 with `auto_https off`. The development operator owns this local-only choice and both external ingress volumes. Record any change to locally issued TLS, certificate custody and verification here before exposing the development stack outside the local network.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Development recovery record
|
|
2
|
-
|
|
3
|
-
Status: pending owner execution. No backup or restore has been run by scaffolding.
|
|
4
|
-
|
|
5
|
-
The development operator owns recovery of the db service's volume `{{project}}-dev-db-data` and both external ingress volumes, `{{project}}-dev-ingress-data` and `{{project}}-dev-ingress-config-data`. Retain seven daily encrypted off-host exports or snapshots for each target.
|
|
6
|
-
|
|
7
|
-
Before relying on the backup policy, restore a database export and both volume snapshots into isolated infrastructure. Record the actual off-host destination, encryption custody, backup artifact digests, restoration time and operator. Verify database connectivity and the API readiness endpoint, then verify the restored ingress configuration and state. Attach the dated result here without credentials.
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Illustrative inventory only. Replace application digests with verified published images.
|
|
2
|
-
schema: starci/images-lock@1
|
|
3
|
-
environment: dev
|
|
4
|
-
status: pending-owner-release
|
|
5
|
-
images:
|
|
6
|
-
api: "{{project}}/api:v0.0.0@sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
7
|
-
web: "{{project}}/web:v0.0.0@sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
8
|
-
ingress: caddy:2.10.2@sha256:c3d7ee5d2b11f9dc54f947f68a734c84e9c9666c92c88a7f30b9cba5da182adb
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
# Illustrative development model. Replace all-zero application digests with verified published release digests before use.
|
|
2
|
-
services:
|
|
3
|
-
api:
|
|
4
|
-
image: "{{project}}/api:v0.0.0@sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
5
|
-
environment:
|
|
6
|
-
NODE_ENV: development
|
|
7
|
-
PORT: "3001"
|
|
8
|
-
HTTP_SECURITY_ALLOWED_ORIGINS: http://dev.{{project}}.example.invalid
|
|
9
|
-
PRIMARY_DB_URL_FILE: /run/secrets/primary-db-url
|
|
10
|
-
PRIMARY_DB_SCHEMA: public
|
|
11
|
-
GOOGLE_CLIENT_ID: REPLACE_WITH_GOOGLE_OAUTH_CLIENT_ID
|
|
12
|
-
GOOGLE_CLIENT_SECRET_FILE: /run/secrets/google-client-secret
|
|
13
|
-
GOOGLE_CALLBACK_URL: http://dev.{{project}}.example.invalid/auth/google/callback
|
|
14
|
-
AUTH_JWT_ISSUER: http://dev.{{project}}.example.invalid
|
|
15
|
-
AUTH_JWT_AUDIENCE: {{project}}
|
|
16
|
-
AUTH_JWT_KEY_ID: dev-1
|
|
17
|
-
AUTH_JWT_PRIVATE_KEY_FILE: /run/secrets/auth-jwt-private-key
|
|
18
|
-
STORAGE_ENDPOINT: https://storage.{{project}}.example.invalid
|
|
19
|
-
STORAGE_REGION: us-east-1
|
|
20
|
-
STORAGE_BUCKET: {{project}}-dev
|
|
21
|
-
STORAGE_ACCESS_KEY_ID_FILE: /run/secrets/storage-access-key-id
|
|
22
|
-
STORAGE_SECRET_ACCESS_KEY_FILE: /run/secrets/storage-secret-access-key
|
|
23
|
-
secrets:
|
|
24
|
-
- source: primary-db-url
|
|
25
|
-
target: primary-db-url
|
|
26
|
-
- source: google-client-secret
|
|
27
|
-
target: google-client-secret
|
|
28
|
-
- source: auth-jwt-private-key
|
|
29
|
-
target: auth-jwt-private-key
|
|
30
|
-
- source: storage-access-key-id
|
|
31
|
-
target: storage-access-key-id
|
|
32
|
-
- source: storage-secret-access-key
|
|
33
|
-
target: storage-secret-access-key
|
|
34
|
-
networks: [app]
|
|
35
|
-
healthcheck:
|
|
36
|
-
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3001/health/live').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
|
37
|
-
interval: 30s
|
|
38
|
-
timeout: 5s
|
|
39
|
-
start_period: 20s
|
|
40
|
-
retries: 3
|
|
41
|
-
deploy:
|
|
42
|
-
replicas: 1
|
|
43
|
-
placement:
|
|
44
|
-
constraints: ["node.labels.{{project}} == dev"]
|
|
45
|
-
update_config:
|
|
46
|
-
parallelism: 1
|
|
47
|
-
order: start-first
|
|
48
|
-
failure_action: rollback
|
|
49
|
-
monitor: 30s
|
|
50
|
-
# Stateless and served behind the ingress: roll back start-first so the serving task stays up until the old release is healthy.
|
|
51
|
-
rollback_config:
|
|
52
|
-
parallelism: 1
|
|
53
|
-
order: start-first
|
|
54
|
-
restart_policy:
|
|
55
|
-
condition: on-failure
|
|
56
|
-
delay: 5s
|
|
57
|
-
web:
|
|
58
|
-
image: "{{project}}/web:v0.0.0@sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
59
|
-
environment:
|
|
60
|
-
NODE_ENV: development
|
|
61
|
-
PORT: "3000"
|
|
62
|
-
HOSTNAME: 0.0.0.0
|
|
63
|
-
NEXT_PUBLIC_SITE_URL: http://dev.{{project}}.example.invalid
|
|
64
|
-
networks: [app]
|
|
65
|
-
healthcheck:
|
|
66
|
-
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health/live').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
|
67
|
-
interval: 30s
|
|
68
|
-
timeout: 5s
|
|
69
|
-
start_period: 20s
|
|
70
|
-
retries: 3
|
|
71
|
-
deploy:
|
|
72
|
-
replicas: 1
|
|
73
|
-
placement:
|
|
74
|
-
constraints: ["node.labels.{{project}} == dev"]
|
|
75
|
-
update_config:
|
|
76
|
-
parallelism: 1
|
|
77
|
-
order: start-first
|
|
78
|
-
failure_action: rollback
|
|
79
|
-
monitor: 30s
|
|
80
|
-
# Stateless and served behind the ingress: roll back start-first so the serving task stays up until the old release is healthy.
|
|
81
|
-
rollback_config:
|
|
82
|
-
parallelism: 1
|
|
83
|
-
order: start-first
|
|
84
|
-
restart_policy:
|
|
85
|
-
condition: on-failure
|
|
86
|
-
delay: 5s
|
|
87
|
-
db:
|
|
88
|
-
image: "{{project}}/db:v0.0.0@sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
89
|
-
environment:
|
|
90
|
-
POSTGRES_DB: postgres
|
|
91
|
-
POSTGRES_PASSWORD_FILE: /run/secrets/db-password
|
|
92
|
-
secrets:
|
|
93
|
-
- source: db-password
|
|
94
|
-
target: db-password
|
|
95
|
-
volumes:
|
|
96
|
-
- db-data:/var/lib/postgresql/data
|
|
97
|
-
networks: [app]
|
|
98
|
-
healthcheck:
|
|
99
|
-
test: ["CMD", "pg_isready", "-U", "postgres", "-h", "127.0.0.1"]
|
|
100
|
-
interval: 10s
|
|
101
|
-
timeout: 5s
|
|
102
|
-
start_period: 30s
|
|
103
|
-
retries: 5
|
|
104
|
-
deploy:
|
|
105
|
-
replicas: 1
|
|
106
|
-
placement:
|
|
107
|
-
constraints: ["node.labels.{{project}} == dev"]
|
|
108
|
-
update_config:
|
|
109
|
-
parallelism: 1
|
|
110
|
-
order: stop-first
|
|
111
|
-
failure_action: rollback
|
|
112
|
-
monitor: 30s
|
|
113
|
-
# The database owns a data volume and is single-writer: never run two at once, so rollback stays stop-first.
|
|
114
|
-
rollback_config:
|
|
115
|
-
parallelism: 1
|
|
116
|
-
order: stop-first
|
|
117
|
-
restart_policy:
|
|
118
|
-
condition: on-failure
|
|
119
|
-
delay: 5s
|
|
120
|
-
ingress:
|
|
121
|
-
image: caddy:2.10.2@sha256:c3d7ee5d2b11f9dc54f947f68a734c84e9c9666c92c88a7f30b9cba5da182adb
|
|
122
|
-
configs:
|
|
123
|
-
- source: ingress-config
|
|
124
|
-
target: /etc/caddy/Caddyfile
|
|
125
|
-
ports:
|
|
126
|
-
- { target: 80, published: 80, protocol: tcp, mode: ingress }
|
|
127
|
-
volumes:
|
|
128
|
-
- ingress-data:/data
|
|
129
|
-
- ingress-config-data:/config
|
|
130
|
-
networks: [app]
|
|
131
|
-
healthcheck:
|
|
132
|
-
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:2019/config/"]
|
|
133
|
-
interval: 30s
|
|
134
|
-
timeout: 5s
|
|
135
|
-
retries: 3
|
|
136
|
-
deploy:
|
|
137
|
-
replicas: 1
|
|
138
|
-
placement:
|
|
139
|
-
constraints: ["node.labels.{{project}} == dev"]
|
|
140
|
-
update_config:
|
|
141
|
-
parallelism: 1
|
|
142
|
-
order: stop-first
|
|
143
|
-
failure_action: rollback
|
|
144
|
-
monitor: 30s
|
|
145
|
-
# The ingress owns published ports and a certificate volume: never run two at once, so rollback stays stop-first.
|
|
146
|
-
rollback_config:
|
|
147
|
-
parallelism: 1
|
|
148
|
-
order: stop-first
|
|
149
|
-
restart_policy:
|
|
150
|
-
condition: on-failure
|
|
151
|
-
delay: 5s
|
|
152
|
-
networks:
|
|
153
|
-
app:
|
|
154
|
-
driver: overlay
|
|
155
|
-
secrets:
|
|
156
|
-
primary-db-url:
|
|
157
|
-
external: true
|
|
158
|
-
name: "{{project}}-dev-primary-db-url-v1"
|
|
159
|
-
db-password:
|
|
160
|
-
external: true
|
|
161
|
-
name: "{{project}}-dev-db-password-v1"
|
|
162
|
-
google-client-secret:
|
|
163
|
-
external: true
|
|
164
|
-
name: "{{project}}-dev-google-client-secret-v1"
|
|
165
|
-
auth-jwt-private-key:
|
|
166
|
-
external: true
|
|
167
|
-
name: "{{project}}-dev-auth-jwt-private-key-v1"
|
|
168
|
-
storage-access-key-id:
|
|
169
|
-
external: true
|
|
170
|
-
name: "{{project}}-dev-storage-access-key-id-v1"
|
|
171
|
-
storage-secret-access-key:
|
|
172
|
-
external: true
|
|
173
|
-
name: "{{project}}-dev-storage-secret-access-key-v1"
|
|
174
|
-
configs:
|
|
175
|
-
ingress-config:
|
|
176
|
-
file: ./Caddyfile
|
|
177
|
-
volumes:
|
|
178
|
-
db-data:
|
|
179
|
-
external: true
|
|
180
|
-
name: "{{project}}-dev-db-data"
|
|
181
|
-
ingress-data:
|
|
182
|
-
external: true
|
|
183
|
-
name: "{{project}}-dev-ingress-data"
|
|
184
|
-
ingress-config-data:
|
|
185
|
-
external: true
|
|
186
|
-
name: "{{project}}-dev-ingress-config-data"
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Secret } from "@modules/platform/config"
|
|
2
|
-
import { createObjectStorage } from "./object-storage.client"
|
|
3
|
-
|
|
4
|
-
const options = {
|
|
5
|
-
endpoint: "http://127.0.0.1:9000",
|
|
6
|
-
region: "us-east-1",
|
|
7
|
-
bucket: "app",
|
|
8
|
-
accessKeyId: new Secret("access"),
|
|
9
|
-
secretAccessKey: new Secret("secret"),
|
|
10
|
-
presignTtlSeconds: 300,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe("createObjectStorage", () => {
|
|
14
|
-
it("presigns a path-style PUT of one key in the bucket, valid for the configured lifetime", async () => {
|
|
15
|
-
const storage = createObjectStorage(options, () => new Date("2026-10-29T00:00:00.000Z"))
|
|
16
|
-
const request = await storage.presignPut("users/u1/avatar.png", "image/png")
|
|
17
|
-
|
|
18
|
-
expect(new URL(request.url).pathname).toBe("/app/users/u1/avatar.png")
|
|
19
|
-
expect(new URL(request.url).searchParams.get("X-Amz-Expires")).toBe("300")
|
|
20
|
-
expect(request.expiresAt).toBe("2026-10-29T00:05:00.000Z")
|
|
21
|
-
})
|
|
22
|
-
})
|
package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3"
|
|
2
|
-
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
|
3
|
-
import type { ObjectStorage, PresignedRequest, ObjectStorageOptions } from "./object-storage.options"
|
|
4
|
-
|
|
5
|
-
/** The one S3 client of the app over S3-compatible storage (path-style, as MinIO serves it). */
|
|
6
|
-
export const createObjectStorage = (options: ObjectStorageOptions, now: () => Date = () => new Date()): ObjectStorage => {
|
|
7
|
-
const client = new S3Client({
|
|
8
|
-
endpoint: options.endpoint,
|
|
9
|
-
region: options.region,
|
|
10
|
-
forcePathStyle: true,
|
|
11
|
-
credentials: { accessKeyId: options.accessKeyId.reveal(), secretAccessKey: options.secretAccessKey.reveal() },
|
|
12
|
-
})
|
|
13
|
-
const expiresAt = (): string => new Date(now().getTime() + options.presignTtlSeconds * 1000).toISOString()
|
|
14
|
-
const presign = async (command: PutObjectCommand | GetObjectCommand): Promise<PresignedRequest> => ({
|
|
15
|
-
url: await getSignedUrl(client, command, { expiresIn: options.presignTtlSeconds }),
|
|
16
|
-
expiresAt: expiresAt(),
|
|
17
|
-
})
|
|
18
|
-
return {
|
|
19
|
-
presignPut: (key, contentType) => presign(new PutObjectCommand({ Bucket: options.bucket, Key: key, ContentType: contentType })),
|
|
20
|
-
presignGet: (key) => presign(new GetObjectCommand({ Bucket: options.bucket, Key: key })),
|
|
21
|
-
remove: async (key) => {
|
|
22
|
-
await client.send(new DeleteObjectCommand({ Bucket: options.bucket, Key: key }))
|
|
23
|
-
},
|
|
24
|
-
}
|
|
25
|
-
}
|
package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { Test } from "@nestjs/testing"
|
|
2
|
-
import type { Request, Response } from "express"
|
|
3
|
-
import { mock } from "@starci/jest-preset"
|
|
4
|
-
import { clearRefreshCookie, newRefreshToken, refreshTokenHash, setRefreshCookie } from "./passport.cookie"
|
|
5
|
-
import { CookieStateStore } from "./strategies/oauth-state.store"
|
|
6
|
-
|
|
7
|
-
const COOKIE: unique symbol = Symbol("passport.cookie")
|
|
8
|
-
|
|
9
|
-
const OPTIONS = { name: "refresh", path: "/auth/refresh", ttlSeconds: 600 }
|
|
10
|
-
|
|
11
|
-
const build = async () => {
|
|
12
|
-
const moduleRef = await Test.createTestingModule({
|
|
13
|
-
providers: [{ provide: COOKIE, useValue: { clearRefreshCookie, newRefreshToken, refreshTokenHash, setRefreshCookie } }],
|
|
14
|
-
}).compile()
|
|
15
|
-
return moduleRef.get<{
|
|
16
|
-
clearRefreshCookie: typeof clearRefreshCookie
|
|
17
|
-
newRefreshToken: typeof newRefreshToken
|
|
18
|
-
refreshTokenHash: typeof refreshTokenHash
|
|
19
|
-
setRefreshCookie: typeof setRefreshCookie
|
|
20
|
-
}>(COOKIE)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe("refresh tokens", () => {
|
|
24
|
-
it("stores the sha256 of a token, never the token", async () => {
|
|
25
|
-
const subject = await build()
|
|
26
|
-
|
|
27
|
-
expect(subject.refreshTokenHash("abc")).toBe("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it("mints a 256-bit base64url token paired with its own hash", async () => {
|
|
31
|
-
const subject = await build()
|
|
32
|
-
const minted = subject.newRefreshToken()
|
|
33
|
-
|
|
34
|
-
expect(Buffer.from(minted.token, "base64url")).toHaveLength(32)
|
|
35
|
-
expect(minted.hash).toBe(subject.refreshTokenHash(minted.token))
|
|
36
|
-
expect(subject.newRefreshToken().token).not.toBe(minted.token)
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe("refresh cookie", () => {
|
|
41
|
-
it("sets the rotated token httpOnly, Secure, SameSite=Strict on the refresh path for its lifetime", async () => {
|
|
42
|
-
const subject = await build()
|
|
43
|
-
const response = mock<Response>()
|
|
44
|
-
|
|
45
|
-
subject.setRefreshCookie(response, OPTIONS, "token-1")
|
|
46
|
-
|
|
47
|
-
expect(response.cookie).toHaveBeenCalledWith("refresh", "token-1", { httpOnly: true, secure: true, sameSite: "strict", path: "/auth/refresh", maxAge: 600_000 })
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it("clears the cookie with the attributes it was set with", async () => {
|
|
51
|
-
const subject = await build()
|
|
52
|
-
const response = mock<Response>()
|
|
53
|
-
|
|
54
|
-
subject.clearRefreshCookie(response, OPTIONS)
|
|
55
|
-
|
|
56
|
-
expect(response.clearCookie).toHaveBeenCalledWith("refresh", { httpOnly: true, secure: true, sameSite: "strict", path: "/auth/refresh" })
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it("stays host-only: no Domain attribute is set or cleared when no cookie domain is configured", async () => {
|
|
60
|
-
const subject = await build()
|
|
61
|
-
const response = mock<Response>()
|
|
62
|
-
|
|
63
|
-
subject.setRefreshCookie(response, OPTIONS, "token-1")
|
|
64
|
-
subject.clearRefreshCookie(response, OPTIONS)
|
|
65
|
-
|
|
66
|
-
expect(response.cookie).toHaveBeenCalledWith("refresh", "token-1", expect.not.objectContaining({ domain: expect.anything() }))
|
|
67
|
-
expect(response.clearCookie).toHaveBeenCalledWith("refresh", expect.not.objectContaining({ domain: expect.anything() }))
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it("sets the rotated token with Domain=<cookieDomain> so a sibling host can forward it", async () => {
|
|
71
|
-
const subject = await build()
|
|
72
|
-
const response = mock<Response>()
|
|
73
|
-
|
|
74
|
-
subject.setRefreshCookie(response, { ...OPTIONS, cookieDomain: "example.com" }, "token-1")
|
|
75
|
-
|
|
76
|
-
expect(response.cookie).toHaveBeenCalledWith("refresh", "token-1", { httpOnly: true, secure: true, sameSite: "strict", path: "/auth/refresh", domain: "example.com", maxAge: 600_000 })
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it("clears the cookie with the same Domain it was set with, or the browser keeps it", async () => {
|
|
80
|
-
const subject = await build()
|
|
81
|
-
const response = mock<Response>()
|
|
82
|
-
|
|
83
|
-
subject.clearRefreshCookie(response, { ...OPTIONS, cookieDomain: "example.com" })
|
|
84
|
-
|
|
85
|
-
expect(response.clearCookie).toHaveBeenCalledWith("refresh", { httpOnly: true, secure: true, sameSite: "strict", path: "/auth/refresh", domain: "example.com" })
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
describe("oauth state cookie", () => {
|
|
90
|
-
const request = (response: Response): Request => ({ res: response, headers: {} }) as unknown as Request
|
|
91
|
-
|
|
92
|
-
it("is host-only without a cookie domain and carries Domain=<cookieDomain> with one, on set and on clear", () => {
|
|
93
|
-
const hostOnly = mock<Response>()
|
|
94
|
-
const shared = mock<Response>()
|
|
95
|
-
|
|
96
|
-
new CookieStateStore("/auth/google/callback").store(request(hostOnly), "verifier", undefined, undefined, () => undefined)
|
|
97
|
-
new CookieStateStore("/auth/google/callback", "example.com").store(request(shared), "verifier", undefined, undefined, () => undefined)
|
|
98
|
-
new CookieStateStore("/auth/google/callback", "example.com").verify(request(shared), "state", () => undefined)
|
|
99
|
-
|
|
100
|
-
expect(hostOnly.cookie).toHaveBeenCalledWith("oauth_state", expect.stringMatching(/^[A-Za-z0-9_-]{32}[.]verifier$/), expect.not.objectContaining({ domain: expect.anything() }))
|
|
101
|
-
expect(shared.cookie).toHaveBeenCalledWith("oauth_state", expect.stringMatching(/^[A-Za-z0-9_-]{32}[.]verifier$/), expect.objectContaining({ domain: "example.com" }))
|
|
102
|
-
expect(shared.clearCookie).toHaveBeenCalledWith("oauth_state", expect.objectContaining({ domain: "example.com" }))
|
|
103
|
-
})
|
|
104
|
-
})
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from "node:crypto"
|
|
2
|
-
import type { CookieOptions, Response } from "express"
|
|
3
|
-
import type { RefreshCookieOptions } from "./passport.options"
|
|
4
|
-
|
|
5
|
-
/** A fresh opaque refresh token (256 bits) and the sha256 the session store keeps instead of it. */
|
|
6
|
-
export const newRefreshToken = (): { readonly token: string; readonly hash: string } => {
|
|
7
|
-
const token = randomBytes(32).toString("base64url")
|
|
8
|
-
return { token, hash: refreshTokenHash(token) }
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** The sha256 a refresh token is stored and looked up by; the token itself is never stored. */
|
|
12
|
-
export const refreshTokenHash = (token: string): string => createHash("sha256").update(token).digest("hex")
|
|
13
|
-
|
|
14
|
-
/** `Domain=<cookieDomain>` when the app configured one; absent (host-only) otherwise. Setting and clearing must agree on it. */
|
|
15
|
-
export const domainAttribute = (cookieDomain: string | undefined): Pick<CookieOptions, "domain"> =>
|
|
16
|
-
cookieDomain === undefined ? {} : { domain: cookieDomain }
|
|
17
|
-
|
|
18
|
-
const attributes = (options: RefreshCookieOptions): CookieOptions => ({
|
|
19
|
-
httpOnly: true,
|
|
20
|
-
secure: true,
|
|
21
|
-
sameSite: "strict",
|
|
22
|
-
path: options.path,
|
|
23
|
-
...domainAttribute(options.cookieDomain),
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
/** Sets the rotated refresh token: httpOnly, Secure, SameSite=Strict and sent to the refresh door only. */
|
|
27
|
-
export const setRefreshCookie = (response: Response, options: RefreshCookieOptions, token: string): void => {
|
|
28
|
-
response.cookie(options.name, token, { ...attributes(options), maxAge: options.ttlSeconds * 1000 })
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Clears the refresh cookie on sign-out or on a detected reuse. */
|
|
32
|
-
export const clearRefreshCookie = (response: Response, options: RefreshCookieOptions): void => {
|
|
33
|
-
response.clearCookie(options.name, attributes(options))
|
|
34
|
-
}
|
package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Test } from "@nestjs/testing"
|
|
2
|
-
import { hashPassword, verifyPassword } from "./passport.password"
|
|
3
|
-
|
|
4
|
-
const PASSWORD: unique symbol = Symbol("passport.password")
|
|
5
|
-
|
|
6
|
-
const build = async () => {
|
|
7
|
-
const moduleRef = await Test.createTestingModule({
|
|
8
|
-
providers: [{ provide: PASSWORD, useValue: { hashPassword, verifyPassword } }],
|
|
9
|
-
}).compile()
|
|
10
|
-
return moduleRef.get<{ hashPassword: typeof hashPassword; verifyPassword: typeof verifyPassword }>(PASSWORD)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe("hashPassword and verifyPassword", () => {
|
|
14
|
-
it("hashes at bcrypt cost 12 and verifies only the password it hashed", async () => {
|
|
15
|
-
const subject = await build()
|
|
16
|
-
const stored = await subject.hashPassword("correct horse")
|
|
17
|
-
|
|
18
|
-
expect(stored.slice(0, 7)).toBe("$2b$12$")
|
|
19
|
-
expect(await subject.verifyPassword("correct horse", stored)).toBe(true)
|
|
20
|
-
expect(await subject.verifyPassword("wrong horse", stored)).toBe(false)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it("never matches an account that has no password hash", async () => {
|
|
24
|
-
const subject = await build()
|
|
25
|
-
|
|
26
|
-
expect(await subject.verifyPassword("correct horse", null)).toBe(false)
|
|
27
|
-
})
|
|
28
|
-
})
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { compare, hash } from "bcryptjs"
|
|
2
|
-
|
|
3
|
-
/** The bcrypt cost of a new password hash; existing `$2a$`/`$2b$` hashes (auth.users.encrypted_password) verify at their own cost. */
|
|
4
|
-
const COST = 12
|
|
5
|
-
|
|
6
|
-
/** Hashes a new password with bcrypt. */
|
|
7
|
-
export const hashPassword = (password: string): Promise<string> => hash(password, COST)
|
|
8
|
-
|
|
9
|
-
/** True when `password` matches the stored bcrypt hash; a missing hash never matches. */
|
|
10
|
-
export const verifyPassword = async (password: string, stored: string | null): Promise<boolean> =>
|
|
11
|
-
stored === null ? false : compare(password, stored)
|